Coverage for python/lsst/verify/spec/threshold.py: 45%
69 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-06-06 10:19 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-06-06 10:19 +0000
1# This file is part of verify.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21__all__ = ['ThresholdSpecification']
23import operator
25import astropy.units as u
26from astropy.tests.helper import quantity_allclose
28from ..jsonmixin import JsonSerializationMixin
29from ..datum import Datum
30from ..naming import Name
31from .base import Specification
34class ThresholdSpecification(Specification):
35 """A threshold-type specification, associated with a `Metric`, that
36 defines a binary comparison against a measurement.
38 Parameters
39 ----------
40 name : `str`
41 Name of the specification for a metric. LPM-17, for example,
42 uses ``'design'``, ``'minimum'`` and ``'stretch'`` terminology.
43 quantity : `astropy.units.Quantity`
44 The specification threshold level.
45 operator_str : `str`
46 The threshold's binary comparison operator. The operator is oriented
47 so that ``measurement {{ operator }} threshold quantity`` is the
48 specification test. Can be one of: ``'<'``, ``'<='``, ``'>'``,
49 ``'>='``, ``'=='``, or ``'!='``.
50 metadata_query : `dict`, optional
51 Dictionary of key-value terms that the measurement's metadata must
52 have for this specification to apply.
53 tags : sequence of `str`, optional
54 Sequence of tags that group this specification with others.
55 kwargs : `dict`
56 Keyword arguments passed directly to the
57 `lsst.validate.base.Specification` constructor.
59 Raises
60 ------
61 TypeError
62 If ``name`` is not compartible with `~lsst.verify.Name`,
63 or `threshold` is not a `~astropy.units.Quantity`, or if the
64 ``operator_str`` cannot be converted into a Python binary comparison
65 operator.
66 """
68 threshold = None
69 """The specification threshold level (`astropy.units.Quantity`)."""
71 def __init__(self, name, threshold, operator_str, **kwargs):
72 Specification.__init__(self, name, **kwargs)
74 self.threshold = threshold
75 if not isinstance(self.threshold, u.Quantity):
76 message = 'threshold {0!r} must be an astropy.units.Quantity'
77 raise TypeError(message.format(self.threshold))
78 if not self.threshold.isscalar:
79 raise TypeError('threshold must be scalar')
81 try:
82 self.operator_str = operator_str
83 except ValueError:
84 message = '{0!r} is not a known operator'.format(operator_str)
85 raise TypeError(message)
87 @property
88 def type(self):
89 return 'threshold'
91 def __eq__(self, other):
92 return (self.type == other.type) and \
93 (self.name == other.name) and \
94 quantity_allclose(self.threshold, other.threshold) and \
95 (self.operator_str == other.operator_str)
97 def __ne__(self, other):
98 return not self.__eq__(other)
100 def __repr__(self):
101 return "ThresholdSpecification({0!r}, {1!r}, {2!r})".format(
102 self.name,
103 self.threshold,
104 self.operator_str)
106 def __str__(self):
107 return '{self.operator_str} {self.threshold}'.format(self=self)
109 def _repr_latex_(self):
110 """Get a LaTeX-formatted string representation of the threshold
111 specification test.
113 Returns
114 -------
115 rep : `str`
116 String representation.
117 """
118 template = ('$x$ {self.operator_str} '
119 '{self.threshold.value} '
120 '{self.threshold.unit:latex_inline}')
121 return template.format(self=self)
123 @property
124 def datum(self):
125 r"""Representation of this `ThresholdSpecification`\ 's threshold as
126 a `Datum`.
127 """
128 return Datum(self.threshold, label=str(self.name))
130 @classmethod
131 def deserialize(cls, name=None, threshold=None,
132 metric=None, package=None, **kwargs):
133 """Deserialize from keys in a specification YAML document or a
134 JSON serialization into a `ThresholdSpecification` instance.
136 Parameters
137 ----------
138 name : `str` or `lsst.validate.base.Name`
139 Specification name, either as a string or
140 `~lsst.validate.base.Name`.
141 threshold : `dict`
142 A `dict` with fields:
144 - ``'value'``: threshold value (`float` or `int`).
145 - ``'unit'``: threshold unit, as an `astropy.units.Unit`-
146 compatible `str`.
147 - ``'operator'``: a binary comparison operator, described in
148 the class parameters documentation (`str`).
149 metric : `str` or `lsst.validate.base.Name`, optional
150 Name of the fully-qualified name of the metric the specification
151 corresponds to. This parameter is optional if ``name`` is already
152 fully-qualified.
153 package : `str` or `lsst.validate.base.Name`, optional
154 Name of the package the specification corresponds to. This
155 parameter is optional if ``name`` or ``metric`` are already
156 fully-qualified.
157 kwargs : `dict`
158 Keyword arguments passed directly to the
159 `lsst.validate.base.Specification` constructor.
161 Returns
162 -------
163 specification : `ThresholdSpecification`
164 A specification instance.
165 """
166 _name = Name(metric=metric, spec=name)
167 operator_str = threshold['operator']
168 _threshold = u.Quantity(threshold['value'],
169 u.Unit(threshold['unit']))
170 return cls(_name, _threshold, operator_str, **kwargs)
172 def _serialize_type(self):
173 """Serialize attributes of this specification type to a `dict` that is
174 JSON-serializable.
175 """
176 return JsonSerializationMixin.jsonify_dict(
177 {
178 'value': self.threshold.value,
179 'unit': self.threshold.unit.to_string(),
180 'operator': self.operator_str
181 }
182 )
184 @property
185 def operator_str(self):
186 """Threshold comparision operator ('str').
188 A measurement *passes* the specification if::
190 measurement {{ operator }} threshold == True
192 The operator string is a standard Python binary comparison token, such
193 as: ``'<'``, ``'>'``, ``'<='``, ``'>='``, ``'=='`` or ``'!='``.
194 """
195 return self._operator_str
197 @operator_str.setter
198 def operator_str(self, v):
199 # Cache the operator function as a means of validating the input too
200 self._operator = ThresholdSpecification.convert_operator_str(v)
201 self._operator_str = v
203 @property
204 def operator(self):
205 """Binary comparision operator that tests success of a measurement
206 fulfilling a specification of this metric.
208 Measured value is on left side of comparison and specification level
209 is on right side.
210 """
211 return self._operator
213 @staticmethod
214 def convert_operator_str(op_str):
215 """Convert a string representing a binary comparison operator to
216 the operator function itself.
218 Operators are oriented so that the measurement is on the left-hand
219 side, and specification threshold on the right hand side.
221 The following operators are permitted:
223 ========== =============
224 ``op_str`` Function
225 ========== =============
226 ``>=`` `operator.ge`
227 ``>`` `operator.gt`
228 ``<`` `operator.lt`
229 ``<=`` `operator.le`
230 ``==`` `operator.eq`
231 ``!=`` `operator.ne`
232 ========== =============
234 Parameters
235 ----------
236 op_str : `str`
237 A string representing a binary operator.
239 Returns
240 -------
241 op_func : obj
242 An operator function from the `operator` standard library
243 module.
245 Raises
246 ------
247 ValueError
248 Raised if ``op_str`` is not a supported binary comparison operator.
249 """
250 operators = {'>=': operator.ge,
251 '>': operator.gt,
252 '<': operator.lt,
253 '<=': operator.le,
254 '==': operator.eq,
255 '!=': operator.ne}
256 try:
257 return operators[op_str]
258 except KeyError:
259 message = '{0!r} is not a supported threshold operator'.format(
260 op_str)
261 raise ValueError(message)
263 def check(self, measurement):
264 """Check if a measurement passes this specification.
266 Parameters
267 ----------
268 measurement : `astropy.units.Quantity`
269 The measurement value. The measurement `~astropy.units.Quantity`
270 must have units *compatible* with `threshold`.
272 Returns
273 -------
274 passed : `bool`
275 `True` if the measurement meets the specification,
276 `False` otherwise.
278 Raises
279 ------
280 astropy.units.UnitError
281 Raised if the measurement cannot be compared to the threshold.
282 For example, if the measurement is not an `astropy.units.Quantity`
283 or if the units are not compatible.
284 """
285 return self.operator(measurement, self.threshold)