Functionality
Besides loading and saving yMMSL files and converting between YAML and Python objects,
the ymmsl package offers some related functionality that is briefly described here.
Format conversion
There are currently two versions of the yMMSL file format: v0.1 and v0.2. Both are
supported by this library, with the corresponding Python classes being available from
ymmsl.v0_1 and ymmsl.v0_2 respectively.
When you use ymmsl.load() to load a yMMSL file, you’ll get a
ymmsl.Document object in return. This is a base class for all yMMSL documents,
regardless of version. If the file is a v0.1 file, then the result will be an instance
of ymmsl.v0_1.Configuration, if it is a v0.2 file then it will be an instance
of ymmsl.v0_2.Configuration.
The ymmsl.convert_to() function can be used to convert a v0.1 Configuration to a
v0.2 Configuration, but not the other way around. In other words, only upgrades are
supported. To use it, tell it what you want and give it what you have:
import ymmsl
v0_1_config = ymmsl.v0_1.Configuration(...)
v0_2_config = ymmsl.convert_to(ymmsl.v0_2.Configuration, v0_1_config)
Note that we’re passing the type we want to have as the first argument, not an object.
Auto-convert on load
When implementing tools, you may want to auto-convert any input files to the latest
version and write your code against that, so that you don’t have to support multiple
versions. This can be done using ymmsl.load_as():
from pathlib import Path
import ymmsl
v0_2_config = ymmsl.load_as(ymmsl.v0_2.Configuration, Path('input.ymmsl'))
Now v0_2_config will be an instance of ymmsl.v0_2.Configuration even if
the input file is a version 0.1 file.
Merging
Because descriptions of models, scenarios, programs and runs are often made at different times and sometimes by different people, and because different combinations of them are needed to do different things, it’s often convenient to split them across multiple yMMSL files. A particular configuration then needs to be assembled from multiple files.
If you have a list of files that need to be merged (as MUSCLE3 does), then you can use
the ymmsl.v0_2.Configuration.update() function to merge them. To get the same
behaviour as MUSCLE3, start with the first Configuration in the list and successively
update it with the remaining ones.
Note that the rules for how files are combined have changed between the versions. For example, in yMMSL v0.1 it was legal to have a model description in one yMMSL file and update it with another description of the same model (with the same name), adding components and conduits. This is no longer allowed in v0.2, where the import functionality must be used instead.
Checking consistency
yMMSL files are full of cross-references, from conduits to ports on components, from
components to implementations, from settings to supported settings, and more. For a
configuration to be able to be run, these all need to be correct. The
ymmsl.v0_2.Configuration.check_consistent() function performs a variety of checks
to ensure that everything is consistent. It will raise a RuntimeError listing any
issues found in a user-friendly format.
Resolving imports
Imports may be used to obtain models and programs from other yMMSL files for use in the
present one. Loading a yMMSL file with imports productes a Configuration object
containing those import statements at config.imports, as a list of
ymmsl.v0_2.ImportStatement objects.
To get the imported objects, these imports must be resolved, which can be done through
the ymmsl.v0_2.resolve() function. This takes the name of the module corresponding
to a Configuration (which is the file name without the .ymmsl extension, wrapped
in a ymmsl.v0_2.Reference) and the configuration itself.
It updates that configuration in place, renaming any local models and programs to their absolute name by prefixing them with the given module name, then imports the implementations described in the import statements and adds them to the model under their full name. For example, an import statement
from a.b.c import implementation x
will result in a new model or program (depending on what it is) named a.b.c.x being
added to the programs or models section. References between components, models
and programs will be updated accordingly.
Imports are resolved recursively, so any implementations needed by an imported model will be imported and added as well. As a result, a single import statement may cause a whole collection of models and programs to be added.
After the imports have been resolved, the import statements are removed from the configuration in order to avoid confusion.