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).
Match
Database
Database(data: dict[NamePattern, 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:
-
data
(dict[NamePattern, T]
) –The key/value pairs to store.
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:
to_dict
to_dict() -> dict[NamePattern, T]
Return the database's data as a dictionary.
Returns:
-
dict[NamePattern, T]
–A copy of this database's data.
DatabaseWithFallback
DatabaseWithFallback(
data: dict[NamePattern, T], fallback: 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:
to_dict
to_dict() -> dict[NamePattern, T]
Return the database's data as a dictionary.
Returns:
-
dict[NamePattern, T]
–A copy of this database's data.
DatabaseWithStrataFallback
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:
-
data
(dict[NamePattern, T]
) –The highest-priority values in the database.
-
children
(dict[str, Database[T]]
) –Fallback databases by strata.
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:
to_dict
to_dict() -> dict[NamePattern, T]
Return the database's data as a dictionary.
Returns:
-
dict[NamePattern, T]
–A copy of this database's data.
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
Key = tuple[AbsoluteName, AttributeType, DataShape]
The type of the key used to track adapted values.
raw_values
property
raw_values: Mapping[AbsoluteName, AttributeArray]
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:
-
name
(AbsoluteName
) –The name to test.
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
, aNamePattern
, or a string. A string will be parsed as anAbsoluteName
if possible, and fall back to parsing it as aNamePattern
. In any case, the name must match exactly one value in order to return successfully.
Returns:
-
AttributeArray
–The requested value, if found.
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:
-
name
(AbsoluteName
) –The name for the value.
-
value
(AttributeArray
) –The value to add.
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:
-
AttributeArray
–The resolved value in adapted form.
Raises:
-
DataAttributeError
–If resolution of adaptation fails.
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:
-
Iterator[list[AttributeValue]]
–An iterator by tau step where each value is the list of attribute values for that step.
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:
-
dict[AbsoluteName, AttributeArray] | dict[str, AttributeArray]
–The dictionary of all values in this resolver, with names either simplified or not according to
simplify_names
.
Requirement
Resolution
MissingValue
dataclass
Bases: Resolution
Requirement was not resolved.
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.
DefaultValue
dataclass
DefaultValue(default_value: AttributeValue)
Bases: Resolution
Requirement was resolved by an attribute default value.
Parameters:
-
default_value
(AttributeValue
) –The value that was resolved.
cacheable
class-attribute
instance-attribute
True if this resolution can be cached.
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.
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
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:
to_string
abstractmethod
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
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:
-
DataAttributeError
–If any attribute fails evaluation.
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,
)
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.
to_string
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
as_res_tree
as_res_tree() -> ResolutionTree
Extract the resolution tree from this requirements tree.
Returns:
-
ResolutionTree
–The resolution 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.
Parameters:
-
data_type
(AttributeType
) –The final data type to adapt to.
-
data_shape
(DataShape
) –The final data shape to adapt to.
-
dim
(Dimensions
) –Simulation dimensions.
-
value
(AttributeArray
) –The value to adapt.
Raises:
-
DataAttributeError
–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.
Parameters:
-
data_type
(AttributeType
) –The final data type to adapt to.
-
data_shape
(DataShape
) –The final data shape to adapt to.
-
dim
(Dimensions
) –Simulation dimensions.
-
value
(AttributeArray
) –The value to adapt.
Returns:
-
AttributeArray
–The adapated array.
Raises:
-
DataAttributeError
–If this fails.
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:
-
TypeGuard[RecursiveValue]
–True if the value can be recursive, as a type-guard.
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:
-
AttributeArray
–The evaluated value.
Raises:
-
DataAttributeError
–If parameter evaluation fails.
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:
-
DataAttributeError
–If any attribute fails evaluation.