Skip to content

Models

Ready-made models of every science, built from the core and organized by field and subfield.

MODELS = ModelCatalog(Path(str(files('skcomplex.models')))) module-attribute

The catalog of the models of the library and of the models registered at run time.

ModelCatalog(root, package='skcomplex.models')

Catalog of the models of a package: their listings read from source, and their entries once registered.

The catalog lists every factory that a module of the package decorates with register_model, reading the registrations from the sources without importing them. Asking for the entry of a listed model imports its module, whose registration adds the entry. Models registered at run time outside the package are listed too.

Parameters:

Name Type Description Default
root Path

The directory of the model package, such as the directory of skcomplex.models.

required
package str

The dotted name of the model package.

'skcomplex.models'

Examples:

>>> from importlib.resources import files
>>> from pathlib import Path
>>> from skcomplex.models import ModelCatalog
>>> catalog = ModelCatalog(Path(str(files('skcomplex.models'))))
>>> catalog.listings(field='biology/ecology')[0].field
'biology/ecology'
Source code in src/skcomplex/models/_model_catalog.py
43
44
45
46
47
def __init__(self, root: Path, package: str = 'skcomplex.models') -> None:
    self.root = root
    self.package = package
    self._entries: dict[str, ModelEntry] = {}
    self._scanned: dict[str, ModelListing] | None = None

entry(name)

Return a model by name, importing the module of a listed model so that it registers.

Parameters:

Name Type Description Default
name str

The name of the model.

required

Returns:

Name Type Description
entry ModelEntry

The registered model.

Raises:

Type Description
RegistryError

No model has the name, with the closest names in the message.

Source code in src/skcomplex/models/_model_catalog.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def entry(self, name: str) -> ModelEntry:
    """Return a model by name, importing the module of a listed model so that it registers.

    Args:
        name:
            The name of the model.

    Returns:
        entry:
            The registered model.

    Raises:
        RegistryError:
            No model has the name, with the closest names in the message.
    """
    if name not in self._entries:
        listing = self._listed().get(name)
        if listing is not None:
            importlib.import_module(listing.module)
    if name not in self._entries:
        known = sorted({*self._listed(), *self._entries})
        close = difflib.get_close_matches(name, known, n=3)
        hint = f' Did you mean {", ".join(close)}?' if close else ' List the models with `catalog()`.'
        error_msg = f'No model is registered as {name!r}.{hint}'
        raise RegistryError(error_msg)
    return self._entries[name]

listings(field=None, tag=None)

Return the listed models, optionally of one field or with one tag, sorted by field and name.

Parameters:

Name Type Description Default
field str | None

The field, such as biology, or the field path, such as biology/ecology, to keep, or None for every field.

None
tag str | None

The tag to keep, or None for every model.

None

Returns:

Name Type Description
listings list[ModelListing]

The listed models.

Raises:

Type Description
RegistryError

A model registers its name, field, description, tags, or references with a value computed at run time.

Source code in src/skcomplex/models/_model_catalog.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def listings(self, field: str | None = None, tag: str | None = None) -> list[ModelListing]:
    """Return the listed models, optionally of one field or with one tag, sorted by field and name.

    Args:
        field:
            The field, such as `biology`, or the field path, such as `biology/ecology`, to keep, or `None` for
            every field.

        tag:
            The tag to keep, or `None` for every model.

    Returns:
        listings:
            The listed models.

    Raises:
        RegistryError:
            A model registers its name, field, description, tags, or references with a value computed at run time.
    """
    listings = dict(self._listed())
    for name, entry in self._entries.items():
        listings.setdefault(
            name,
            ModelListing(
                entry.name,
                entry.field,
                entry.description,
                entry.tags,
                entry.references,
                entry.factory.__module__,
            ),
        )
    return sorted(
        (
            listing
            for listing in listings.values()
            if (field is None or listing.field == field or listing.field.startswith(f'{field}/'))
            and (tag is None or tag in listing.tags)
        ),
        key=lambda listing: (listing.field, listing.name),
    )

register(entry)

Register a model under its name, replacing any previous one.

An entry without a description takes the first line of its factory's docstring, without a leading Return.

Parameters:

Name Type Description Default
entry ModelEntry

The registered model.

required
Source code in src/skcomplex/models/_model_catalog.py
49
50
51
52
53
54
55
56
57
58
59
60
def register(self, entry: ModelEntry) -> None:
    """Register a model under its name, replacing any previous one.

    An entry without a description takes the first line of its factory's docstring, without a leading `Return`.

    Args:
        entry:
            The registered model.
    """
    if not entry.description:
        entry = dataclasses.replace(entry, description=self._summarize(inspect.getdoc(entry.factory) or ''))
    self._entries[entry.name] = entry

ModelEntry(name, factory, field, description, tags=(), references=(), defaults=dict(), observables=None, initial_state=None, settings=None) dataclass

Registered model: a factory with its metadata and default run settings.

Parameters:

Name Type Description Default
name str

The name the model is created by.

required
factory Factory

The function that returns the model's system.

required
field str

The field path of the model, its science and subfield, such as biology/ecology.

required
description str

One sentence on what the model shows.

required
tags tuple[str, ...]

Keywords for filtering.

()
references tuple[str, ...]

The sources of the model.

()
defaults Mapping[str, object]

The default run settings: until and any Simulation argument such as dt or solver.

dict()
observables Callable[[System], Sequence[ObservableSpec]] | None

A function of the model's system that returns its standard observables, or None for none.

None
initial_state Callable[..., Mapping[str, object] | None] | None

A function of the factory's arguments that returns the initial values run gives the simulation, by dotted state name, or None for the elements' own.

None
settings Callable[..., Mapping[str, object]] | None

A function of the factory's arguments that returns the run settings that replace defaults for these arguments, or None.

None

formalism property

Mathematical form of the model with its default parameters, inferred by compiling it once.

parameters property

Parameters of the factory with their defaults.

check()

Validate that the default run settings are known and every argument of the factory has a default.

Returns:

Name Type Description
entry ModelEntry

The entry itself.

Raises:

Type Description
ValidationError

A default run setting is unknown, or a factory argument has no default.

Source code in src/skcomplex/models/_model_entry.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def check(self) -> 'ModelEntry':
    """Validate that the default run settings are known and every argument of the factory has a default.

    Returns:
        entry:
            The entry itself.

    Raises:
        ValidationError:
            A default run setting is unknown, or a factory argument has no default.
    """
    unknown = set(self.defaults) - set(RUN_SETTINGS)
    if unknown:
        error_msg = f'Unknown run settings {sorted(unknown)} for {self.name}. Use {", ".join(RUN_SETTINGS)}.'
        raise ValidationError(error_msg)
    required = [
        parameter.name
        for parameter in inspect.signature(self.factory).parameters.values()
        if parameter.default is inspect.Parameter.empty
        and parameter.kind not in {inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD}
    ]
    if required:
        names = ', '.join(required)
        error_msg = f'The factory {self.factory.__name__} needs defaults for {names} to be registered.'
        raise ValidationError(error_msg)
    return self

initial_state_of(**parameters)

Return the initial values run gives the simulation for arguments of the factory.

Parameters:

Name Type Description Default
**parameters object

Arguments of the factory that replace its defaults.

{}

Returns:

Name Type Description
initial_state dict[str, object]

The initial values by dotted state name, empty for the elements' own.

Source code in src/skcomplex/models/_model_entry.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def initial_state_of(self, **parameters: object) -> dict[str, object]:
    """Return the initial values `run` gives the simulation for arguments of the factory.

    Args:
        **parameters:
            Arguments of the factory that replace its defaults.

    Returns:
        initial_state:
            The initial values by dotted state name, empty for the elements' own.
    """
    if self.initial_state is None:
        return {}
    return dict(self.initial_state(**{**self.parameters, **parameters}) or {})

observables_of(system)

Return the standard observables of a system of the model, which run records.

Parameters:

Name Type Description Default
system System

A system the model's factory returned.

required

Returns:

Name Type Description
observables list[ObservableSpec]

The observables, labelled by their path from the root.

Source code in src/skcomplex/models/_model_entry.py
139
140
141
142
143
144
145
146
147
148
149
150
def observables_of(self, system: System) -> list[ObservableSpec]:
    """Return the standard observables of a system of the model, which `run` records.

    Args:
        system:
            A system the model's factory returned.

    Returns:
        observables:
            The observables, labelled by their path from the root.
    """
    return [] if self.observables is None else list(self.observables(system))

settings_of(**parameters)

Return the default run settings for arguments of the factory.

Parameters:

Name Type Description Default
**parameters object

Arguments of the factory that replace its defaults.

{}

Returns:

Name Type Description
settings dict[str, object]

The run settings, defaults replaced by what settings returns for the arguments.

Source code in src/skcomplex/models/_model_entry.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def settings_of(self, **parameters: object) -> dict[str, object]:
    """Return the default run settings for arguments of the factory.

    Args:
        **parameters:
            Arguments of the factory that replace its defaults.

    Returns:
        settings:
            The run settings, `defaults` replaced by what `settings` returns for the arguments.
    """
    if self.settings is None:
        return dict(self.defaults)
    return {**self.defaults, **self.settings(**{**self.parameters, **parameters})}

ModelListing(name, field, description, tags, references, module) dataclass

Model as the catalog lists it: its name and metadata, and the module that registers it.

Parameters:

Name Type Description Default
name str

The name the model is created by.

required
field str

The field path of the model, such as biology/ecology.

required
description str

One sentence on what the model shows.

required
tags tuple[str, ...]

Keywords for filtering.

required
references tuple[str, ...]

The sources of the model.

required
module str

The dotted name of the module that registers the model.

required

catalog(field=None, tag=None)

Return the listed models, optionally of one field or with one tag, sorted by field and name.

The listing reads the registration of every model of the library from its source, and imports no model module. Models registered at run time outside the library are listed too.

Parameters:

Name Type Description Default
field str | None

The field, such as biology, or the field path, such as biology/ecology, to keep, or None for every field.

None
tag str | None

The tag to keep, or None for every model.

None

Returns:

Name Type Description
listings list[ModelListing]

The listed models.

Raises:

Type Description
RegistryError

A model registers its name, field, description, tags, or references with a value computed at run time.

Examples:

>>> from skcomplex.models import catalog
>>> 'lotka_volterra' in [listing.name for listing in catalog(field='biology/ecology')]
True
>>> sorted({listing.field for listing in catalog(field='chemistry')})
['chemistry/kinetics', 'chemistry/pattern_formation']
Source code in src/skcomplex/models/_catalog.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
def catalog(field: str | None = None, tag: str | None = None) -> list[ModelListing]:
    """Return the listed models, optionally of one field or with one tag, sorted by field and name.

    The listing reads the registration of every model of the library from its source, and imports no model module.
    Models registered at run time outside the library are listed too.

    Args:
        field:
            The field, such as `biology`, or the field path, such as `biology/ecology`, to keep, or `None` for every
            field.

        tag:
            The tag to keep, or `None` for every model.

    Returns:
        listings:
            The listed models.

    Raises:
        RegistryError:
            A model registers its name, field, description, tags, or references with a value computed at run time.

    Examples:
        >>> from skcomplex.models import catalog
        >>> 'lotka_volterra' in [listing.name for listing in catalog(field='biology/ecology')]
        True
        >>> sorted({listing.field for listing in catalog(field='chemistry')})
        ['chemistry/kinetics', 'chemistry/pattern_formation']
    """
    return MODELS.listings(field, tag)

create(name, **parameters)

Return the system of a model, built by its factory.

It imports the model's module and nothing else of the library.

Parameters:

Name Type Description Default
name str

The name of the model.

required
**parameters object

Arguments of the factory that replace its defaults.

{}

Returns:

Name Type Description
system System

The model's system.

Raises:

Type Description
RegistryError

No model has the name.

Examples:

>>> from skcomplex.models import create
>>> len(create('lotka_volterra').elements)
2
Source code in src/skcomplex/models/_catalog.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def create(name: str, **parameters: object) -> System:
    """Return the system of a model, built by its factory.

    It imports the model's module and nothing else of the library.

    Args:
        name:
            The name of the model.

        **parameters:
            Arguments of the factory that replace its defaults.

    Returns:
        system:
            The model's system.

    Raises:
        RegistryError:
            No model has the name.

    Examples:
        >>> from skcomplex.models import create
        >>> len(create('lotka_volterra').elements)
        2
    """
    return MODELS.entry(name).factory(**parameters)

describe(name)

Return a model by name, with its factory, parameters, metadata, and default run settings.

It imports the model's module and nothing else of the library.

Parameters:

Name Type Description Default
name str

The name of the model.

required

Returns:

Name Type Description
entry ModelEntry

The registered model.

Raises:

Type Description
RegistryError

No model has the name.

Examples:

>>> from skcomplex.models import describe
>>> describe('lotka_volterra').field
'biology/ecology'
Source code in src/skcomplex/models/_catalog.py
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def describe(name: str) -> ModelEntry:
    """Return a model by name, with its factory, parameters, metadata, and default run settings.

    It imports the model's module and nothing else of the library.

    Args:
        name:
            The name of the model.

    Returns:
        entry:
            The registered model.

    Raises:
        RegistryError:
            No model has the name.

    Examples:
        >>> from skcomplex.models import describe
        >>> describe('lotka_volterra').field
        'biology/ecology'
    """
    return MODELS.entry(name)

register_model(name=None, *, field, description=None, tags=(), references=(), defaults=None, settings=None, observables=None, initial_state=None)

Return a decorator that registers a factory as a ready-made model.

Parameters:

Name Type Description Default
name str | None

The name of the model, or None for the factory's name.

None
field str

The field path of the model, its science and subfield, such as biology/ecology.

required
description str | None

One sentence on what the model shows, or None for the first line of the factory's docstring.

None
tags Sequence[str]

Keywords for filtering.

()
references Sequence[str]

The sources of the model.

()
defaults Mapping[str, object] | None

The default run settings: until and any Simulation argument such as dt or solver.

None
settings Callable[..., Mapping[str, object]] | None

A function of the factory's arguments that returns the run settings that replace defaults for these arguments, or None.

None
observables Callable[[System], Sequence[ObservableSpec]] | None

A function of the model's system that returns its standard observables, which run records, or None.

None
initial_state Callable[..., Mapping[str, object] | None] | None

A function of the factory's arguments that returns the initial values run gives the simulation, by dotted state name, or None.

None

Returns:

Name Type Description
decorator Callable[[Factory], Factory]

The decorator, which returns the factory unchanged.

Raises:

Type Description
ValidationError

A default run setting is unknown, or a factory argument has no default.

Examples:

>>> from typing import Annotated
>>> from skcomplex.core.elements import Element
>>> from skcomplex.specs import StateSpec
>>> from skcomplex.core.system import System
>>> from skcomplex.models import create, register_model
>>> class PopulationElement(Element):
...     def __init__(self, label, n: Annotated[float, StateSpec()] = 1.0):
...         super().__init__(label)
...         self.n = n
>>> @register_model(field='examples/demo', references=('This docstring',), defaults={'until': 5})
... def constant_population(n: float = 3.0) -> System:
...     '''Return one population that does not change.'''
...     return System(elements=[PopulationElement('a', n=n)])
>>> create('constant_population').elements[0].n
3.0
Source code in src/skcomplex/models/_catalog.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
def register_model(
    name: str | None = None,
    *,
    field: str,
    description: str | None = None,
    tags: Sequence[str] = (),
    references: Sequence[str] = (),
    defaults: Mapping[str, object] | None = None,
    settings: Callable[..., Mapping[str, object]] | None = None,
    observables: Callable[[System], Sequence[ObservableSpec]] | None = None,
    initial_state: Callable[..., Mapping[str, object] | None] | None = None,
) -> Callable[[Factory], Factory]:
    """Return a decorator that registers a factory as a ready-made model.

    Args:
        name:
            The name of the model, or `None` for the factory's name.

        field:
            The field path of the model, its science and subfield, such as `biology/ecology`.

        description:
            One sentence on what the model shows, or `None` for the first line of the factory's docstring.

        tags:
            Keywords for filtering.

        references:
            The sources of the model.

        defaults:
            The default run settings: `until` and any `Simulation` argument such as `dt` or `solver`.

        settings:
            A function of the factory's arguments that returns the run settings that replace `defaults` for these
            arguments, or `None`.

        observables:
            A function of the model's system that returns its standard observables, which `run` records, or `None`.

        initial_state:
            A function of the factory's arguments that returns the initial values `run` gives the simulation, by dotted
            state name, or `None`.

    Returns:
        decorator:
            The decorator, which returns the factory unchanged.

    Raises:
        ValidationError:
            A default run setting is unknown, or a factory argument has no default.

    Examples:
        >>> from typing import Annotated
        >>> from skcomplex.core.elements import Element
        >>> from skcomplex.specs import StateSpec
        >>> from skcomplex.core.system import System
        >>> from skcomplex.models import create, register_model
        >>> class PopulationElement(Element):
        ...     def __init__(self, label, n: Annotated[float, StateSpec()] = 1.0):
        ...         super().__init__(label)
        ...         self.n = n
        >>> @register_model(field='examples/demo', references=('This docstring',), defaults={'until': 5})
        ... def constant_population(n: float = 3.0) -> System:
        ...     '''Return one population that does not change.'''
        ...     return System(elements=[PopulationElement('a', n=n)])
        >>> create('constant_population').elements[0].n
        3.0
    """

    def decorator(factory: Factory) -> Factory:
        entry = ModelEntry(
            name or factory.__name__,
            factory,
            field,
            description or '',
            tuple(tags),
            tuple(references),
            dict(defaults or {}),
            observables,
            initial_state,
            settings,
        )
        MODELS.register(entry.check())
        return factory

    return decorator

run(name, *, settings=None, **parameters)

Create a model and simulate it with its default run settings.

It imports the model's module and nothing else of the library.

Parameters:

Name Type Description Default
name str

The name of the model.

required
settings Mapping[str, object] | None

Run settings that replace the model's defaults: until and any Simulation argument. The run is seeded with random_state=0 unless the settings give another seed, or None for a fresh run.

None
**parameters object

Arguments of the factory that replace its defaults.

{}

Returns:

Name Type Description
simulation Simulation

The finished simulation, whose trajectory_ holds the run.

Raises:

Type Description
RegistryError

No model has the name.

ValidationError

The setting until is not a number, or initial_state is not a mapping.

ArgumentError

A setting is neither until nor an argument of Simulation.

Source code in src/skcomplex/models/_catalog.py
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
def run(name: str, *, settings: Mapping[str, object] | None = None, **parameters: object) -> Simulation:
    """Create a model and simulate it with its default run settings.

    It imports the model's module and nothing else of the library.

    Args:
        name:
            The name of the model.

        settings:
            Run settings that replace the model's defaults: `until` and any `Simulation` argument. The run is seeded
            with `random_state=0` unless the settings give another seed, or `None` for a fresh run.

        **parameters:
            Arguments of the factory that replace its defaults.

    Returns:
        simulation:
            The finished simulation, whose `trajectory_` holds the run.

    Raises:
        RegistryError:
            No model has the name.

        ValidationError:
            The setting `until` is not a number, or `initial_state` is not a mapping.

        ArgumentError:
            A setting is neither `until` nor an argument of `Simulation`.
    """
    entry = MODELS.entry(name)
    chosen: dict[str, object] = {'random_state': 0, **entry.settings_of(**parameters), **(settings or {})}
    until = chosen.pop('until', 10.0)
    if not isinstance(until, int | float):
        error_msg = f'The run setting `until` of {name} is {until!r}. Give it as a number.'
        raise ValidationError(error_msg)
    initial_state = chosen.pop('initial_state', None) or {}
    if not isinstance(initial_state, Mapping):
        error_msg = f'The run setting `initial_state` of {name} is {initial_state!r}. Give it as a mapping.'
        raise ValidationError(error_msg)
    system = entry.factory(**parameters)
    initial = {**entry.initial_state_of(**parameters), **initial_state}
    simulation = Simulation(system, observables=entry.observables_of(system), initial_state=initial)
    unknown = sorted(set(chosen) - set(simulation.get_params(deep=False)))
    if unknown:
        error_msg = f'No run setting named {unknown[0]!r}. Give `until` or an argument of `Simulation`.'
        raise ArgumentError(error_msg)
    return simulation.set_params(**chosen).check().prepare().run(until=float(until))