*************************************** 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 :doc:`scripted-simulation-controller` 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 :class:`~midas_palaestrai.Descriptor` (see :ref:`cross-repo`) 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 :doc:`scripted-simulation-controller`). 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 ``.definition.``. 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: .. code-block:: yaml 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: .. code-block:: python 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-repo: 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.