diff --git a/examples/log-mpi.py b/examples/log-mpi.py index 094a49c5..44f02fcc 100755 --- a/examples/log-mpi.py +++ b/examples/log-mpi.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from time import sleep +from typing import Any from random import uniform from logpyle import (LogManager, add_general_quantities, add_simulation_quantities, add_run_info, IntervalTimer, @@ -10,6 +11,26 @@ from mpi4py import MPI +class PushLogQuantity(LogQuantity): + """Logging support for arbitrary user quantities.""" + + def __init__(self, name, value=None, unit=None, + description=None) -> None: + LogQuantity.__init__(self, name=name, unit=unit, + description=description) + self._quantity_value = value + + def __call__(self) -> float: + """Return the actual logged quantity.""" + val = self._quantity_value + self._quantity_value = None + return val + + def set_quantity_value(self, value: Any) -> None: + """Set the value of the logged quantity.""" + self._quantity_value = value + + class Fifteen(LogQuantity): @property def default_aggregator(self): @@ -43,14 +64,18 @@ def main(): vis_timer = IntervalTimer("t_vis", "Time spent visualizing") logmgr.add_quantity(vis_timer) logmgr.add_quantity(Fifteen("fifteen")) + logmgr.add_quantity(PushLogQuantity("q1")) # Watches are printed periodically during execution logmgr.add_watches([("step.max", "step={value} "), - ("t_step.min", "\nt_step({value:g},"), ("t_step.max", " {value:g})\n"), - "t_sim.max", "fifteen", "t_vis.max"]) + ("t_step.min", "\nt_step({value:g},"), + ("t_step.max", " {value:g})\n"), + ("q1.max", " UserQ1:({value:g}), "), + "t_sim.max", "fifteen", "t_vis.max"]) for istep in range(200): logmgr.tick_before() + logmgr.set_quantity_value("q1", 2*istep) dt = uniform(0.01, 0.1) set_dt(logmgr, dt) diff --git a/examples/log.py b/examples/log.py index 357def95..7d99f7b1 100755 --- a/examples/log.py +++ b/examples/log.py @@ -5,10 +5,29 @@ from logpyle import (LogManager, add_general_quantities, add_simulation_quantities, add_run_info, IntervalTimer, LogQuantity, set_dt) +from typing import Any from warnings import warn +class PushLogQuantity(LogQuantity): + """Logging support for arbitrary user quantities.""" + + def __init__(self, name, value=None, unit=None, + description=None) -> None: + LogQuantity.__init__(self, name=name, unit=unit, + description=description) + self._quantity_value = value + + def __call__(self) -> float: + """Return the actual logged quantity.""" + return self._quantity_value + + def set_quantity_value(self, value: Any) -> None: + """Set the value of the logged quantity.""" + self._quantity_value = value + + class Fifteen(LogQuantity): def __call__(self): return 15 @@ -33,13 +52,15 @@ def main(): vis_timer = IntervalTimer("t_vis", "Time spent visualizing") logmgr.add_quantity(vis_timer) logmgr.add_quantity(Fifteen("fifteen")) + logmgr.add_quantity(PushLogQuantity("q1", value=0)) # Watches are printed periodically during execution logmgr.add_watches(["step.max", "t_sim.max", "t_step.max", "fifteen", - "t_vis", "t_log"]) + "t_vis", "t_log", "q1"]) for istep in range(200): logmgr.tick_before() + logmgr.set_quantity_value("q1", 2*istep + 1) dt = uniform(0.01, 0.1) set_dt(logmgr, dt) diff --git a/logpyle/__init__.py b/logpyle/__init__.py index 2734adfe..fe3b4d32 100644 --- a/logpyle/__init__.py +++ b/logpyle/__init__.py @@ -97,6 +97,7 @@ class LogQuantity: .. automethod:: tick .. autoproperty:: default_aggregator .. automethod:: __call__ + .. automethod:: set_quantity_value """ sort_weight = 0 @@ -136,6 +137,10 @@ def __call__(self) -> Optional[float]: """ raise NotImplementedError + def set_quantity_value(self, value: Any) -> None: + """Set the logged quantity value.""" + raise NotImplementedError + class PostLogQuantity(LogQuantity): """A source of a loggable scalar that is gathered after each time step. @@ -289,10 +294,12 @@ def __init__(self, quantity: LogQuantity, interval: int) -> None: class _QuantityData: def __init__(self, unit: str, description: str, - default_aggregator: Callable) -> None: + default_aggregator: Callable, + value_setter: Callable = None) -> None: self.unit = unit self.description = description self.default_aggregator = default_aggregator + self.value_setter = value_setter def _join_by_first_of_tuple(list_of_iterables): @@ -741,6 +748,20 @@ def set_constant(self, name: str, value: Any) -> None: self._commit() + def set_quantity_value(self, name: str, value: Any) -> None: + """Set a the value of a named LogQuantity. + + :param name: the name of the logged quantity. + :param value: the value of the logged quantity. + """ + if name in self.quantity_data: + value_setter = self.quantity_data[name].value_setter + if value_setter: + value_setter(value) + else: + from warnings import warn + warn(f"No value_setter defined for log quantity (name={name}).") + def _insert_datapoint(self, name: str, value: Optional[float]) -> None: if value is None: return @@ -867,12 +888,13 @@ def add_quantity(self, quantity: LogQuantity, interval: int = 1) -> None: :param interval: interval (in time steps) when to gather this quantity. """ - def add_internal(name, unit, description, def_agg): + def add_internal(name, unit, description, def_agg, val_set=None): logger.debug("add log quantity '%s'" % name) if name in self.quantity_data: raise RuntimeError("cannot add the same quantity '%s' twice" % name) - self.quantity_data[name] = _QuantityData(unit, description, def_agg) + self.quantity_data[name] = _QuantityData(unit, description, def_agg, + val_set) from pickle import dumps self.db_conn.execute("""insert into quantities values (?,?,?,?)""", ( @@ -901,8 +923,9 @@ def add_internal(name, unit, description, def_agg): add_internal(name, unit, description, def_agg) else: add_internal(quantity.name, - quantity.unit, quantity.description, - quantity.default_aggregator) + quantity.unit, quantity.description, + quantity.default_aggregator, + quantity.set_quantity_value) def get_expr_dataset(self, expression, description=None, unit=None): """Prepare a time-series dataset for a given expression.