Source code for pymt.component.component

"""
Examples
--------
Create an instance of the *AirPort* component and run it. The `go`
method will initialize the component, run it for
its duration, and finalize it.

>>> from pymt.framework.services import register_component_classes
>>> register_component_classes(['pymt.testing.services.AirPort'])
>>> comp = Component('AirPort')
>>> comp.start_time
0.0
>>> comp.current_time
0.0
>>> comp.end_time
100.0
>>> comp.go() # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
{name: air_port, status: running, time: 1.0}
...
{name: air_port, status: running, time: 100.0}
>>> comp.current_time
100.0

If you try to create a new component with the same name, it will raise
an exception. To create a new instance of the component, you have to
give it a new name.

>>> comp = Component('AirPort') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
ValueError: AirPort
>>> comp = Component('AirPort', name='air_port')
>>> comp.current_time
0.0

You can gain finer control over component execution with the
`initialize`, `run`, and `finalize` methods.

>>> comp.initialize()
>>> comp.run(10.) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
{name: air_port, status: running, time: 1.0}
...
{name: air_port, status: running, time: 10.0}
>>> comp.current_time
10.0
>>> comp.run(101.) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
ValueError: AirPort
>>> comp.current_time
10.0
>>> comp.run(100.) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
{name: air_port, status: running, time: 11.0}
...
{name: air_port, status: running, time: 100.0}
>>> comp.current_time
100.0
>>> comp.finalize()
"""
import warnings

import yaml

from ..events.chain import ChainEvent
from ..events.manager import EventManager
from ..events.port import PortEvent, PortMapEvent
from ..events.printer import PrintEvent
from ..framework import services
from .grid import GridMixIn


[docs]def clip_stop_time(stop, stop_min, stop_max): """Clip time between two values. Clip a stopping time between two values. This is the same as the numpy clip function except that if *stop* is `None` then *stop_max* is returned. Parameters ---------- stop : float Stop time. stop_min : float Minimum stop time stop_max : float Maximum stop time Returns ------- float The, possibly clipped, stop time. Examples -------- >>> clip_stop_time(1., 0, 2.) 1.0 >>> clip_stop_time(1., 2, 3) 2.0 >>> clip_stop_time(4., 2, 3) 3.0 >>> clip_stop_time(None, 0, 1) 1.0 """ from numpy import clip if stop is None: return float(stop_max) else: return clip(stop, stop_min, stop_max)
[docs]class Component(GridMixIn): """Wrap a BMI object as a component. Use the Component class to wrap an object that exposes a BMI so that it can operate within the model-coupling framework. Parameters ---------- port : str or port-like Name of the port to wrap or a Port instance. uses : list, optional List of uses port names. provides : list, optional List of provides port names. argv : tuple, optional List of command line argument used to initialize the component. time_step : float, optional Time interval over which component will run uses ports. run_dir : str, optional Directory where the component will run. """ def __init__( self, port, uses=None, provides=None, events=(), argv=(), time_step=1.0, run_dir=".", name=None, ): if uses is None: uses = set() if provides is None: provides = set() if isinstance(port, str): if name is None: name = port self._port = services.instantiate_component(port, name) else: self._port = port self._uses = uses self._provides = provides self._time_step = time_step self._events = EventManager( [(PortEvent(port=self._port, init_args=argv, run_dir=run_dir), time_step)] + list(events) ) # self._events = EventManager([(self._port, 1.)] + events) # self._events = EventManager( # [(self._port, self._port.time_step)] + events) GridMixIn.__init__(self) @property def time_step(self): """Component time step. Time step over which a component will update any connected uses ports. """ return self._time_step @property def current_time(self): """Current time for component updating.""" try: return self._port.current_time except AttributeError: return self._port.get_current_time() @property def start_time(self): """Start time for component updating.""" try: return self._port.start_time except AttributeError: return self._port.get_start_time() @property def end_time(self): """End time for component updating.""" try: return self._port.end_time except AttributeError: return self._port.get_end_time() @property def uses(self): """Names of connected *uses* ports.""" return self._uses @property def provides(self): """Names of connected *provides* ports.""" return self._provides
[docs] def connect(self, uses, port, vars_to_map=()): """Connect a *uses* port to a *provides* port. Parameters ---------- uses : str Name of the *uses* port. port : port-like Port-like object to connect to. var_to_map : iterable, optional Names of variables to map. """ if uses not in self.uses: warnings.warn("component does not use %s" % uses) if len(vars_to_map) > 0: event = ChainEvent( [ port, PortMapEvent(src_port=port, dst_port=self, vars_to_map=vars_to_map), ] ) # PortMapEvent(src_port=port, dst_port=self._port, else: event = port self._events.add_recurring_event(event, port.time_step)
# self._events.add_recurring_event(event, port._port.time_step) # self._events.add_recurring_event(port, port._port.time_step)
[docs] def go(self, stop=None): """Run a component from start to end. Run a component starting from its start time and ending at its stop time. The component and any attached ports are first initialized, then updated until the end time and, finally, their finalize methods are called. Parameters ---------- stop : float, optional Stop time, or None to run until `end_time`. """ self.initialize() stop_time = clip_stop_time(stop, self.time_step, self.end_time) try: self.run(stop_time) except Exception: raise finally: self._events.finalize()
[docs] def initialize(self): """Initialize a component and any connected events.""" self._events.initialize()
[docs] def run(self, stop_time): """Run a component and any connect events. Parameters ---------- stop_time : float Time to run the component until. """ if stop_time > self.end_time: raise ValueError("stop time is greater than end time.") self._events.run(stop_time)
[docs] def finalize(self): """Finalize a component and any connected events.""" self._events.finalize()
[docs] def register(self, name): """Register component with the framework. Associate *name* with the component instance withing the framework. Parameters ---------- name : str Name to associate component with. """ services.register_component_instance(name, self)
[docs] @classmethod def from_string(cls, source): """Deprecated. .. note:: Deprecated. Use :meth:`~Component.load` instead. Create a component from a string. Parameters ---------- source : str Contents of a yaml file. Returns ------- Component A newly-created component. """ return cls._from_yaml(source)
[docs] @classmethod def load(cls, source): """Create a Component from a string. This is an alternate constructor that create a component using values from a yaml-formatted string. Parameters ---------- source : str Yaml-formatted string. Returns ------- Component A newly-created component. """ return cls.from_dict(yaml.safe_load(source))
[docs] @classmethod def load_all(cls, source): """Create multiple components from a string. This is an alternate constructor that creates a series of components using values from a yaml-formatted string. Parameters ---------- source : str Yaml-formatted string. Returns ------- List of Components A list of newly-created component. """ components = [] for section in yaml.safe_load_all(source): components.append(cls.from_dict(section)) return components
[docs] @classmethod def from_dict(cls, d): """Create a Component instance from a dictionary. Use configuration paramters from a dict-like object to create a new Component. The dictionary must contain the following keys: * `name` * `class` * `initialize_args` * `time_step` * `run_dir` Parameters ---------- d : dict-like Configuration parameters for the component. Returns ------- Component A newly-created Component instance. """ # port = services.get_port(d['name']) port = services.instantiate_component(d["class"], d["name"]) # argv = d.get('argv', []) try: argv = d["initialize_args"] except KeyError: argv = d.get("argv", []) time_step = float(d.get("time_step", 1.0)) run_dir = d.get("run_dir", ".") events = [] for conf in d.get("print", []): conf["port"] = port conf["run_dir"] = run_dir events.append((PrintEvent(**conf), conf["interval"])) uses = d.get("uses", []) provides = d.get("provides", []) return cls( port, uses=uses, provides=provides, events=events, argv=argv, time_step=time_step, run_dir=run_dir, )
[docs] @classmethod def from_path(cls, path): """Create a component from a file. Parameters ---------- path : str Path to yaml file. Returns ------- Component A newly-created component. """ with open(path) as yaml_file: return cls._from_yaml(yaml_file.read())
@classmethod def _from_yaml(cls, contents): comp = yaml.safe_load(contents) return cls.from_dict(comp)