Source code for simkit.core.outputs

# -*- coding: utf-8 -*-

"""
This module provides the framework for output from SimKit. It is similar
to the data layer except output sources are always calculations.
"""

from simkit.core import logging, CommonBase, UREG, Q_, Registry, Parameter
import json
import numpy as np

LOGGER = logging.getLogger(__name__)


[docs]class OutputParameter(Parameter): """ Fields for outputs. """ _attrs = ['units', 'init', 'size', 'isconstant', 'isproperty', 'timeseries']
[docs]class OutputRegistry(Registry): """ A registry for output from calculations. """ meta_names = [ 'initial_value', 'size', 'uncertainty', 'variance', 'jacobian', 'isconstant', 'isproperty', 'timeseries', 'output_source' ] def register(self, new_outputs, *args, **kwargs): """ Register outputs and metadata. * ``initial_value`` - used in dynamic calculations * ``size`` - number of elements per timestep * ``uncertainty`` - in percent of nominal value * ``variance`` - dictionary of covariances, diagonal is square of uncertianties, no units * ``jacobian`` - dictionary of sensitivities dxi/dfj * ``isconstant`` - ``True`` if constant, ``False`` if periodic * ``isproperty`` - ``True`` if output stays at last value during thresholds, ``False`` if reverts to initial value * ``timeseries`` - name of corresponding time series output, ``None`` if no time series * ``output_source`` - name :param new_outputs: new outputs to register. """ kwargs.update(zip(self.meta_names, args)) # call super method super(OutputRegistry, self).register(new_outputs, **kwargs)
[docs]class OutputBase(CommonBase): """ Metaclass for outputs. Setting the ``__metaclass__`` attribute to :class:`OutputBase` adds the full path to the specified output parameter file as ``param_file`` or adds ``parameters`` with outputs specified. Also checks that outputs is a subclass of :class:`Output`. Sets `output_path` and `output_file` as the class attributes that specify the parameter file full path. """ _path_attr = 'outputs_path' _file_attr = 'outputs_file' _param_cls = OutputParameter def __new__(mcs, name, bases, attr): # use only with Output subclasses if not CommonBase.get_parents(bases, OutputBase): return super(OutputBase, mcs).__new__(mcs, name, bases, attr) # set _meta combined from bases attr = mcs.set_meta(bases, attr) # set param file full path if outputs path and file specified or # try to set parameters from class attributes except private/magic attr = mcs.set_param_file_or_parameters(attr) return super(OutputBase, mcs).__new__(mcs, name, bases, attr)
[docs]class Output(object): """ A class for formatting outputs. Do not use this class directly. Instead subclass it in your output model and list the path and file of the outputs parameters or provide the parameters as class members. Example of specified output parameter file:: import os PROJ_PATH = os.path.join('project', 'path') # project path class PVPowerOutputs(Output): outputs_file = 'pvpower.json' outputs_path = os.path.join(PROJ_PATH, 'outputs') Example of specified output parameters:: class PVPowerOutputs(Output): hourly_energy = {'init': 0, 'units': 'Wh', 'size': 8760} yearly_energy = {'init': 0, 'units': 'kWh'} """ __metaclass__ = OutputBase def __init__(self): #: outputs initial value self.initial_value = {} #: size of outputs self.size = {} #: outputs uncertainty self.uncertainty = {} #: variance self.variance = {} #: jacobian self.jacobian = {} #: outputs isconstant flag self.isconstant = {} #: outputs isproperty flag self.isproperty = {} #: name of corresponding time series, ``None`` if no time series self.timeseries = {} #: name of :class:`Output` superclass self.output_source = {} #: calculation outputs self.outputs = {} for k, v in self.parameters.iteritems(): self.initial_value[k] = v.get('init') # returns None if missing self.size[k] = v.get('size') or 1 # minimum size is 1 self.uncertainty[k] = None # uncertainty for outputs is calculated self.isconstant[k] = v.get('isconstant', False) # True or False self.isproperty[k] = v.get('isproperty', False) # True or False units = str(v.get('units', '')) # default is non-dimensional # NOTE: np.empty is faster than zeros! self.outputs[k] = Q_(np.zeros((1, self.size[k])), UREG(units)) # NOTE: Initial values are assigned and outputs resized when # simulation "start" method is called from the model. self.timeseries[k] = v.get('timeseries') # None if not time series self.output_source[k] = self.__class__.__name__ # output source