Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add value setter for simple logged quantities. #69

Closed
wants to merge 11 commits into from
27 changes: 25 additions & 2 deletions examples/log-mpi.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -10,6 +11,24 @@
from mpi4py import MPI


class UserLogQuantity(LogQuantity):
MTCam marked this conversation as resolved.
Show resolved Hide resolved
"""Logging support for arbitrary user quantities."""
MTCam marked this conversation as resolved.
Show resolved Hide resolved

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
MTCam marked this conversation as resolved.
Show resolved Hide resolved

def set_quantity_value(self, value: Any) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming? I think set_value would suffice.

"""Set the value of the logged quantity."""
self._quantity_value = value
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to move this to a place where it's importable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, somehow this fell off my radar. I am not accustomed to having logpyle PRs that I'm watching.

Yes, we could definitely move this and make it "a thing" offered by logpyle - but before we commit to doing that, I just want to point out that this is just an example of how to use the capability added by this change set - which was just the part that allows a setter function to be passed into, and then invoked from inside logging.

It would be my preference to add a class similar to this to the logging_quantities.py of MIRGE-Com, instead of a canned one offered by logpyle, but I defer to @inducer and @matthiasdiener about whether to offer this or similar as a logpyle construct.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My vote would be to offer it here... the duplication between the two examples is already a bit much for me to bear. OTOH, if you'd like to demonstrate how (not very) hard it is to make your own LogQuantity, then maybe I could be convinced.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the value in offering a base one. How about we offer a base one, and demonstrate how to make one's own one?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(There could be a comment on the demo one that this specific thing already exists and can just be imported.)



class Fifteen(LogQuantity):
@property
def default_aggregator(self):
Expand Down Expand Up @@ -43,14 +62,18 @@ def main():
vis_timer = IntervalTimer("t_vis", "Time spent visualizing")
logmgr.add_quantity(vis_timer)
logmgr.add_quantity(Fifteen("fifteen"))
logmgr.add_quantity(UserLogQuantity("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)
Expand Down
24 changes: 23 additions & 1 deletion examples/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 UserLogQuantity(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
Expand All @@ -33,12 +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(UserLogQuantity("q1", value=0))

# Watches are printed periodically during execution
logmgr.add_watches(["step.max", "t_sim.max", "t_step.max", "fifteen", "t_vis"])
logmgr.add_watches(["step.max", "t_sim.max", "t_step.max", "fifteen", "t_vis",
"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)
Expand Down
33 changes: 28 additions & 5 deletions logpyle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class LogQuantity:
.. automethod:: tick
.. autoproperty:: default_aggregator
.. automethod:: __call__
.. automethod:: set_quantity_value
"""

sort_weight = 0
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just access it and handle the KeyError.

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}).")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be an error.


Comment on lines +751 to +764
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this a function instead.

def _insert_datapoint(self, name: str, value: Optional[float]) -> None:
if value is None:
return
Expand Down Expand Up @@ -849,12 +870,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 (?,?,?,?)""", (
Expand Down Expand Up @@ -883,8 +905,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.
Expand Down