Source code for postinfer.report.pdg

import math
import warnings


[docs] def exp_of_first_sigfig(value: float) -> int: """Get the exponent of the first significant figure of a number. Parameters ---------- value : float The input number. Returns ------- int The exponent of the first significant figure. """ a = abs(float(value)) if a == 0.0 or not math.isfinite(a): return 0 return math.floor(math.log10(a))
[docs] def round_err_pdg(err: float) -> tuple[float, int]: """Round the error based on PDG convention [1]_. Parameters ---------- err : float The error value. Returns ------- float, int The `err` to display, and the exponent of last precise digit. References ---------- .. [1] https://pdg.lbl.gov/2024/reviews/rpp2024-rev-rpp-intro.pdf """ exp10 = exp_of_first_sigfig(err) if exp10 >= 0: digits = int(err / 10.0 ** (exp10 - 2)) else: digits = int(err / 10.0 ** (exp10 + 1) * 1000) # PDG Rules if 0 < digits <= 354: precision = 1 else: precision = 0 if digits >= 950: err = 10.0 ** (exp10 + 1) return err, exp10 - precision
[docs] def round_pdg( value: float, err: float, err2: float | None = None, exp10: int | None = None, no_sci_nota_exp10_range: tuple[int, int] = (-1, 2), force_asymmetric: bool = False, ) -> str: """Round the value and error based on PDG convention [1]_. The PDG convention states that:: If the three highest order digits of the error lie between 100 and 354, we round to two significant digits. If they lie between 355 and 949, we round to one significant digit. Finally, if they lie between 950 and 999, we round up to 1000 and keep two significant digits. In cases where there are asymmetric errors, if these errors differ by less than 10 percent of the average of the two errors, the average of the two errors is instead used as a symmetric error in the displayed result and the rounding is determined by this average. Otherwise, the narrower of the two asymmetric errors is used to determine the rounding on both. Parameters ---------- value : float The value to be rounded. err : float The error value. err2 : float, optional If provided, the `err` is considered as the lower error, and `err2` as the upper error. exp10 : int, optional The exponent to display. If not provided, it will be determined based on the value and error. no_sci_nota_exp10_range : tuple of int, optional If the exponent of the last digit of errors is in this range, then the result will not be formatted in scientific notation. If `exp10` is provided, it will be ignored. The default is ``(-1, 2)``. force_asymmetric : bool, optional If ``True``, the asymmetric errors will be formatted as asymmetric, regardless of the difference between the two errors. The default is ``False``. Returns ------- str The rounded value and error formatted in LaTeX. References ---------- .. [1] https://pdg.lbl.gov/2024/reviews/rpp2024-rev-rpp-intro.pdf """ value = float(value) err = float(err) if err2 is None: if err < 0.0: raise ValueError('error must be positive') err, precision_exp10 = round_err_pdg(err) else: err2 = float(err2) if err > 0.0: raise ValueError('lower error must be negative') if err2 < 0.0: raise ValueError('upper error must be positive') err_abs = abs(err) if not force_asymmetric: err_diff = abs(err_abs - err2) err_avg = 0.5 * (err_abs + err2) if err_diff <= 0.1 * err_avg: return round_pdg( value, err_avg, exp10=exp10, no_sci_nota_exp10_range=no_sci_nota_exp10_range, ) err, precision_exp10 = round_err_pdg(err_abs) err2_ = err2 err2, precision2_exp10 = round_err_pdg(err2) if err2_ < err_abs: precision_exp10 = precision2_exp10 if exp10 is None: exp10 = max(exp_of_first_sigfig(value), precision_exp10) if no_sci_nota_exp10_range[0] <= exp10 <= no_sci_nota_exp10_range[1]: # Only set exp10=0 if it doesn't violate the precision constraint exp10 = 0 if precision_exp10 <= 0 else exp10 else: if exp10 < precision_exp10: warnings.warn( f'for {value=}, {err=} and {err2=}, {exp10=} is clipped to ' f'the error precision ({precision_exp10})', Warning, ) exp10 = max(int(exp10), precision_exp10) f = 10.0**-exp10 if exp10 >= 0 else 1.0 / 10.0**exp10 p = exp10 - precision_exp10 value_str = f'{value * f:.{p}f}' err_str = f'{err * f:.{p}f}' if err2 is None: s = rf'{value_str} \pm {err_str}' if exp10 != 0.0: s = rf'\left({s}\right) \times 10^{{{exp10}}}' else: err2_str = f'{err2 * f:.{p}f}' s = f'{value_str}_{{-{err_str}}}^{{+{err2_str}}}' if exp10 != 0.0: s = rf'{s} \times 10^{{{exp10}}}' return f'${s}$'