Skip to content

epymorph.database

A Database in epymorph organizes data attributes in a hierarchical namespace of three components, as in: "strata::module::attribute_id".

The goal is to be as flexibile as possible for users specifying requirements and values which fulfill those requirements. For example, you can provide a value for "::::population" to indicate that every strata and every module should use the same value if they require a "population" attribute. Or you can provide "*::init::population" to indicate that only initializers should use this value, and, presumably, another value is more appropriate for other modules like movement.

Specialized database instances provide the ability to "layer" data for a simulation -- if the outermost database has a matching value, that value is used, otherwise the search for a match proceeds to the inner layers (recursively).

T module-attribute

T = TypeVar('T')

Type of database values.

V module-attribute

V = TypeVar('V')

The value type for a requirements tree.

Match

Bases: NamedTuple, Generic[T]

The result of a database query.

pattern instance-attribute

pattern: NamePattern

The name pattern of the value that matched.

value instance-attribute

value: T

The matched value.

Database

Database(data: dict[NamePattern, T])

Bases: Generic[T]

A dictionary of key/value pairs where keys obey epymorph's namespace system.

Namespaces are in the form "a::b::c", where "a" is a strata, "b" is a module, and "c" is an attribute name. Values can be assigned with wildcards (specified by asterisks), so that key "*::b::c" matches queries for both "a::b::c" and "z::b::c".

See Also

Database is mostly used to store raw-form parameter values declared by the user. In constrast epymorph.database.DataResolver stores fully-evaluated parameters optimized for use by simulation logic.

Parameters:

query

query(key: str | AbsoluteName) -> Match[T] | None

Query this database for a key match.

Parameters:

  • key (str | AbsoluteName) –

    The name to find. If given as a string, it is first parsed as an AbsoluteName.

Returns:

  • Match[T] | None

    The found value, if any, else None.

to_dict

to_dict() -> dict[NamePattern, T]

Return the database's data as a dictionary.

Returns:

DatabaseWithFallback

DatabaseWithFallback(
    data: dict[NamePattern, T], fallback: Database[T]
)

Bases: Database[T]

A specialization of Database which has its own key/value pairs as well as a fallback Database.

If a match is not found in this database, the fallback is checked (recursively).

Parameters:

  • data (dict[NamePattern, T]) –

    The highest priority values in the database.

  • fallback (Database[T]) –

    A database containing fallback values; if a match cannot be found in data, this database will be checked.

query

query(key: str | AbsoluteName) -> Match[T] | None

Query this database for a key match.

Parameters:

  • key (str | AbsoluteName) –

    The name to find. If given as a string, it is first parsed as an AbsoluteName.

Returns:

  • Match[T] | None

    The found value, if any, else None.

to_dict

to_dict() -> dict[NamePattern, T]

Return the database's data as a dictionary.

Returns:

DatabaseWithStrataFallback

DatabaseWithStrataFallback(
    data: dict[NamePattern, T],
    children: dict[str, Database[T]],
)

Bases: Database[T]

A specialization of Database which has per-strata fallback databases.

For example consider querying this database for "a::b::c". If there is no match in this database but there is a fallback for strata "a", the search proceeds to that database (which could continue recursively).

Parameters:

query

query(key: str | AbsoluteName) -> Match[T] | None

Query this database for a key match.

Parameters:

  • key (str | AbsoluteName) –

    The name to find. If given as a string, it is first parsed as an AbsoluteName.

Returns:

  • Match[T] | None

    The found value, if any, else None.

to_dict

to_dict() -> dict[NamePattern, T]

Return the database's data as a dictionary.

Returns:

DataResolver

DataResolver(
    dim: Dimensions,
    values: dict[AbsoluteName, AttributeArray]
    | None = None,
)

Stores evaluated data attributes for a simulation.

Data (typically parameter values) are provided by the user as a collection of key-value pairs, with the values in several forms. These are evaluated to turn them into our internal data representation which is optimized for simulation execution.

Users typically provide parameter values with keys in NamePattern form, but during parameter resolution values are stored by AbsoluteName to facilitate lookups by the systems that use them. It is possible that multiple AbsoluteNames resolve to the same value (which is done in a memory-efficient way).

Data requirements also specify the expected type and shape of the data, and DataResolver also manages the resulting complexity. Two requirements could be fulfilled by the same value while having different type and shape specifications. Instead of rejecting this as an error, epymorph allows this provided the given value can be successfully coerced to fit both requirements independently. For example, a scalar integer value can be coerced to fit requirements for an N-shaped array of floats as well as a T-shaped array of integers. DataResolver accomplishes this in an efficient way by storing both the original values and all adapted values. If multiple requirements specify the same type/shape adaptation for one value, the adaptation only needs to happen once.

DataResolver is partially mutable -- new values can be added but values cannot be overwritten or removed.

Parameters:

  • dim (Dimensions) –

    The simulation dimensions; needed to perform shape adaptations.

  • values (dict[AbsoluteName, AttributeArray] | None, default: None ) –

    The values that should be in the resolver to begin with.

Key class-attribute instance-attribute

The type of the key used to track adapted values.

raw_values property

The mapping of raw values in the resolver, by absolute name.

Warning:

It is not safe to modify this mapping!

has

has(name: AbsoluteName) -> bool

Test whether or not a given name is in this resolver.

Parameters:

Returns:

  • bool

    True if name is in this resolver.

get_raw

get_raw(
    name: str | NamePattern | AbsoluteName,
) -> AttributeArray

Retrieve a raw value that matches the given name.

Parameters:

  • name (str | NamePattern | AbsoluteName) –

    The name of the value to retrieve. This can be an AbsoluteName, a NamePattern, or a string. A string will be parsed as an AbsoluteName if possible, and fall back to parsing it as a NamePattern. In any case, the name must match exactly one value in order to return successfully.

Returns:

Raises:

  • ValueError

    If the name is not present in the resolver, or if the name is ambiguous and matches more than one value.

add

add(name: AbsoluteName, value: AttributeArray) -> None

Add a value to this resolver. You may not overwrite an existing name.

Parameters:

Raises:

  • ValueError

    If name is already in this resolver.

resolve

resolve(
    name: AbsoluteName, definition: AttributeDef
) -> AttributeArray

Resolve a value known by name to fit the given requirement definition.

Parameters:

  • name (AbsoluteName) –

    The name of the value to resolve.

  • definition (AttributeDef) –

    The definition of the requirement being fulfilled; this provides the necessary type and shape information.

Returns:

Raises:

resolve_txn_series

resolve_txn_series(
    requirements: Iterable[
        tuple[AbsoluteName, AttributeDef]
    ],
    tau_steps: int,
) -> Iterator[list[AttributeValue]]

Generate an iterator on the series of values for the given requirements. Each item produced by the generator is a sequence of scalar values, one for each attribute (in the given order).

The sequence of items is generated in simulation order --

  • day=0, tau step=0, node=0 => [beta, gamma, xi]
  • day=0, tau_step=0; node=1 => [beta, gamma, xi]
  • day=0, tau_step=1; node=0 => [beta, gamma, xi]
  • day=0, tau_step=1; node=1 => [beta, gamma, xi]
  • and so on.

This is a convenient alternative to resolving all of the TxN arrays separately, and managing the iteration yourself.

Parameters:

  • requirements (Iterable[tuple[AbsoluteName, AttributeDef]]) –

    The name-definition pairs for all of the attributes to include.

  • tau_steps (int) –

    The number of tau steps per day; since T in a TxN array is simulation days, this simply repeats values such that all of a day's tau steps see the same value.

Returns:

to_dict

to_dict(
    *, simplify_names: bool = False
) -> (
    dict[AbsoluteName, AttributeArray]
    | dict[str, AttributeArray]
)

Extract a dictionary from this resolver containing all of its raw (non-adapted) key-value pairs.

Parameters:

  • simplify_names (bool, default: False ) –

    True to return stringified names. By default, names are returned in AbsoluteName form.

Returns:

Requirement

Bases: NamedTuple

A RUME data requirement: a name and a definition.

name instance-attribute

The requirement name.

definition instance-attribute

definition: AttributeDef

The requirement definition.

Resolution

Bases: ABC

Describes the source used to resolve a requirement in a RUME.

cacheable instance-attribute

cacheable: bool

True if this resolution can be cached.

MissingValue dataclass

MissingValue()

Bases: Resolution

Requirement was not resolved.

cacheable class-attribute instance-attribute

cacheable: bool = field(init=False, default=False)

True if this resolution can be cached.

ParameterValue dataclass

ParameterValue(cacheable: bool, pattern: NamePattern)

Bases: Resolution

Requirement was resolved by a RUME parameter.

Parameters:

  • cacheable (bool) –

    True if this resolution can be cached.

  • pattern (NamePattern) –

    The name by which the value was resolved.

cacheable instance-attribute

cacheable: bool

True if this resolution can be cached.

pattern instance-attribute

pattern: NamePattern

The name by which the value was resolved.

DefaultValue dataclass

DefaultValue(default_value: AttributeValue)

Bases: Resolution

Requirement was resolved by an attribute default value.

Parameters:

cacheable class-attribute instance-attribute

cacheable: bool = field(init=False, default=True)

True if this resolution can be cached.

default_value instance-attribute

default_value: AttributeValue

The value that was resolved.

ResolutionTree dataclass

ResolutionTree(
    resolution: Resolution,
    children: tuple[ResolutionTree, ...],
)

In resolving parameters, parameter values might have their own requirements. This potentially forms a tree of requirements that must be resolved first in order to resolve the root of the tree.

If two values share the same resolution tree, and if the tree is comprised entirely of pure functions and constant values, then the resolution result would be the same.

Parameters:

  • resolution (Resolution) –

    The root node resolution.

  • children (tuple[ResolutionTree, ...]) –

    The resolution of the root's requirements (empty if it has no requirements).

See Also

A resolution tree is a dual of the epymorph.database.ReqTree, but contains only the resolution results and not any resolved values.

resolution instance-attribute

resolution: Resolution

The root node resolution.

children instance-attribute

children: tuple[ResolutionTree, ...]

The resolution of the root's requirements (empty if it has no requirements).

is_tree_cacheable property

is_tree_cacheable: bool

Is the entire tree cacheable? Only true if all resolutions are cacheable.

RecursiveValue

Bases: Protocol

The interface for parameter values that may depend on other parameter values.

requirements instance-attribute

requirements: Sequence[AttributeDef]

Defines the data requirements for this value.

randomized instance-attribute

randomized: bool

Should this value be re-evaluated every time it's referenced? Mostly useful for results that incorporate random values.

ReqTree dataclass

ReqTree(children: tuple[ReqNode, ...])

Bases: Generic[V]

A requirements tree describes how data requirements are resolved for a RUME.

Models used in the RUME have a set of requirements, each of which may be fulfilled by RUME parameters or default values. RUME parameters may also have data requirements, and those requirements may have requirements, etc., hence the need to represent this as a tree structure. The top of a ReqTree is a ReqRoot and each requirement is a ReqNode. Each ReqNode tracks the attribute itself (its AbsoluteName and AttributeDef), whether it is fulfilled, and if so how.

Parameters:

  • children (tuple[ReqNode, ...]) –

    The child nodes of this node.

children instance-attribute

children: tuple[ReqNode, ...]

The child nodes of this node.

to_string abstractmethod

to_string(
    format_name: Callable[[AbsoluteName], str] = str,
    depth: int = 0,
) -> str

Convert this tree to a string.

Parameters:

  • format_name (Callable[[AbsoluteName], str], default: str ) –

    A method to convert an absolute name into a string. This allows you to control how absolute names are rendered.

  • depth (int, default: 0 ) –

    The resolution "depth" of this node in the tree. Top-level requirements have a depth of 0. A requirement that itself is required by one other requirement would have a depth of 1.

Returns:

  • str

    The stringified tree.

traverse

traverse() -> Iterable[ReqNode]

Perform a depth-first traversal of the nodes of this tree.

Yields:

missing

missing() -> Iterable[Requirement]

Return the missing requirements from this tree.

Returns:

  • Iterable[Requirement]

    All requirements which were determined by resolution to be missing.

evaluate

evaluate(
    scope: GeoScope | None,
    time_frame: TimeFrame | None,
    ipm: BaseCompartmentModel | None,
    rng: Generator | None,
) -> DataResolver

Evaluate this tree. Defers to evaluate_requirements().

Parameters:

  • scope (GeoScope | None) –

    The geographic scope information of this simulation context, if available.

  • time_frame (TimeFrame | None) –

    The temporal scope information of this simulation context, if available.

  • ipm (BaseCompartmentModel | None) –

    The disease model for this simulation context, if available.

  • rng (Generator | None) –

    The random number generator to use, if available.

Returns:

  • DataResolver

    The resolver containing all evaluated parameters.

Raises:

of staticmethod

of(
    requirements: Mapping[AbsoluteName, AttributeDef],
    params: Database[V],
) -> ReqTree

Compute the requirements tree for the given set of requirements and a database supplying values. Note that missing values do not stop us from computing the tree -- these nodes will have MissingValue as the resolution.

Parameters:

  • requirements (Mapping[AbsoluteName, AttributeDef]) –

    The top-level requirements of the tree.

  • params (Database[V]) –

    The database of values, where each value may be recursive in the sense of having its own data requirements.

Returns:

  • ReqTree

    The requirements tree.

Raises:

  • DataAttributeError

    If the tree cannot be evaluated, for instance, due to it containing circular dependencies.

ReqNode dataclass

ReqNode(
    children: tuple[ReqNode, ...],
    name: AbsoluteName,
    definition: AttributeDef,
    resolution: Resolution,
    value: V | None,
)

Bases: ReqTree[V]

A non-root node of a requirements tree, identifying a requirement, how (or if) it was resolved, and its value (if available).

Parameters:

  • children (tuple[ReqNode, ...]) –

    The dependencies of this node (if any).

  • name (AbsoluteName) –

    The name of this requirement.

  • definition (AttributeDef) –

    The definition of this requirement.

  • resolution (Resolution) –

    How this requirement was resolved (or not).

  • value (V | None) –

    The value for this requirement if available.

children instance-attribute

children: tuple[ReqNode, ...]

The dependencies of this node (if any).

name instance-attribute

The name of this requirement.

definition instance-attribute

definition: AttributeDef

The definition of this requirement.

resolution instance-attribute

resolution: Resolution

How this requirement was resolved (or not).

value instance-attribute

value: V | None

The value for this requirement if available.

to_string

to_string(
    format_name: Callable[[AbsoluteName], str] = str,
    depth: int = 0,
) -> str

Convert this tree to a string.

Parameters:

  • format_name (Callable[[AbsoluteName], str], default: str ) –

    A method to convert an absolute name into a string. This allows you to control how absolute names are rendered.

  • depth (int, default: 0 ) –

    The resolution "depth" of this node in the tree. Top-level requirements have a depth of 0. A requirement that itself is required by one other requirement would have a depth of 1.

Returns:

  • str

    The stringified tree.

traverse

traverse() -> Iterable[ReqNode]

Perform a depth-first traversal of the nodes of this tree.

Yields:

as_res_tree

as_res_tree() -> ResolutionTree

Extract the resolution tree from this requirements tree.

Returns:

assert_can_adapt

assert_can_adapt(
    data_type: AttributeType,
    data_shape: DataShape,
    dim: Dimensions,
    value: AttributeArray,
) -> None

Check that we can adapt the given value to the given type and shape, given dimensional information.

Parameters:

Raises:

adapt

adapt(
    data_type: AttributeType,
    data_shape: DataShape,
    dim: Dimensions,
    value: AttributeArray,
) -> AttributeArray

Adapt the given value to the given type and shape, given dimensional information.

Parameters:

Returns:

Raises:

is_recursive_value

is_recursive_value(
    value: object,
) -> TypeGuard[RecursiveValue]

Check if a value is recursive, as a type guard.

Implemented by single dispatch.

Parameters:

  • value (object) –

    The value to check.

Returns:

evaluate_param

evaluate_param(
    value: object,
    name: AbsoluteName,
    data: DataResolver,
    scope: GeoScope | None,
    time_frame: TimeFrame | None,
    ipm: BaseCompartmentModel | None,
    rng: Generator | None,
) -> AttributeArray

Evaluate a parameter, transforming acceptable input values (type: ParamValue) to the form required internally by epymorph (AttributeArray).

Different types of parameters are supported by single dispatch, so there should be a registered implementation for every supported type of parameter.

It is possible that the user is attempting to evaluate parameters with a partial context (scope, time_frame, ipm, rng), and so one or more of these may be missing. In that case, parameter evaluation is expected to happen on a "best effort" basis -- if no parameter accesses the missing scope elements, parameter evaluation succeeds. Otherwise, it raises a DataAttributeError.

Parameters:

  • value (object) –

    The value being evaluated.

  • name (AbsoluteName) –

    The full name given to the value in the simulation context.

  • data (DataResolver) –

    A resolved instance which should contain values for all of the data requirements needed by this value (only RecursiveValues have requirements).

  • scope (GeoScope | None) –

    The geographic scope information of this simulation context, if available.

  • time_frame (TimeFrame | None) –

    The temporal scope information of this simulation context, if available.

  • ipm (BaseCompartmentModel | None) –

    The disease model for this simulation context, if available.

  • rng (Generator | None) –

    The random number generator to use, if available.

Returns:

Raises:

evaluate_requirements

evaluate_requirements(
    req: ReqTree,
    scope: GeoScope | None,
    time_frame: TimeFrame | None,
    ipm: BaseCompartmentModel | None,
    rng: Generator | None,
) -> DataResolver

Evaluate all parameters in the tree, using the given simulation context (scope, time_frame, ipm, rng).

You may attempt to evaluate parameters with a partial context, so one or more of these may be missing. In that case, parameter evaluation happens on a "best effort" basis -- if no parameter accesses the missing scope elements, parameter evaluation succeeds; otherwise it will raise an DataAttributeError.

Parameters:

  • req (ReqTree) –

    The requirements tree.

  • scope (GeoScope | None) –

    The geographic scope information of this simulation context, if available.

  • time_frame (TimeFrame | None) –

    The temporal scope information of this simulation context, if available.

  • ipm (BaseCompartmentModel | None) –

    The disease model for this simulation context, if available.

  • rng (Generator | None) –

    The random number generator to use, if available.

Returns:

  • DataResolver

    The resolver containing all evaluated parameters.

Raises: