# -*- coding: utf-8 -*-
import numpy
import re
from ..workbench import ScalarOutcome
from .names import ShortnameMixin, TaggableMixin
[docs]class Measure(ScalarOutcome, ShortnameMixin, TaggableMixin):
'''
Measure represents an outcome measure of the model.
Args:
name (str): Name of the measure.
kind (str or int, optional): one of {'info', 'minimize', 'maximize'},
defaults to 'info' if not given. This represents the
generally preferred direction of the measure.
min (number, optional): An expected minimum value that
might be observed under any policy and scenario. This
value is currently only used by the HyperVolume convergence
metric.
max (number, optional): An expected maximum value that
might be observed under any policy and scenario. This
value is currently only used by the HyperVolume convergence
metric.
address (obj, optional): The address or instruction for how to
extract this measure from the model. This is model-specific
and can potentially be any Python object. For example, if the
model is an Excel model, this can be a cell reference given
as a `str`.
dtype ({'real','int','bool','cat'}, default 'real'): The desired
dtype to be enforced for this measure.
function (callable, optional): A callable function that will be
used to transform the raw measure as returned by a core model.
This transformation will be applied to core model results by
the evaluator before they are returned to the user or stored
in a database. It is recommended that EMAT analysis work with the
original untransformed raw values, and employ the `metamodeltype`
functionality as required for non-linear responses.
transform (str, optional): As an alternative to passing a callable
object, use this argument to pass the name of a `numpy` function.
This argument is ignored if `function` is given.
variable_name (str, optional): The name of the raw measure as
output by the underlying core model. If not given, this name is
assumed to be the same as `name`. If no `transform` is set,
it is strongly recommended to not give this argument either.
A principal use of this argument is to descriptively rename
measures that have been transformed, for example if the raw
output measure is 'Total VMT' and a log transform function is
applied, the result can be more descriptively renamed
as 'log(Total VMT)'.
metamodeltype (str, optional): The transformation type to use for
metamodel estimation. This transformation is applied only
internally within the metamodel, and all inputs and outputs
passed to or from the metamodel will not appear in a transformed
state, including measure values stored within the database.
Available metamodel types include:
+ *log*: The natural log of the performance measure is taken before
fitting the regression model. This is appropriate only when the performance
measure will always give a strictly positive outcome. If the performance
measure can take on non-positive values, this may result in errors.
+ *log1p*: The natural log of 1 plus the performance measure is taken before
fitting the regression model. This is preferred to log-linear when the
performance measure is only guaranteed to be non-negative, rather than
strictly positive.
+ *logxp(X)*: The natural log of X plus the performance measure is taken before
fitting the regression model. This allows shifting the position of the
regression intercept to a point other than 0.
+ *linear*: No transforms are made. This is the default.
Attributes:
name (str): Name of the measure.
kind (int): {MINIMIZE, MAXIMIZE, INFO}
transform (str): The name of the transform function, if any.
address (obj): The address or instruction for how to
extract this measure from the model.
metamodeltype (str): The transformation type to use for
metamodel estimation.
'''
def __init__(
self,
name,
kind=ScalarOutcome.INFO,
min=None,
max=None,
address=None,
dtype=None,
function=None,
transform=None,
variable_name=None,
metamodeltype=None,
shortname=None,
desc=None,
formula=None,
tags=None,
parser=None,
):
if isinstance(kind, str):
if kind.lower()=='minimize':
kind = ScalarOutcome.MINIMIZE
elif kind.lower()=='maximize':
kind = ScalarOutcome.MAXIMIZE
elif kind.lower() == 'info':
kind = ScalarOutcome.INFO
else:
raise TypeError(f'invalid kind {kind}')
if transform is None:
func = function
if function is not None:
transform = re.sub(' at 0x[0-9a-fA-F]*', '', f'f:{function}')
elif isinstance(transform, str) and hasattr(numpy, transform):
func = getattr(numpy, transform)
elif isinstance(transform, str) and transform.lower() in ('none',):
func = None
else:
raise TypeError(f'invalid transform {transform}')
if min is not None and max is not None:
expected_range = (min, max)
else:
expected_range = None
super().__init__(name, kind=kind, function=func,
expected_range=expected_range,
variable_name=variable_name)
self.transform = transform if transform is not None else 'none'
self.address = address
self.dtype = dtype if dtype is not None else 'real'
self.metamodeltype = metamodeltype if metamodeltype is not None else 'linear'
self._shortname = shortname
self.desc = desc
"""str: Human readable description of this performance measure, for reference only"""
self.formula = formula
"""str: An eval-able expression to compute this performance measure from other measures"""
self.parser = parser
"""dict: Instructions for how to parse this performance measure from raw output files"""
if tags:
if isinstance(tags, str):
tags = [tags]
for tag in tags:
self.add_tag(tag)
def __repr__(self):
return super().__repr__()
def _hash_it(self, ha=None):
from ..util.hasher import hash_it
return hash_it(
self.name,
self.kind,
self._expected_range,
self.address,
self.dtype,
self.function is None,
self.transform,
tuple(self.variable_name),
self.shape,
self.shortname,
self.metamodeltype,
ha=ha,
)
[docs] def info(self, return_string=False):
"""Print some information about this measure
Args:
return_string (bool): Defaults False (print to stdout) but if given as True
then this function returns the string instead of printing it.
"""
if return_string:
import io
f = io.StringIO
else:
f = None
print(f"{self.name}:")
if self._shortname:
print(f" shortname: {self._shortname}", file=f)
kind = {
ScalarOutcome.MINIMIZE: 'minimize',
ScalarOutcome.MAXIMIZE: 'maximize',
ScalarOutcome.INFO: 'info',
}.get(self.kind)
print(f" kind: {kind}", file=f)
if self.address:
print(f" address: {self.address}", file=f)
if self.dtype != 'real':
print(f" dtype: {self.dtype}", file=f)
if self.metamodeltype != 'linear':
print(f" metamodeltype: {self.metamodeltype}", file=f)
try:
expected_range = self.expected_range
except ValueError:
expected_range = None
if expected_range is not None:
print(f" min: {expected_range[0]}", file=f)
print(f" max: {expected_range[1]}", file=f)
if return_string:
return f.getvalue()