This page contains only API docs. For more info, visit scinum on GitHub or open the example.ipynb notebook on binder: Open in binder

Scientific numbers with multiple uncertainties and correlation-aware, gaussian propagation and numpy support.



class Number(nominal=0.0, uncertainties={})

Implementation of a scientific number, i.e., a nominal value with named uncertainties. uncertainties mist be a dict or convertable to a dict with strings as keys. If a value is an int or float, it is interpreted as an absolute, symmetric uncertainty. If it is a tuple, it is interpreted in different ways. Examples:

from scinum import Number, REL, ABS, UP, DOWN

num = Number(2.5, {
    "sourceA": 0.5,                  # absolute 0.5, both up and down
    "sourceB": (1.0, 1.5),           # absolute 1.0 up, 1.5 down
    "sourceC": (REL, 0.1),           # relative 10%, both up and down
    "sourceD": (REL, 0.1, 0.2),      # relative 10% up, 20% down
    "sourceE": (1.0, REL, 0.2),      # absolute 1.0 up, relative 20% down
    "sourceF": (REL, 0.3, ABS, 0.3)  # relative 30% up, absolute 0.3 down

# get the nominal value via direct access
num.nominal # => 2.5

# get the nominal value via __call__() (same as get())
num()                     # => 2.5
num(direction="nominal")  # => 2.5

# get uncertainties
num.get_uncertainty("sourceA")  # => (0.5, 0.5)
num.get_uncertainty("sourceB")  # => (1.0, 1.5)
num.get_uncertainty("sourceC")  # => (0.25, 0.25)
num.get_uncertainty("sourceD")  # => (0.25, 0.5)
num.get_uncertainty("sourceE")  # => (1.0, 0.5)
num.get_uncertainty("sourceF")  # => (0.75, 0.3)

# get shifted values via __call__() (same as get())
num(UP, "sourceA")               # => 3.0
num(DOWN, "sourceB")             # => 1.0
num(UP, ("sourceC", "sourceD"))  # => 2.854...
num(UP)                          # => 4.214... (all uncertainties)

# get only the uncertainty (unsigned)
num(DOWN, ("sourceE", "sourceF"), unc=True)  # => 0.583...

# get the uncertainty factor (unsigned)
num(DOWN, ("sourceE", "sourceF"), factor=True)  # => 1.233...

# combined
num(DOWN, ("sourceE", "sourceF"), unc=True, factor=True)  # => 0.233...

When uncertainties is not a dictionary, it is interpreted as the default uncertainty, named Number.DEFAULT.

This class redefines most of Python’s magic functions to allow transparent use in standard operations like +, *, etc. Gaussian uncertainty propagation is applied automatically. When operations connect two number instances, their uncertainties are combined assuming there is no correlation. For correlation-aware operations, please refer to methods such as add() or mul() below. Examples:

num = Number(5, 1)
print(num + 2)  # -> '7.0 +- 1.0'
print(num * 3)  # -> '15.0 +- 3.0'

num2 = Number(2.5, 1.5)
print(num + num2)  # -> '7.5 +- 1.80277563773'
print(num * num2)  # -> '12.5 +- 7.90569415042'

num.add(num2, rho=1)
print(num)  # -> '7.5 +- 2.5'

See str() for information on string formatting.

classattribute default_format

The default format string ("%s") that is used in str() when no format string was passed.

classattribute DEFAULT

Constant that denotes the default uncertainty ("default").

classattribute ALL

Constant that denotes all uncertainties ("all").

classattribute REL

Constant that denotes relative errors ("rel").

classattribute ABS

Constant that denotes absolute errors ("abs").

classattribute NOMINAL

Constant that denotes the nominal value ("nominal").

classattribute UP

Constant that denotes the up direction ("up").

classattribute DOWN

Constant that denotes the down direction ("down").

classattribute N

Shorthand for NOMINAL.

classattribute U

Shorthand for UP.

classattribute D

Shorthand for DOWN.

type: float

The nominal value.

type: float

Shorthand for nominal.

type: dictionary

The uncertainty dictionary that maps names to 2-tuples holding absolute up/down effects.

type: bool

Whether or not a NumPy array is wrapped.

type: tuple

The shape of the wrapped NumPy array or None, depending on what type is wrapped.

type: type

The default dtype to use when a NumPy array is wrapped. The initial value is numpy.float32 when NumPy is available, None otherwise.

get_uncertainty(name='default', direction=None, default=None)

Returns the absolute up and down variaton in a 2-tuple for an uncertainty name. When direction is set, the particular value is returned instead of a 2-tuple. In case no uncertainty was found and default is not None, that value is returned.

u(*args, **kwargs)

Shorthand for get_uncertainty().

set_uncertainty(name, value)

Sets the uncertainty value for an uncertainty name. value should have one of the formats as described in uncertainties().

clear(nominal=None, uncertainties=None)

Removes all uncertainties and sets the nominal value to zero (float). When nominal and uncertainties are given, these new values are set on this instance.

str(format=None, unit=None, scientific=False, si=False, labels=True, style='plain', styles=None, force_asymmetric=False, **kwargs)

Returns a readable string representiation of the number. format is used to format non-NumPy nominal and uncertainty values. It can be a string such as "%d", a function that is called with the value to format, or a rounding function as accepted by round_value(). When None (the default), default_format is used. All keyword arguments except wildcard kwargs are only used to format non-NumPy values. In case of NumPy objects, kwargs are passed to numpy.array2string.

When unit is set, it is appended to the end of the string. When scientific is True, all values are represented by their scientific notation. When scientific is False and si is True, the appropriate SI prefix is used. labels controls whether uncertainty labels are shown in the string. When True, uncertainty names are used, but it can also be a list of labels whose order should match the uncertainty dict traversal order. style can be "plain", "latex", or "root". styles can be a dict with fields "space", "label", "unit", "sym", "asym", "sci" to customize every aspect of the format style on top of style_dict. Unless force_asymmetric is True, an uncertainty is quoted symmetric if it yields identical values in both directions.


n = Number(17.321, {"a": 1.158, "b": 0.453})
n.str()               # -> '17.321 +- 1.158 (a) +- 0.453 (b)'
n.str("%.1f")         # -> '17.3 +- 1.2 (a) +- 0.5 (b)'
n.str("publication")  # -> '17.32 +- 1.16 (a) +- 0.45 (b)'
n.str("pdg")          # -> '17.3 +- 1.2 (a) +- 0.5 (b)'

n = Number(8848, 10)
n.str(unit="m")                           # -> "8848.0 +- 10.0 m"
n.str(unit="m", force_asymmetric=True)    # -> "8848.0 +10.0-10.0 m"
n.str(unit="m", scientific=True)          # -> "8.848 +- 0.01 x 1E3 m"
n.str("%.2f", unit="m", scientific=True)  # -> "8.85 +- 0.01 x 1E3 m"
n.str(unit="m", si=True)                  # -> "8.848 +- 0.01 km"
n.str("%.2f", unit="m", si=True)          # -> "8.85 +- 0.01 km"
n.str(unit="m", style="latex")            # -> "8848.0 \pm 10.0\,m"
n.str(unit="m", style="latex", si=True)   # -> "8.848 \pm 0.01\,km"
n.str(unit="m", style="root")             # -> "8848.0 #pm 10.0 m"
n.str(unit="m", style="root", si=True)    # -> "8.848 #pm 0.01 km"
repr(*args, **kwargs)

Returns the unique string representation of the number.

copy(nominal=None, uncertainties=None)

Returns a deep copy of the number instance. When nominal or uncertainties are set, they overwrite the fields of the copied instance.

get(direction=NOMINAL, names=ALL, unc=False, factor=False)

Returns different representations of the contained value(s). direction should be any of NOMINAL, UP or DOWN. When not NOMINAL, names decides which uncertainties to take into account for the combination. When unc is True, only the unsigned, combined uncertainty is returned. When False, the nominal value plus or minus the uncertainty is returned. When factor is True, the ratio w.r.t. the nominal value is returned.

add(other, rho=1.0, inplace=True)

Adds an other Number or DeferredResult instance, propagating all uncertainties. Uncertainties with the same name are combined with the correlation rho, which can either be a Correlation instance, a dict with correlations defined per uncertainty, or a plain float. When inplace is False, a new instance is returned.

sub(other, rho=1.0, inplace=True)

Subtracts an other Number or DeferredResult instance, propagating all uncertainties. Uncertainties with the same name are combined with the correlation rho, which can either be a Correlation instance, a dict with correlations defined per uncertainty, or a plain float. When inplace is False, a new instance is returned.

mul(other, rho=1.0, inplace=True)

Multiplies by an other Number or DeferredResult instance, propagating all uncertainties. Uncertainties with the same name are combined with the correlation rho, which can either be a Correlation instance, a dict with correlations defined per uncertainty, or a plain float. When inplace is False, a new instance is returned.

Unlike the other operations, other can also be a Correlation instance, in which case a DeferredResult is returned to resolve the combination of uncertainties later on.

div(other, rho=1.0, inplace=True)

Divides by an other Number or DeferredResult instance, propagating all uncertainties. Uncertainties with the same name are combined with the correlation rho, which can either be a Correlation instance, a dict with correlations defined per uncertainty, or a plain float. When inplace is False, a new instance is returned.

pow(other, rho=1.0, inplace=True)

Raises by the power of an other Number or DeferredResult instance, propagating all uncertainties. Uncertainties with the same name are combined with the correlation rho, which can either be a Correlation instance, a dict with correlations defined per uncertainty, or a plain float. When inplace is False, a new instance is returned.


class Correlation([default, ]**rhos)

Container class describing correlations to be applied to equally named uncertainties when combining two Number instances through an operator.

A correlation object is therefore applied to a number by means of multiplication or matrix multiplication (i.e. * or @), resulting in a DeferredResult object which is used subsequently by the actual combination operation with an other number. See DeferredResult for more examples.

Correlation coefficients can be defined per named source of uncertainty via rhos. When a coefficient is retrieved (by get()) with a name that was not defined before, a default value is used, which itself defaults to one.

get(name, default=None)

Returns a correlation coefficient rho named name. When no coefficient with that name exists and default is set, which itself defaults to default, this value is returned instead. Otherwise, a KeyError is raised.


class DeferredResult(number, correlation)

Class that wraps a Number instance number and a Correlation instance correlation that is automatically produced as a result of a multiplication or matrix multiplication between the two. Internally, this is used for the deferred resolution of uncertainty correlations when combined with an other Number. Example:

n = Number(2, 5)

n * Correlation(1) * n
# -> '25.0 +- 20.0' (the default)

n * Correlation(0) * n
# -> '25.00 +- 14.14'

# note the multiplication n * c, which creates the DeferredResult
n**(n * c)
# -> '3125.00 +- 11842.54'
type: Number

The wrapped number object.

type: Correlation

The wrapped correlation object.


class ops

Number-aware replacement for the global math (or numpy) module. The purpose of the class is to provide operations (e.g. pow, cos, sin, etc.) that automatically propagate the uncertainties of a Number instance through the derivative of the operation. Example:

num = ops.pow(Number(5, 1), 2)
print(num) # -> 25.00 (+10.00, -10.00)
classmethod register(function=None, name=None, py_op=None, ufuncs=None)

Registers a new math function function with name and returns an Operation instance. A math function expects a Number as its first argument, followed by optional (keyword) arguments. When name is None, the name of the function is used. The returned object can be used to set the derivative (similar to property). Example:

def my_op(x):
    return x * 2 + 1

def my_op(x):
    return 2

num = Number(5, 2)
print(num) # -> 5.00 (+2.00, -2.00)

num = ops.my_op(num)
print(num) # -> 11.00 (+4.00, -4.00)

Please note that there is no need to register simple functions as in the particular example above as most of them are just composite operations whose derivatives are already known.

When the registered operation is a member of operators and thus capable of propagating uncertainties with two operands, py_op should be set to the symbol of the operation (e.g. "*", see _py_ops).

To comply with NumPy’s ufuncs ( that are dispatched by Number.__array_ufunc__(), an operation might register the ufuncs objects that it handles. When strings, they are interpreted as a name of a NumPy function.

classmethod get_operation(name)

Returns an operation that was previously registered with name.

classmethod op(*args, **kwargs)

Shorthand for get_operation().

classmethod get_ufunc_operation(ufunc)

Returns an operation that was previously registered to handle a NumPy ufunc, which can be a string or the function itself. None is returned when no operation was found to handle the function.

classmethod rebuilt_ufunc_cache()

Rebuilts the internal cache of ufuncs.


class Operation(function, derivative=None, name=None, py_op=None, ufuncs=None)

Wrapper around a function and its derivative.

type: function

The wrapped function.

type: function

The wrapped derivative.

type: string

The name of the operation.

type: None, string

The symbol referring to an operation that implements uncertainty propagation combining two operands.

type: list

List of ufunc objects that this operation handles.


class typed(fparse=None, setter=True, deleter=True, name=None)

Shorthand for the most common property definition. Can be used as a decorator to wrap around a single function. Example:

 class MyClass(object):

    def __init__(self):
        self._foo = None

    def foo(self, foo):
        if not isinstance(foo, str):
            raise TypeError("not a string: {}".format(foo))
        return foo

myInstance = MyClass() = 123    # -> TypeError = "bar"  # -> ok
print(   # -> prints "bar"

In the exampe above, set/get calls target the instance member _foo, i.e. “_<function_name>”. The member name can be configured by setting name. If setter (deleter) is True (the default), a setter (deleter) method is booked as well. Prior to updating the member when the setter is called, fparse is invoked which may implement sanity checks.



combine_uncertainties(op, unc1, unc2, nom1=None, nom2=None, rho=0.0)

Combines two uncertainties unc1 and unc2 according to an operator op which must be either "+", "-", "*", "/", or "**". The three latter operators require that you also pass the nominal values nom1 and nom2, respectively. The correlation can be configured via rho.


calculate_uncertainty(terms, rho=0.0)

Generically calculates the uncertainty of a quantity that depends on multiple terms. Each term is expected to be a 2-tuple containing the derivative and the uncertainty of the term. Correlations can be defined via rho. When rho is a numner, all correlations are set to this value. It can also be a mapping of a 2-tuple, the two indices of the terms to describe, to their correlation coefficient. In case the indices of two terms are not included in this mapping, they are assumed to be uncorrelated. Example:

calculate_uncertainty([(3, 0.5), (4, 0.5)])
# uncorrelated
# -> 2.5

calculate_uncertainty([(3, 0.5), (4, 0.5)], rho=1)
# fully correlated
# -> 3.5

calculate_uncertainty([(3, 0.5), (4, 0.5)], rho={(0, 1): 1})
# fully correlated
# -> 3.5

calculate_uncertainty([(3, 0.5), (4, 0.5)], rho={(1, 2): 1})
# no rho value defined for pair (0, 1), assumes zero correlation
# -> 2.5


ensure_number(num, *args, **kwargs)

Returns num again if it is an instance of Number, or uses all passed arguments to create one and returns it.



Returns nominal again if it is not an instance of Number, or returns its nominal value.



Returns True when numpy is available on your system and x is a numpy type.



Returns True when the “uncertainties” package is available on your system and x is a ufloat.


parse_ufloat(x, default_tag='default')

Takes a ufloat object x from the “uncertainties” package and returns a tuple with two elements containing its nominal value and a dictionary with its uncertainties. When the error components of x contain multiple uncertainties with the same name, they are combined under the assumption of full correlation. When an error component is not tagged, default_tag is used.



Returns the numpy module when is_numpy() for x is True, and the math module otherwise.


make_list(obj, cast=True)

Converts an object obj to a list and returns it. Objects of types tuple and set are converted if cast is True. Otherwise, and for all other types, obj is put in a new list.



Splits a value val into its significand and decimal exponent (magnitude) and returns them in a 2-tuple. val might also be a numpy array. Example:

split_value(1)     # -> (1.0, 0)
split_value(0.123) # -> (1.23, -1)
split_value(-42.5) # -> (-4.25, 1)

a = np.array([1, 0.123, -42.5])
split_value(a) # -> ([1.0, 1.23, -4.25], [0, -1, 1])

The significand will be a float while magnitude will be an integer. val can be reconstructed via significand * 10**magnitude.


match_precision(val, ref, force_float=False, **kwargs)

Returns a string version of a value val matching the significant digits as given in ref. val might also be a numpy array. Unless force_float is True, the returned string might represent an integer in case the decimal digits are removed. All remaining kwargs are forwarded to Decimal.quantize. Example:

match_precision(1.234, "0.1") # -> "1.2"
match_precision(1.234, "1.0") # -> "1"
match_precision(1.234, "0.1", decimal.ROUND_UP) # -> "1.3"

a = np.array([1.234, 5.678, -9.101])
match_precision(a, "0.1") # -> ["1.2", "5.7", "-9.1"]


round_uncertainty(unc, method=1, precision=None, **kwargs)

Rounds an uncertainty unc following a specific method and returns a 3-tuple containing the significant digits as a string, the decimal magnitude that is required to recover the uncertainty, and the precision (== number of significant digits). unc might also be a numpy array. Possible values for the rounding method are:

  • "pdg": Rounding rules as defined by the PDG.

  • "pdg+1": Same rules as for "pdg" with an additional significant digit.

  • "publication", "pub": Same rules as for``”pdg+1”`` but without the rounding of the first three significant digits above 949 to 1000.

  • positive integer: Enforces a fixed number of significant digits.

By default, the target precision is derived from the rounding method itself. However, a value can be defined to enfore a certain number of significant digits after the rounding took place. This is only useful for methods that include fixed rounding thresholds ("pdg"). All remaining kwargs are forwarded to match_precision() which is performing the rounding internally.


round_uncertainty(0.123, 1)      # -> ("1", -1, 1)
round_uncertainty(0.123, "pub")  # -> ("123", -3, 3)
round_uncertainty(0.123, "pdg")  # -> ("12", -2, 2)

round_uncertainty(0.456, 1)      # -> ("5", -1, 1)
round_uncertainty(0.456, "pub")  # -> ("46", -2, 2)
round_uncertainty(0.456, "pdg")  # -> ("5", -1, 1)

round_uncertainty(9.87, 1)      # -> ("1", 1, 1)
round_uncertainty(9.87, "pub")  # -> ("99", -1, 2)
round_uncertainty(9.87, "pdg")  # -> ("10", 0, 2)

# enfore higher precision
round_uncertainty(0.987, "pub", precision=3)  # -> ("990", -3, 3)
round_uncertainty(0.987, "pdg", precision=3)  # -> ("100", -2, 3)

# numpy array support
a = np.array([0.123, 0.456, 0.987])
round_uncertainty(a, "pub")  # -> (["123", "46", "987"], [-3, -2, -3])


round_value(val, unc=None, method=0, align_precision=True, **kwargs)

Rounds a number val with an uncertainty unc which can be a single float or array (symmetric) or a 2-tuple (asymmetric up / down) of floats or arrays. It also supports a list of these values for simultaneous evaluation. When val is a Number instance, its uncertainties are used in their default iteration order. Returns a 3-tuple containing:

  • The string representation of the central value.

  • The string representation(s) of uncertainties. The structure is identical to the one passed on unc.

  • The decimal magnitude.

method controls the behavior of the rounding:

  1. When "pdg", "pdg+1", "publication", or "pub", uncertainties are required and internally round_uncertainty() is used to infer the precision based on the smallest uncertainty.

  2. When a formatting string is passed, it should have the (default) pattern "%*.<N>f", and N is interpreted as the number of digits after the decimal point.

  3. When a negative integer or zero (the default) is passed, the value is interpreted as the number of digits after the decimal point (similar to passing a format string).

  4. When a positive number is passed, it is interpreted as the amount of significant digits to keep, evaluated on the smallest number among either the nominal or uncertainty values.

In case multiple uncertainties are given and the rounding method is uncertainty-based (1. above), the precision is derived based on the smallest uncertainty as a reference and then enforced to the nominal value and all other uncertainties when align_precision is True. Otherwise, values are allowed to have different precisions. All remaining kwargs are forwarded to match_precision() which is performing the rounding internally.


# differnt uncertainty structures
round_value(1.23, 0.456, 1)             # -> ("12", "5", -1)
round_value(1.23, [0.456], 1)           # -> ("12", ["5"], -1)
round_value(1.23, (0.456, 0.987), 1)    # -> ("12", ("5", "10"), -1)
round_value(1.23, [(0.456, 0.987)], 1)  # -> ("12", [("5", "10")], -1)
round_value(1.23, [0.456, 0.987], 1)    # -> ("12", ["5", "10"], -1)

# different rounding methods
round_value(125.09, (0.56, 0.97))          # -> ("125", ("1", "1"), 0)
round_value(125.09, (0.56, 0.97), "pub")   # -> ("12509", ("56", "97"), -2)
round_value(125.09, (0.56, 0.97), "%.2f")  # -> ("12509", ("56", "97"), -2)
round_value(125.09, (0.56, 0.97), -2)      # -> ("12509", ("56", "97"), -2)
round_value(125.09, (0.56, 0.97), 3)       # -> ("125090", ("560", "970"), -3)

# without uncertainties
round_value(125.09, method=2)       # -> ("13", None, 1)
round_value(125.09, method=-2)      # -> ("12509", None, -2)
round_value(125.09, method="%.2f")  # -> ("12509", None, -2)
round_value(125.09, method="pdg")   # -> Exception, "pdg" is uncertainty based

# array support
vals = np.array([1.23, 4.56])
uncs = np.array([0.45678, 0.078])
round_value(vals, uncs, 2)  # -> (["123", "4560"], ["46", "78"], [-2, -3])



Infers the SI prefix of a value f and returns the string label and decimal magnitude in a 2-tuple. Example:

infer_si_prefix(1)     # -> ("", 0)
infer_si_prefix(25)    # -> ("", 0)
infer_si_prefix(4320)  # -> ("k", 3)


create_hep_data_representer(method=None, force_asymmetric=False, force_float=False, **kwargs)

Creates a PyYAML representer function that encodes a Number as a data structure that is compatible to the HEPData format for values in data files.

import yaml
import scinum as sn

yaml.add_representer(sn.Number, sn.create_hep_data_representer())

For documentation of the rounding method, see round_uncertainty(). When None, the default_format of the number instance is used in case it is not a python format string. Otherwise "pdg+1" is assumed. When the up and down variations of an uncertainty are identical after rounding, they are encoded as a symmetric uncertainty unless force_asymmetric is True. Also, when all decimal digits are removed during rounding, the final value is encoded as an integer value unless force_float is True.

All remaining kwargs are forwarded to match_precision() which is performing the rounding internally.

Other attributes

type: dict

Dictionaly containing formatting styles for "plain", "latex" and "root" styles which are used in Number.str(). Each style dictionary contains 6 fields: "space", "label", "unit", "sym", "asym", and "sci". As an example, the plain style is configured as

    "space": " ",
    "label": "({label})",
    "unit": " {unit}",
    "sym": "+- {unc}",
    "asym": "+{up}-{down}",
    "sci": "x 1E{mag}",
type: bool

A flag that is True when NumPy is available on your system, False otherwise.

type: bool

A flag that is True when the uncertainties package is available on your system, False otherwise.

type: bool

A flag that is True when PyYAML is available on your system, False otherwise.

type: string

Shorthand for Number.NOMINAL.

type: string

Shorthand for Number.UP.

type: string

Shorthand for Number.DOWN.

type: string

Shorthand for Number.REL.

type: string

Shorthand for Number.ABS.