Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More compatibility improvements in Accuracy and Precision. Adding tests. #769

Merged
merged 8 commits into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -921,8 +908,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 @@ -948,43 +935,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