Inline MIDAS Scenarios

MIDAS is a mosaik-based co-simulation framework for energy grids. Historically, a palaestrAI MIDAS environment referenced a scenario by name and loaded its definition from a file on disk. This document describes the inline MIDAS scenario provider, which embeds the complete scenario inside the run configuration, together with how a MIDAS environment proposes its meta-loop definition interface.

In the current version, the scripted MIDAS example makes one initial scenario change: the meta agent acts once in episode_start before the world is created, and the validated candidate is used to start the operating environment. The operating return is handed back to the meta agent as a terminal reward/update, but in-place episode resets do not trigger another scenario decision. A repeated meta-learning loop that adapts the scenario across episodes is not currently implemented and would require additional orchestration. See Scripted Simulation Controller and the Definition Meta-Loop for the generic controller behavior and its current limitations.

Configuration

The inline provider is selected with scenario_provider: inline inside the MIDAS environment’s params, together with a nested scenario mapping:

params:
  module: midas_palaestrai:Descriptor
  description_func: describe
  instance_func: get_world
  params:
    scenario_provider: inline
    config: tests/fixtures/this-file-must-not-exist.yml   # ignored
    scenario:
      name: carl_cigre_base
      modules: [store, powergrid]
      step_size: 1
      start_date: "2020-04-13 12:00:00+0100"
      end: 10
      with_arl: true
      powergrid_params: { ... }
      store_params: { ... }

Resolution

The MIDAS Descriptor (see Cross-Repository References) detects scenario_provider == "inline" and resolves the nested mapping through midas.scenario.configurator.Configurator.load_from_mapping, which owns subtle behaviour such as parent-scenario merging and unsupported-key handling. The resolved scenario and its normalised parameters are kept together, and the resolved_mapping is exposed as ProviderProvenance.

Crucially, inline mode ignores the config path and never falls back to file mode. A run that accidentally relies on the file path fails, which is used as an explicit regression signal in the integration test.

The definition override block

The Mosaik environment proposes its meta-loop interface from the inline scenario mapping without constructing the MIDAS descriptor or world. By default, every finite int/float leaf is exposed, recursively through mappings and nested lists, not only constant_* leaves (see Scripted Simulation Controller and the Definition Meta-Loop). Booleans are excluded. The definition block inside the environment’s inner params optionally filters discovery or replaces selector metadata:

params:
  scenario_provider: inline
  scenario: { ... }
  definition:
    selector_manifest:
      - alias: constant_load_p_mw
        json_pointer: /powergrid_params/midasmv/grid_params/constant_load_p_mw
        space: "Box(low=0.0, high=2.0, shape=(), dtype=np.float64)"
        normalization: {center: 0.7, scale: 0.7}

Each manifest entry names:

alias

A short identifier (must be unique, non-empty).

json_pointer

An RFC 6901 JSON Pointer resolved against the scenario mapping.

space

A run-file string parsed by palaestrai.types.Space.from_string. Only scalar Box and Discrete spaces are supported.

normalization

An optional fixed {center, scale} policy, required for unbounded spaces and used when vectorizing values for the meta agent.

Every manifest selector is exposed as both a sensor and an actuator with the stable UID <environment-uid>.definition.<alias>. The provider validates selector aliases, pointers, spaces, and normalisation, and rejects patches whose pointers are not declared, whose revision or content hash do not match, or whose application would change the descriptor interface fingerprint.

Worked Example

The repository ships a complete, self-contained example as tests/fixtures/scripted_inline_midas_meta_loop_run.yml. Its essential parts, annotated:

schedule:
  - scripted_meta_loop:
      environments:
        - environment:
            uid: midas
            name: palaestrai_mosaik:MosaikEnvironment
            params:
              module: midas_palaestrai:Descriptor
              description_func: describe
              instance_func: get_world
              params:
                scenario_provider: inline           # (1) inline provider
                scenario:
                  name: carl_cigre_base
                  modules: [store, powergrid]
                  powergrid_params: { ... }        # selector targets
      agents:
        - name: meta_agent                        # (2) definition interface
          sensors:  [midas.definition.constant_load_p_mw]
          actuators: [midas.definition.constant_load_p_mw]
        - name: operating_agent                   # (3) environment interface
          sensors:  [midas.Powergrid-0.0-bus-1.vm_pu, ...]
          actuators: [midas.Powergrid-0.0-sgen-0-1.p_mw, ...]
      simulation:
        name: palaestrai.simulation:ScriptedSimulationController
        params:
          script:
            episode_start:                          # (4) meta turn
              - agents: [meta_agent]
                environments: [midas]
            loop:                                   # (5) operating turn
              - agents: [operating_agent]
                environments: [midas]
            episode_end: []
  1. The MIDAS environment uses the inline provider; its scenario mapping is the definition the meta agent will operate on.

  2. meta_agent is declared against the definition interface (midas.definition.*), selected by being listed in episode_start.

  3. operating_agent is declared against the real environment interface (midas.Powergrid-*), selected by being listed in loop.

  4. The meta agent acts once before the world exists; its action becomes a validated definition patch for this initial scenario change. An in-place reset does not run another meta action.

  5. The operating agent runs the normal act/step loop against the world built from the validated definition. Its terminal reward is handed to the meta agent, but that update does not schedule another definition decision.

  6. The environment discovers constant_load_p_mw automatically. The fixture uses definition.include: ['^constant_load_p_mw$'] to keep this small dummy-agent integration run focused on one parameter; it has no manifest.

Nested load and PV scales

The fuller example is tests/fixtures/Train_meta-loop_simple_change.yml, mirroring midas-palaestrai/tests/unit/fixtures/Train_meta-loop_simple_change.yml. Its TD3 meta agent observes and controls the nested numeric load/PV scales. The relevant environment inner params and meta-agent assignments are:

params:
  scenario_provider: inline
  scenario:
    # Other required scenario parameters are in the complete fixture.
    comdata_params:
      midasmv:
        active_mapping:
          3: [[Hospital, 0.427]]
          4: [[LargeHotel, 0.913]]
    der_params:
      midasmv:
        mapping:
          6: [[PV, 2.2]]
  definition:
    # Optional: focus training on load/PV scales, not timing or topology.
    include:
      - '^/comdata_params/midasmv/active_mapping/(3|4)/0/1$'
      - '^/der_params/midasmv/mapping/6/0/1$'

# In the meta_agent declaration:
sensors:
  - midas.definition./comdata_params/midasmv/active_mapping/3/0/1
  - midas.definition./comdata_params/midasmv/active_mapping/4/0/1
  - midas.definition./der_params/midasmv/mapping/6/0/1
actuators:
  - midas.definition./comdata_params/midasmv/active_mapping/3/0/1
  - midas.definition./comdata_params/midasmv/active_mapping/4/0/1
  - midas.definition./der_params/midasmv/mapping/6/0/1

No aliases or pointers are hand-declared in a selector manifest. Discovery walks the integer bus key, outer list index 0, and inner list index 1 to reach each numeric value. The path is its alias, so the period immediately before the leading slash in each UID is intentional. Hospital, LargeHotel, and PV are strings, not exposed numeric selectors.

Using the same UID-valued actions as the meta agent, the provider path is:

from palaestrai_mosaik.definition import propose_mosaik_definition
from palaestrai.simulation.environment_definition import DefinitionProvider

# inner_params is the fixture's environment.params.params mapping.
proposal = propose_mosaik_definition(inner_params, "midas")
provider = DefinitionProvider(proposal)
baseline = {"midas": proposal.scenario}
readings = provider.vectorize(baseline, None)
assert tuple(readings.values) == (0.427, 0.913, 2.2)
actions = {
    "midas.definition./comdata_params/midasmv/active_mapping/3/0/1": 0.5,
    "midas.definition./comdata_params/midasmv/active_mapping/4/0/1": 1.0,
    "midas.definition./der_params/midasmv/mapping/6/0/1": 2.4,
}
patch = provider.propose_patch(baseline, actions)
candidate = provider.validate(baseline, patch)
changed = candidate.definition["midas"]
assert changed["comdata_params"]["midasmv"]["active_mapping"][3][0][1] == 0.5
assert changed["comdata_params"]["midasmv"]["active_mapping"][4][0][1] == 1.0
assert changed["der_params"]["midasmv"]["mapping"][6][0][1] == 2.4

The scripted controller follows that same provider path after receiving the meta agent’s ordinary action response. It copies candidate.definition’s scenario into the request-scoped setup override. The MIDAS inline provider then resolves that changed mapping when the operating environment is started, rather than the original values. Provider validation checks declared spaces, finite values, revision/hash, and interface consistency; it is not a power-grid feasibility test.

Removing definition.include exposes all finite numeric scenario leaves, including integer step_size/end, other load/PV scales, and numeric entries in weather_provider_mapping. Default integer selectors still use continuous Box spaces; filter structural/timing values out if training must not alter them. The meta agent receives the complete proposed interface, not a subset determined by its sensors/actuators lists. Use the optional filters to control that interface, or a manifest to override individual selectors’ bounds, aliases, and discrete choices.

A companion fixture, tests/fixtures/simple-end-to-end-meta-loop.yml, demonstrates the same flow against a synthetic environment without MIDAS.

Cross-Repository References

The one-shot definition step and inline MIDAS support span several repositories. This is not a repeated meta-learning loop: adapting the scenario across episodes is not currently implemented and would require additional orchestration. The relevant symbols are referenced from run files by module path, so they must be installed alongside palaestrAI.

midas-palaestrai

The MIDAS-to-palaestrAI bridge. Provides:

  • midas_palaestrai.Descriptor — the module used by palaestrai_mosaik.MosaikEnvironment. Implements describe and (async) get_world, and resolves the inline scenario provider via midas.scenario.configurator.Configurator.load_from_mapping. Records ProviderProvenance describing the provider, scenario name, resolution path, input keys, and ignored keys.

  • reward/objective classes such as midas_palaestrai.rewards.GridHealthReward used by the operating agent.

This package imports MIDAS lazily: the descriptor fails with a clear ImportError at construction if the optional midas dependency is not installed.

palaestrai-mosaik

The mosaik co-simulation interface. Provides palaestrai_mosaik.MosaikEnvironment (name: palaestrai_mosaik:MosaikEnvironment in run files), which wraps a user-supplied module/description_func/instance_func triple — here the MIDAS descriptor — as a palaestrAI environment. Its config-only propose_definition calls palaestrai_mosaik.definition.propose_mosaik_definition, which uses palaestrAI’s derive_selectors on the inline scenario. The descriptor is only used later for the real environment interface/world setup.

midas

The co-simulation scenario engine itself. Provides midas.scenario.configurator.Configurator.load_from_mapping, which the inline provider uses to turn a scenario mapping into a configured, normalised scenario, and the Scenario/world objects operated by Descriptor.

The integration test tests/integration/test_scripted_inline_midas_meta_loop.robot walks this full stack — real worker processes and a real mosaik/MIDAS world — asserting a zero exit, no traceback, and a completed run artifact, thereby proving the inline provider, controller, and environment-proposed interface are all activated by a single run file.