Check uncercalculation for uniform/rect uncer serDeser behaves strange
Input
<?xml version='1.0' encoding='utf-8'?>
<dcc:quantity xmlns:dcc="https://ptb.de/dcc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:si="https://ptb.de/si" refType="vibration_sensorTemperature">
<dcc:name>
<dcc:content lang="en">Sensor temperature</dcc:content>
<dcc:content lang="de">Temperatur des Aufnehmers</dcc:content>
</dcc:name>
<si:hybrid>
<si:real>
<si:value>296.15</si:value>
<si:unit>\kelvin</si:unit>
<si:measurementUncertaintyUnivariate>
<si:expandedMU>
<si:valueExpandedMU>1</si:valueExpandedMU>
<si:coverageFactor>1.73</si:coverageFactor>
<si:coverageProbability>0.95</si:coverageProbability>
<si:distribution>rectangular</si:distribution>
</si:expandedMU>
</si:measurementUncertaintyUnivariate>
</si:real>
<si:real>
<si:value>23.0</si:value>
<si:unit>\degreecelsius</si:unit>
<si:measurementUncertaintyUnivariate>
<si:expandedMU>
<si:valueExpandedMU>1</si:valueExpandedMU>
<si:coverageFactor>1.73</si:coverageFactor>
<si:coverageProbability>0.95</si:coverageProbability>
<si:distribution>rectangular</si:distribution>
</si:expandedMU>
</si:measurementUncertaintyUnivariate>
</si:real>
</si:hybrid>
</dcc:quantity>
Output
<?xml version='1.0' encoding='utf-8'?>
<dcc:quantity xmlns:dcc="https://ptb.de/dcc" xmlns:si="https://ptb.de/si" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="U6a8247a1-9808-4157-8613-2275e382eef0" refType="vibration_sensorTemperature">
<dcc:name>
<dcc:content lang="en">Sensor temperature</dcc:content>
<dcc:content lang="de">Temperatur des Aufnehmers</dcc:content>
</dcc:name>
<si:hybrid>
<si:real>
<si:value>296.15</si:value>
<si:unit>\kelvin</si:unit>
<si:measurementUncertaintyUnivariate>
<si:expandedMU>
<si:valueExpandedMU>1.0</si:valueExpandedMU>
<si:coverageFactor>1.73</si:coverageFactor>
<si:coverageProbability>0.9994079828490263</si:coverageProbability>
<si:distribution>uniform</si:distribution>
</si:expandedMU>
</si:measurementUncertaintyUnivariate>
</si:real>
<si:real>
<si:value>23.0</si:value>
<si:unit>\degreecelsius</si:unit>
<si:measurementUncertaintyUnivariate>
<si:expandedMU>
<si:valueExpandedMU>1.0</si:valueExpandedMU>
<si:coverageFactor>1.73</si:coverageFactor>
<si:coverageProbability>0.9994079828490263</si:coverageProbability>
<si:distribution>uniform</si:distribution>
</si:expandedMU>
</si:measurementUncertaintyUnivariate>
</si:real>
</si:hybrid>
</dcc:quantity>
def handle_coverage_params(coverageProbability, k_factor, distribution):
"""
Calculate and check coverageProbability and k_factor for normal and uniform distributions.
For normal distributions:
- If k_factor is provided, compute coverageProbability = erf(k_factor/√2).
- If only coverageProbability is provided, compute k_factor = √2 * erfinv(coverageProbability).
- If both are provided, recalc coverageProbability from k_factor (priority to k_factor)
and ensure the mismatch is within dccConfiguration.allowedCoveragePropabilityMissmatch.
- If a missmatch occures either an warning is generated and given k is used (DccConfiguration.CoveragePropabilityMissmatchBehavior.WARNING_TAKE_K_VALUE)
or and Exception is raised DccConfiguration.CoveragePropabilityMissmatchBehavior.EXCEPTION
For uniform distributions:
- If k_factor is provided:
computed_cov = (k_factor/√3 + 1)/2 if k_factor <= √3, else computed_cov = 1.0.
- If only coverageProbability is provided, compute k_factor = √3*(2*coverageProbability - 1).
- If both are provided, compute computed_cov and check against the provided coverageProbability.
Parameters:
coverageProbability (float or None): The provided coverage probability.
k_factor (float or None): The provided coverage factor.
distribution (str): "normal" or "uniform".
Returns:
tuple: (coverageProbability, k_factor) after calculation/validation.
Raises:
ValueError: If neither parameter is provided or if the provided values are inconsistent.
"""
try:
allowed = dccConfiguration.allowedCoveragePropabilityMissmatch[distribution]
except KeyError:
allowed = dccConfiguration.allowedCoveragePropabilityMissmatch['default']
if distribution == "normal":
if k_factor is not None:
computed_cov = math.erf(k_factor / math.sqrt(2))
if coverageProbability is None:
coverageProbability = computed_cov
else:
if abs(coverageProbability - computed_cov) > allowed:
if dccConfiguration.coveragePropabilityMissmatchBehaivior == DccConfiguration.CoveragePropabilityMissmatchBehavior.EXCEPTION:
raise ValueError(
f"Normal distribution mismatch: provided coverageProbability {coverageProbability} vs computed {computed_cov}"
)
elif dccConfiguration.coveragePropabilityMissmatchBehaivior == DccConfiguration.CoveragePropabilityMissmatchBehavior.WARNING_TAKE_K_VALUE:
warnings.warn( f"Normal distribution mismatch: provided coverageProbability {coverageProbability} vs computed {computed_cov} using k value for further calculations. Since dccConfiguration.CoveragePropabilityMissmatchBehavior.WARNING_TAKE_K_VALUE is set" )
return computed_cov, k_factor
elif coverageProbability is not None:
# Requires Python 3.8+ for math.erfinv
k_factor = math.sqrt(2) * math.erfinv(coverageProbability)
else:
raise ValueError(
"For normal distribution, at least one of coverageProbability or k_factor must be provided.")
return coverageProbability, k_factor
elif distribution == "uniform":
# For a rectangular (i.e. uniform) distribution, one commonly used result is that if a random variable is uniformly distributed between –a and a, then its standard uncertainty is given by
# \[
# u = \frac{a}{\sqrt{3}},
# \]
# because the variance of a uniform distribution on [–a, a] is \( \sigma^2 = a^2/3 \). Consequently, if you define an "expanded uncertainty" U (which might be provided by the user) and wish to express it in terms of standard uncertainty via a coverage factor \(k\), then
# \[
# u = \frac{U}{k}.
# \]
# Combining these gives a way to relate U, \(k\), and the half-width \(a\) of the distribution:
# \[
# a = \frac{U \sqrt{3}}{k}.
# \]
# Furthermore, the probability that a value falls within the interval \([-k\cdot u, k\cdot u]\) is obtained by integrating the uniform density. For \([-c, c]\) (with \(c \le a\)), the cumulative probability is
# \[
# P = \frac{2c}{2a} = \frac{c}{a}.
# \]
# Substituting \(c = k\cdot u\) and \(u = a/\sqrt{3}\) yields
# \[
# P = \frac{k\,(a/\sqrt{3})}{a} = \frac{k}{\sqrt{3}} \quad \text{(for } k \le \sqrt{3}\text{)},
# \]
# with \(P = 1\) if \(k > \sqrt{3}\).
# These relationships are widely cited in metrology and uncertainty analysis. For example:
# - The *Guide to the Expression of Uncertainty in Measurement* (GUM, JCGM 100:2008) explains that for a uniform (rectangular) distribution the standard uncertainty is \(u = a/\sqrt{3}\).
# - NIST’s *Technical Note 1297* provides guidelines for evaluating uncertainties where similar derivations for the uniform distribution are given.
if k_factor is not None:
# Calculate computed coverageProbability from k_factor.
if k_factor <= math.sqrt(3):
computed_cov = (k_factor / math.sqrt(3) + 1) / 2
else:
computed_cov = 1.0 # Entire range covered.
if coverageProbability is None:
coverageProbability = computed_cov
else:
if abs(coverageProbability - computed_cov) > allowed:
if dccConfiguration.coveragePropabilityMissmatchBehaivior == DccConfiguration.CoveragePropabilityMissmatchBehavior.EXCEPTION:
raise ValueError(
f"Normal distribution mismatch: provided coverageProbability {coverageProbability} vs computed {computed_cov}"
)
elif dccConfiguration.coveragePropabilityMissmatchBehaivior == DccConfiguration.CoveragePropabilityMissmatchBehavior.WARNING_TAKE_K_VALUE:
warnings.warn( f"Normal distribution mismatch: provided coverageProbability {coverageProbability} vs computed {computed_cov} using k value for further calculations. Since dccConfiguration.CoveragePropabilityMissmatchBehavior.WARNING_TAKE_K_VALUE is set" )
return computed_cov, k_factor
elif coverageProbability is not None:
# Invert the relationship for uniform distributions.
k_factor = math.sqrt(3) * (2 * coverageProbability - 1)
else:
raise ValueError(
"For uniform distribution, at least one of coverageProbability or k_factor must be provided.")
return coverageProbability, k_factor
else:
raise ValueError("Unsupported distribution type. >>" +str(distribution)+"<<")