Source code for idaes.core.property_meta
##############################################################################
# Institute for the Design of Advanced Energy Systems Process Systems
# Engineering Framework (IDAES PSE Framework) Copyright (c) 2018-2019, by the
# software owners: The Regents of the University of California, through
# Lawrence Berkeley National Laboratory, National Technology & Engineering
# Solutions of Sandia, LLC, Carnegie Mellon University, West Virginia
# University Research Corporation, et al. All rights reserved.
#
# Please see the files COPYRIGHT.txt and LICENSE.txt for full copyright and
# license information, respectively. Both files are also available online
# at the URL "https://github.com/IDAES/idaes-pse".
##############################################################################
"""
These classes handle the metadata aspects of classes representing
property packages.
Implementors of property packages need to do the following:
1. Create a new class that inherits from
:class:`idaes.core.property_base.PhysicalParameterBlock`, which in turn
inherits from :class:`HasPropertyClassMetadata`, in this module.
2. In that class, implement the `define_metadata()` method, inherited from
:class:`HasPropertyClassMetadata`. This method is called
automatically, once, when the `get_metadata()` method is first invoked.
An empty metadata object (an instance of :class:`PropertyClassMetadata`)
will be passed in, which the method should populate with information about
properties and default units.
Example::
from idaes.core.property_base import PhysicalParameterBlock
class MyPropParams(PhysicalParameterBlock):
@classmethod
def define_metadata(cls, meta):
meta.add_default_units({foo.U.TIME: 'fortnights',
foo.U.MASS: 'stones'})
meta.add_properties({'under_sea': {'units': 'leagues'},
'tentacle_size': {'units': 'yards'}})
meta.add_required_properties({'under_sea': 'leagues',
'tentacle_size': 'yards'})
# Also, of course, implement the non-metadata methods that
# do the work of the class.
"""
import six
__author__ = 'Dan Gunter <dkgunter@lbl.gov>'
[docs]class HasPropertyClassMetadata(object):
"""Interface for classes that have PropertyClassMetadata.
"""
_metadata = None
[docs] @classmethod
def get_metadata(cls):
"""Get property parameter metadata.
If the metadata is not defined, this will instantiate a new
metadata object and call `define_metadata()` to set it up.
If the metadata is already defined, it will be simply returned.
Returns:
PropertyClassMetadata: The metadata
"""
if cls._metadata is None:
pcm = PropertyClassMetadata()
cls.define_metadata(pcm)
cls._metadata = pcm
return cls._metadata
[docs] @classmethod
def define_metadata(cls, pcm):
"""Set all the metadata for properties and units.
This method should be implemented by subclasses.
In the implementation, they should set information into the
object provided as an argument.
Args:
pcm (PropertyClassMetadata): Add metadata to this object.
Returns:
None
"""
raise NotImplementedError()
[docs]class UnitNames(object):
"""Names for recognized units.
"""
TM = TIME = 'time'
LN = LENGTH = 'length'
MA = MASS = 'mass'
AM = AMOUNT = 'amount'
TE = TEMPERATURE = 'temperature'
EN = ENERGY = 'energy'
CU = CURRENT = 'current'
LI = LUMINOUS_INTENSITY = 'luminous intensity'
[docs]class PropertyClassMetadata(object):
"""Container for metadata about the property class, which includes
default units and properties.
Example usage::
foo = PropertyClassMetadata()
foo.add_default_units({foo.U.TIME: 'fortnights',
foo.U.MASS: 'stones'})
foo.add_properties({'under_sea': {'units': 'leagues'},
'tentacle_size': {'units': 'yards'}})
foo.add_required_properties({'under_sea': 'leagues',
'tentacle_size': 'yards'})
"""
#: Alias for class enumerating supported/known unit types
U = UnitNames
def __init__(self):
self._default_units = {}
self._properties = {}
self._required_properties = {}
@property
def default_units(self):
return self._default_units
@property
def properties(self):
return self._properties
@property
def required_properties(self):
return self._required_properties
[docs] def add_default_units(self, u):
"""Add a dict with keys for the
quantities used in the property package (as strings) and values of
their default units as strings.
The quantities used by the framework are in constants
defined in :class:`UnitNames`, aliased here in the class
attribute `U`.
Args:
u (dict): Key=property, Value=units
Returns:
None
"""
self._default_units.update(u)
[docs] def add_properties(self, p):
"""Add properties to the metadata.
For each property, the value should be another dict which may contain
the following keys:
- 'method': (required) the name of a method to construct the
property as a str, or None if the property will be
constructed by default.
- 'units': (optional) units of measurement for the property.
Args:
p (dict): Key=property, Value=PropertyMetadata or equiv. dict
Returns:
None
"""
for k, v in six.iteritems(p):
if not isinstance(v, PropertyMetadata):
v = PropertyMetadata(name=k, **v)
self._properties[k] = v
[docs] def add_required_properties(self, p):
"""Add required properties to the metadata.
For each property, the value should be the expected units of
measurement for the property.
Args:
p (dict): Key=property, Value=units
Returns:
None
"""
# Using the same PropertyMetadata class as for units, but 'method'
# will always be none
for k, v in six.iteritems(p):
if not isinstance(v, PropertyMetadata):
v = PropertyMetadata(name=k, units=v)
self._required_properties[k] = v
[docs]class PropertyMetadata(dict):
"""Container for property parameter metadata.
Instances of this class are exactly dictionaries, with the
only difference being some guidance on the values expected in the
dictionary from the constructor.
"""
def __init__(self, name=None, method=None, units=None):
if name is None:
raise TypeError('"name" is required')
d = {'name': name, 'method': method}
if units is not None:
d['units'] = units
else:
# Adding a default "null" unit in case it is not provided by user
d['units'] = "-"
super(PropertyMetadata, self).__init__(d)