diff options
Diffstat (limited to 'pint/facets/measurement/__init__.py')
-rw-r--r-- | pint/facets/measurement/__init__.py | 219 |
1 files changed, 11 insertions, 208 deletions
diff --git a/pint/facets/measurement/__init__.py b/pint/facets/measurement/__init__.py index f43598b..83454dc 100644 --- a/pint/facets/measurement/__init__.py +++ b/pint/facets/measurement/__init__.py @@ -1,213 +1,16 @@ -from __future__ import annotations - -import copy -import re - -from ...compat import ufloat -from ...formatting import _FORMATS, extract_custom_flags, siunitx_format_unit -from ...util import build_dependent_class, create_class_with_registry -from ..plain import PlainQuantity - -MISSING = object() - - -class MeasurementQuantity: - - # Measurement support - def plus_minus(self, error, relative=False): - if isinstance(error, self.__class__): - if relative: - raise ValueError("{} is not a valid relative error.".format(error)) - error = error.to(self._units).magnitude - else: - if relative: - error = error * abs(self.magnitude) - - return self._REGISTRY.Measurement(copy.copy(self.magnitude), error, self._units) - - -class Measurement(PlainQuantity): - """Implements a class to describe a quantity with uncertainty. - - Parameters - ---------- - value : pint.Quantity or any numeric type - The expected value of the measurement - error : pint.Quantity or any numeric type - The error or uncertainty of the measurement - - Returns - ------- - - """ - - def __new__(cls, value, error, units=MISSING): - if units is MISSING: - try: - value, units = value.magnitude, value.units - except AttributeError: - # if called with two arguments and the first looks like a ufloat - # then assume the second argument is the units, keep value intact - if hasattr(value, "nominal_value"): - units = error - error = MISSING # used for check below - else: - units = "" - try: - error = error.to(units).magnitude - except AttributeError: - pass - - if error is MISSING: - mag = value - elif error < 0: - raise ValueError("The magnitude of the error cannot be negative") - else: - mag = ufloat(value, error) - - inst = super().__new__(cls, mag, units) - return inst - - @property - def value(self): - return self._REGISTRY.Quantity(self.magnitude.nominal_value, self.units) - - @property - def error(self): - return self._REGISTRY.Quantity(self.magnitude.std_dev, self.units) - - @property - def rel(self): - return abs(self.magnitude.std_dev / self.magnitude.nominal_value) - - def __reduce__(self): - # See notes in Quantity.__reduce__ - from pint import _unpickle_measurement - - return _unpickle_measurement, (Measurement, self.magnitude, self._units) +""" + pint.facets.measurement + ~~~~~~~~~~~~~~~~~~~~~~~ - def __repr__(self): - return "<Measurement({}, {}, {})>".format( - self.magnitude.nominal_value, self.magnitude.std_dev, self.units - ) + Adds pint the capability to handle measurements (quantities with uncertainties). - def __str__(self): - return "{}".format(self) + :copyright: 2022 by Pint Authors, see AUTHORS for more details. + :license: BSD, see LICENSE for more details. +""" - def __format__(self, spec): - - spec = spec or self.default_format - - # special cases - if "Lx" in spec: # the LaTeX siunitx code - # the uncertainties module supports formatting - # numbers in value(unc) notation (i.e. 1.23(45) instead of 1.23 +/- 0.45), - # using type code 'S', which siunitx actually accepts as input. - # However, the implementation is incompatible with siunitx. - # Uncertainties will do 9.1(1.1), which is invalid, should be 9.1(11). - # TODO: add support for extracting options - # - # Get rid of this code, we'll deal with it here - spec = spec.replace("Lx", "") - # The most compatible format from uncertainties is the default format, - # but even this requires fixups. - # For one, SIUnitx does not except some formats that unc does, like 'P', - # and 'S' is broken as stated, so... - spec = spec.replace("S", "").replace("P", "") - # get SIunitx options - # TODO: allow user to set this value, somehow - opts = _FORMATS["Lx"]["siopts"] - if opts != "": - opts = r"[" + opts + r"]" - # SI requires space between "+-" (or "\pm") and the nominal value - # and uncertainty, and doesn't accept "+/-", so this setting - # selects the desired replacement. - pm_fmt = _FORMATS["Lx"]["pm_fmt"] - mstr = format(self.magnitude, spec).replace(r"+/-", pm_fmt) - # Also, SIunitx doesn't accept parentheses, which uncs uses with - # scientific notation ('e' or 'E' and sometimes 'g' or 'G'). - mstr = mstr.replace("(", "").replace(")", " ") - ustr = siunitx_format_unit(self.units._units, self._REGISTRY) - return r"\SI%s{%s}{%s}" % (opts, mstr, ustr) - - # standard cases - if "L" in spec: - newpm = pm = r" \pm " - pars = _FORMATS["L"]["parentheses_fmt"] - elif "P" in spec: - newpm = pm = "±" - pars = _FORMATS["P"]["parentheses_fmt"] - else: - newpm = pm = "+/-" - pars = _FORMATS[""]["parentheses_fmt"] - - if "C" in spec: - sp = "" - newspec = spec.replace("C", "") - pars = _FORMATS["C"]["parentheses_fmt"] - else: - sp = " " - newspec = spec - - if "H" in spec: - newpm = "±" - newspec = spec.replace("H", "") - pars = _FORMATS["H"]["parentheses_fmt"] - - mag = format(self.magnitude, newspec).replace(pm, sp + newpm + sp) - if "(" in mag: - # Exponential format has its own parentheses - pars = "{}" - - if "L" in newspec and "S" in newspec: - mag = mag.replace("(", r"\left(").replace(")", r"\right)") - - if "L" in newspec: - space = r"\ " - else: - space = " " - - uspec = extract_custom_flags(spec) - ustr = format(self.units, uspec) - if not ("uS" in newspec or "ue" in newspec or "u%" in newspec): - mag = pars.format(mag) - - if "H" in spec: - # Fix exponential format - mag = re.sub(r"\)e\+0?(\d+)", r")×10<sup>\1</sup>", mag) - mag = re.sub(r"\)e-0?(\d+)", r")×10<sup>-\1</sup>", mag) - - return mag + space + ustr - - -# TODO: Remove in the near future -# This is kept for easy backward compatibility during refactoring. -_Measurement = Measurement - - -class MeasurementRegistry: - - _quantity_class = MeasurementQuantity - _measurement_class = Measurement - - def __init_subclass__(cls, **kwargs): - super().__init_subclass__() - - cls.Measurement = build_dependent_class( - cls, "Measurement", "_measurement_class" - ) - - def _init_dynamic_classes(self) -> None: - """Generate subclasses on the fly and attach them to self""" - super()._init_dynamic_classes() - - if ufloat is not None: - self.Measurement = create_class_with_registry(self, self.Measurement) - else: +from __future__ import annotations - def no_uncertainties(*args, **kwargs): - raise RuntimeError( - "Pint requires the 'uncertainties' package to create a Measurement object." - ) +from .objects import Measurement, MeasurementQuantity +from .registry import MeasurementRegistry - self.Measurement = no_uncertainties +__all__ = [Measurement, MeasurementQuantity, MeasurementRegistry] |