.. _abc: Abstract base classes ===================== NonExact -------- The main purpose of the class :py:class:`~ddg.abc.NonExact` is to add tolerance attributes `atol` and `rtol` to a class, which can then be used internally in numerical calculations. A simple example: .. doctest:: >>> from ddg.abc import NonExact >>> class MyNonExactClass(NonExact): ... pass ... >>> A = MyNonExactClass(atol=1e-8, rtol=0.) >>> A.atol 1e-08 >>> A.rtol 0.0 There are global default values for `atol` and `rtol`, which are used everywhere if `atol` or `rtol` are set to None (the default). These defaults are stored as class attributes of `NonExact`. .. doctest:: >>> NonExact.atol_default 1e-07 >>> NonExact.rtol_default 0.0 >>> A = MyNonExactClass() >>> A.atol 1e-07 >>> NonExact.atol_default = 1e-13 >>> A.atol 1e-13 >>> A.atol = 1e-5 >>> A.atol 1e-05 >>> A.atol = None >>> A.atol 1e-13 Since these class attributes are not in a particularly accessible place, they can also be read and changed via the top-level functions :py:func:`ddg.get_tol_defaults` and :py:func:`ddg.set_tol_defaults` and reset to their original values with :py:func:`ddg.reset_tol_defaults`: .. doctest:: >>> import ddg >>> ddg.get_tol_defaults() {'atol': 1e-13, 'rtol': 0.0} >>> ddg.set_tol_defaults(rtol=1e-3) >>> ddg.get_tol_defaults() {'atol': 1e-13, 'rtol': 0.001} For simple utility functions which are not members of a class, we can use the decorator :py:meth:`ddg.abc.NonExact.nonexact_function`, which is realized as a class method. If a function decorated with this method receives None as `atol` or `rtol`, it will be replaced by the global default. The function can still set its own defaults. .. doctest:: >>> import numpy as np >>> @NonExact.nonexact_function ... def is_almost_zero(a, atol=None, rtol=0.0): ... return np.isclose(a, 0, atol=atol, rtol=rtol) ... >>> is_almost_zero(1e-14) True >>> is_almost_zero(1e-12) False >>> is_almost_zero(1e-14, atol=1e-15) False >>> ddg.set_tol_defaults(atol=1e-15) >>> is_almost_zero(1e-14) False .. Clean up so this doctest doesn't affect doctests that come after it. One interesting thing is that the order of doctests seems to change from test run to test run, so if this were not present, doctests would sometimes fail, seemingly at random. .. doctest:: :hide: >>> ddg.reset_tol_defaults() >>> ddg.get_tol_defaults() {'atol': 1e-07, 'rtol': 0.0}