Skip to content

Commit

Permalink
Fix wrong precision for exponential formatted uncertainties (senaite#…
Browse files Browse the repository at this point in the history
…2224)

* Removed old comment

* Added example in comment

* Removed superfluous result parameter

* Doctest updated

* Fix precision for exponential formatted numbers

* changelog updated
  • Loading branch information
ramonski authored Jan 9, 2023
1 parent 52afb04 commit 6901b2c
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changelog
2.4.0 (unreleased)
------------------

- #2224 Fix wrong precision for exponential formatted uncertainties
- #2219 Make `UIDReferenceField` to not keep back-references by default
- #2209 Migrate AnalysisRequest's ReferenceField fields to UIDReferenceField
- #2218 Improve performance of legacy AT `UIDReferenceField`'s setter
Expand Down
3 changes: 1 addition & 2 deletions src/bika/lims/browser/analyses/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -1192,9 +1192,8 @@ def _folder_item_uncertainty(self, analysis_brain, item):
item["allow_edit"].append("Uncertainty")
return

result = obj.getResult()
formatted = format_uncertainty(
obj, result, decimalmark=self.dmk, sciformat=int(self.scinot))
obj, decimalmark=self.dmk, sciformat=int(self.scinot))
if formatted:
item["replace"]["Uncertainty"] = formatted
item["before"]["Uncertainty"] = "± "
Expand Down
3 changes: 2 additions & 1 deletion src/bika/lims/browser/worksheet/views/printview.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,8 @@ def _analysis_data(self, analysis):
elif specs.get('max', None):
fs = '< %s' % specs['max']
andict['formatted_specs'] = formatDecimalMark(fs, decimalmark)
andict['formatted_uncertainty'] = format_uncertainty(analysis, analysis.getResult(), decimalmark=decimalmark, sciformat=int(scinot))
andict['formatted_uncertainty'] = format_uncertainty(
analysis, decimalmark=decimalmark, sciformat=int(scinot))

# Out of range?
andict['outofrange'] = is_out_of_range(analysis)[0]
Expand Down
35 changes: 11 additions & 24 deletions src/bika/lims/utils/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,8 @@ def _format_decimal_or_sci(result, precision, threshold, sciformat):
return formatted


def format_uncertainty(analysis, result, decimalmark='.', sciformat=1):
"""
Returns the formatted uncertainty according to the analysis, result
and decimal mark specified following these rules:
def format_uncertainty(analysis, decimalmark=".", sciformat=1):
"""Return formatted uncertainty value
If the "Calculate precision from uncertainties" is enabled in
the Analysis service, and
Expand Down Expand Up @@ -221,9 +219,6 @@ def format_uncertainty(analysis, result, decimalmark='.', sciformat=1):
the uncertainty neither the result. The fixed length precision is
used instead.
For further details, visit
https://jira.bikalabs.com/browse/LIMS-1334
If the result is not floatable or no uncertainty defined, returns
an empty string.
Expand All @@ -232,8 +227,6 @@ def format_uncertainty(analysis, result, decimalmark='.', sciformat=1):
:param analysis: the analysis from which the uncertainty, precision
and other additional info have to be retrieved
:param result: result of the analysis. Used to retrieve and/or
calculate the precision and/or uncertainty
:param decimalmark: decimal mark to use. By default '.'
:param sciformat: 1. The sci notation has to be formatted as aE^+b
2. The sci notation has to be formatted as ax10^b
Expand All @@ -244,26 +237,19 @@ def format_uncertainty(analysis, result, decimalmark='.', sciformat=1):
:returns: the formatted uncertainty
"""
try:
result = float(result)
except ValueError:
return ""

objres = None
try:
objres = float(analysis.getResult())
except ValueError:
result = float(analysis.getResult())
except (ValueError, TypeError):
pass

uncertainty = None
if result == objres:
# To avoid problems with DLs
uncertainty = analysis.getUncertainty()
else:
uncertainty = analysis.getUncertainty(result)
uncertainty = analysis.getUncertainty()

if not uncertainty:
return ""

# always convert exponential notation to decimal
if "e" in uncertainty.lower():
uncertainty = api.float_to_string(float(uncertainty))

precision = -1
# always get full precision of the uncertainty if user entered manually
# => avoids rounding and cut-off
Expand All @@ -281,7 +267,8 @@ def format_uncertainty(analysis, result, decimalmark='.', sciformat=1):
formatted = _format_decimal_or_sci(
uncertainty, precision, threshold, sciformat)

# strip off trailing zeros and the orphane dot
# strip off trailing zeros and the orphane dot,
# e.g.: 1.000000 -> 1
if "." in formatted:
formatted = formatted.rstrip("0").rstrip(".")

Expand Down
52 changes: 44 additions & 8 deletions src/senaite/core/tests/doctests/Uncertainties.rst
Original file line number Diff line number Diff line change
Expand Up @@ -339,23 +339,23 @@ Since we have neither specified a precision in the analysis service, nor did we
set the precision from uncertainty, we get a precision of 0:

>>> au.setResult("1.4")
>>> format_uncertainty(au, au.getResult())
>>> format_uncertainty(au)
'0'

When we set the precision in the analysis, the uncertainty is formatted to this value:

XXX: Why is it not rounded to 0.0002?

>>> au.setPrecision(4)
>>> format_uncertainty(au, au.getResult())
>>> format_uncertainty(au)
'0.0001'

>>> au.setPrecision(5)
>>> format_uncertainty(au, au.getResult())
>>> format_uncertainty(au)
'0.00015'

>>> au.setPrecision(6)
>>> format_uncertainty(au, au.getResult())
>>> format_uncertainty(au)
'0.00015'

When the user manually entered an uncertainty and overrides an uncertainty
Expand All @@ -364,7 +364,7 @@ range, we always show all digits:
>>> au.setPrecision(None)
>>> au.setAllowManualUncertainty(True)
>>> au.setUncertainty("0.00000123")
>>> format_uncertainty(au, au.getResult())
>>> format_uncertainty(au)
'0.00000123'

The uncertainty can be also defined as a percentage of the result and is then
Expand All @@ -384,6 +384,42 @@ Test the range 10-20 with an unertainty value of 5% of the result:
'0.75'


Test exponential format
.......................

Create a new sample:

>>> sample = new_sample([Cu, Fe, Au])
>>> au = get_analysis(sample, Au)
>>> au.setAllowManualUncertainty(True)
>>> au.setPrecisionFromUncertainty(False)

Manually set the result and uncertainty:

>>> au.setResult("1.000123e-5")
>>> au.setUncertainty("0.002e-5")

We expect manual uncertainties in full precision:

>>> au.getUncertainty()
'0.002e-5'

>>> format_uncertainty(au, sciformat=1)
'2e-08'

>>> format_uncertainty(au, sciformat=2)
'2x10^-8'

>>> format_uncertainty(au, sciformat=3)
'2x10<sup>-8</sup>'

>>> format_uncertainty(au, sciformat=4)
'2\xc2\xb710^-8'

>>> format_uncertainty(au, sciformat=5)
'2\xc2\xb710<sup>-8</sup>'


Test floating point arithmetic
..............................

Expand Down Expand Up @@ -431,7 +467,7 @@ Define it as an uncertainty as percentage of the result:
>>> au.getUncertainty()
'0.000001'

>>> format_uncertainty(au, au.getResult())
>>> format_uncertainty(au)
'0.000001'


Expand All @@ -446,11 +482,11 @@ Define it as an uncertainty as percentage of the result:

Because it exceeded the Exponential format precision, it is returned with the scientific notation:

>>> format_uncertainty(au, au.getResult())
>>> format_uncertainty(au)
'1.0e-21'

Change to a higher precision threshold:

>>> au.setExponentialFormatPrecision(30)
>>> format_uncertainty(au, au.getResult())
>>> format_uncertainty(au)
'0.000000000000000000001'

0 comments on commit 6901b2c

Please sign in to comment.