Computations with tolerances
Note
If you’re a developer who wants to find out how to write a nonexact function or class, see The developer’s guide.
In many cases, computations can not be carried out in an exact way and we have to work with tolerances. For example, for some computations, we might want to consider numbers that are small but not equal to 0 to actually be 0. In general, we follow the absolute/relative tolerance concept familiar from e.g. numpy, but we add some of our own mechanisms to handle tolerances in a unified way. Namely, we have a mechanism to globally set defaults for these tolerances.
Tolerances generally appear in the following ways and follow these conventions:
Functions can have arguments
atolandrtolthat are used for computations inside the function.Classes can have attributes
atolandrtolthat are used for all computations internal to the class.If a function is passed one main object with tolerance attributes, it can use its attributes as tolerances instead of having tolerance parameters itself.
If a function gets multiple (equal priority) objects with tolerance attributes and doesn’t have its own tolerance parameters, it generally uses the maximum of the tolerances of the objects.
The following sections explain how the global defaults work in these contexts.
Accessing the global tolerance defaults
The global tolerance defaults can be accessed through the module ddg.nonexact using the function get_tol_defaults(). They can be changed with a context manager or functions in the module.
The safest way to change the defaults is the context manager ddg.nonexact.TolDefaults. It can be used to temporarily set defaults so you don’t have to worry about resetting them later. For example:
>>> import ddg
>>> ddg.nonexact.get_tol_defaults()
{'atol': 1e-07, 'rtol': 0.0}
>>> with ddg.nonexact.TolDefaults(atol=1e-6):
... ddg.nonexact.get_tol_defaults()
...
{'atol': 1e-06, 'rtol': 0.0}
>>> ddg.nonexact.get_tol_defaults() # Context has been exited
{'atol': 1e-07, 'rtol': 0.0}
To permanently alter tolerance defaults, use the functions set_tol_defaults() and reset_tol_defaults():
>>> ddg.nonexact.get_tol_defaults()
{'atol': 1e-07, 'rtol': 0.0}
>>> ddg.nonexact.set_tol_defaults(rtol=0.01)
>>> ddg.nonexact.get_tol_defaults()
{'atol': 1e-07, 'rtol': 0.01}
>>> ddg.nonexact.reset_tol_defaults()
>>> ddg.nonexact.get_tol_defaults()
{'atol': 1e-07, 'rtol': 0.0}
These functions essentially just set global variables. In the other sections, we explain where and how these global defaults are used.
In classes
Classes that use the global tolerances internally if their attributes atol and rtol are set to None will mention this in their documentation. Here is an example with the Subspace class:
>>> from ddg.geometry.subspaces import subspace_from_affine_points as sfap
>>> p = sfap([0, 1])
>>> q = sfap([0, 1 + 1e-9])
>>> print(p.atol)
None
>>> print(p.rtol)
None
>>> p == q
True
>>> p.atol = 1e-13
>>> p == q
False
Note that some classes could provide their own default for atol / rtol upon initialization. If this is the case and you want your object to use the global defaults, you have to explicitly set atol or rtol to None.
In functions
Functions that take keyword arguments atol and/or rtol behave in a similar way to classes, as explained in the last section: If None is given for one of these arguments, the function will use the global default internally. Functions that use the global defaults in this way will mention this in their docstring. For this example, we use the function ddg.math.projective.dehomogenize():
>>> import numpy as np
>>> import ddg
>>> import ddg.math.projective as pmath
>>> v = np.array([1.0, 2.0, 3.0, 1e-6])
>>> pmath.dehomogenize(v)
array([1000000., 2000000., 3000000.])
>>> ddg.nonexact.set_tol_defaults(atol=1e-5)
>>> pmath.dehomogenize(v)
Traceback (most recent call last):
...
ValueError: Given point is at infinity.
>>> pmath.dehomogenize(v, atol=1e-7)
array([1000000., 2000000., 3000000.])
Again, note that some functions provide their own tolerance defaults that are hand-picked for that particular function. If you want the function to use the global defaults, you have to explicitly pass atol=None / rtol=None in that case.