diff --git a/USAGE.ipynb b/USAGE.ipynb index 3f73222c..37de9ef2 100644 --- a/USAGE.ipynb +++ b/USAGE.ipynb @@ -112,17 +112,17 @@ "name": "stdout", "output_type": "stream", "text": [ - "Loading epymorph.adrio.commuting_flows.Commuters:\n", - " |####################| 100% (9.727s)\n", - "Loading epymorph.adrio.acs5.Population:\n", - " |####################| 100% (1.296s)\n", - "Loading epymorph.adrio.us_tiger.PostalCode:\n", - " |####################| 100% (0.171s)\n", + "Loading gpm:all::mm::commuters (epymorph.adrio.commuting_flows.Commuters):\n", + " |####################| 100% (9.563s)\n", + "Loading gpm:all::init::population (epymorph.adrio.acs5.Population):\n", + " |####################| 100% (1.101s)\n", + "Loading meta::geo::label (epymorph.adrio.us_tiger.PostalCode):\n", + " |####################| 100% (0.173s)\n", "Running simulation (BasicSimulator):\n", "• 2015-01-01 to 2015-05-30 (150 days)\n", "• 6 geo nodes\n", " |####################| 100% \n", - "Runtime: 0.313s\n" + "Runtime: 0.336s\n" ] } ], diff --git a/epymorph/adrio/adrio.py b/epymorph/adrio/adrio.py index 9499ed6c..768eefb2 100644 --- a/epymorph/adrio/adrio.py +++ b/epymorph/adrio/adrio.py @@ -41,21 +41,16 @@ class Adrio(SimulationFunction[NDArray[ResultDType]]): web APIs, local files or database, or anything imaginable. """ - @property - def full_name(self) -> str: - return f"{self.__class__.__module__}.{self.__class__.__qualname__}" - def estimate_data(self) -> DataEstimate: - """Estimate the data usage of this ADRIO in a RUME. - If a reasonable estimate cannot be made, None is returned.""" - return EmptyDataEstimate(self.full_name) + """Estimate the data usage for this ADRIO in a RUME. + If a reasonable estimate cannot be made, return EmptyDataEstimate.""" + return EmptyDataEstimate(self.class_name) @abstractmethod def evaluate_adrio(self) -> NDArray[ResultDType]: - """ - Implement this method to provide logic for the function. - Your implementation is free to use `data`, `dim`, and `rng`. - You can also use `defer` to utilize another SimulationFunction instance. + """Implement this method to provide logic for the function. + Use self methods and properties to access the simulation context or defer + processing to another function. """ @override @@ -65,7 +60,8 @@ def evaluate(self) -> NDArray[ResultDType]: functionality. ADRIO implementations should override `evaluate_adrio`.""" _events.on_adrio_progress.publish( AdrioProgress( - adrio_name=self.full_name, + adrio_name=self.class_name, + attribute=self.name, final=False, ratio_complete=0, download=None, @@ -77,7 +73,8 @@ def evaluate(self) -> NDArray[ResultDType]: t1 = perf_counter() _events.on_adrio_progress.publish( AdrioProgress( - adrio_name=self.full_name, + adrio_name=self.class_name, + attribute=self.name, final=True, ratio_complete=1, download=None, @@ -95,7 +92,8 @@ def progress( """Emit a progress event.""" _events.on_adrio_progress.publish( AdrioProgress( - adrio_name=self.full_name, + adrio_name=self.class_name, + attribute=self.name, final=False, ratio_complete=ratio_complete, download=download, diff --git a/epymorph/adrio/commuting_flows.py b/epymorph/adrio/commuting_flows.py index 29829527..f9c23eeb 100644 --- a/epymorph/adrio/commuting_flows.py +++ b/epymorph/adrio/commuting_flows.py @@ -117,7 +117,7 @@ def estimate_data(self) -> DataEstimate: key = f"commflows:{year}" return AvailableDataEstimate( - name=self.full_name, + name=self.class_name, cache_key=key, new_network_bytes=est.missing_cache_size, new_cache_bytes=est.missing_cache_size, diff --git a/epymorph/adrio/us_tiger.py b/epymorph/adrio/us_tiger.py index cd9cb87b..1382678f 100644 --- a/epymorph/adrio/us_tiger.py +++ b/epymorph/adrio/us_tiger.py @@ -128,7 +128,7 @@ def estimate_data(self) -> DataEstimate: ) key = f"us_tiger:{scope.granularity}:{year}" return AvailableDataEstimate( - name=self.full_name, + name=self.class_name, cache_key=key, new_network_bytes=est.missing_cache_size, new_cache_bytes=est.missing_cache_size, diff --git a/epymorph/data_usage.py b/epymorph/data_usage.py index 69ad8a80..4446cbf3 100644 --- a/epymorph/data_usage.py +++ b/epymorph/data_usage.py @@ -1,9 +1,10 @@ +from abc import abstractmethod from dataclasses import dataclass from functools import partial from math import floor, inf from pathlib import Path from shutil import disk_usage -from typing import Sequence +from typing import Protocol, Sequence, runtime_checkable from humanize import naturaldelta, naturalsize @@ -55,6 +56,14 @@ class AvailableDataEstimate: DataEstimate = EmptyDataEstimate | AvailableDataEstimate +@runtime_checkable +class CanEstimateData(Protocol): + @abstractmethod + def estimate_data(self) -> DataEstimate: + """Estimate the data usage for this entity. + If a reasonable estimate cannot be made, return EmptyDataEstimate.""" + + @dataclass(frozen=True) class DataEstimateTotal: new_network_bytes: int diff --git a/epymorph/event.py b/epymorph/event.py index 5ac6db17..8b49e920 100644 --- a/epymorph/event.py +++ b/epymorph/event.py @@ -8,6 +8,7 @@ from numpy.typing import NDArray +from epymorph.attribute import AbsoluteName from epymorph.data_type import SimDType from epymorph.rume import Rume from epymorph.util import Event, Singleton @@ -125,9 +126,9 @@ class AdrioProgress(NamedTuple): to report as many intermediate progress events as they like.""" adrio_name: str - """The name of the ADRIO.""" - # attribute: AbsoluteName # TODO: see if we can get AbsoluteName here... - # """The name of the attribute.""" + """The full name of the ADRIO class.""" + attribute: AbsoluteName + """The name of the attribute being evaluated.""" final: bool """Is this the last progress update for this ADRIO?""" ratio_complete: float diff --git a/epymorph/log/messaging.py b/epymorph/log/messaging.py index fccd036e..8a241705 100644 --- a/epymorph/log/messaging.py +++ b/epymorph/log/messaging.py @@ -90,7 +90,7 @@ def on_finish(_: None) -> None: def on_adrio_progress(e: AdrioProgress) -> None: nonlocal last_progress_length if e.ratio_complete == 0: - print(f"Loading {e.adrio_name}:") + print(f"Loading {e.attribute} ({e.adrio_name}):") if not e.final: if e.download is None: dl = "" diff --git a/epymorph/movement_model.py b/epymorph/movement_model.py index 1f79b38c..dcb7984c 100644 --- a/epymorph/movement_model.py +++ b/epymorph/movement_model.py @@ -170,7 +170,7 @@ def is_active(self, tick: Tick) -> bool: return self.leaves.step == tick.step and self.predicate.evaluate(tick) @property - def name(self) -> str: + def clause_name(self) -> str: return self.__class__.__name__ @abstractmethod diff --git a/epymorph/rume.py b/epymorph/rume.py index 1955e946..6bc6c67e 100644 --- a/epymorph/rume.py +++ b/epymorph/rume.py @@ -45,7 +45,7 @@ ) from epymorph.data_shape import Shapes from epymorph.data_type import SimArray, dtype_str -from epymorph.data_usage import estimate_report +from epymorph.data_usage import CanEstimateData, estimate_report from epymorph.database import ( Database, DatabaseWithFallback, @@ -429,10 +429,9 @@ def estimate_data( scope=self.scope, time_frame=self.time_frame, ipm=self.ipm, - ).estimate_data() # type: ignore + ).estimate_data() for p in self.params.values() - if isinstance(p, SimulationFunction) and hasattr(p, "estimate_data") - # TODO: this is a bit of a hack to avoid importing Adrio here... + if isinstance(p, SimulationFunction) and isinstance(p, CanEstimateData) ] lines = list[str]() diff --git a/epymorph/simulation.py b/epymorph/simulation.py index 5c38c103..afb5060a 100644 --- a/epymorph/simulation.py +++ b/epymorph/simulation.py @@ -494,6 +494,11 @@ class BaseSimulationFunction(ABC, Generic[ResultT], metaclass=SimulationFunction _ctx: _FullContext | _PartialContext = _EMPTY_CONTEXT + @property + def class_name(self) -> str: + """The class name of the SimulationFunction.""" + return f"{self.__class__.__module__}.{self.__class__.__qualname__}" + def validate(self, result: ResultT) -> None: """Override this method to validate the evaluation result. Implementations should raise an appropriate error if results @@ -567,6 +572,12 @@ def defer_context( rng=self._ctx._rng, ) + @final + @property + def name(self) -> AbsoluteName: + """The name under which this attribute is being evaluated.""" + return self._ctx.name + @final def data(self, attribute: AttributeDef | str) -> NDArray: """Retrieve the value of a specific attribute.""" diff --git a/epymorph/simulator/basic/mm_exec.py b/epymorph/simulator/basic/mm_exec.py index 1be8c54e..4697b4ce 100644 --- a/epymorph/simulator/basic/mm_exec.py +++ b/epymorph/simulator/basic/mm_exec.py @@ -118,7 +118,7 @@ def __init__( namespace = ModuleNamespace(gpm_strata(strata), "mm") for clause in model.clauses: c = clause.with_context_internal( - namespace.to_absolute(clause.name), + namespace.to_absolute(clause.clause_name), data, rume.scope, rume.time_frame, @@ -155,7 +155,7 @@ def apply(self, tick: Tick) -> None: available_movers = self._world.get_local_array() clause_event = calculate_travelers( - clause.name, + clause.clause_name, self._rume.compartment_mobility[strata], requested_movers, available_movers,