Source code for nerea.utils

from collections.abc import Iterable
import numpy as np
import pandas as pd

__all__ = ['integral_v_u', 'time_integral_v_u', 'ratio_uncertainty', 'ratio_v_u', 'product_v_u', 'dot_product_v_u',
           '_make_df']

[docs] def integral_v_u(s: pd.Series) -> tuple[float, float]: """ `nerea.utils.integral_v_u()` ---------------------------- Compute the integral (sum) of a series and its associated uncertainty. Parameters ---------- **s** : ``pd.Series`` The series of values to sum. Returns ------- ``tuple[float, float]`` The sum value and uncertainty. Examples -------- >>> import numpy as np >>> from nerea.utils import integral_v_u >>> s = np.array([1, 2, 3, 4]) >>> v, u = integral_v_u(s) >>> print(f"Sum: {v}, Uncertainty: {u}") Sum: 10, Uncertainty: 3.1622776601683794 """ v = s.sum() u = np.sqrt(v) return v, u
[docs] def time_integral_v_u(s: pd.DataFrame) -> tuple[float, float]: """ `nerea.utils.integral_v_u()` ---------------------------- Compute the time integral (c.dot(dt)) of a series and its associated uncertainty. Parameters ---------- **s** : ``pd.DataFrame`` The series of values to inegrate. Has ``Time`` and ``value`` columns. Returns ------- ``tuple[float, float]`` The integral value and uncertainty. Notes ----- - ``s`` is assumed to be steps-post and the data are treated accordingly, hence ``s`` should end 1 time step after the desired end of integration. To allow calculation of time differences, s should start 1 time spep before the desired start time.""" v = (s.value * s.Time.diff().shift(-1).apply(lambda x: x.total_seconds())).sum() u = np.sqrt(v) return v, u
[docs] def ratio_uncertainty(n, un, d, ud) -> tuple[float, float]: """ `nerea.utils.ratio_uncertainty()` --------------------------------- Compute the uncertainty of a ratio given the values and uncertainties of the numerator and denominator. Parameters ---------- **n** : ``float`` The value of the numerator. **un** : ``float`` The absolute uncertainty of the numerator. **d** : ``float`` The value of the denominator. **ud** : ``float`` The absolute uncertainty of the denominator. Returns ------- ``float`` The absolute uncertainty of the ratio. Examples -------- >>> from nerea.utils import ratio_uncertainty >>> n, un = 10, 0.5 >>> d, ud = 5, 0.2 >>> u_ratio = ratio_uncertainty(n, un, d, ud) >>> print(f"Uncertainty of the ratio: {u_ratio}") Uncertainty of the ratio: 0.1118033988749895""" return np.sqrt(((1 / d * un)**2 + (n / d**2 * ud)**2))
[docs] def ratio_v_u(n: pd.DataFrame, d: pd.DataFrame) -> tuple[float, float]: """ `nerea.utils.ratio_v_u()` ------------------------- Compute the value and uncertainty of a ratio given objects with value and uncertainty attributes. Parameters ---------- **n** : ``pd.DataFrame`` An object with ``'value'`` and ``'uncertainty'`` attributes representing the numerator. **d** : ``pd.DataFrame`` An object with ``'value'`` and ``'uncertainty'`` attributes representing the denominator. Returns ------- ``tuple[float, float]`` The integral value and uncertainty. Examples -------- >>> class Measurement: ... def __init__(self, value, uncertainty): ... self.value = value ... self.uncertainty = uncertainty ... >>> from nerea.utils import ratio_v_u >>> n = Measurement(10, 0.5) >>> d = Measurement(5, 0.2) >>> v, u = ratio_v_u(n, d) >>> print(f"Ratio: {v}, Uncertainty: {u}") Ratio: 2.0, Uncertainty: 0.1118033988749895""" v = n.value / d.value u = ratio_uncertainty(n.value, n.uncertainty, d.value, d.uncertainty) return v, u
[docs] def product_v_u(factors: Iterable[pd.DataFrame]) -> tuple[float, float]: """ `nerea.utils.product_v_u()` --------------------------- Computes the product of a number of values and propagates their uncertainty to the result. Parameters ---------- **factors** : ``Iterable[pd.DataFrame]`` the factors to multiply. Each dataframe should come with ``'value'`` and ``'uncertainty [%]'`` columns. Returns ------- ``tuple[float, float]`` The integral value and uncertainty.""" v = pd.concat([df["value"] for df in factors], axis=1).prod(axis=1) # it is easier to work in relative terms for products and turn to absolute later on u = (pd.concat([df["uncertainty [%]"] for df in factors], axis=1) / 100 ).pow(2).sum(axis=1) return v, np.sqrt(u) * v
[docs] def dot_product_v_u(a: pd.DataFrame, b: pd.DataFrame) -> tuple[float, float]: """ `nerea.utils.dot_product_v_u()` ------------------------------- Calculates value and uncertainty of the dot product of two vectors. Parameters ---------- **a** : ``pd.DataFrame`` First vector: a data frame with ``'value'`` and ``'uncertainty'`` columns. **b** : ``pd.DataFrame`` Second vector: a data frame with ``'value'`` and ``'uncertainty'`` columns. Returns ------- ``tuple[float, float]`` The integral value and uncertainty.""" idx = a.index.intersection(b.index) a_ = a.loc[idx] b_ = b.loc[idx] v = a_.value @ b_.value u = (a_.value **2 @ b_.uncertainty **2) + (a_.uncertainty **2 @ b_.value **2) return v, np.sqrt(u)
def sum_v_u(addends: Iterable[pd.DataFrame]) -> tuple[float, float]: """ `nerea.utils.sum_v_u()` ----------------------- Calculates value and uncertainty of a sum. Params ------ **a** : ``Iterable[pd.DataFrame]`` Lists all items to sum. Each is a data frame with ``'value'`` and ``'uncertainty'`` columns. Returns ------- ``tuple[float, float]`` The integral value and uncertainty.""" a = pd.concat(addends) return a["value"].sum(), np.sum(a["uncertainty"] **2) def _make_df(v, u, relative: bool=True, idx: pd.Index=None) -> pd.DataFrame: """ `nerea.utils._make_df()` ------------------------ Create a ``pd.DataFrame`` with the given value and uncertainty. Parameters ---------- **v** : ``Iterable | float`` The value to store in the DataFrame. **u** : ``Iterable | float`` The uncertainty to store in the DataFrame. **relative** : ``bool``, optional Flag to enable calulation of the relative uncertainty too. **idx** : ``pd.Index``, optional Index of the output dataframe. Default is ``None``, index is set to ``'value'``. Returns ------- ``pd.DataFrame`` A DataFrame with ``'value'`` and ``'uncertainty'`` columns. Examples -------- >>> from nerea.utils import _make_df >>> v, u = 10, 0.5 >>> df = _make_df(v, u) >>> print(df) value uncertainty value 10.0 0.5""" if not isinstance(v, Iterable) and not isinstance(v, (str, bytes)): idx_ = idx if idx is not None else ['value'] rel = u / v * 100 if relative else np.nan out = pd.DataFrame({'value': v, 'uncertainty': u, 'uncertainty [%]': rel}, index=idx_) elif isinstance(v, (str, bytes)): raise TypeError(f"{type(v)} is not an allowed type for _make_df") else: v_, u_ = np.array(v), np.array(u) rel = u_ / v_ * 100 if relative else [np.nan] * len(v_) idx_ = idx if idx is not None else ['value'] * len(v_) out = pd.DataFrame({'value': v_, 'uncertainty': u_, 'uncertainty [%]': rel}, index=idx_) return out