Skip to content

epymorph.database

A Database in epymorph is a way to organize values with our namespace system of three hierarchical components, as in: "strata::module::attribute_id". This gives us a lot of flexibility when specifying data 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 need 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.

Hierarchical Database instances are also included which 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

value instance-attribute

value: T

Database

Database(data: dict[NamePattern, T])

Bases: Generic[T]

A simple database implementation which provides namespaced key/value pairs. Namespaces are in the form "a::b::c", where "a" is the strata, "b" is the module, and "c" is the attribute name. Values are permitted to be assigned with wildcards (specified by asterisks), so that key "*::b::c" matches queries for "a::b::c" as well as "z::b::c".

This is intended for tracking parameter values as given by the user, in constrast to DataResolver which is for tracking fully-evaluated parameters.

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, we must be able to parse it as a valid AbsoluteName

Returns:

  • Match[T] | None

    the found value, if any, else None

to_dict

to_dict() -> dict[NamePattern, T]

Return a copy of this database's data.

DatabaseWithFallback

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

Bases: Database[T]

A specialization of Database which has a fallback Database. If a match is not found in this DB's keys, 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, we must be able to parse it as a valid AbsoluteName

Returns:

  • Match[T] | None

    the found value, if any, else None

to_dict

to_dict() -> dict[NamePattern, T]

Return a copy of this database's data.

DatabaseWithStrataFallback

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

Bases: Database[T]

A specialization of Database which has a set of fallback Databases, one per strata. For example, we might query this DB for "a::b::c". If we do not have a match in our own key/values, but we do have a fallback DB for "a", we will query that fallback (which could continue recursively).

Parameters:

  • data (dict[NamePattern, T]) –

    the highest-priority values in the database

  • children (dict[str, Database[T]]) –

    fallback databases by strata; if a match can't be found in data, the database for the matching strata (if any) 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, we must be able to parse it as a valid AbsoluteName

Returns:

  • Match[T] | None

    the found value, if any, else None

to_dict

to_dict() -> dict[NamePattern, T]

Return a copy of this database's data.

DataResolver

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

A sort of database for data attributes in the context of 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 most useful for simulation execution (a numpy array, to be exact).

While the keys can be described using NamePatterns, when they are resolved they are tracked by their full AbsoluteName to facilitate lookups by the systems that use them. It is possible that different AbsoluteNames actually resolve to the same value (which is done in a memory-efficient way).

Meanwhile the usage of values adds its own complexity. When a value is used to fulfill the data requirements of a system, we want that value to be of a known type and shape. If two requirements are fulfilled by the same value, it it possible that the requirements will have different specifications for type and shape. Rather than be over-strict, and enforce that this can never happen, we allow this provided the given value can be successfully coerced to fit both requirements independently. For example, a scalar integer value can be coerced to both an N-shaped array of floats as well as a T-shaped array of integers. DataResolver accomplishes this flexibility 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 critical dimensions of the context in which these values have been evaluated; this is needed to perform shape adaptations

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

    a collection of values that should be in the resolver to begin with

Key class-attribute instance-attribute

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

Tests 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

Adds 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

Resolves 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 (which is needed because it contains the type and shape information)

Returns:

Raises:

  • AttributeException

    if the resolution fails

resolve_txn_series

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

Generates a 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.

to_dict

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

Extract a dictionary from this DataResolver of all of its (non-adapted) keys and values.

Parameters:

  • simplify_names (bool, default: False ) –

    by default, names are returned as AbsoluteName objects; if True, return stringified names as a convenience

Returns:

Requirement

Bases: NamedTuple

A RUME data requirement: a name and a definition.

name instance-attribute

definition instance-attribute

definition: AttributeDef

Resolution

Bases: ABC

What source was used to resolve a requirement in a RUME?

cacheable instance-attribute

cacheable: bool

MissingValue dataclass

MissingValue()

Bases: Resolution

Requirement was not resolved.

cacheable class-attribute instance-attribute

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

ParameterValue dataclass

ParameterValue(cacheable: bool, pattern: NamePattern)

Bases: Resolution

Requirement was resolved by a RUME parameter.

cacheable instance-attribute

cacheable: bool

pattern instance-attribute

pattern: NamePattern

DefaultValue dataclass

DefaultValue(default_value: AttributeValue)

Bases: Resolution

Requirement was resolved by an attribute default value.

cacheable class-attribute instance-attribute

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

default_value instance-attribute

default_value: AttributeValue

ResolutionTree dataclass

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

Just the resolution part of a requirements tree; children are the dependencies of this resolution. If two values share the same resolution tree, and if the tree is comprised entirely of pure functions and values, then the resolution result would be the same.

resolution instance-attribute

resolution: Resolution

children instance-attribute

children: tuple[ResolutionTree, ...]

is_tree_cacheable property

is_tree_cacheable: bool

RecursiveValue

Bases: Protocol

A parameter value that itself 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 randomized results.)

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) and whether it is fulfilled and if so how.

children instance-attribute

children: tuple[ReqNode, ...]

to_string abstractmethod

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

Convert this ReqTree 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. A requirement that itself is required by one other requirement would have a depth of 1. Top-level requirements have a depth of 0.

traverse

traverse() -> Iterable[ReqNode]

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

missing

missing() -> Iterable[Requirement]

Return missing requirements.

evaluate

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

Evaluate this tree. See: evaluate_requirements().

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

Raises:

  • DataAttributeError

    If the tree cannot be evaluated, for instance, due to 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).

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 ReqTree 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. A requirement that itself is required by one other requirement would have a depth of 1. Top-level requirements have a depth of 0.

traverse

traverse() -> Iterable[ReqNode]

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

as_res_tree

as_res_tree() -> ResolutionTree

Extracts the resolution tree from this requirements tree.

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. Raises AttributeException if not.

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. Raises AttributeException if this fails.

is_recursive_value

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

TypeGuard for RecursiveValues, implemented by single dispatch.

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). This handles different types of parameters by single dispatch, so there should be a registered implementation for every unique type. 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 requires the missing scope elements, parameter evaluation succeeds. Otherwise, this is expected to raise an AttributeException.

Parameters:

  • value (object) –

    the value being evaluated

  • name (AbsoluteName) –

    the full name given to the value in the simulation context

  • data (DataResolver) –

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

  • scope (GeoScope) –

    the geographic scope information of this simulation context, if available

  • time_frame (TimeFrame) –

    the temporal scope information of this simulation context, if available

  • ipm (BaseCompartmentModel) –

    the disease model for this simulation context, if available

  • rng (Generator) –

    the random number generator to use, if available

Returns:

evaluate_requirements

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

Evaluate all parameters in req, 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 requires the missing scope elements, parameter evaluation succeeds; otherwise raises an AttributeException.

Parameters:

  • req (ReqTree) –

    the requirements tree

  • scope (GeoScope) –

    the geographic scope information of this simulation context, if available

  • time_frame (TimeFrame) –

    the temporal scope information of this simulation context, if available

  • ipm (BaseCompartmentModel) –

    the disease model for this simulation context, if available

  • rng (Generator) –

    the random number generator to use, if available

Returns:

  • DataResolver

    the resolver containing all evaluated parameters