Source code for algebraixlib.mathobjects.atom

r"""Provide the class :class:`Atom` that represents :term:`atom`\s and the function
:func:`auto_convert` that passes through instances of `MathObject`\s and converts other types into
:class:`Atom` instances.
"""

# Copyright Algebraix Data Corporation 2015 - 2017
#
# This file is part of algebraixlib <http://github.com/AlgebraixData/algebraixlib>.
#
# algebraixlib is free software: you can redistribute it and/or modify it under the terms of version
# 3 of the GNU Lesser General Public License as published by the Free Software Foundation.
#
# algebraixlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with algebraixlib.
# If not, see <http://www.gnu.org/licenses/>.
# --------------------------------------------------------------------------------------------------
import functools as _functools

import algebraixlib.structure as _structure
from algebraixlib.util.miscellaneous import get_hash as _get_hash

from .mathobject import MathObject
from ..cache_status import CacheStatus
from ._flags import Flags as _Flags


# --------------------------------------------------------------------------------------------------

[docs]def auto_convert(arg): """Return always a `MathObject`. If ``arg`` is not a `MathObject`, make it an :class:`Atom`. This function is used in several constructors as convenience wrapper to allow the creation of `MathObject` instances from non-`MathObject` values. """ return arg if isinstance(arg, MathObject) else Atom(arg)
def _init_cache() -> int: """Initialization function for `Atom._INIT_CACHE`.""" flags = _Flags() # Known to be true: flags.f.atom = CacheStatus.IS flags.f.absolute = CacheStatus.IS # Known to be false: flags.f.couplet = CacheStatus.IS_NOT flags.f.set = CacheStatus.IS_NOT flags.f.relation = CacheStatus.IS_NOT flags.f.clan = CacheStatus.IS_NOT flags.f.multiset = CacheStatus.IS_NOT flags.f.multiclan = CacheStatus.IS_NOT # Known to be undefined/not apply: flags.f.functional = CacheStatus.N_A flags.f.right_functional = CacheStatus.N_A flags.f.regular = CacheStatus.N_A flags.f.reflexive = CacheStatus.N_A flags.f.symmetric = CacheStatus.N_A flags.f.transitive = CacheStatus.N_A return flags.asint @_functools.total_ordering
[docs]class Atom(MathObject): """Represent a value (of a non-`MathObject`, hashable type) like numbers or strings. All instances of :class:`Atom` are members of :term:`set A` (:math:`A`), or conversely, :term:`set A` is the set of all instances of :class:`Atom`. .. note:: Instances of :class:`Atom` are immutable and hashable. Therefore they only accept immutable and hashable values. """
[docs] def __new__(cls, value, direct_load=False): """If ``value`` is an instance of :class:`Atom`, reuse it. This mechanism is used to reduce the number of created :class:`Atom` instances. We then need to check in ``__init__`` whether we have an already initialized instance or still have to initialize it. ``__init__`` is always called after ``__new__``. :return: ``value`` if it is an instance of :class:`Atom` (in this case we simply reuse it). If not, follow the normal path for creating an instance. """ # pylint: disable=unused-argument if isinstance(value, Atom): return value return super().__new__(cls)
_INIT_CACHE = _init_cache() def __init__(self, value, direct_load=False): """ :param value: The value of this instance. May not be an instance of `MathObject` other than :class:`Atom`. (If it is of type :class:`Atom`, the instance is re-used; see `__new__`.) ``value`` must be immutable and hashable. :param direct_load: Set to ``True`` if you can be sure that ``value`` is not an instance of `MathObject`. Default is ``False``. :raise: `TypeError` if ``value`` is not hashable. """ # Check whether we received an already initialized instance (by __new__). if hasattr(self, '_value'): return super().__init__(self._INIT_CACHE) if direct_load: assert not isinstance(value, MathObject) else: if isinstance(value, MathObject): raise TypeError("'value' must not be a MathObject") self._value = value self._hash = _get_hash('algebraixlib.mathobjects.atom.Atom', self._value) # ---------------------------------------------------------------------------------------------- # Characteristics of the instance. @property def value(self) -> '( A )': """Read-only; return the value of this instance. Is always a member of :term:`set A`.""" return self._value @property def type(self): """Read-only; return the type of this instance.""" return type(self._value)
[docs] def get_ground_set(self) -> _structure.Structure: """Return the :term:`ground set` of the lowest-level algebra of ``self``. Is always :math:`A`. """ return _structure.GenesisSetA()
# ---------------------------------------------------------------------------------------------- # (Python-)Special functions.
[docs] def __eq__(self, other): """A value-based comparison for equality. Return ``True`` if types and values match. Type-matching follows Python rules, so ``not Atom(1) == Atom(1.0)``. """ if isinstance(other, Atom): # NOTE: using the explicit type check vs isinstance(other.value, type(self.value) # ..to prevent True being compared equal to 1..ie isinstance(True, type(1)) == True if other.type != self.type: return False return other.value == self.value return NotImplemented
[docs] def __ne__(self, other): """A value-based comparison for inequality. Return ``True`` if types or values don't match. Type-matching follows Python rules, so ``Atom(1) != Atom(1.0)``. """ if isinstance(other, Atom): # NOTE: using the explicit type check vs isinstance(other.value, type(self.value) # ..to prevent True being compared equal to 1..ie isinstance(True, type(1)) == True # noinspection PyPep8 if type(other.value) != type(self.value): return True return other.value != self.value return NotImplemented
# noinspection PyUnresolvedReferences def __lt__(self, other): """A value-based comparison for less than. Return ``True`` if ``self < other``. This implementation must be aligned with `__eq__`; an object must not be equal to and less than another object at the same time. :return Normally a `bool` (`True` if ``self`` is less than ``other``), or `NotImplemented` if the types can't be compared. """ if not isinstance(other, MathObject): return NotImplemented if other.is_atom: try: if self.type == other.type: return self.value < other.value except TypeError: pass return repr(self.value) < repr(other.value) else: return super()._less_than(other)
[docs] def __hash__(self): """Return a hash based on the value calculated during construction.""" return self._hash
[docs] def __repr__(self): """Return the instance's code representation.""" return 'Atom({value})'.format(value=repr(self._value))
[docs] def __str__(self): """Return the instance's string representation.""" return repr(self._value)
def __getnewargs__(self): """Necessary to allow Atom to be pickled""" return Atom.__repr__(self),