diff --git a/docs/conduit_filters_bypass.svg b/docs/conduit_filters_bypass.svg new file mode 100644 index 0000000..aa13003 --- /dev/null +++ b/docs/conduit_filters_bypass.svg @@ -0,0 +1 @@ +Model: macro_meso_micro_filteredmacrobypass_outbc_outbc_inbypass_inmesoinit_infinal_outbc_outbc_inmicroinit_inbypass_infinal_outbypass_out diff --git a/docs/conduit_filters_bypass.ymmsl b/docs/conduit_filters_bypass.ymmsl new file mode 100644 index 0000000..7275f25 --- /dev/null +++ b/docs/conduit_filters_bypass.ymmsl @@ -0,0 +1,31 @@ +ymmsl_version: v0.2 + +description: macro-meso-micro with a bypass conduit needing filters + +models: + macro_meso_micro_filtered: + components: + macro: + ports: + o_i: bc_out bypass_out + s: bc_in bypass_in + description: '' + meso: + ports: + f_init: init_in + o_i: bc_out + s: bc_in + o_f: final_out + description: '' + micro: + ports: + f_init: init_in bypass_in + o_f: bypass_out final_out + description: '' + conduits: + macro.bc_out: meso.init_in + macro.bypass_out: repeat micro.bypass_in + meso.final_out: macro.bc_in + meso.bc_out: micro.init_in + micro.final_out: meso.bc_in + micro.bypass_out: last macro.bypass_in diff --git a/docs/describing_models.rst b/docs/describing_models.rst index 531ab77..54afcc2 100644 --- a/docs/describing_models.rst +++ b/docs/describing_models.rst @@ -132,6 +132,127 @@ separated by periods. Depending on the context, this may represent a name in a n or an attribute of an object (as we will see below with Conduits). +Timelines +````````` + +Different components of a coupled simulation typically run at their own pace: a fast, +detailed micro model may take many small steps for every single step of the macro model +driving it, and a meso model may sit somewhere in between the two. yMMSL captures this +idea of "running at a different pace" as a *timeline*. Wiring a component's ``o_i``/``s`` +ports to another component's ``f_init``/``o_f`` ports puts that other component, and anything +it in turn drives, on a timeline nested inside the first. yMMSL works this out automatically +from how components are wired together with conduits. + +A component that nobody calls sits on the outermost, root timeline, written ``:``. Every +level of nesting adds one more name, giving each timeline in the model an addressable +path, a bit like a folder structure. + +Take a macro model that calls a meso model in a loop, and where that meso model in turn +calls a micro model in its own loop. This produces three levels of timelines: the root +timeline for ``macro``, a timeline nested inside it for ``meso``, and a timeline nested +inside *that* for ``micro``: + +.. literalinclude:: timelines_macro_meso_micro.ymmsl + :caption: ``docs/timelines_macro_meso_micro.ymmsl`` + :language: yaml + +.. figure:: timelines_macro_meso_micro.svg + :align: center + :alt: macro connects to meso through F_INIT/O_F and O_I/S ports, and meso connects to + micro the same way, producing three nested timelines. + + The same model, visualized with `ymmsl2svg + `_. The order of the boxes in the figure, + from top to bottom, mirrors the nesting in time: ``macro`` first, then ``meso`` + below it, then ``micro`` below ``meso``. + +A single component can also be connected to more than one timeline at once, for example +when it drives two other components that run at different rates. ``macro`` calling +``micro1`` in one loop and ``micro2`` in a separate loop puts ``micro1`` and ``micro2`` on +two independent sub-timelines of ``macro``. The following example shows how you can +use ``timeline :`` to indicate that the ports connecting to ``micro1`` belong to +a different subtimeline than the ports connecting to ``micro2``: + +.. literalinclude:: timelines_two_subtimelines.ymmsl + :caption: ``docs/timelines_two_subtimelines.ymmsl`` + :language: yaml + +.. figure:: timelines_two_subtimelines.svg + :align: center + :alt: macro has two separate pairs of O_I/S ports, one connecting down to micro1 and + one connecting down to micro2, side by side. + + The same model, visualized with `ymmsl2svg + `_. ``macro``'s two named timelines are drawn + side by side beneath it, each with its own pair of ports, one leading to ``micro1`` + and the other to ``micro2``. + +Matching timelines +^^^^^^^^^^^^^^^^^^^ + +The timeline hierarchy above is worked out automatically from how ``f_init``/``o_f`` +and ``o_i``/``s`` ports are wired together, and a conduit can only connect ports whose +timelines match. Sometimes, though, two components are expected to produce matching +time points without one being nested inside the other's timeline this way, for example +two components that call each other directly and are expected to stay in lock-step, or +a component that adapts its output to another component's timeline, as time bridges do. +``matching_timelines`` lets you declare such timelines equivalent, so that a conduit can +still connect ports on them directly: + +.. code-block:: yaml + :caption: Declaring matching timelines + + components: + left: + ports: + o_i: out + s: in + description: Left side of the domain + right: + ports: + o_i: out + s: in + description: Right side of the domain + + matching_timelines: + left: right + + conduits: + left.out: right.in + right.out: left.in + +``left`` and ``right`` call each other directly rather than through a shared driver, so +their O_I and S ports live on their own default timelines, ``:left`` and ``:right``, +named after the component as usual. A conduit between these ports would therefore not be +allowed. The entry under ``matching_timelines`` declares ``left`` and ``right``'s +timelines equivalent, so that the conduits connecting them are valid after all. + +On the Python side, ``matching_timelines`` is a list of +:class:`.ymmsl.v0_2.MatchingTimelines` objects, each representing a set of equivalent +timelines, with a ``head`` attribute and a ``matches`` attribute holding the full set, +including the head. + +A head can have more than one match, for example if ``left`` is expected to stay in +lock-step with both ``right`` and ``top``. The matches can then be written as a +whitespace-separated string: + +.. code-block:: yaml + :caption: A head with multiple matches + + matching_timelines: + left: right top + +or, equivalently, as a YAML list: + +.. code-block:: yaml + :caption: The same, as a YAML list + + matching_timelines: + left: + - right + - top + + Conduits ```````` @@ -186,6 +307,63 @@ sender: print(conduits[0]) # output: Conduit(sender.port -> receiver1.port) print(conduits[1]) # output: Conduit(sender.port -> receiver2.port) +Conduit filters +^^^^^^^^^^^^^^^ + +A conduit connects two components that call each other directly, for example ``macro`` +and ``meso``, or ``meso`` and ``micro``. ``macro`` and ``micro`` are not directly +connected in this sense: ``meso`` sits between them. Connecting ``macro`` and ``micro`` +directly, bypassing ``meso``, means their pace no longer matches: ``micro`` is still +called many times for every step ``macro`` takes, and still produces a message on +every one of those calls, even though there is no longer a ``meso`` in between to +absorb the difference. A conduit filter reconciles that mismatch. + +Extending the macro-meso-micro example from :ref:`Timelines` with a conduit that +bypasses ``meso`` to connect ``macro`` and ``micro`` directly shows both filters in +use: + +.. literalinclude:: conduit_filters_bypass.ymmsl + :caption: ``docs/conduit_filters_bypass.ymmsl`` + :language: yaml + +.. figure:: conduit_filters_bypass.svg + :align: center + :alt: macro and micro have an extra pair of ports directly connecting them, + bypassing meso, labeled "repeat" and "last". + + The same model, visualized with `ymmsl2svg + `_. + +``macro`` produces the ``bypass_out`` message once, but ``micro`` is called many times +for every step of ``macro`` and needs the message on each of those calls. The conduit +from ``macro.bypass_out`` to ``micro.bypass_in`` uses a ``repeat`` filter for this: the +single message ``macro`` sends is resent to ``micro`` every time it runs, without +``meso`` having to relay it. + +The reverse happens on the way back: ``micro`` produces a ``bypass_out`` message on +every one of its many runs, but ``macro`` still expects only one message per call. The +conduit from ``micro.bypass_out`` to ``macro.bypass_in`` uses a ``last`` filter to +reduce those many messages down to the single most recently produced one. + +- ``repeat`` and ``pad`` go from the shallower side to the deeper one: a single message + is repeated, or followed by empty messages, to match every time the deeper side + receives. +- ``last`` goes from the deeper side back to the shallower one: of the many messages + produced, only the last one is passed on. + +A conduit between two components that call each other directly doesn't skip anything, +so it can't take a filter at all. + +Filters are written in front of the receiver and may be combined: + +.. code-block:: yaml + :caption: Specifying conduit filters in yMMSL + + conduits: + macro.init_out: repeat micro.init_in + micro.state_out: last macro.final_in + + Nesting models `````````````` diff --git a/docs/timelines_macro_meso_micro.svg b/docs/timelines_macro_meso_micro.svg new file mode 100644 index 0000000..f19f6b6 --- /dev/null +++ b/docs/timelines_macro_meso_micro.svg @@ -0,0 +1 @@ +Model: macro_meso_micro_modelmacrobc_outbc_inmesoinit_infinal_outbc_outbc_inmicroinit_infinal_out diff --git a/docs/timelines_macro_meso_micro.ymmsl b/docs/timelines_macro_meso_micro.ymmsl new file mode 100644 index 0000000..f815651 --- /dev/null +++ b/docs/timelines_macro_meso_micro.ymmsl @@ -0,0 +1,29 @@ +ymmsl_version: v0.2 + +description: A macro-meso-micro model, three levels of timelines + +models: + macro_meso_micro_model: + components: + macro: + ports: + o_i: bc_out + s: bc_in + description: '' + meso: + ports: + f_init: init_in + o_i: bc_out + s: bc_in + o_f: final_out + description: '' + micro: + ports: + f_init: init_in + o_f: final_out + description: '' + conduits: + macro.bc_out: meso.init_in + meso.final_out: macro.bc_in + meso.bc_out: micro.init_in + micro.final_out: meso.bc_in diff --git a/docs/timelines_two_subtimelines.svg b/docs/timelines_two_subtimelines.svg new file mode 100644 index 0000000..4126c23 --- /dev/null +++ b/docs/timelines_two_subtimelines.svg @@ -0,0 +1 @@ +Model: two_subtimelines_modelmacromicro1_outmicro2_outmicro2_inmicro1_inmicro1init_infinal_outmicro2init_infinal_out diff --git a/docs/timelines_two_subtimelines.ymmsl b/docs/timelines_two_subtimelines.ymmsl new file mode 100644 index 0000000..221da3d --- /dev/null +++ b/docs/timelines_two_subtimelines.ymmsl @@ -0,0 +1,31 @@ +ymmsl_version: v0.2 + +description: One component (macro) connected to two independent timelines + +models: + two_subtimelines_model: + components: + macro: + ports: + timeline tl1: + o_i: micro1_out + s: micro1_in + timeline tl2: + o_i: micro2_out + s: micro2_in + description: '' + micro1: + ports: + f_init: init_in + o_f: final_out + description: '' + micro2: + ports: + f_init: init_in + o_f: final_out + description: '' + conduits: + macro.micro1_out: micro1.init_in + micro1.final_out: macro.micro1_in + macro.micro2_out: micro2.init_in + micro2.final_out: macro.micro2_in