Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

midas-omod

PyPI version License: LGPL-2.1 Build Status

A MIDAS module for integrating synthetic mobility demand from OMOD (OpenStreetMap Mobility Demand Generator) into a mosaik-based co-simulation, enabling electric vehicles (EVs) to be moved temporally within a power distribution grid simulation.

Overview

midas-omod reads OMOD-generated daily activity plans (JSON format) and creates mosaik EVAgent entities that simulate the movement and charging of electric vehicles. Each agent follows its OMOD daily plan, consuming battery energy while driving and charging while parked at locations with charging infrastructure (HOME, WORK).

Data Flow

OMOD (Java) → JSON file → midas-omod (parser) → EVAgent entities
                                                        ↓
                                           mosaik co-simulation
                                                        ↓
                                            midas-powergrid (pandapower)
                                                        ↓
                                            midas-store (persistence)
  1. OMOD generates a JSON file with daily activity plans per agent (Activities and Trips with coordinates, times, distances, and transport modes).
  2. midas-omod parses the JSON, filters for agents with at least one CAR_DRIVER trip, and creates EVAgent mosaik entities.
  3. Each EVAgent follows its daily plan: driving trips consume SoC, parking at HOME/WORK charges the battery (if grid_connected is True).
  4. The charging power output is connected to a midas-powergrid load node's p_mw attribute, coupling mobility to the grid.
  5. Results are persisted via midas-store.

Installation

pip install midas-omod

For development:

git clone <repo-url>
cd midas-omod
pip install -e ".[dev]"

Dependencies

  • Python >= 3.10
  • mosaik_api
  • midas-mosaik >= 2.1.0
  • mosaik (for running scenarios)
  • midas-powergrid (for grid coupling)
  • midas-store (for persistence, optional)

OMOD: Automatic Execution from Python

OMOD is a Java-based tool available at github.com/L-Strobel/omod. Since v0.2.0, midas-omod can run OMOD automatically from Python — no manual java command required.

Quick start

from midas_omod.omod_runner import run_omod

output = run_omod(
    jar_path="/path/to/omod.jar",
    osm_file="region.osm.pbf",
    area_geojson="area.geojson",
    n_agents=100,
    n_days=1,
    out_path="omod_output.json",
)

Auto-download the OMOD JAR

from midas_omod.omod_runner import ensure_omod_jar

jar = ensure_omod_jar(version="latest", target_dir="./jars")

CLI usage

python scenario_example.py \
    --omod-jar /path/to/omod.jar \
    --osm-file region.osm.pbf \
    --area-geojson area.geojson \
    --n-agents-omod 100 --n-days-omod 1

If --json-path is provided, it takes priority and OMOD is not re-run. Results are cached by parameter hash in .cache/omod/.

Manual fallback

You can still run OMOD manually and pass the JSON file:

java -jar omod.jar \
    --osm_file area.osm.pbf \
    --area_geojson focus.geojson \
    --n_agents 100 \
    --n_days 1 \
    --out output.json \
    --return_path_coords y

Then use --json-path output.json.

Requirements

  • Java JDK 17+ must be installed and java on PATH.
  • The OMOD JAR can be downloaded from GitHub Releases.
  • An OpenStreetMap .osm.pbf file for your region.
  • A focus area GeoJSON (e.g. from geojson.io).

See also the OMOD publication: Strobel et al., "OMOD: An open-source tool for creating disaggregated mobility demand based on OpenStreetMap", Computers, Environment and Urban Systems, 2023 (arXiv).

Usage

Running the Example Scenario

# Using the bundled test data (no OMOD installation needed):
python scenario_example.py \
    --json-path midas_omod/tests/data/test_agents.json \
    --num-agents 2

# Using real OMOD output:
python scenario_example.py --json-path path/to/omod_output.json

Programmatic API

import mosaik

SIM_CONFIG = {
    "Omod": {"python": "midas_omod.simulator:OmodSimulator"},
}

world = mosaik.World(SIM_CONFIG)
sim = world.start(
    "Omod",
    time_resolution=60,
    json_path="omod_output.json",
    battery_capacity_kwh=60,
    charging_power_kw=11,
    consumption_kwh_per_km=0.18,
)
agents = sim.EVAgent.create(num=10)

EVAgent Model

Attributes (mosaik META)

Attribute Type Direction Description
lat float output Current latitude
lon float output Current longitude
soc_kwh float output State of charge in kWh
charging_power_kw float output Current charging power in kW (0 if not charging)
status str output "parked" or "driving"
grid_connected bool input Whether a grid/charging connection is available

Parameters (init/create)

Parameter Default Description
json_path — Path to OMOD JSON file
battery_capacity_kwh 60.0 Max battery capacity (kWh)
charging_power_kw 11.0 Nominal charging power (kW)
consumption_kwh_per_km 0.18 Energy consumption (kWh/km)

SoC Logic

  • Driving: SoC decreases by distanceKilometer × consumption_kwh_per_km (prorated over the trip duration across multiple steps).
  • Charging: SoC increases by charging_power_kw × step_hours while parked at HOME or WORK if grid_connected == True, capped at battery_capacity_kwh.
  • Position: Interpolated linearly along lats/lons path coordinates during trips (or between start/end activities if no path coordinates are available).

Testing

pytest midas_omod/tests/ -v

Tests cover:

  • OMOD JSON parsing and CAR_DRIVER filtering (test_parser.py)
  • SoC discharging during trips (test_simulator_soc.py)
  • SoC charging at HOME/WORK with grid connection (test_simulator_soc.py)
  • Position interpolation (test_simulator_soc.py)
  • get_data output (test_simulator_soc.py)

Package Structure

midas-omod/
├── midas_omod/
│   ├── __init__.py          # Version
│   ├── meta.py              # mosaik META definition
│   ├── model.py             # EVAgent data model
│   ├── parser.py            # OMOD JSON parser
│   ├── simulator.py         # OmodSimulator (mosaik API)
│   ├── module.py            # MIDAS module registration
│   └── tests/
│       ├── test_parser.py
│       ├── test_simulator_soc.py
│       └── data/
│           └── test_agents.json   # OMOD-format test fixture
├── scenario_example.py      # Example mosaik scenario
├── pyproject.toml           # Package configuration
└── README.md

Dynamic Bus Coupling

In a real co-simulation, each EV agent should feed its charging (or V2G discharge) power to the grid bus at its current location. However, mosaik connections are static — an entity cannot reconnect to a different target at runtime. midas-omod solves this with a LoadRouterSimulator:

  1. Each LoadRoute entity has a fixed target_bus_id.
  2. It receives grid_power_mw and bus_id from all EV agents.
  3. At each step, it forwards only power from agents whose current bus_id matches target_bus_id; all others contribute 0.
  4. The output p_mw is connected to a midas-powergrid Load.p_mw.

This is not a power aggregation workaround — pandapower natively sums multiple loads at the same bus (see docs/aggregation_verification.md). The LoadRouter is needed because mosaik's topology is static while EV positions are dynamic.

BusMapper

The BusMapper class maps geographic coordinates to bus IDs using:

  1. Haversine distance (when bus coordinates are available).
  2. Activity-type mapping ({"HOME": "Bus-0", ...}) as fallback.
  3. Round-robin assignment for test scenarios without geodata.

The bus_id is recomputed at every activity transition in the OMOD plan, and set to None while driving (no grid connection).

V2G support

Vehicle-to-grid (V2G) discharge is opt-in via enable_v2g=True. When enabled and v2g_request_mw > 0, the agent discharges up to the requested power, bounded by available SoC above min_soc_kwh. grid_power_mw is negative during V2G discharge.

Contributing

Contributions are welcome. Please follow these guidelines:

  • Coding style: The project uses ruff for linting and formatting with a line length of 79 characters. Run ruff check . before submitting a pull request.
  • Tests: All new features or bug fixes must include pytest tests. Run pytest midas_omod/tests/ -v to verify.
  • Commits: Use atomic commits with descriptive messages (e.g. Add CI workflow, Fix SoC calculation edge case).
  • Pull requests: Open a PR against the main branch. Ensure CI passes (pytest + ruff on Python 3.10, 3.11, 3.12) before requesting review.
  • License: By contributing, you agree that your contributions are licensed under the LGPL-2.1.

Limitations

  • No live coupling with OMOD: OMOD runs as a separate Java process and must be executed beforehand. The JSON output is read statically at simulation start; there is no runtime interface between OMOD and midas-omod.
  • Static 24-hour window: The simulator models a single day (day 0) from the OMOD output. Multi-day plans are not yet supported.
  • Single-day plan only: Only the first day's mobilityDemand entry is used. Agents with multi-day plans will only simulate day 0.
  • No traffic simulation: Vehicles move according to OMOD's pre-computed schedules. There is no interaction with a traffic simulator (e.g. SUMO).
  • Linear position interpolation: During trips, positions are interpolated linearly along OMOD's lats/lons path coordinates or between start/end activities. Realistic routing is not performed.

Limitations of Bus Mapping

The BusMapper uses a three-tier fallback strategy to assign EV agents to power grid buses. Users should be aware of the following limitations:

  1. Haversine mapping requires bus coordinates: Many MIDAS reference grids (including midasmv) do not include geographic coordinates (bus_geodata) for their buses. Without coordinates, the Haversine-based nearest-bus lookup is not available.

  2. Activity-type mapping is coarse: The fallback {activity_type: bus_id} mapping assigns all HOME activities to one bus, all WORK activities to another, etc. This does not reflect the spatial distribution of real charging points within a grid area.

  3. Round-robin fallback is arbitrary: When neither coordinates nor activity-type mapping are available, the BusMapper falls back to round-robin assignment across fallback_bus_ids. This assignment is not geographically meaningful — it simply cycles through the available bus IDs. A WARNING log message is emitted each time this fallback is used. For any realistic scenario, provide either bus_coords (with lat/lon per bus) or a location_bus_mapping.

  4. No runtime re-mapping: The bus assignment is computed at activity-transition time from the OMOD plan. If the grid topology changes during the simulation (e.g. due to switching), the bus mapping is not updated.

  5. Bus ID format depends on the grid: The EID format for grid entities is simulator-specific. For midas-powergrid with the midasmv grid, load EIDs follow the pattern 0-load-{load_idx}-{bus_idx} and bus EIDs follow 0-bus-{bus_idx}. When using a different grid, the location_bus_mapping must be updated accordingly.

License

LGPL-2.1-only — see LICENSE for details.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages