Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse uncertainties #1615

Merged
merged 28 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3e743a3
Parse uncertain numbers e.g. (1.0+/-0.2)e+03
MichaelTiemannOSC Oct 14, 2022
a54c597
Fix problems identified by python -m pre_commit run --all-files
MichaelTiemannOSC Oct 15, 2022
7d2fada
Enhance support for `uncertainties`. See #1611, #1614.
MichaelTiemannOSC Oct 16, 2022
fc8564b
Fix up failures and errors found by test suite.
MichaelTiemannOSC Oct 19, 2022
c8fe27f
Copy in changes from PR1596
MichaelTiemannOSC Oct 21, 2022
126a859
Create modular uncertainty parser layer
MichaelTiemannOSC Nov 18, 2022
d17b70c
Resolving conflicts against pint 20.1
MichaelTiemannOSC Dec 29, 2022
f89e183
Fix conflict merge error
MichaelTiemannOSC Dec 29, 2022
7198cf0
Update util.py
MichaelTiemannOSC Jan 3, 2023
e5004a5
Update pint_eval.py
MichaelTiemannOSC Jan 17, 2023
a4a1fa5
Update pint_eval.py
MichaelTiemannOSC Jan 17, 2023
7938056
Fixed to work with both + and - e notation in the actually processing…
MichaelTiemannOSC Feb 18, 2023
4e551da
Merge branch 'master' into parse-uncertainties
MichaelTiemannOSC May 1, 2023
caa5a1a
Fix test suite failures
MichaelTiemannOSC May 1, 2023
f577436
Merge branch 'master' into parse-uncertainties
MichaelTiemannOSC Jun 25, 2023
b810af6
Fix tokenizer merge error in pint/util.py
MichaelTiemannOSC Jun 25, 2023
810a092
Merge cleanup: pint_eval.py needs tokenize
MichaelTiemannOSC Jun 25, 2023
5a4eb10
Make black happier
MichaelTiemannOSC Jun 25, 2023
945e93f
Make ruff happy
MichaelTiemannOSC Jun 25, 2023
397969d
Make ruff happier
MichaelTiemannOSC Jun 25, 2023
ec4123c
Update toktest.py
MichaelTiemannOSC Jun 25, 2023
032d972
Update test_util.py
MichaelTiemannOSC Jun 26, 2023
772da53
Fix additional regressions in test suite
MichaelTiemannOSC Jun 28, 2023
3c54747
Update quantity.py
MichaelTiemannOSC Jul 2, 2023
10e07ea
Merge remote-tracking branch 'origin/master' into parse-uncertainties
MichaelTiemannOSC Jul 23, 2023
4e20d99
Make `babel` a dependency for testbase
MichaelTiemannOSC Sep 15, 2023
f55b8de
Update .readthedocs.yaml
MichaelTiemannOSC Sep 15, 2023
00f08f3
Fix failing tests
MichaelTiemannOSC Sep 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ Pint Changelog
(Issue #1030, #574)
- Added angular frequency documentation page.
- Move ASV benchmarks to dedicated folder. (Issue #1542)
- An ndim attribute has been added to Quantity and DataFrame has been added to upcast
types for pint-pandas compatibility. (#1596)
- Fix a recursion error that would be raised when passing quantities to `cond` and `x`.
(Issue #1510, #1530)
- Update test_non_int tests for pytest.
- Better support for uncertainties (See #1611, #1614)

0.19.2 (2022-04-23)
-------------------
Expand Down
46 changes: 27 additions & 19 deletions pint/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,18 @@
from __future__ import annotations

import math
import tokenize
from decimal import Decimal
from io import BytesIO
from numbers import Number

try:
from uncertainties import UFloat, ufloat
from uncertainties import unumpy as unp

HAS_UNCERTAINTIES = True
except ImportError:
UFloat = ufloat = unp = None
HAS_UNCERTAINTIES = False


def missing_dependency(package, display_name=None):
display_name = display_name or package
Expand All @@ -29,12 +36,6 @@ def _inner(*args, **kwargs):
return _inner


def tokenizer(input_string):
for tokinfo in tokenize.tokenize(BytesIO(input_string.encode("utf-8")).readline):
if tokinfo.type != tokenize.ENCODING:
yield tokinfo


# TODO: remove this warning after v0.10
class BehaviorChangeWarning(UserWarning):
pass
Expand All @@ -47,7 +48,10 @@ class BehaviorChangeWarning(UserWarning):

HAS_NUMPY = True
NUMPY_VER = np.__version__
NUMERIC_TYPES = (Number, Decimal, ndarray, np.number)
if HAS_UNCERTAINTIES:
NUMERIC_TYPES = (Number, Decimal, ndarray, np.number, UFloat)
else:
NUMERIC_TYPES = (Number, Decimal, ndarray, np.number)

def _to_magnitude(value, force_ndarray=False, force_ndarray_like=False):
if isinstance(value, (dict, bool)) or value is None:
Expand All @@ -56,6 +60,11 @@ def _to_magnitude(value, force_ndarray=False, force_ndarray_like=False):
raise ValueError("Quantity magnitude cannot be an empty string.")
elif isinstance(value, (list, tuple)):
return np.asarray(value)
elif HAS_UNCERTAINTIES:
from pint.facets.measurement.objects import Measurement

if isinstance(value, Measurement):
return ufloat(value.value, value.error)
if force_ndarray or (
force_ndarray_like and not is_duck_array_type(type(value))
):
Expand Down Expand Up @@ -109,16 +118,13 @@ def _to_magnitude(value, force_ndarray=False, force_ndarray_like=False):
"lists and tuples are valid magnitudes for "
"Quantity only when NumPy is present."
)
return value
elif HAS_UNCERTAINTIES:
from pint.facets.measurement.objects import Measurement

if isinstance(value, Measurement):
return ufloat(value.value, value.error)
return value

try:
from uncertainties import ufloat

HAS_UNCERTAINTIES = True
except ImportError:
ufloat = None
HAS_UNCERTAINTIES = False

try:
from babel import Locale as Loc
Expand Down Expand Up @@ -155,9 +161,9 @@ def _to_magnitude(value, force_ndarray=False, force_ndarray_like=False):

# Pandas (Series)
try:
from pandas import Series
from pandas import DataFrame, Series

upcast_types.append(Series)
upcast_types += [DataFrame, Series]
except ImportError:
pass

Expand Down Expand Up @@ -271,6 +277,8 @@ def isnan(obj, check_all: bool):
try:
return math.isnan(obj)
except TypeError:
if HAS_UNCERTAINTIES:
return unp.isnan(obj)
return False


Expand Down
19 changes: 10 additions & 9 deletions pint/facets/measurement/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class Measurement(PlainQuantity):

"""

def __new__(cls, value, error, units=MISSING):
def __new__(cls, value, error=MISSING, units=MISSING):
if units is MISSING:
try:
value, units = value.magnitude, value.units
Expand All @@ -60,17 +60,18 @@ def __new__(cls, value, error, units=MISSING):
error = MISSING # used for check below
else:
units = ""
try:
error = error.to(units).magnitude
except AttributeError:
pass

if error is MISSING:
# We've already extracted the units from the Quantity above
mag = value
elif error < 0:
raise ValueError("The magnitude of the error cannot be negative")
else:
mag = ufloat(value, error)
try:
error = error.to(units).magnitude
except AttributeError:
pass
if 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
Expand Down
12 changes: 12 additions & 0 deletions pint/facets/numpy/quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
set_units_ufuncs,
)

try:
import uncertainties.unumpy as unp
from uncertainties import ufloat, UFloat
HAS_UNCERTAINTIES = True
except ImportError:
unp = np
ufloat = Ufloat = None
HAS_UNCERTAINTIES = False


def method_wraps(numpy_func):
if isinstance(numpy_func, str):
Expand Down Expand Up @@ -223,6 +232,9 @@ def __getattr__(self, item) -> Any:
)
else:
raise exc
elif HAS_UNCERTAINTIES and item=="ndim" and isinstance(self._magnitude, UFloat):
# Dimensionality of a single UFloat is 0, like any other scalar
return 0

try:
return getattr(self._magnitude, item)
Expand Down
22 changes: 21 additions & 1 deletion pint/facets/plain/quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@
if HAS_NUMPY:
import numpy as np # noqa

try:
import uncertainties.unumpy as unp
from uncertainties import ufloat, UFloat
HAS_UNCERTAINTIES = True
except ImportError:
unp = np
ufloat = Ufloat = None
HAS_UNCERTAINTIES = False


def reduce_dimensions(f):
def wrapped(self, *args, **kwargs):
Expand Down Expand Up @@ -143,6 +152,12 @@ class PlainQuantity(PrettyIPython, SharedRegistryObject, Generic[_MagnitudeType]
default_format: str = ""
_magnitude: _MagnitudeType

@property
def ndim(self) -> int:
if isinstance(self.magnitude, numbers.Number):
return 0
return self.magnitude.ndim

@property
def force_ndarray(self) -> bool:
return self._REGISTRY.force_ndarray
Expand Down Expand Up @@ -267,7 +282,12 @@ def __bytes__(self) -> bytes:
return str(self).encode(locale.getpreferredencoding())

def __repr__(self) -> str:
if isinstance(self._magnitude, float):
if HAS_UNCERTAINTIES:
if isinstance(self._magnitude, UFloat):
return f"<Quantity({self._magnitude:.6}, '{self._units}')>"
else:
return f"<Quantity({self._magnitude}, '{self._units}')>"
elif isinstance(self._magnitude, float):
return f"<Quantity({self._magnitude:.9}, '{self._units}')>"
else:
return f"<Quantity({self._magnitude}, '{self._units}')>"
Expand Down
5 changes: 3 additions & 2 deletions pint/facets/plain/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@
from pint import Quantity, Unit

from ... import parser
from ... import pint_eval
from ..._typing import QuantityOrUnitLike, UnitLike
from ..._vendor import appdirs
from ...compat import HAS_BABEL, babel_parse, tokenizer
from ...compat import HAS_BABEL, babel_parse
from ...definitions import Definition
from ...errors import (
DefinitionSyntaxError,
Expand Down Expand Up @@ -1346,7 +1347,7 @@ def parse_expression(
for p in self.preprocessors:
input_string = p(input_string)
input_string = string_preprocessor(input_string)
gen = tokenizer(input_string)
gen = pint_eval.tokenizer(input_string)

return build_eval_tree(gen).evaluate(
lambda x: self._eval_token(x, case_sensitive=case_sensitive, **values)
Expand Down
Loading