Source code for threeML.io.uncertainty_formatter

```from __future__ import division

import re

import numpy as np
import uncertainties
from past.utils import old_div

from threeML.io.logging import setup_logger

log = setup_logger(__name__)

[docs]def interval_to_errors(value, low_bound, hi_bound):
"""
Convert error intervals to errors

:param value: central value
:param low_bound: interval low bound
:param hi_bound: interval high bound
:return: (error minus, error plus)
"""

error_plus = hi_bound - value
error_minus = value - low_bound

return error_minus, error_plus

[docs]def get_uncertainty_tokens(x):
"""
Split the given uncertainty in number, error and exponent.

:param x: an uncertainty instance
:return: number, error and exponent
"""

this_str = x.__str__()

is_inf = False

if "inf" in this_str:
is_inf = True

this_str = this_str.replace("inf", "nan")

try:

number, uncertainty, exponent = re.match(
"\(?(\-?[0-9]+\.?[0-9]*) ([0-9]+\.?[0-9]*)\)?(e[\+|\-][0-9]+)?",
this_str.replace("+/-", " ").replace("nan", "0"),
).groups()

except:

log.error(
f"Could not extract number, uncertainty and exponent from  {x.__str__()}. This is likely a bug.")

raise RuntimeError()

if is_inf:

uncertainty = "inf"

return number, uncertainty, exponent

def _order_of_magnitude(value):

return 10 ** np.floor(np.log10(abs(value)))

[docs]def uncertainty_formatter(value, low_bound, hi_bound):
"""
Gets a value and its error in input, and returns the value, the uncertainty and the common exponent with the proper
number of significant digits in a string like (4.2 -0.023 +5.23) x 10^5

:param value:
:param error: a *positive* value
:return: string representation of interval
"""

# Get the errors (instead of the boundaries)

error_m, error_p = interval_to_errors(value, low_bound, hi_bound)

error_p_is_nan = False
error_m_is_nan = False

if not np.isfinite(error_p):

log.warning(f"the positive uncertainty is not finite ")

error_p_is_nan = True

if not np.isfinite(error_m):

log.warning(f"the negative uncertainty is not finite ")

error_m_is_nan = True

# Compute the sign of the errors
# NOTE: sometimes value is not within low_bound - hi_bound, so these sign might not always
# be -1 and +1 respectively

sign_m = _sign(low_bound - value)
sign_p = _sign(hi_bound - value)

# Scale the values to the order of magnitude of the value

tmp = [_order_of_magnitude(value)]

if not error_m_is_nan:

tmp.append(_order_of_magnitude(error_m))

if not error_p_is_nan:

tmp.append(_order_of_magnitude(error_p))

order_of_magnitude = max(tmp)

scaled_value = old_div(value, order_of_magnitude)
scaled_error_m = old_div(error_m, order_of_magnitude)
scaled_error_p = old_div(error_p, order_of_magnitude)

# Get the uncertainties instance of the scaled values/errors

x = uncertainties.ufloat(scaled_value, abs(scaled_error_m))

# Split the uncertainty in number, negative error, and exponent (if any)

num1, unc1, exponent1 = get_uncertainty_tokens(x)

# Repeat the same for the other error

y = uncertainties.ufloat(scaled_value, abs(scaled_error_p))

num2, unc2, exponent2 = get_uncertainty_tokens(y)

# Choose the representation of the number with more digits
# This is necessary for asymmetric intervals where one of the two errors is much larger in magnitude
# then the others. For example, 1 -0.01 +90. This will choose 1.00 instead of 1,so that the final
# representation will be 1.00 -0.01 +90

if len(num1) > len(num2):

num = num1

else:

num = num2

# Get the exponent of 10 to use for the representation

expon = int(np.log10(order_of_magnitude))

if unc1 != unc2:

# Asymmetric error

repr1 = "%s%s" % (sign_m, unc1)
repr2 = "%s%s" % (sign_p, unc2)

if expon == 0:

# No need to show any power of 10

return "%s %s %s" % (num, repr1, repr2)

elif expon == 1:

# Display 10 instead of 10^1

return "(%s %s %s) x 10" % (num, repr1, repr2)

else:

# Display 10^expon

return "(%s %s %s) x 10^%s" % (num, repr1, repr2, expon)

else:

# Symmetric error
repr1 = "+/- %s" % unc1

if expon == 0:

return "%s %s" % (num, repr1)

elif expon == 1:

return "(%s %s) x 10" % (num, repr1)

else:

return "(%s %s) x 10^%s" % (num, repr1, expon)

def _sign(number):

if number < 0:

return "-"

else:

return "+"
```