Source code for lsst.verify.spec.threshold

#
# LSST Data Management System
#
# This product includes software developed by the
# LSST Project (http://www.lsst.org/).
#
# See COPYRIGHT file at the top of the source tree.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the LSST License Statement and
# the GNU General Public License along with this program.  If not,
# see <https://www.lsstcorp.org/LegalNotices/>.
#
from __future__ import print_function, division

__all__ = ['ThresholdSpecification']

import operator

import astropy.units as u
from astropy.tests.helper import quantity_allclose

from ..jsonmixin import JsonSerializationMixin
from ..datum import Datum
from ..naming import Name
from .base import Specification


[docs]class ThresholdSpecification(Specification): """A threshold-type specification, associated with a `Metric`, that defines a binary comparison against a measurement. Parameters ---------- name : `str` Name of the specification for a metric. LPM-17, for example, uses ``'design'``, ``'minimum'`` and ``'stretch'`` terminology. quantity : `astropy.units.Quantity` The specification threshold level. operator_str : `str` The threshold's binary comparison operator. The operator is oriented so that ``measurement {{ operator }} threshold quantity`` is the specification test. Can be one of: ``'<'``, ``'<='``, ``'>'``, ``'>='``, ``'=='``, or ``'!='``. metadata_query : `dict`, optional Dictionary of key-value terms that the measurement's metadata must have for this specification to apply. tags : sequence of `str`, optional Sequence of tags that group this specification with others. kwargs : `dict` Keyword arguments passed directly to the `lsst.validate.base.Specification` constructor. Raises ------ TypeError If ``name`` is not compartible with `~lsst.verify.Name`, or `threshold` is not a `~astropy.units.Quantity`, or if the ``operator_str`` cannot be converted into a Python binary comparison operator. """ threshold = None """The specification threshold level (`astropy.units.Quantity`).""" def __init__(self, name, threshold, operator_str, **kwargs): Specification.__init__(self, name, **kwargs) self.threshold = threshold if not isinstance(self.threshold, u.Quantity): message = 'threshold {0!r} must be an astropy.units.Quantity' raise TypeError(message.format(self.threshold)) if not self.threshold.isscalar: raise TypeError('threshold must be scalar') try: self.operator_str = operator_str except ValueError: message = '{0!r} is not a known operator'.format(operator_str) raise TypeError(message) @property def type(self): return 'threshold' def __eq__(self, other): return (self.type == other.type) and \ (self.name == other.name) and \ quantity_allclose(self.threshold, other.threshold) and \ (self.operator_str == other.operator_str) def __ne__(self, other): return not self.__eq__(other) def __repr__(self): return "ThresholdSpecification({0!r}, {1!r}, {2!r})".format( self.name, self.threshold, self.operator_str) def __str__(self): return '{self.operator_str} {self.threshold}'.format(self=self) def _repr_latex_(self): """Get a LaTeX-formatted string representation of the threshold specification test. Returns ------- rep : `str` String representation. """ template = ('$x$ {self.operator_str} ' '{self.threshold.value} ' '{self.threshold.unit:latex_inline}') return template.format(self=self) @property def datum(self): """Representation of this `ThresholdSpecification`\ 's threshold as a `Datum`. """ return Datum(self.threshold, label=str(self.name)) @classmethod
[docs] def deserialize(cls, name=None, threshold=None, metric=None, package=None, **kwargs): """Deserialize from keys in a specification YAML document or a JSON serialization into a `ThresholdSpecification` instance. Parameters ---------- name : `str` or `lsst.validate.base.Name` Specification name, either as a string or `~lsst.validate.base.Name`. threshold : `dict` A `dict` with fields: - ``'value'``: threshold value (`float` or `int`). - ``'unit'``: threshold unit, as an `astropy.units.Unit`- compatible `str`. - ``'operator'``: a binary comparison operator, described in the class parameters documentation (`str`). metric : `str` or `lsst.validate.base.Name`, optional Name of the fully-qualified name of the metric the specification corresponds to. This parameter is optional if ``name`` is already fully-qualified. package : `str` or `lsst.validate.base.Name`, optional Name of the package the specification corresponds to. This parameter is optional if ``name`` or ``metric`` are already fully-qualified. kwargs : `dict` Keyword arguments passed directly to the `lsst.validate.base.Specification` constructor. Returns ------- specification : `ThresholdSpecification` A specification instance. """ _name = Name(metric=metric, spec=name) operator_str = threshold['operator'] _threshold = u.Quantity(threshold['value'], u.Unit(threshold['unit'])) return cls(_name, _threshold, operator_str, **kwargs)
def _serialize_type(self): """Serialize attributes of this specification type to a `dict` that is JSON-serializable. """ return JsonSerializationMixin.jsonify_dict( { 'value': self.threshold.value, 'unit': self.threshold.unit.to_string(), 'operator': self.operator_str } ) @property def operator_str(self): """Threshold comparision operator ('str'). A measurement *passes* the specification if:: measurement {{ operator }} threshold == True The operator string is a standard Python binary comparison token, such as: ``'<'``, ``'>'``, ``'<='``, ``'>='``, ``'=='`` or ``'!='``. """ return self._operator_str @operator_str.setter def operator_str(self, v): # Cache the operator function as a means of validating the input too self._operator = ThresholdSpecification.convert_operator_str(v) self._operator_str = v @property def operator(self): """Binary comparision operator that tests success of a measurement fulfilling a specification of this metric. Measured value is on left side of comparison and specification level is on right side. """ return self._operator @staticmethod
[docs] def convert_operator_str(op_str): """Convert a string representing a binary comparison operator to the operator function itself. Operators are oriented so that the measurement is on the left-hand side, and specification threshold on the right hand side. The following operators are permitted: ========== ============= ``op_str`` Function ========== ============= ``>=`` `operator.ge` ``>`` `operator.gt` ``<`` `operator.lt` ``<=`` `operator.le` ``==`` `operator.eq` ``!=`` `operator.ne` ========== ============= Parameters ---------- op_str : `str` A string representing a binary operator. Returns ------- op_func : obj An operator function from the `operator` standard library module. Raises ------ ValueError Raised if ``op_str`` is not a supported binary comparison operator. """ operators = {'>=': operator.ge, '>': operator.gt, '<': operator.lt, '<=': operator.le, '==': operator.eq, '!=': operator.ne} try: return operators[op_str] except KeyError: message = '{0!r} is not a supported threshold operator'.format( op_str) raise ValueError(message)
[docs] def check(self, measurement): """Check if a measurement passes this specification. Parameters ---------- measurement : `astropy.units.Quantity` The measurement value. The measurement `~astropy.units.Quantity` must have units *compatible* with `threshold`. Returns ------- passed : `bool` `True` if the measurement meets the specification, `False` otherwise. Raises ------ astropy.units.UnitError Raised if the measurement cannot be compared to the threshold. For example, if the measurement is not an `astropy.units.Quantity` or if the units are not compatible. """ return self.operator(measurement, self.threshold)