API reference

Package ymmsl

Package ymmsl.v0_2

class BaseEnv(*values)

Describes the base shell environment for execution.

Several options in Implementation describe additions to the shell environment to make subsequently, but we need to start somewhere. Different starting points make sense in different contexts, so there’s a choice:

LOGIN starts from an environment that resembles the default environment of the user running the simulation. The exact environment you get when you log in depends on whether you’re on a text terminal, graphical terminal, or using SSH, and also on the operating system and any local changes made by your system administrator. This cannot be reproduced exactly in all circumstances, but the below should get close on most systems.

This will copy TERM, HOME, SHELL, USER, and LOGNAME from the manager environment, then run /bin/bash and have it load /etc/environment, /etc/profile, and then the first of ~/.bash_profile, ~/.bash_login, and ~/.profile that it finds. Note that on most machines, these files will load other files in turn, often including ~/.bashrc.

CLEAN starts from the environment that muscle_manager was started in, and then unloads any loaded modules and deactivates any active Python virtual environments. Any modules specified in modules and any virtual environment specified in virtual_env will be activate after that, of course.

Note that on some HPC machines, SLURM is made available though the environment modules. If you use the SRUNMPI execution model, then you’ll have to load it again explicitly to make the srun command available, or MUSCLE3 won’t be able to start your program.

MANAGER starts from the exact environment that muscle_manager was started in, including any loaded modules and active virtual environments. Any modules specified in modules are then loaded on top of this, which may cause some of the existing modules to be unloaded if they are incompatible. If a virtual environment is specified in virtual_env, then it will replace the active environment.

CLEAN = 2

Like MANAGER, but with any modules unloaded and venvs deactivated.

LOGIN = 1

The environment you get after logging in when using bash.

MANAGER = 3

The environment the manager was started in.

class CheckpointAtRule(at: List[float | int] | None)

Defines an “at” checkpoint rule.

An “at” checkpoint rule creates a snapshot at the specified moments.

at

List of checkpoints.

class CheckpointRangeRule(start: float | int | None = None, stop: float | int | None = None, every: float | int = 0)

Defines a range of checkpoint moments.

If start is supplied, this rule specifies a checkpoint at: start, start + every, start + 2*every, etc., for as long as start + n*every <= stop, with n a whole number. If stop is not given, the range continues indefinitely.

Start may be omitted, in which case a checkpoint is defined for 0, every, 2*every, etc. Note that in this case the range also extends to negative numbers (-every, -2*every, etc.), as simulated time may be negative (e.g. in rocket launch, t=0 is generally taken as lift-off time, but events already take place before that moment).

start

Start of the range.

stop

Stopping criterium of the range.

every

Step size of the range, must be positive.

class CheckpointRule

Defines a checkpoint rule.

There are two flavors of rules: CheckpointRangeRule and CheckpointAtRule. Do not use this class directly.

class Checkpoints(at_end: bool = False, wallclock_time: List[CheckpointRule] | None = None, simulation_time: List[CheckpointRule] | None = None)

Defines checkpoints in a configuration.

There are three checkpoint triggers: at_end, wallclock_time and simulation_time. The at_end trigger specifies that a checkpoint should be created just before the workflow finishes. The wallclock_time trigger is based on the elapsed real time since starting the muscle_manager in seconds. The simulation_time trigger is based on the time in the simulation as reported by the instances.

Note that the simulation_time trigger assumes a shared concept of time among all components of the model.

at_end

Whether a checkpoint should be created just before ending the workflow.

wallclock_time

Checkpoint rules for the wallclock_time trigger.

simulation_time

Checkpoint rules for the simulation_time trigger.

update(overlay: Checkpoints) None

Update this checkpoints with the given overlay.

Sets at_end to True if it is set in the overlay, otherwise at_end remains as is. Updates the checkpoint rules for wallclock time and simulation time. See CheckpointRules.update().

class Component(name: str, ports: Ports, description: str, implementation: str | None = None, optional: bool = False, multiplicity: None | int | List[int] = None)

Describes a simulation component

Simulation components are the abstract boxes that models consist of. Components are implemented by an implementation, which describes how to do the calculations needed for this component, they have a multiplicity, which describes how many copies of that implementation need to be started (for e.g. ensembles), and they have ports, which are used to connect components to each other.

Components may be optional. If a component is optional, then a missing implementation in the final configuration that is run is valid, and will result in the component and all connected conduits being removed from the simulation. If a component is not optional, then trying to run a simulation with a component that does not have an implementation will result in an error.

name

The name of this component

ports

The ports by which this component can be connected to others

description

A human-readable description of this component

implementation

A Model or Program implementing this component

optional

Whether this component is optional

multiplicity

The shape of the set of instances

instances() List[Reference]

Creates a list of instances needed.

Returns:

A list with one Reference for each instance of this component.

class Conduit(sender: str, receiver: str, filters: str | List[ConduitFilter] | None = None)

A conduit transports data between simulation components.

A conduit has two endpoints, which are references to a port on a simulation component. These references must be of one of the following forms:

  • component.port

  • namespace.component.port (or several namespace prefixes)

As a special feature, the receiver may be a whitespace-separated list of words, in which case the last of these words is the receiver, and the words before that will be interpreted as filters, using the (lowercase) values from ConduitFilter. In this case, the filters argument must be absent or empty.

sender

The sending port that this conduit is connected to.

receiver

The receiving port that this conduit is connected to.

filters

A list of filters, or a string containing space-separated filter names

receiving_component() Reference

Returns a reference to the receiving component.

receiving_port() Identifier

Returns the identity of the receiving port.

sending_component() Reference

Returns a reference to the sending component.

sending_port() Identifier

Returns the identity of the sending port.

class ConduitFilter(*values)

Represent a filter to apply to messages passing through a conduit.

In multiscale models, scale separation between simulated processes results in a communication pattern where a component simulating slow dynamics repeatedly calls a component simulating fast dynamics. If the fast dynamics component needs to be initialised once on its first run, or needs to send a final result at the end of its last run, then the number of sends and receives will not match up, resulting in a deadlock.

Conduit filters solve this problem by telling the conduit to only pass the last sent message on to the receiver, or by repeating a single message as often as needed or by padding it with null messages to cover subsequent receives.

Objects of this class represent the different possible filters.

LAST = 'last'

Pass only the last message and drop any preceding ones.

PAD = 'pad'

Pass a single message and then generate nil-messages as needed.

REPEAT = 'repeat'

Repeat a single message as often as needed.

is_reducer() bool

Return whether this filter is a reducer.

Reducers take one or more sent messages and filter them down to a single one for the recipient.

is_repeater() bool

Return whether this filter is a repeater.

Repeaters take a single message and turn it into multiple to cover multiple receive attempts.

class Configuration(description: str = '', imports: Sequence[ImportStatement] | None = None, models: Sequence[Model] | MutableMapping[Reference, Model] | None = None, custom_implementations: MutableMapping[Reference, Reference | None] | None = None, settings: Settings | None = None, programs: Sequence[Program] | MutableMapping[Reference, Program] | None = None, resources: Sequence[ResourceRequirements] | MutableMapping[Reference, ResourceRequirements] | None = None, checkpoints: Checkpoints | None = None, resume: Dict[Reference, Path] | None = None)

Top-level class for all information in a yMMSL file.

description

A human-readable description of the configuration

imports

A list of import statements

models

Model (with submodels) to run. Dictionary mapping model names (as References) to Model objects.

custom_implementations

Non-default implementations for model components that fill in or override what those components specify.

settings

Settings to run the models with

programs

Programs to use to run the model. Dictionary mapping program names (as References) to Program objects.

resources

Resources to allocate for the model components. Dictionary mapping component names to ResourceRequirements objects.

checkpoints

Defines when each model component should create a snapshot

resume

Defines what snapshot each model component should resume from

check_consistent(check_runnable: bool = True, selected_model: str | None = None) None

Checks that the configuration is internally consistent.

This checks:

  • Whether all models are consistent, via Model.check_consistent()

  • That all implementations (programs and models) have a unique name

  • Whether all given component implementations exist

  • Whether all ports on components are consistent with their implementations

  • Whether all custom implementations have a component they apply to

  • Whether all settings are consistent with supported_settings, if specified

  • That resources have been requested for each component that has an implementation

If any of these requirements is false, this function will raise a RuntimeError with an explanation of the problem.

Parameters:
  • check_runnable – if False, skip the checks for whether component implementations exist and whether resources have been requested.

  • selected_model – if set, gives the name of the root model that we intend to run, and which must therefore have resources defined. Only used if check_runnable is True.

get_resources(name: Reference) ResourceRequirements

Get the resource requirements for a component.

If no resources are defined for this component and it uses a non-MPI execution model (DIRECT), a default of 1 thread is returned.

Parameters:

name – The name of the component to get resources for.

Returns:

The resource requirements for the component.

root_model(selected_model: Reference | None = None) Model

Return the root model of this configuration.

If there are multiple models that are not used as an implementation in any component (root models), and selected_model is given and names one of them, then that model is returned, otherwise an exception is raised.

If there is only a single root model, then it is returned, unless selected_model is given and does not match, in which case an exception is raised.

If there are no models, an exception is raised.

Parameters:

selected_model – Name of the model to return, in case of multiple options

Returns:

The sole or selected model that is not used as an implementation in any component in the configuration.

Raises:

RuntimeError if an error occurs, as described above.

update(overlay: Configuration) None

Update this configuration with the given overlay.

This will copy settings from overlay on top of the current settings, and merge in the various parts according to their update() functions.

Parameters:

overlay – A configuration to overlay onto this one.

class Document

Represents a yMMSL document.

This gets specialised by a top-level content class, and takes care of a special ymmsl_version attribute in the root of the YAML file.

class ExecutionModel(*values)

Describes how to start a model component.

DIRECT = 1

Start directly on the allocated core(s), without MPI.

INTELMPI = 3

Start using Intel MPI’s mpirun.

MANUAL = 5

Let the user start it by hand.

OPENMPI = 2

Start using OpenMPI’s mpirun.

SRUNMPI = 4

Start MPI implementation using srun.

class Identifier(seq: Any)

A custom string type that represents an identifier.

An identifier may consist of upper- and lowercase characters, digits, and underscores.

class Implementation(name: str, ports: Ports | None = None, description: str = 'Please add a description!', supported_settings: SupportedSettings | None = None)

Describes an implementation of a submodel

Models consist of connected components, and each component needs to be implemented somehow to be able to actually run the simulation. So we have Implementations, which can be either a Program, or another Model. This class represents the information all implementations need to have to use them as part of a model.

name

Name of this implementation, must be a valid Reference

ports

The ports this implementation has on which it sends and receives messages

class ImportKind(*values)

Describes the kind of object to import.

Currently only IMPLEMENTATION is supported, which is used to import either a program or a model. In the future other things will be importable as well.

class ImportStatement(module: str, kind: str, name: str)

Represents a yMMSL import statement.

These are of the form

from <a.b.c> import <kind> <name>

where a.b.c is the import path, corresponding to a relative filesystem path of a/b/c.ymmsl, kind is ‘implementation’, and name is the name of a model or program. Once imported, that model or program can then be referred to by its name in places where an implementation is required.

module

a.b.c module to import from

kind

Kind of object to import, currently always ‘implementation’

name

Name of the object to import from it

full_name() Reference

Return the full name of the imported object.

This returns a reference a.b.c.p for a statement import p from a.b.c.

module_path() Path

Return a relative path of the file to import from.

This returns a path a/b/c.ymmsl for module a.b.c.

class KeepsStateForNextUse(*values)

Describes whether an implementation keeps internal state between iterations of the reuse loop.

See also Keeps state for next use.

HELPFUL = 3

The implementation has an internal state, though this could be regenerated. Doing so may be expensive.

NECESSARY = 1

The implementation has an internal state that is necessary for continuing the implementation.

NO = 2

The implementation has no internal state.

class MPICoresResReq(name: Reference, mpi_processes: int, threads_per_mpi_process: int = 1)

Describes resources for simple MPI implementations.

This allocates individual cores or sets of cores on the same node for a given number of MPI processes per instance.

name

Name of the component to configure.

mpi_processes

Number of MPI processes to start.

threads_per_mpi_process

Number of threads/cores per process.

class MPINodesResReq(name: Reference, nodes: int, mpi_processes_per_node: int, threads_per_mpi_process: int = 1)

Describes resources for node based MPI implementations.

This allocates resources for an MPI process in terms of nodes and cores, processes and threads on them.

name

Name of the component to configure.

nodes

Number of nodes to reserve.

mpi_processes_per_node

Number of MPI processes to start on each node.

threads_per_mpi_process

Number of threads/cores per process.

class Model(name: str, ports: Ports | None = None, description: str = '', supported_settings: SupportedSettings | None = None, components: Sequence[Component] | None = None, conduits: Sequence[Conduit | MulticastConduit] | None = None)

Describes a simulation model.

A model consists of a number of components connected by conduits. It may have ports to communicate with models it’s embedded in.

Note that there may be no conduits, if there is only a single component. In that case, the conduits argument may be omitted when constructing the object, and also from the YAML file; the conduits attribute will then be set to an empty list.

name

The name by which this simulation model is known to the system.

ports

Ports through which this model can communicate with other models.

description

Human-readable description of the model.

supported_settings

Settings supported by this model.

components

A list of components making up the model.

conduits

A list of conduits connecting the components.

check_consistent() List[str]

Check that the model is internally consistent.

This checks:
  • that every conduit is connected to two existing ports on existing components, or on the model itself.

Returns a list of errors, or an empty list if none were found.

class Operator(*values)

An operator of a component.

This is a combination of the Submodel Execution Loop operators, and operators for other components such as mappers.

F_INIT = 1

Initialisation phase, before start of the SEL

NONE = 0

No operator

O_F = 5

Observation of final state, after the SEL

O_I = 2

State observation within the model’s main loop

S = 3

State update in the model’s main loop

allows_receiving() bool

Whether ports on this operator can receive.

allows_sending() bool

Whether ports on this operator can send.

class Port(name: Identifier, operator: Operator, timeline: Timeline | None = None)

A port on a component.

Ports are used by component to send or receive messages on. They are connected by conduits to enable communication between components.

name

The name of the port

operator

The MMSL operator in which this port is used

timeline

The timeline this port is on, relative to its component.

class Ports(f_init: None | str | List[str] = None, o_i: None | str | List[str] = None, s: None | str | List[str] = None, o_f: None | str | List[str] = None)
class Ports(f_init: List[Port])

Ports declaration for a component or implementation.

In YAML, ports on the default timelines are organised by operator, as follows:

ports:
  f_init:   # list of names
  - a
  - b
  o_f: d e  # on one line, space-separated
  o_i: c    # single port

Note that operators for which there are no ports may be omitted. If there are O_I and/or S ports that are on a non-default timeline, then they can be described like this:

ports:
  f_init: a
  o_f: b
  +timeline1:
    o_i: c d
    s: e
  +timeline2:
    o_i: f
    s: g h

Note that the + symbol is not a part of the timeline name, it’s just there to make it a bit easier to distinguish timelines from operators.

On the Python side, this class acts like a dictionary mapping port names to Port objects.

receiving_port_names() List[Identifier]

Return the names of all the receiving ports.

These are ports associated with F_INIT or S operators.

sending_port_names() List[Identifier]

Return the names of all the sending ports.

These are ports associated with O_I or O_F operators.

class Program(name: str, ports: Ports | None = None, description: str = '', supported_settings: SupportedSettings | None = None, base_env: BaseEnv | None = None, modules: str | List[str] | None = None, virtual_env: Path | None = None, env: Dict[str, str] | None = None, execution_model: ExecutionModel = ExecutionModel.DIRECT, executable: Path | None = None, args: str | List[str] | None = None, script: str | List[str] | None = None, can_share_resources: bool = True, keeps_state_for_next_use: KeepsStateForNextUse = KeepsStateForNextUse.NECESSARY)

Describes an installed program.

A Program normally has an executable and any other needed attributes, with script set to None. You should specify a script only as a last resort, probably after getting some help from the authors of this library. If a script is specified, all other attributes except for the name, the execution model, can_share_resources and keeps_state_for_next_use must be None.

If base_env is not specified then it defaults to MANAGER.

For execution_model, the following values are supported:

direct

The program will be executed directly. Use this for non-MPI programs.

openmpi

For MPI programs that should be started using OpenMPI’s mpirun.

intelmpi

For MPI programs that should be started using Intel MPI’s mpirun.

The can_share_resources attribute describes whether this program can share resources (cores) with other components in a macro-micro coupling. Set this to False if the program does significant computing inside of its time update loop after having sent messages on its O_I port(s) but before receiving messages on its S port(s). In the unlikely case that it’s doing significant computing before receiving for F_INIT or after sending its O_F messages, likewise set this to False.

Setting this to False unnecessarily will waste core hours, setting it to True incorrectly will slow down your simulation.

name

Name of the program

ports

Ports this program supports, if fixed

description

Human-readable description of this program

supported_settings

Settings supported by this program

base_env

Base environment to start from

modules

HPC software modules to load

virtual_env

Path to a virtual env to activate

env

Environment variables to set

execution_model

How to start the executable

executable

Full path to executable to run

args

Arguments to pass to the executable

script

A script that starts the program

can_share_resources

Whether this program can share resources (cores) with other components or not

keeps_state_for_next_use

Does this program keep state for the next iteration of the reuse loop. See KeepsStateForNextUse.

class Reference(parts: str | List[Identifier | int])

A reference to an object in the MMSL execution model.

References in string form are written as either:

  • an Identifier,

  • a Reference followed by a period and an Identifier, or

  • a Reference followed by an integer enclosed in square brackets.

In object form, they consist of a list of Identifiers and ints. The first list item is always an Identifier. For the rest of the list, an Identifier represents a period operator with that argument, while an int represents the indexing operator with that argument.

Reference objects act like a list of Identifiers and ints, you can get their length using len(), iterate through the parts using a loop, and get sublists or individual items using []. Note that the sublist has to be a valid Reference, so it cannot start with an int.

References can be compared for equality to each other or to a plain string, and they can be used as dictionary keys. Reference objects are immutable (or they’re supposed to be anyway), so do not try to change any of the elements. Instead, make a new Reference. Especially References that are used as dictionary keys must not be modified, this will get your dictionary in a very confused state.

without_trailing_ints() Reference

Returns a copy of this Reference with trailing ints removed.

Examples

a.b.c[1][2] -> a.b.c a[1].b.c -> a[1].b.c a.b.c -> a.b.c a[1].b.c[2] -> a[1].b.c

class ResourceRequirements(name: Reference)

Describes resources to allocate for components.

For non-MPI components, specifying resources is optional. If no resource is defined for a non-MPI component, a default of 1 thread will be assigned to it at runtime (e.g. by MUSCLE3).

name

Name of the component to configure.

class SettingType(*values)
class Settings(settings: Dict[str, str | int | float | bool | List[int] | List[float] | List[List[float]] | bool_union_fix] | None = None)

Settings for doing an experiment.

An experiment is done by running a model with particular settings, for the submodel scales, model parameters and any other configuration.

as_ordered_dict() OrderedDict

Represent as a dictionary of plain built-in types.

Returns: A dictionary that uses only built-in types, containing

the configuration.

copy() Settings

Makes a shallow copy of these settings and returns it.

get(key: Any, /) Any | None
get(key: Any, /, default: _T) Any | _T

Return the given setting, or default if it is not set.

If default is not given, returns None.

ordered_items() List[Tuple[Reference, str | int | float | bool | List[int] | List[float] | List[List[float]] | bool_union_fix]]

Return settings as a list of tuples.

class SupportedSetting(name: str | Identifier, typ: str | SettingType, description: str)

Supported setting for an implementation.

Attributes:

name: Name of the setting typ: Type of the setting description: Description of what this sets, allowed values, etc.

class SupportedSettings(supported_settings: Mapping[str, str] | List[SupportedSetting] | None = None)

Supported settings for an Implementation.

This allows describing which settings are supported, by name and type.

In YAML, this object is represented by a dict/mapping:

mode: str Which mode to use when calculating
accuracy:
  type: str
  description: |
    Accuracy of calculations

    Possible values:

    - low: Not very accurate, but fast. Good for testing.
    - medium: Slower and more accurate. Good for most use.
    - high: Slowest, but very accurate. Good for reference runs.
D: '[[float]] Diffusion kernel'
D2: [[float]]

Note the use of a text block to enable a longer description, and note that description of D needs to be quoted to avoid it being invalid YAML. If there is only the type, then the quotes can be omitted.

copy() SupportedSettings

Makes a shallow copy of these supported settings and returns it.

class ThreadedResReq(name: Reference, threads: int)

Describes resources for threaded implementations.

This includes singlethreaded and multithreaded implementations that do not support MPI. As many cores as specified will be allocated on a single node, for each instance.

If no resource is defined for a non-MPI component, a default of 1 thread will be assigned to it at runtime (e.g. by MUSCLE3).

name

Name of the component to configure.

threads

Number of threads/cores per instance.

class Timeline(timeline: str)
class Timeline(timeline: Sequence[str | Reference], absolute: bool = True)

Identify a timeline on which a port sends or receives.

Timeline objects describe when a port sends or receives, either relative to a parent timeline that calls the component they’re a part of, or by describing the whole list of components calling each other from the root timeline down.

A component c1 that is not called by any other component will have its F_INIT and O_F ports (if any) on the root timeline, which is represented by ‘:’. If c1’s implementation has a loop in which it sends on an O_I port and receives on an S port, then those ports are on a subtimeline, which is named after the component by default, ‘:c1’.

If we add a component c2 and connect its F_INIT and O_F to those ports, then we create a macro-micro type coupling. The F_INIT and O_F ports of c2 will then be on timeline ‘:c1’, because they’ll receive and send at the exact points in simulated time that c1’s O_I and S ports send and receive.

If c2 has its own O_I and S ports, then those will be on timeline ‘:c1:c2’, this being the concatenation of the parent timeline and the relative timeline within c2.

Some implementations may have more than one set of O_I/S ports, on which they communicate at different rates. In that case, each port should be given a local relative timeline explicitly. In the above example, if c1’s O_I and S ports were specified to be on local relative timeline ‘tl1’, then their full relative timeline is ‘c1.tl’ and their absolute timeline is ‘:c1.tl`, putting c2’s O_I and S ports on ‘:c1.tl:c2’ unless they too have an explicit timeline designation.

Timelines have a technical representation as a list of References, and a string representation in which those References are joined using colons.

resolve(module: Reference, config: Configuration) None

Resolve imports for the given configuration.

This updates the given configuration in place, removing all import statements and adding in the imported objects. Implementation names are updated to their absolute names by prefixing them with the given module, so they end up a.b.c.z instead of just z.

Parameters:
  • module – The module corresponding to this configuration

  • config – The configuration to resolve

Raises:

RuntimeError – if an error occurs due to an invalid configuration. This will leave config in a broken state, so reload it if you want to try again.

Package ymmsl.v0_1

Python bindings for yMMSL.

This package contains all the classes needed to represent a yMMSL file, as well as to read and write yMMSL files.

class BaseEnv(*values)

Describes the base shell environment for execution.

Several options in Implementation describe additions to the shell environment to make subsequently, but we need to start somewhere. Different starting points make sense in different contexts, so there’s a choice:

LOGIN starts from an environment that resembles the default environment of the user running the simulation. The exact environment you get when you log in depends on whether you’re on a text terminal, graphical terminal, or using SSH, and also on the operating system and any local changes made by your system administrator. This cannot be reproduced exactly in all circumstances, but the below should get close on most systems.

This will copy TERM, HOME, SHELL, USER, and LOGNAME from the manager environment, then run /bin/bash and have it load /etc/environment, /etc/profile, and then the first of ~/.bash_profile, ~/.bash_login, and ~/.profile that it finds. Note that on most machines, these files will load other files in turn, often including ~/.bashrc.

CLEAN starts from the environment that muscle_manager was started in, and then unloads any loaded modules and deactivates any active Python virtual environments. Any modules specified in modules and any virtual environment specified in virtual_env will be activate after that, of course.

Note that on some HPC machines, SLURM is made available though the environment modules. If you use the SRUNMPI execution model, then you’ll have to load it again explicitly to make the srun command available, or MUSCLE3 won’t be able to start your program.

MANAGER starts from the exact environment that muscle_manager was started in, including any loaded modules and active virtual environments. Any modules specified in modules are then loaded on top of this, which may cause some of the existing modules to be unloaded if they are incompatible. If a virtual environment is specified in virtual_env, then it will replace the active environment.

CLEAN = 2

Like MANAGER, but with any modules unloaded and venvs deactivated.

LOGIN = 1

The environment you get after logging in when using bash.

MANAGER = 3

The environment the manager was started in.

class CheckpointAtRule(at: List[float | int] | None)

Defines an “at” checkpoint rule.

An “at” checkpoint rule creates a snapshot at the specified moments.

at

List of checkpoints.

class CheckpointRangeRule(start: float | int | None = None, stop: float | int | None = None, every: float | int = 0)

Defines a range of checkpoint moments.

If start is supplied, this rule specifies a checkpoint at: start, start + every, start + 2*every, etc., for as long as start + n*every <= stop, with n a whole number. If stop is not given, the range continues indefinitely.

Start may be omitted, in which case a checkpoint is defined for 0, every, 2*every, etc. Note that in this case the range also extends to negative numbers (-every, -2*every, etc.), as simulated time may be negative (e.g. in rocket launch, t=0 is generally taken as lift-off time, but events already take place before that moment).

start

Start of the range.

stop

Stopping criterium of the range.

every

Step size of the range, must be positive.

class CheckpointRule

Defines a checkpoint rule.

There are two flavors of rules: CheckpointRangeRule and CheckpointAtRule. Do not use this class directly.

class Checkpoints(at_end: bool = False, wallclock_time: List[CheckpointRule] | None = None, simulation_time: List[CheckpointRule] | None = None)

Defines checkpoints in a configuration.

There are three checkpoint triggers: at_end, wallclock_time and simulation_time. The at_end trigger specifies that a checkpoint should be created just before the workflow finishes. The wallclock_time trigger is based on the elapsed real time since starting the muscle_manager in seconds. The simulation_time trigger is based on the time in the simulation as reported by the instances.

Note that the simulation_time trigger assumes a shared concept of time among all components of the model.

at_end

Whether a checkpoint should be created just before ending the workflow.

wallclock_time

Checkpoint rules for the wallclock_time trigger.

simulation_time

Checkpoint rules for the simulation_time trigger.

update(overlay: Checkpoints) None

Update this checkpoints with the given overlay.

Sets at_end to True if it is set in the overlay, otherwise at_end remains as is. Updates the checkpoint rules for wallclock time and simulation time. See CheckpointRules.update().

class Component(name: str, implementation: str | None = None, multiplicity: None | int | List[int] = None, ports: Ports | None = None)

An object declaring a simulation component.

Simulation components are things like submodels, scale bridges, proxies, and any other program that makes up a model. This class represents a declaration of a set of instances of a simulation component, and it’s used to describe which instances are needed to perform a certain simulation.

name

The name of this component.

Type:

ymmsl.Reference

implementation

A reference to the implementation to use.

Type:

ymmsl.Reference

multiplicity

The shape of the array of instances that execute simultaneously.

Type:

List[int]

ports

The ports of this component, organised by operator. None if not specified.

Type:

Optional[Ports]

instances() List[Reference]

Creates a list of instances needed.

Returns:

A list with one Reference for each instance of this component.

class Conduit(sender: str, receiver: str)

A conduit transports data between simulation components.

A conduit has two endpoints, which are references to a port on a simulation component. These references must be of one of the following forms:

  • component.port

  • namespace.component.port (or several namespace prefixes)

sender

The sending port that this conduit is connected to.

receiver

The receiving port that this conduit is connected to.

receiving_component() Reference

Returns a reference to the receiving component.

receiving_port() Identifier

Returns the identity of the receiving port.

receiving_slot() List[int]

Returns the slot on the receiving port.

If no slot was given, an empty list is returned.

Returns:

A list of slot indexes.

sending_component() Reference

Returns a reference to the sending component.

sending_port() Identifier

Returns the identity of the sending port.

sending_slot() List[int]

Returns the slot on the sending port.

If no slot was given, an empty list is returned.

Returns:

A list of slot indexes.

class Configuration(model: Model, settings: Settings | None = None, implementations: List[Implementation] | Dict[Reference, Implementation] = [], resources: Sequence[ResourceRequirements] | MutableMapping[Reference, ResourceRequirements] = [], description: str | None = None, checkpoints: Checkpoints | None = None, resume: Dict[Reference, Path] | None = None)

Configuration that includes all information for a simulation.

PartialConfiguration has some optional attributes, because we want to allow configuration files which only contain some of the information needed to run a simulation. At some point however, you need all the bits, and this class requires them.

When loading a yMMSL file, you will automatically get an object of this class if all the required components are there; if the file is incomplete, you’ll get a PartialConfiguration instead.

model

A model to run.

settings

Settings to run the model with.

implementations

Implementations to use to run the model. Dictionary mapping implementation names (as References) to Implementation objects.

resources

Resources to allocate for the model components. Dictionary mapping component names to Resources objects.

description

A human-readable description of the configuration.

checkpoints

Defines when each model component should create a snapshot

resume

Defines what snapshot each model component should resume from

check_consistent() None

Checks that the configuration is internally consistent.

This checks whether all conduits are connected to existing components, that there’s an implementation for every component, and that resources have been requested for each component.

If any of these requirements is false, this function will raise a RuntimeError with an explanation of the problem.

class ExecutionModel(*values)

Describes how to start a model component.

DIRECT = 1

Start directly on the allocated core(s), without MPI.

INTELMPI = 3

Start using Intel MPI’s mpirun.

OPENMPI = 2

Start using OpenMPI’s mpirun.

SRUNMPI = 4

Start MPI implementation using srun.

class Identifier(seq: Any)

A custom string type that represents an identifier.

An identifier may consist of upper- and lowercase characters, digits, and underscores.

class Implementation(name: Reference, base_env: BaseEnv | None = None, modules: str | List[str] | None = None, virtual_env: Path | None = None, env: Dict[str, str] | None = None, execution_model: ExecutionModel = ExecutionModel.DIRECT, executable: Path | None = None, args: str | List[str] | None = None, script: str | List[str] | None = None, can_share_resources: bool = True, keeps_state_for_next_use: KeepsStateForNextUse = KeepsStateForNextUse.NECESSARY)

Describes an installed implementation.

An Implementation normally has an executable and any other needed attributes, with script set to None. You should specify a script only as a last resort, probably after getting some help from the authors of this library. If a script is specified, all other attributes except for the name, the execution model, can_share_resources and keeps_state_for_next_use must be None.

If base_env is not specified then it defaults to MANAGER.

For execution_model, the following values are supported:

direct

The program will be executed directly. Use this for non-MPI programs.

openmpi

For MPI programs that should be started using OpenMPI’s mpirun.

intelmpi

For MPI programs that should be started using Intel MPI’s mpirun.

The can_share_resources attribute describes whether this implementation can share resources (cores) with other components in a macro-micro coupling. Set this to False if the implementation does significant computing inside of its time update loop after having sent messages on its O_I port(s) but before receiving messages on its S port(s). In the unlikely case that it’s doing significant computing before receiving for F_INIT or after sending its O_F messages, likewise set this to False.

Setting this to False unnecessarily will waste core hours, setting it to True incorrectly will slow down your simulation.

name

Name of the implementation

base_env

Base environment to start from

modules

HPC software modules to load

Type:

list[str] | None

virtual_env

Path to a virtual env to activate

env

Environment variables to set

execution_model

How to start the executable

executable

Full path to executable to run

args

Arguments to pass to the executable

Type:

list[str] | None

script

A script that starts the implementation

Type:

str | None

can_share_resources

Whether this implementation can share resources (cores) with other components or not

keeps_state_for_next_use

Does this implementation keep state for the next iteration of the reuse loop. See KeepsStateForNextUse.

class KeepsStateForNextUse(*values)

Describes whether an implementation keeps internal state between iterations of the reuse loop.

See also Keeps state for next use.

HELPFUL = 3

The implementation has an internal state, though this could be regenerated. Doing so may be expensive.

NECESSARY = 1

The implementation has an internal state that is necessary for continuing the implementation.

NO = 2

The implementation has no internal state.

class MPICoresResReq(name: Reference, mpi_processes: int, threads_per_mpi_process: int = 1)

Describes resources for simple MPI implementations.

This allocates individual cores or sets of cores on the same node for a given number of MPI processes per instance.

name

Name of the component to configure.

mpi_processes

Number of MPI processes to start.

threads_per_mpi_process

Number of threads/cores per process.

class MPINodesResReq(name: Reference, nodes: int, mpi_processes_per_node: int, threads_per_mpi_process: int = 1)

Describes resources for node based MPI implementations.

This allocates resources for an MPI process in terms of nodes and cores, processes and threads on them.

name

Name of the component to configure.

nodes

Number of nodes to reserve.

mpi_processes_per_node

Number of MPI processes to start on each node.

threads_per_mpi_process

Number of threads/cores per process.

class Model(name: str, components: List[Component], conduits: Sequence[Conduit | MulticastConduit] | None = None)

Describes a simulation model.

A model consists of a number of components connected by conduits.

Note that there may be no conduits, if there is only a single component. In that case, the conduits argument may be omitted when constructing the object, and also from the YAML file; the conduits attribute will then be set to an empty list.

name

The name by which this simulation model is known to the system.

components

A list of components making up the model.

conduits

A list of conduits connecting the components.

check_consistent() None

Checks that the model is internally consistent.

This checks whether all conduits are connected to existing components, and will raise a RuntimeError with an explanation if one is not.

update(overlay: Model) None

Overlay another model definition on top of this one.

This updates the object with the name, components and conduits given in the argument. The name is overwritten, and components are overwritten if they have the same name as an existing argument or else added.

Conduits are added. If a receiving port was already connected, the old conduit is removed. If a sending port was already connected, the new conduit is added and the sending port acts as a multicast port.

Parameters:

overlay – A Model definition to overlay on top of this one.

class ModelReference(name: str)

Describes a reference (by name) to a model.

name

The name of the simulation model this refers to.

class Operator(*values)

An operator of a component.

This is a combination of the Submodel Execution Loop operators, and operators for other components such as mappers.

F_INIT = 1

Initialisation phase, before start of the SEL

NONE = 0

No operator

O_F = 5

Observation of final state, after the SEL

O_I = 2

State observation within the model’s main loop

S = 3

State update in the model’s main loop

allows_receiving() bool

Whether ports on this operator can receive.

allows_sending() bool

Whether ports on this operator can send.

class PartialConfiguration(model: ModelReference | None = None, settings: Settings | None = None, implementations: List[Implementation] | Dict[Reference, Implementation] | None = None, resources: Sequence[ResourceRequirements] | MutableMapping[Reference, ResourceRequirements] | None = None, description: str | None = None, checkpoints: Checkpoints | None = None, resume: Dict[Reference, Path] | None = None)

Top-level class for all information in a yMMSL file.

model

A model to run.

settings

Settings to run the model with.

implementations

Implementations to use to run the model. Dictionary mapping implementation names (as References) to Implementation objects.

resources

Resources to allocate for the model components. Dictionary mapping component names to ResourceRequirements objects.

description

A human-readable description of the configuration.

checkpoints

Defines when each model component should create a snapshot

resume

Defines what snapshot each model component should resume from

as_configuration() Configuration

Converts to a full Configuration object.

This checks that this PartialConfiguration has all the pieces needed to run a simulation, and if so converts it to a Configuration object.

Note that this doesn’t check references, just that there is a model, implementations and resources. For the more extensive check, see Configuration.check_consistent().

Returns:

A corresponding Configuration.

Raises:

ValueError – If this configuration isn’t complete.

update(overlay: PartialConfiguration) None

Update this configuration with the given overlay.

This will update the model according to Model.update(), copy settings from overlay on top of the current settings, overwrite implementations with the same name and add implementations with a new name, and likewise for resources and resume. The description of the overlay is appended to the current description. Checkpoints are updated according to Checkpoints.update().

Parameters:

overlay – A configuration to overlay onto this one.

class Port(name: Identifier, operator: Operator)

A port on a component.

Ports are used by component to send or receive messages on. They are connected by conduits to enable communication between components.

name

The name of the port.

Type:

ymmsl.v0_1.identity.Identifier

operator

The MMSL operator in which this port is used.

Type:

ymmsl.v0_1.component.Operator

class Ports(f_init: None | str | List[str] = None, o_i: None | str | List[str] = None, s: None | str | List[str] = None, o_f: None | str | List[str] = None)

Ports declaration for a component.

Ports objects compare for equality by value. The names may be specified as a list of strings, or separated by spaces in a single string. If a particular operator has no associated ports, it may be omitted. For example:

ports:
  f_init:   # list of names
  - a
  - b
  o_i: c d  # on one line, space-separated
  s: e      # single port
            # o_f omitted as it has no ports
f_init

The ports associated with the F_INIT operator.

o_i

The ports associated with the O_I operator

s

The ports associated with the S operator.

o_f

The ports associated with the O_F operator

all_ports() Iterable[Port]

Returns an iterable containing all ports.

operator(port_name: Identifier) Operator

Looks up the operator for a given port.

Parameters:

port_name – Name of the port to look up.

Returns:

The operator for that port.

Raises:

KeyError – If no port with this name was found.

port_names() Iterable[Identifier]

Returns an iterable containing the names of all ports.

class Reference(parts: str | List[Identifier | int])

A reference to an object in the MMSL execution model.

References in string form are written as either:

  • an Identifier,

  • a Reference followed by a period and an Identifier, or

  • a Reference followed by an integer enclosed in square brackets.

In object form, they consist of a list of Identifiers and ints. The first list item is always an Identifier. For the rest of the list, an Identifier represents a period operator with that argument, while an int represents the indexing operator with that argument.

Reference objects act like a list of Identifiers and ints, you can get their length using len(), iterate through the parts using a loop, and get sublists or individual items using []. Note that the sublist has to be a valid Reference, so it cannot start with an int.

References can be compared for equality to each other or to a plain string, and they can be used as dictionary keys. Reference objects are immutable (or they’re supposed to be anyway), so do not try to change any of the elements. Instead, make a new Reference. Especially References that are used as dictionary keys must not be modified, this will get your dictionary in a very confused state.

without_trailing_ints() Reference

Returns a copy of this Reference with trailing ints removed.

Examples

a.b.c[1][2] -> a.b.c a[1].b.c -> a[1].b.c a.b.c -> a.b.c a[1].b.c[2] -> a[1].b.c

class ResourceRequirements(name: Reference)

Describes resources to allocate for components.

For non-MPI components, specifying resources is optional. If no resource is defined for a non-MPI component, a default of 1 thread will be assigned to it at runtime (e.g. by MUSCLE3).

name

Name of the component to configure.

class Settings(settings: Dict[str, str | int | float | bool | List[int] | List[float] | List[List[float]] | bool_union_fix] | None = None)

Settings for doing an experiment.

An experiment is done by running a model with particular settings, for the submodel scales, model parameters and any other configuration.

as_ordered_dict() OrderedDict

Represent as a dictionary of plain built-in types.

Returns: A dictionary that uses only built-in types, containing

the configuration.

copy() Settings

Makes a shallow copy of these settings and returns it.

get(key: Any, /) Any | None
get(key: Any, /, default: _T) Any | _T

Return the given setting, or default if it is not set.

If default is not given, returns None.

ordered_items() List[Tuple[Reference, str | int | float | bool | List[int] | List[float] | List[List[float]] | bool_union_fix]]

Return settings as a list of tuples.

class ThreadedResReq(name: Reference, threads: int)

Describes resources for threaded implementations.

This includes singlethreaded and multithreaded implementations that do not support MPI. As many cores as specified will be allocated on a single node, for each instance.

If no resource is defined for a non-MPI component, a default of 1 thread will be assigned to it at runtime (e.g. by MUSCLE3).

name

Name of the component to configure.

threads

Number of threads/cores per instance.