Skip to content

Commit

Permalink
More compatibility improvements in Accuracy and Precision. Adding tes…
Browse files Browse the repository at this point in the history
…ts. (#769)

This PR is another fix coming from issues found when I was working on
#766. In particular, the way in which Mathics handles Precision and
Accuracy for real numbers with 0. nominal value. Also, a bug that
prevents parsing Real numbers with fixed accuracy was fixed.
  • Loading branch information
mmatera committed Jan 30, 2023
1 parent de71edc commit 40a446d
Show file tree
Hide file tree
Showing 9 changed files with 359 additions and 133 deletions.
9 changes: 7 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ New Builtins
#. ``Kurtosis``
#. ``ListLogPlot``
#. ``LogPlot``
#. ``$MaxMachineNumber``
#. ``$MinMachineNumber``
#. ``NumberLinePlot``
#. ``PauliMatrix``
#. ``Remove``
Expand Down Expand Up @@ -92,9 +94,10 @@ Bugs

#. Units and Quantities were sometimes failing. Also they were omitted from documentation.
#. Better handling of ``Infinite`` quantities.
#. Fix ``Precision`` compatibility with WMA.
#. Improved ``Precision`` and ``Accuracy``compatibility with WMA. In particular, ``Precision[0.]`` and ``Accuracy[0.]``
#. Accuracy in numbers using the notation ``` n.nnn``acc ``` now is properly handled.



PyPI Package requirements
+++++++++++++++++++++++++

Expand All @@ -115,6 +118,8 @@ Enhancements
#. ``Grid`` compatibility with WMA was improved. Now it supports non-uniform list of lists and lists with general elements.
#. Support for BigEndian Big TIFF



5.0.2
-----

Expand Down
12 changes: 6 additions & 6 deletions SYMBOLS_MANIFEST.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ System`$Machine
System`$MachineEpsilon
System`$MachineName
System`$MachinePrecision
System`$MaxMachineNumber
System`$MaxPrecision
System`$MinMachineNumber
System`$MinPrecision
System`$ModuleNumber
System`$OperatingSystem
Expand Down Expand Up @@ -245,8 +247,8 @@ System`Continue
System`ContinuedFraction
System`Convert`B64Dump`B64Decode
System`Convert`B64Dump`B64Encode
System`ConvertersDump`$extensionMappings
System`ConvertersDump`$formatMappings
System`ConvertersDump`$ExtensionMappings
System`ConvertersDump`$FormatMappings
System`CoprimeQ
System`CopyDirectory
System`CopyFile
Expand Down Expand Up @@ -613,7 +615,6 @@ System`MachinePrecision
System`Magenta
System`MakeBoxes
System`ManhattanDistance
System`Manipulate
System`MantissaExponent
System`Map
System`MapAt
Expand Down Expand Up @@ -687,10 +688,10 @@ System`Null
System`NullSpace
System`Number
System`NumberForm
System`NumberLinePlot
System`NumberQ
System`NumberString
System`Numerator
System`NumberLinePlot
System`NumericFunction
System`NumericQ
System`O
Expand Down Expand Up @@ -739,7 +740,6 @@ System`Pi
System`Pick
System`PieChart
System`Piecewise
System`PillowImageFilter
System`Pink
System`PixelValue
System`PixelValuePositions
Expand Down Expand Up @@ -781,7 +781,6 @@ System`Print
System`PrintTrace
System`Private`$ContextPathStack
System`Private`$ContextStack
System`Private`ManipulateParameter
System`Product
System`ProductLog
System`Projection
Expand Down Expand Up @@ -830,6 +829,7 @@ System`Reap
System`Record
System`Rectangle
System`RectangleBox
System`Rectangular
System`Red
System`RegularExpression
System`RegularPolygon
Expand Down
113 changes: 48 additions & 65 deletions mathics/builtin/atomic/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,35 @@

from mathics.builtin.base import Builtin, Predefined, Test
from mathics.core.atoms import (
Complex,
Integer,
Integer0,
Integer10,
MachineReal,
MachineReal0,
Number,
Rational,
Real,
)
from mathics.core.attributes import A_LISTABLE, A_PROTECTED
from mathics.core.convert.python import from_python
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
from mathics.core.number import dps, machine_epsilon, machine_precision
from mathics.core.number import (
MACHINE_PRECISION_VALUE,
machine_epsilon,
machine_precision,
)
from mathics.core.symbols import Symbol, SymbolDivide
from mathics.core.systemsymbols import (
SymbolIndeterminate,
SymbolInfinity,
SymbolLog,
SymbolMachinePrecision,
SymbolN,
SymbolPrecision,
SymbolRealDigits,
SymbolRound,
)
from mathics.eval.nevaluator import eval_N
from mathics.eval.numbers import eval_Accuracy, eval_Precision

SymbolIntegerDigits = Symbol("IntegerDigits")
SymbolIntegerExponent = Symbol("IntegerExponent")
Expand Down Expand Up @@ -157,7 +160,8 @@ class Accuracy(Builtin):
<dd>examines the number of significant digits of $expr$ after the \
decimal point in the number x.
</dl>
<i>This is rather a proof-of-concept than a full implementation.</i>
<i>Notice that the result could be slighly different than the obtained\
in WMA, due to differencs in the internal representation of the real numbers.</i>
Accuracy of a real number is estimated from its value and its precision:
Expand All @@ -183,11 +187,17 @@ class Accuracy(Builtin):
= Infinity
>> Accuracy[F[1.3, Pi, A]]
= 14.8861
= ...
'Accuracy' for the value 0 is a fixed-precision Real number:
>> 0``2
= 0.00
>> Accuracy[0.``2]
= 2.
For 0.`, the accuracy satisfies:
>> Accuracy[0.`] == $MachinePrecision - Log[10, $MinMachineNumber]
= True
In compound expressions, the 'Accuracy' is fixed by the number with
the lowest 'Accuracy':
Expand All @@ -203,33 +213,10 @@ class Accuracy(Builtin):

def eval(self, z, evaluation):
"Accuracy[z_]"
if isinstance(z, Real):
if z.is_zero:
return MachineReal(dps(z.get_precision()))
z_f = z.to_python()
log10_z = mpmath.log((-z_f if z_f < 0 else z_f), 10)
return MachineReal(dps(z.get_precision()) - log10_z)

if isinstance(z, Complex):
acc_real = self.eval(z.real, evaluation)
acc_imag = self.eval(z.imag, evaluation)
if acc_real is SymbolInfinity:
return acc_imag
if acc_imag is SymbolInfinity:
return acc_real
return Real(min(acc_real.to_python(), acc_imag.to_python()))

if isinstance(z, Expression):
result = None
for element in z.elements:
candidate = self.eval(element, evaluation)
if isinstance(candidate, Real):
candidate_f = candidate.to_python()
if result is None or candidate_f < result:
result = candidate_f
if result is not None:
return Real(result)
return SymbolInfinity
acc = eval_Accuracy(z)
if acc is None:
return SymbolInfinity
return MachineReal(acc)


class ExactNumberQ(Test):
Expand Down Expand Up @@ -927,8 +914,8 @@ class Precision(Builtin):
<dt>'Precision[$expr$]'
<dd>examines the number of significant digits of $expr$.
</dl>
<i>This is rather a proof-of-concept than a full implementation.</i>
<i>Notice that the result could be slighly different than the obtained\
in WMA, due to differencs in the internal representation of the real numbers.</i>
The precision of an exact number, e.g. an Integer, is 'Infinity':
Expand All @@ -954,43 +941,39 @@ class Precision(Builtin):
>> Precision[{{1, 1.`},{1.`5, 1.`10}}]
= 5.
For non-zero Real values, it holds in general:
'Accuracy'[$z$] == 'Precision'[$z$] + 'Log'[$z$]
>> (Accuracy[z] == Precision[z] + Log[z])/.z-> 37.`
= True
The case of `0.` values is special. Following WMA, in a Machine Real\
representation, the precision is set to 'MachinePrecision':
>> Precision[0.]
= MachinePrecision
On the other hand, for a Precision Real with fixed accuracy,\
the precision is evaluated to 0.:
>> Precision[0.``3]
= 0.
See also <url>
:'Accuracy':
/doc/reference-of-built-in-symbols/atomic-elements-of-expressions/representation-of-numbers/accuracy/</url>.
"""

rules = {
"Precision[z_?MachineNumberQ]": "MachinePrecision",
}

summary_text = "find the precision of a number"

def eval(self, z, evaluation):
"Precision[z_]"
if isinstance(z, Real):
if z.is_zero:
return MachineReal0
return MachineReal(dps(z.get_precision()))

if isinstance(z, Complex):
prec_real = self.eval(z.real, evaluation)
prec_imag = self.eval(z.imag, evaluation)
if prec_real is SymbolInfinity:
return prec_imag
if prec_imag is SymbolInfinity:
return prec_real

return Real(min(prec_real.to_python(), prec_imag.to_python()))

if isinstance(z, Expression):
result = None
for element in z.elements:
candidate = self.eval(element, evaluation)
if isinstance(candidate, Real):
candidate_f = candidate.to_python()
if result is None or candidate_f < result:
result = candidate_f
if result is not None:
return Real(result)
return SymbolInfinity
if isinstance(z, MachineReal):
return SymbolMachinePrecision

prec = eval_Precision(z)
if prec is None:
return SymbolInfinity
if prec == MACHINE_PRECISION_VALUE:
return SymbolMachinePrecision
return MachineReal(prec)
62 changes: 61 additions & 1 deletion mathics/builtin/numbers/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@
from mathics.builtin.base import Builtin, Predefined, SympyObject
from mathics.core.atoms import MachineReal, PrecisionReal
from mathics.core.attributes import A_CONSTANT, A_PROTECTED, A_READ_PROTECTED
from mathics.core.number import PrecisionValueError, get_precision, machine_precision
from mathics.core.evaluation import Evaluation
from mathics.core.number import (
MAX_MACHINE_NUMBER,
MIN_MACHINE_NUMBER,
PrecisionValueError,
get_precision,
machine_precision,
)
from mathics.core.symbols import Atom, Symbol, strip_context
from mathics.core.systemsymbols import SymbolIndeterminate

Expand Down Expand Up @@ -575,6 +582,59 @@ class Overflow(Builtin):
summary_text = "overflow in numeric evaluation"


class MaxMachineNumber(Predefined):
"""
Largest normalizable machine number (<url>
:WMA:
https://reference.wolfram.com/language/ref/$MaxMachineNumber.html
</url>)
<dl>
<dt>'$MaxMachineNumber'
<dd>Represents the largest positive number that can be represented \
as a normalized machine number in the system.
</dl>
The product of '$MaxMachineNumber' and '$MinMachineNumber' is a constant:
>> $MaxMachineNumber * $MinMachineNumber
= 4.
"""

name = "$MaxMachineNumber"
summary_text = "largest normalized positive machine number"

def evaluate(self, evaluation: Evaluation) -> MachineReal:
return MachineReal(MAX_MACHINE_NUMBER)


class MinMachineNumber(Predefined):
"""
Smallest normalizable machine number (<url>
:WMA:
https://reference.wolfram.com/language/ref/$MinMachineNumber.html
</url>)
<dl>
<dt>'$MinMachineNumber'
<dd>Represents the smallest positive number that can be represented \
as a normalized machine number in the system.
</dl>
'MachinePrecision' minus the 'Log' base 10 of this number is the\
'Accuracy' of 0`:
>> MachinePrecision -Log[10., $MinMachineNumber]==Accuracy[0`]
= True
"""

name = "$MinMachineNumber"
summary_text = "smallest normalized positive machine number"

def evaluate(self, evaluation: Evaluation) -> MachineReal:
return MachineReal(MIN_MACHINE_NUMBER)


class Pi(_MPMathConstant, _SympyConstant):
"""
<url>
Expand Down
7 changes: 6 additions & 1 deletion mathics/core/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
SymbolDirectedInfinity,
SymbolFunction,
SymbolMinus,
SymbolOverflow,
SymbolPattern,
SymbolPower,
SymbolSequence,
Expand Down Expand Up @@ -1266,7 +1267,11 @@ def rules():
yield rule

for rule in rules():
result = rule.apply(new, evaluation, fully=False)
try:
result = rule.apply(new, evaluation, fully=False)
except OverflowError:
evaluation.message("General", "ovfl")
return Expression(SymbolOverflow), False
if result is not None:
if not isinstance(result, EvalMixin):
return result, False
Expand Down
Loading

0 comments on commit 40a446d

Please sign in to comment.