Skip to content

Commit

Permalink
Update style, fix hep data export.
Browse files Browse the repository at this point in the history
  • Loading branch information
riga committed Mar 15, 2023
1 parent 0a196ac commit 7b5cd82
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 27 deletions.
1 change: 1 addition & 0 deletions requirements_test.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
PyYAML
flake8>=4.0;python_version>="3.6"
flake8>=3.0,<4.0;python_version<="2.7"
flake8-commas>=2.1
Expand Down
77 changes: 50 additions & 27 deletions scinum.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,8 @@ def nominal(self, nominal):
# compare shape if already an array
elif nominal.shape != first_unc.shape:
raise ValueError("shape not matching uncertainty shape: {}".format(
nominal.shape))
nominal.shape,
))
nominal = nominal.astype(self.dtype)
else:
raise TypeError("invalid nominal value: {}".format(nominal))
Expand Down Expand Up @@ -639,8 +640,8 @@ def str(self, format=None, unit=None, scientific=False, si=False, labels=True, s
else:
# special formatting implemented by round_value
nominal, uncs, _mag = round_value(nominal, uncs, method=format, **kwargs)
fmt = lambda x, **kwargs: match_precision(float(x) * 10.0**_mag, 10.0**_mag,
**kwargs)
def fmt(x, **kwargs):
return match_precision(float(x) * 10.0**_mag, 10.0**_mag, **kwargs)

# helper to build the ending consisting of scientific notation or SI prefix, and unit
def ending():
Expand Down Expand Up @@ -711,8 +712,9 @@ def repr(self, *args, **kwargs):
if not self.is_numpy:
text = "'" + self.str(*args, **kwargs) + "'"
else:
text = "numpy array, shape {}, {} uncertainties".format(self.shape,
len(self.uncertainties))
text = "numpy array, shape {}, {} uncertainties".format(
self.shape, len(self.uncertainties),
)

return "<{} at {}, {}>".format(self.__class__.__name__, hex(id(self)), text)

Expand Down Expand Up @@ -838,16 +840,20 @@ def _apply(self, op, other, rho=1.0, inplace=True, **kwargs):
if isinstance(op, Operation):
py_op = op.py_op
if not py_op:
raise RuntimeError("cannot apply operation using {} intance that is not configured "
"to combine uncertainties of two operations".format(op))
raise RuntimeError(
"cannot apply operation using {} intance that is not configured "
"to combine uncertainties of two operations".format(op),
)

# when other is a correlation object and op is (mat)mul, return a deferred result that is to
# be resolved in the next operation
if isinstance(other, Correlation):
if py_op not in correlation_ops:
names = ",".join(o.__name__ for o in correlation_ops)
raise ValueError("cannot apply correlation object {} via operator {}, supported "
"operators are: {}".format(other, py_op.__name__, names))
raise ValueError(
"cannot apply correlation object {} via operator {}, supported "
"operators are: {}".format(other, py_op.__name__, names),
)
return DeferredResult(self, other)

# when other is a deferred result, use its number of correlation
Expand Down Expand Up @@ -907,8 +913,10 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
# let the operation itself handle the uncerainty update
if op.has_py_op():
if len(inputs) != 2:
raise RuntimeError("the operation '{}' is configured to combine uncertainties of "
"two operands, but received only {}: {}".format(op, len(inputs), inputs))
raise RuntimeError(
"the operation '{}' is configured to combine uncertainties of "
"two operands, but received only {}: {}".format(op, len(inputs), inputs),
)
result = inputs[0]._apply(op, inputs[1], inplace=False, **kwargs)
else:
result = op(*inputs, **kwargs)
Expand Down Expand Up @@ -1280,8 +1288,7 @@ def __repr__(self):

def __call__(self, num, *args, **kwargs):
if self.derivative is None:
raise Exception("cannot run operation '{}', no derivative registered".format(
self.name))
raise Exception("cannot run operation '{}', no derivative registered".format(self.name))

# ensure we deal with a number instance
num = ensure_number(num)
Expand Down Expand Up @@ -1896,10 +1903,12 @@ def combine_uncertainties(op, unc1, unc2, nom1=None, nom2=None, rho=0.0):
# when numpy arrays, the shapes of unc and nom must match
if is_numpy(unc1) and is_numpy(nom1) and unc1.shape != nom1.shape:
raise ValueError("the shape of unc1 and nom1 must be equal, found {} and {}".format(
unc1.shape, nom1.shape))
unc1.shape, nom1.shape,
))
if is_numpy(unc2) and is_numpy(nom2) and unc2.shape != nom2.shape:
raise ValueError("the shape of unc2 and nom2 must be equal, found {} and {}".format(
unc2.shape, nom2.shape))
unc2.shape, nom2.shape,
))

# prepare values for combination, depends on operator
if op in ("*", "/", "**"):
Expand Down Expand Up @@ -1948,8 +1957,11 @@ def ensure_numpy(nom, unc):

# combined formula
if op == "**":
return (nom * abs(nom2) * (
unc1**2.0 + (math.log(nom1) * unc2)**2.0 + 2 * rho * math.log(nom1) * unc1 * unc2)**0.5)
return (
nom *
abs(nom2) *
(unc1**2.0 + (math.log(nom1) * unc2)**2.0 + 2 * rho * math.log(nom1) * unc1 * unc2)**0.5
)

# flip rho for sub and div
if op in ("-", "/"):
Expand Down Expand Up @@ -2059,7 +2071,8 @@ def infer_uncertainty_precision(sig, mag, method):
if isinstance(method, integer_types):
if method <= 0:
raise ValueError("cannot infer precision for non-positive method value '{}'".format(
method))
method,
))

prec = method
if _is_numpy:
Expand All @@ -2084,8 +2097,10 @@ def infer_uncertainty_precision(sig, mag, method):

else: # is_numpy
if not is_numpy(mag) or sig.shape != mag.shape:
raise ValueError("sig and mag must both be NumPy arrays with the same shape, got\n"
"{}\nand\n{}".format(sig, mag))
raise ValueError(
"sig and mag must both be NumPy arrays with the same shape, got\n"
"{}\nand\n{}".format(sig, mag),
)

prec = np.ones(sig.shape, int) * prec

Expand Down Expand Up @@ -2298,8 +2313,10 @@ def round_value(val, unc=None, method=0, align_precision=True, **kwargs):
if method in infer_uncertainty_precision.uncertainty_methods:
# uncertainty based rounding
if not has_unc:
raise ValueError("cannot perform uncertainty based rounding with method '{}' "
"without uncertainties on value {}".format(method, val))
raise ValueError(
"cannot perform uncertainty based rounding with method '{}' "
"without uncertainties on value {}".format(method, val),
)

# use the uncertainty with the smallest magnitude
get_mag = lambda u: round_uncertainty(u, method=method)[1]
Expand Down Expand Up @@ -2412,8 +2429,10 @@ def create_hep_data_representer(method=None, force_asymmetric=False, force_float
rounding internally.
"""
if not HAS_YAML:
raise RuntimeError("create_hep_data_representer requires PyYAML (https://pyyaml.org) to be "
"installed on your system")
raise RuntimeError(
"create_hep_data_representer requires PyYAML (https://pyyaml.org) to be installed on " +
"your system",
)

# yaml node factories
y_map = lambda value: yaml.MappingNode(tag="tag:yaml.org,2002:map", value=value)
Expand All @@ -2440,12 +2459,16 @@ def representer(dumper, num):

# apply the rounding method
nom = num.nominal
uncs = num.uncertainties.values()
uncs = list(num.uncertainties.values())
_method = method or num.default_format or "pdg+1"
nom, uncs, mag = round_value(nom, uncs, method=_method, **kwargs)
def fmt(x, sign=1.0):
return match_precision(sign * float(x) * 10.0**mag, 10.0**mag, force_float=force_float,
**kwargs)
return match_precision(
sign * float(x) * 10.0**mag,
10.0**mag,
force_float=force_float,
**kwargs # noqa
)

# build error nodes
error_nodes = []
Expand Down
37 changes: 37 additions & 0 deletions tests/test_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from scinum import (
Number, Correlation, DeferredResult, ops, HAS_NUMPY, HAS_UNCERTAINTIES, split_value,
match_precision, calculate_uncertainty, round_uncertainty, round_value, infer_si_prefix,
create_hep_data_representer,
)

if HAS_NUMPY:
Expand Down Expand Up @@ -676,3 +677,39 @@ def test_deferred_resolution(self):
n = (self.num * Correlation(A=0)) + self.num
self.assertEqual(n.u("A"), (0.5**0.5, 0.5**0.5))
self.assertEqual(n.u("B"), (2.0, 2.0))

def test_hep_data_export(self):
import yaml

yaml.add_representer(Number, create_hep_data_representer())

self.assertEqual(yaml.dump(self.num).strip(), """
value: 2.500
errors:
- label: A
symerror: 0.500
- label: B
symerror: 1.000
- label: C
asymerror:
plus: 1.000
minus: -1.500
- label: D
symerror: 0.250
- label: E
asymerror:
plus: 0.250
minus: -0.500
- label: F
asymerror:
plus: 1.000
minus: -0.500
- label: G
asymerror:
plus: 0.750
minus: -0.300
- label: H
asymerror:
plus: 0.750
minus: -0.300
""".strip())

0 comments on commit 7b5cd82

Please sign in to comment.