def cross_intersect(clan1: 'PP(M x M)', clan2: 'PP(M x M)', _checked=True) -> 'PP(M x M)': r"""Return the cross-intersection of ``clan1`` and ``clan2``. The :term:`cross-intersection` of two :term:`clan`\s is a clan that contains the result of intersecting every :term:`relation` from one clan with every relation from the other clan. :return: The :term:`binary extension` of :term:`intersection` from the :term:`algebra of relations` (which inherits it from the :term:`algebra of sets`) to the :term:`algebra of clans` applied to ``clan1`` and ``clan2``, or `Undef()` if ``clan1`` or ``clan2`` are not :term:`clan`\s. """ if _checked: if not is_member(clan1): return _undef.make_or_raise_undef() if not is_member(clan2): return _undef.make_or_raise_undef() else: assert is_member(clan1) assert is_member(clan2) result = _extension.binary_extend(clan1, clan2, _functools.partial( _sets.intersect, _checked=False), _checked=False) if not result.is_empty: result.cache_clan(_mo.CacheStatus.IS) if clan1.cached_is_functional or clan2.cached_is_functional: result.cache_functional(_mo.CacheStatus.IS) if clan1.cached_is_right_functional or clan2.cached_is_right_functional: result.cache_right_functional(_mo.CacheStatus.IS) return result
def binary_multi_extend(multiset1: 'P( M x N )', multiset2: 'P( M x N )', op, _checked=True) -> 'P( M x N )': r"""Return the :term:`binary extension` of ``op`` from one :term:`algebra` to another algebra. For this extension, the elements of the extended algebra must be :term:`multiset`\s of the elements of the original algebra. :param multiset1: A :term:`multiset` with elements on which ``op`` operates. :param multiset2: A multiset with elements on which ``op`` operates. :param op: A :term:`binary operation` that operates on the elements of ``multiset1`` and ``multiset2``. :return: A multiset that consists of the defined results of ``op`` when executed on all combinations of the elements of ``multiset1`` and ``multiset2``, or `Undef()` if either set is not a :class:`~.Multiset`. """ if _checked: if not isinstance(multiset1, _mo.Multiset): return _ud.make_or_raise_undef() if not isinstance(multiset2, _mo.Multiset): return _ud.make_or_raise_undef() else: assert multiset1.is_multiset assert multiset2.is_multiset def _get_values(_set1, _set2): return_count = _collections.Counter() for elem1, multi1 in _set1.data.items(): for elem2, multi2 in _set2.data.items(): result = op(elem1, elem2) if result is not _ud.Undef(): return_count[result] += multi1 * multi2 return return_count return _mo.Multiset(_get_values(multiset1, multiset2), direct_load=True)
def big_intersect(set_: 'PP( M )', _checked=True) -> 'P( M )': """Return the intersection of all members of ``set_``. :return: The :term:`intersection` of all members of ``set_`` or `Undef()` if ``set_`` or any of its members are not instances of :class:`~.Set`. Example code: .. code:: from algebraixlib.mathobjects import Set from algebraixlib.algebras.sets import big_intersect big_intersect(Set(Set('a', 'b'), Set('b', 'c'))) # Output: Set(Atom('b')) big_intersect(Set(Set('a', 'b'), 'a')) # Output: <algebraixlib.undef.Undef at 0x4004978> """ if _checked: if not isinstance(set_, _mo.Set): return _undef.make_or_raise_undef() for element in set_: if not is_member(element): return _undef.make_or_raise_undef() else: assert set_.is_set return chain_binary_operation(set_, _functools.partial(intersect, _checked=False), is_member)
def get_left(rel: 'P(M x M)', right: '( M )', _checked=True) -> '( M )': r"""Return the left component of the couplet that has a right component of ``right``. In general, use with :term:`right-functional` :term:`relation`\s; that is, relations where all :term:`right component`\s appear at most once. :return: The :term:`left component` of the :term:`couplet` that has a :term:`right component` of ``right``, or `Undef()` if there is not exactly one couplet with the right component ``right`` in ``rel`` or ``rel`` is not a :term:`relation`. """ if _checked: if not is_member(rel): return _undef.make_or_raise_undef2(rel) if right is _undef.Undef(): return _undef.make_or_raise_undef(2) right = _mo.auto_convert(right) else: assert is_member_or_undef(rel) assert _mo.is_mathobject_or_undef(right) if right is _undef.Undef() or rel is _undef.Undef(): return _undef.make_or_raise_undef(2) result = None for elem in rel: assert elem.is_couplet if elem.right == right: if result is not None: return _undef.make_or_raise_undef() # Early Undef() exit if more than one found. result = elem.left if result is None: return _undef.make_or_raise_undef() # Undef() exit if none found. return result
def compose(multiclan1: 'P(P(M x M) x N)', multiclan2: 'P(P(M x M) x N)', _checked=True) -> 'P(P(M x M) x N)': r"""Return the composition of ``multiclan1`` with ``multiclan2``. :return: The :term:`binary multi-extension` of :term:`composition` from the :term:`algebra of relations` to the :term:`algebra of multiclans`, applied to ``multiclan1`` and ``multiclan2``, or `Undef()` if ``multiclan1`` or ``multiclan2`` are not :term:`multiclan`\s. """ if _checked: if not is_member(multiclan1): return _undef.make_or_raise_undef() if not is_member(multiclan2): return _undef.make_or_raise_undef() else: assert is_member(multiclan1) assert is_member(multiclan2) result = _extension.binary_multi_extend(multiclan1, multiclan2, _functools.partial( _relations.compose, _checked=False), _checked=False) if not result.is_empty: result.cache_multiclan(_mo.CacheStatus.IS) if multiclan1.cached_is_absolute and multiclan2.cached_is_absolute: result.cache_absolute(_mo.CacheStatus.IS) if multiclan1.cached_is_functional and multiclan2.cached_is_functional: result.cache_functional(_mo.CacheStatus.IS) if multiclan1.cached_is_right_functional and multiclan2.cached_is_right_functional: result.cache_right_functional(_mo.CacheStatus.IS) return result
def right_functional_union(rel1: 'P(M x M)', rel2: 'P(M x M)', _checked=True) -> 'P(M x M)': r"""Return the union of ``rel1`` and ``rel2`` if it is right-functional, otherwise `Undef()`. :return: The :term:`right-functional union` of the :term:`relation`\s ``rel1`` and ``rel2``; that is, the :term:`union` if the result is :term:`right-functional`, otherwise `Undef()`. Also return `Undef()` if ``rel1`` or ``rel2`` are not relations. """ if _checked: if not is_member(rel1): return _undef.make_or_raise_undef2(rel1) if not is_member(rel2): return _undef.make_or_raise_undef2(rel2) else: assert is_member_or_undef(rel1) assert is_member_or_undef(rel2) if rel1 is _undef.Undef() or rel2 is _undef.Undef(): return _undef.make_or_raise_undef(2) rel_union = _sets.union(rel1, rel2, _checked=False).cache_relation( _mo.CacheStatus.IS) if not is_right_functional(rel_union, _checked=False): return _undef.make_or_raise_undef(2) return rel_union
def fill_lefts(rel: 'P(M x M)', renames: 'P(M x M)', _checked=True) -> 'P(M x M)': r"""Return the left components in ``rel`` that are missing in ``renames`` as a diagonal unioned with ``renames``. The purpose is to create a :term:`relation` that can be used with the :term:`composition` operation to change (rename) one or more :term:`left component`\s and leave the rest alone. :param rel: The :term:`relation` that provides the full :term:`left set`. :param renames: A relation where the :term:`right component`\s are meant to be :term:`composition` 'origins' and the :term:`left component`\s composition 'targets'. :return: A relation that contains all members of ``renames`` unioned with a :term:`diagonal` that consists of all left components in ``rel`` that are missing in ``renames``. """ if _checked: if not is_member(rel): return _undef.make_or_raise_undef() if not is_member(renames): return _undef.make_or_raise_undef() else: assert is_member(rel) assert is_member(renames) missing_lefts = _sets.minus(get_lefts(rel, _checked=False), get_rights(renames, _checked=False), _checked=False) diag_missing_lefts = diag(*missing_lefts, _checked=False) result = _sets.union(renames, diag_missing_lefts, _checked=False) assert result.cached_is_relation return result
def compose(rel1: 'P(M x M)', rel2: 'P(M x M)', _checked=True) -> 'P(M x M)': r"""Return the composition of ``rel1`` with ``rel2``. :return: The :term:`binary extension` of :term:`composition` from the :term:`algebra of couplets` to the :term:`algebra of relations`, applied to the :term:`relation`\s ``rel1`` and ``rel2``, or `Undef()` if ``rel1`` or ``rel2`` are not relations. """ if _checked: if not is_member(rel1): return _undef.make_or_raise_undef() if not is_member(rel2): return _undef.make_or_raise_undef() else: assert is_member(rel1) assert is_member(rel2) result = _extension.binary_extend(rel1, rel2, _functools.partial( _couplets.compose, _checked=False), _checked=False) if not result.is_empty: result.cache_relation(_mo.CacheStatus.IS) if rel1.cached_is_absolute and rel2.cached_is_absolute: result.cache_absolute(_mo.CacheStatus.IS) if rel1.cached_is_functional and rel2.cached_is_functional: result.cache_functional(_mo.CacheStatus.IS) if rel1.cached_is_right_functional and rel2.cached_is_right_functional: result.cache_right_functional(_mo.CacheStatus.IS) return result
def cross_right_functional_union(clan1: 'PP(M x M)', clan2: 'PP(M x M)', _checked=True) -> 'PP(M x M)': r"""Return the cross-right-functional union of ``clan1`` and ``clan2``. The :term:`cross-right-functional union` of two :term:`clan`\s is the :term:`cross-union` of these clans, but removing all resulting :term:`relation`\s that are not :term:`right-functional`. :return: The :term:`binary extension` of the :term:`right-functional union` from the :term:`algebra of relations` to the :term:`algebra of clans`, applied to ``clan1`` and ``clan2``, or `Undef()` if ``clan1`` or ``clan2`` are not :term:`clan`\s. """ if _checked: if not is_member(clan1): return _undef.make_or_raise_undef() if not is_member(clan2): return _undef.make_or_raise_undef() else: assert is_member(clan1) assert is_member(clan2) result = _extension.binary_extend(clan1, clan2, _functools.partial( _relations.right_functional_union, _checked=False), _checked=False) if not result.is_empty: result.cache_clan(_mo.CacheStatus.IS) result.cache_right_functional(_mo.CacheStatus.IS) if clan1.cached_is_not_functional or clan2.cached_is_not_functional: result.cache_functional(_mo.CacheStatus.IS_NOT) return result
def lhs_cross_functional_union(lhs: 'PP( MxM )', rhs: 'PP( MxM )', _checked=True) -> 'PP(M x M)': """Return the :term:`lhs-cross-functional union` ('left join') of ``lhs`` and ``rhs``. This operation results in a :term:`clan` that contains every :term:`relation` of a :term:`cross-functional union`, but also contains all relations in ``lhs`` that are not already part of one of the resulting relations. :param lhs: All relations in this clan are guaranteed to be represented in the result. :return: The resulting clan or `Undef()` if ``lhs`` or ``rhs`` are not clans. """ if _checked: if not is_member(lhs): return _undef.make_or_raise_undef() if not is_member(rhs): return _undef.make_or_raise_undef() else: assert is_member(lhs) assert is_member(rhs) cfu = cross_functional_union(lhs, rhs, _checked=False) lhs_rest = _mo.Set( lhs_elem for lhs_elem in lhs if cross_functional_union(_mo.Set(lhs_elem, direct_load=True), rhs).is_empty) result = _sets.union(cfu, lhs_rest, _checked=False) if not result.is_empty: result.cache_clan(_mo.CacheStatus.IS) if lhs.cached_is_functional: result.cache_functional(_mo.CacheStatus.IS) if lhs.cached_is_not_right_functional or rhs.cached_is_not_right_functional: result.cache_right_functional(_mo.CacheStatus.IS_NOT) if not rhs.is_empty and not lhs_rest.is_empty: result.cache_regular(_mo.CacheStatus.IS_NOT) return result
def superstrict(multiset1: 'P( M x N )', multiset2: 'P( M X N )', _checked=True) -> 'P( M X N )': """Return ``multiset1`` if ``multiset1`` is a superset of ``multiset2`` or `Undef()` if not. :return: The :term:`superstriction` of ``multiset1`` and ``multiset2`` (may return `Undef()`). Also return `Undef()` if ``multiset1`` or ``multiset2`` are not instances of :class:`~.Multiset`. """ if _checked: if not is_member(multiset1): return _undef.make_or_raise_undef2(multiset1) if not is_member(multiset2): return _undef.make_or_raise_undef2(multiset2) else: assert is_member_or_undef(multiset1) assert is_member_or_undef(multiset2) if multiset1 is _undef.Undef() or multiset2 is _undef.Undef(): return _undef.make_or_raise_undef(2) if not is_superset_of(multiset1, multiset2, _checked=False): return _undef.make_or_raise_undef(2) if not multiset1.is_empty: # Multiclan flags: if multiset1.cached_is_clan: if multiset2.cached_is_not_absolute: multiset1.cache_absolute(_mo.CacheStatus.IS_NOT) if multiset2.cached_is_not_functional: multiset1.cache_functional(_mo.CacheStatus.IS_NOT) if multiset2.cached_is_not_right_functional: multiset1.cache_right_functional(_mo.CacheStatus.IS_NOT) if multiset2.cached_is_not_regular: multiset1.cache_regular(_mo.CacheStatus.IS_NOT) if multiset2.cached_is_not_right_regular: multiset1.cache_right_regular(_mo.CacheStatus.IS_NOT) return multiset1
def export_csv(absolute_clan: 'PP(A x A)', file_or_path, column_order=None, sort_key=None): """This function takes an absolute, left regular clan and produces a CSV file, where the left components are in the header line and the rows are the individual relations. :param absolute_clan: A :class:`algebraixlib.mathobjects.set.Set`. It must be an absolute and left-regular clan. :param file_or_path: Either a file path (in this case the CSV data is written to a file at this location) or a file object (in this case the CSV data is written to its ``.write()`` function). :param column_order: The optional left set to be exported in the specified order. :param sort_key: The optional key to be provided to sorted (sorted not called if None). :return: ``True`` if the CSV export succeeded. """ if not _clans.is_absolute_member(absolute_clan) \ and not _multiclans.is_absolute_member(absolute_clan): return _ud.make_or_raise_undef() if column_order is None and not absolute_clan.is_left_regular(): return _ud.make_or_raise_undef() if column_order is None: # Since this clan is left regular, get first relation to acquire left set. rel = next(iter(absolute_clan)) # left_set is sorted to guarantee consistent iterations column_order = sorted([left.value for left in rel.get_left_set()]) # Build list of dictionaries that associates left components with their right components for each relation. clan_as_list_of_dicts = _convert_clan_to_list_of_dicts(column_order, (absolute_clan if sort_key is None else sorted(absolute_clan, key=sort_key))) # Write that dictionary. _csv_dict_writer(file_or_path, column_order, clan_as_list_of_dicts) return True
def superstrict(multiset1: 'P( M x N )', multiset2: 'P( M X N )', _checked=True) -> 'P( M X N )': """Return ``multiset1`` if ``multiset1`` is a superset of ``multiset2`` or `Undef()` if not. :return: The :term:`superstriction` of ``multiset1`` and ``multiset2`` (may return `Undef()`). Also return `Undef()` if ``multiset1`` or ``multiset2`` are not instances of :class:`~.Multiset`. """ if _checked: if not is_member(multiset1): return _undef.make_or_raise_undef2(multiset1) if not is_member(multiset2): return _undef.make_or_raise_undef2(multiset2) else: assert is_member_or_undef(multiset1) assert is_member_or_undef(multiset2) if multiset1 is _undef.Undef() or multiset2 is _undef.Undef(): return _undef.make_or_raise_undef(2) if not is_superset_of(multiset1, multiset2, _checked=False): return _undef.make_or_raise_undef(2) if not multiset1.is_empty: # Multiclan flags: if multiset1.cached_is_clan: if multiset2.cached_is_not_absolute: multiset1.cache_absolute(CacheStatus.IS_NOT) if multiset2.cached_is_not_functional: multiset1.cache_functional(CacheStatus.IS_NOT) if multiset2.cached_is_not_right_functional: multiset1.cache_right_functional(CacheStatus.IS_NOT) if multiset2.cached_is_not_regular: multiset1.cache_regular(CacheStatus.IS_NOT) if multiset2.cached_is_not_right_regular: multiset1.cache_right_regular(CacheStatus.IS_NOT) return multiset1
def chain_binary_operation(set_, binary_op, is_algebra_member): r"""Chain all elements of ``set_`` with the binary operation ``binary_op`` and return the result. :param set_: A :term:`set` of sets or :term:`multiset`\s. :param binary_op: The operation through which the members of ``set_`` are chained. It must be commutative and associative. :param is_algebra_member: The ``is_member()`` function of the :term:`algebra` of which the elements of ``set_`` must be members. :return: A member of ``algebra`` that is the result of chaining all elements of ``set_`` with the :term:`binary operation` ``binary_op``. """ if not is_member(set_): return _undef.make_or_raise_undef2(set_) if set_.is_empty: return set_ set_itr = iter(set_) element1 = next(set_itr) if not is_algebra_member(element1): return _undef.make_or_raise_undef() result = element1 for element in set_itr: if not is_algebra_member(element): return _undef.make_or_raise_undef() result = binary_op(result, element) return result
def chain_binary_operation(set_, binary_op, is_algebra_member): r"""Chain all elements of ``set_`` with the binary operation ``binary_op`` and return the result. :param set_: A :term:`set` of sets or :term:`multiset`\s. :param binary_op: The operation through which the members of ``set_`` are chained. It must be commutative and associative. :param is_algebra_member: The ``is_member()`` function of the :term:`algebra` of which the elements of ``set_`` must be members. :return: A member of ``algebra`` that is the result of chaining all elements of ``set_`` with the :term:`binary operation` ``binary_op``. """ if not is_member(set_): return _undef.make_or_raise_undef() if set_.is_empty: return set_ set_itr = iter(set_) element1 = next(set_itr) if not is_algebra_member(element1): return _undef.make_or_raise_undef() result = element1 for element in set_itr: if not is_algebra_member(element): return _undef.make_or_raise_undef() result = binary_op(result, element) return result
def binary_extend(set1: 'P( M )', set2: 'P( M )', op, _checked=True) -> 'P( M )': r"""Return the :term:`binary extension` of ``op`` from one :term:`algebra` to another algebra. For this extension, the elements of the extended algebra must be :term:`set`\s of the elements of the original algebra. :param set1: A :term:`set` with elements on which ``op`` operates. :param set2: A set with elements on which ``op`` operates. :param op: A :term:`binary operation` that operates on the elements of ``set1`` and ``set2``. :return: A set that consists of the defined results of ``op`` when executed on all combinations of the elements of ``set1`` and ``set2``, or `Undef()` if either set is not a :class:`~.Set`. """ if _checked: if not isinstance(set1, _mo.Set): return _ud.make_or_raise_undef() if not isinstance(set2, _mo.Set): return _ud.make_or_raise_undef() else: assert set1.is_set assert set2.is_set def _get_values(_set1, _set2): for e1 in _set1: for e2 in _set2: result = op(e1, e2) if result is not _ud.Undef(): yield result return _mo.Set(_get_values(set1, set2), direct_load=True)
def _to_listgen_check_args(mclan: 'P(P(M x M) x N)', offset: '( A )', limit: '( A )', _checked: bool=True) -> (): """Check the arguments of `multiclan_to_listgen` and `order_slice_to_listgen`. Return a tuple with the clean values of ``offset`` and ``limit`` or `Undef()` if there was a problem. """ if _checked: if not is_member(mclan): return _undef.make_or_raise_undef2(mclan) if isinstance(offset, _mo.Atom) and isinstance(offset.value, int): pass elif isinstance(offset, int): offset = _mo.Atom(limit) elif offset is _undef.Undef(): return _undef.make_or_raise_undef(2) else: return _undef.make_or_raise_undef() if isinstance(limit, _mo.Atom) \ and (isinstance(limit.value, int) or limit.value == float('inf')): pass elif isinstance(limit, int) or limit == float('inf'): limit = _mo.Atom(limit) elif limit is _undef.Undef(): return _undef.make_or_raise_undef(2) else: return _undef.make_or_raise_undef() else: if mclan is _undef.Undef() or offset is _undef.Undef() or limit is _undef.Undef(): return _undef.make_or_raise_undef(2) assert is_member(mclan) assert offset.is_atom and isinstance(offset.value, int) assert limit.is_atom and (isinstance(limit.value, int) or limit.value == float('inf')) return offset, limit
def get_left(rel: 'P(M x M)', right: '( M )', _checked=True) -> '( M )': r"""Return the left component of the couplet that has a right component of ``right``. In general, use with :term:`right-functional` :term:`relation`\s; that is, relations where all :term:`right component`\s appear at most once. :return: The :term:`left component` of the :term:`couplet` that has a :term:`right component` of ``right``, or `Undef()` if there is not exactly one couplet with the right component ``right`` in ``rel`` or ``rel`` is not a :term:`relation`. """ if _checked: if not is_member(rel): return _undef.make_or_raise_undef2(rel) if right is _undef.Undef(): return _undef.make_or_raise_undef(2) right = _mo.auto_convert(right) else: assert is_member_or_undef(rel) assert _mo.is_mathobject_or_undef(right) if right is _undef.Undef() or rel is _undef.Undef(): return _undef.make_or_raise_undef(2) result = None for elem in rel: assert elem.is_couplet if elem.right == right: if result is not None: return _undef.make_or_raise_undef( ) # Early Undef() exit if more than one found. result = elem.left if result is None: return _undef.make_or_raise_undef() # Undef() exit if none found. return result
def intersect(multiset1: 'P( M x N )', multiset2: 'P( M x N )', _checked=True) -> 'P( M x N )': """Return the multiset intersection of ``multiset1`` with ``multiset2``. :return: The :term:`multiset intersection` of ``multiset1`` and ``multiset2`` or `Undef()` if ``multiset1`` or ``multiset2`` are not instances of :class:`~.Multiset`. """ if _checked: if not is_member(multiset1): return _undef.make_or_raise_undef() if not is_member(multiset2): return _undef.make_or_raise_undef() else: assert is_member(multiset1) assert is_member(multiset2) values = multiset1.data & multiset2.data result = _mo.Multiset(values) if not result.is_empty: # Multiclan flags: if multiset1.cached_is_multiclan or multiset2.cached_is_multiclan: result.cache_multiclan(_mo.CacheStatus.IS) if multiset1.cached_is_absolute or multiset2.cached_is_absolute: result.cache_absolute(_mo.CacheStatus.IS) if multiset1.cached_is_functional or multiset2.cached_is_functional: result.cache_functional(_mo.CacheStatus.IS) if multiset1.cached_is_right_functional or multiset2.cached_is_right_functional: result.cache_right_functional(_mo.CacheStatus.IS) if multiset1.cached_is_regular or multiset2.cached_is_regular: result.cache_regular(_mo.CacheStatus.IS) if multiset1.cached_is_right_regular or multiset2.cached_is_right_regular: result.cache_right_regular(_mo.CacheStatus.IS) return result
def cross_functional_union(mclan1: 'P(P(M x M) x N)', mclan2: 'P(P(M x M) x N)', _checked=True) -> 'P(P(M x M) x N)': r"""Return the :term:`cross-functional union` of ``mclan1`` and ``mclan2``. :return: The :term:`binary multi-extension` of the :term:`functional union` from the :term:`algebra of relations` to the :term:`algebra of multiclans`, applied to ``mclan1`` and ``mclan2``, or `Undef()` if ``mclan1`` or ``mclan2`` are not :term:`multiclan`\s. """ if _checked: if not is_member(mclan1): return _undef.make_or_raise_undef() if not is_member(mclan2): return _undef.make_or_raise_undef() else: assert is_member(mclan1) assert is_member(mclan2) result = _extension.binary_multi_extend(mclan1, mclan2, _functools.partial( _relations.functional_union, _checked=False), _checked=False) if not result.is_empty: result.cache_multiclan(_mo.CacheStatus.IS) result.cache_functional(_mo.CacheStatus.IS) if mclan1.cached_is_not_right_functional or mclan2.cached_is_not_right_functional: result.cache_right_functional(_mo.CacheStatus.IS_NOT) return result
def cross_intersect(multiclan1: 'P(P(M x M) x N)', multiclan2: 'PP(M x M)', _checked=True) -> 'PP(M x M)': r"""Return the :term:`cross-intersection` of ``multiclan1`` and ``multiclan2``. :return: The :term:`binary multi-extension` of :term:`intersection` from the :term:`algebra of relations` (which inherits it from the :term:`algebra of sets`) to the :term:`algebra of multiclans` applied to ``multiclan1`` and ``multiclan2``, or `Undef()` if ``multiclan1`` or ``multiclan2`` are not :term:`multiclan`\s. """ if _checked: if not is_member(multiclan1): return _undef.make_or_raise_undef() if not is_member(multiclan2): return _undef.make_or_raise_undef() else: assert is_member(multiclan1) assert is_member(multiclan2) result = _extension.binary_multi_extend(multiclan1, multiclan2, _functools.partial( _sets.intersect, _checked=False), _checked=False) if not result.is_empty: result.cache_multiclan(_mo.CacheStatus.IS) if multiclan1.cached_is_functional or multiclan2.cached_is_functional: result.cache_functional(_mo.CacheStatus.IS) if multiclan1.cached_is_right_functional or multiclan2.cached_is_right_functional: result.cache_right_functional(_mo.CacheStatus.IS) return result
def defined_at(mclan: 'P(P(M x M) x N)', left: '( M )', _checked=True): r"""Return the :term:`relation`\s of ``mclan`` that are defined for ``left``.""" if not is_member(mclan): return _undef.make_or_raise_undef(2) if left is _undef.Undef(): return _undef.make_or_raise_undef(2) result = _extension.unary_multi_extend(mclan, _functools.partial( _relations.defined_at, left=left, _checked=_checked), _checked=_checked) if result is _undef.Undef() or not result: return _undef.make_or_raise_undef2(result) result.cache_multiclan(_mo.CacheStatus.IS) if not result.is_empty: if mclan.cached_is_functional: result.cache_functional(_mo.CacheStatus.IS) if mclan.cached_is_right_functional: result.cache_right_functional(_mo.CacheStatus.IS) if mclan.cached_is_regular: result.cache_regular(_mo.CacheStatus.IS) if mclan.cached_is_right_regular: result.cache_right_regular(_mo.CacheStatus.IS) return result
def big_intersect(set_: 'PP( M )', _checked=True) -> 'P( M )': """Return the intersection of all members of ``set_``. :return: The :term:`intersection` of all members of ``set_`` or `Undef()` if ``set_`` or any of its members are not instances of :class:`~.Set`. Example code: .. code:: from algebraixlib.mathobjects import Set from algebraixlib.algebras.sets import big_intersect big_intersect(Set(Set('a', 'b'), Set('b', 'c'))) # Output: Set(Atom('b')) big_intersect(Set(Set('a', 'b'), 'a')) # Output: <algebraixlib.undef.Undef at 0x4004978> """ if _checked: if not is_member(set_): return _undef.make_or_raise_undef2(set_) for element in set_: if not is_member(element): return _undef.make_or_raise_undef(2) else: assert is_member_or_undef(set_) if set_ is _undef.Undef(): return _undef.make_or_raise_undef(2) return chain_binary_operation( set_, _functools.partial(intersect, _checked=False), is_member)
def single(set_: _mo.Set): """Return the single element of ``set_``. :return: Return the single element of ``set_``, or `Undef()` if ``set_`` has not exactly one element or is not a :term:`set` (that is, an instance of :class:`~.Set`). """ if not is_member(set_): return _undef.make_or_raise_undef() if set_.cardinality == 1: return next(iter(set_)) return _undef.make_or_raise_undef(2)
def test_make_or_raise_undef(self): """Test make_or_raise_undef() together with RaiseOnUndef.""" self.assertEqual(RaiseOnUndef.get_level(), 0) self.assertIs(make_or_raise_undef(), Undef()) RaiseOnUndef.set_level(1) self.assertRaises(UndefException, lambda: make_or_raise_undef()) self.assertIs(make_or_raise_undef(2), Undef()) RaiseOnUndef.set_level(2) self.assertRaises(UndefException, lambda: make_or_raise_undef(2)) RaiseOnUndef.reset() self.assertIs(make_or_raise_undef(2), Undef())
def union(set1: 'P( M )', set2: 'P( M )', _checked=True) -> 'P( M )': r"""Return the union of ``set1`` with ``set2``. :return: The :term:`binary union` of ``set1`` and ``set2`` or `Undef()` if ``set1`` or ``set2`` are not :term:`set`\s (that is, instances of :class:`~.Set`). """ # pylint: disable=too-many-branches if _checked: if not is_member(set1): return _undef.make_or_raise_undef() if not is_member(set2): return _undef.make_or_raise_undef() else: assert is_member(set1) assert is_member(set2) values = set1.data.union(set2.data) result = _mo.Set(values, direct_load=True) if not result.is_empty: # Relation flags: if set1.cached_is_relation and set2.cached_is_relation: result.cache_relation(_mo.CacheStatus.IS) if set1.cached_is_absolute and set2.cached_is_absolute: result.cache_absolute(_mo.CacheStatus.IS) elif set1.cached_is_not_absolute or set2.cached_is_not_absolute: result.cache_absolute(_mo.CacheStatus.IS_NOT) if set1.cached_is_not_functional or set2.cached_is_not_functional: result.cache_functional(_mo.CacheStatus.IS_NOT) if set1.cached_is_not_right_functional or set2.cached_is_not_right_functional: result.cache_right_functional(_mo.CacheStatus.IS_NOT) elif set1.cached_is_not_relation or set2.cached_is_not_relation: result.cache_relation(_mo.CacheStatus.IS_NOT) # Clan flags: if set1.cached_is_clan and set2.cached_is_clan: result.cache_clan(_mo.CacheStatus.IS) if set1.cached_is_absolute and set2.cached_is_absolute: result.cache_absolute(_mo.CacheStatus.IS) elif set1.cached_is_not_absolute or set2.cached_is_not_absolute: result.cache_absolute(_mo.CacheStatus.IS_NOT) if set1.cached_is_functional and set2.cached_is_functional: result.cache_functional(_mo.CacheStatus.IS) elif set1.cached_is_not_functional or set2.cached_is_not_functional: result.cache_functional(_mo.CacheStatus.IS_NOT) if set1.cached_is_right_functional and set2.cached_is_right_functional: result.cache_right_functional(_mo.CacheStatus.IS) elif set1.cached_is_not_right_functional or set2.cached_is_not_right_functional: result.cache_right_functional(_mo.CacheStatus.IS_NOT) if set1.cached_is_not_regular or set2.cached_is_not_regular: result.cache_regular(_mo.CacheStatus.IS_NOT) if set1.cached_is_not_right_regular or set2.cached_is_not_right_regular: result.cache_right_regular(_mo.CacheStatus.IS_NOT) elif set1.cached_is_not_clan or set2.cached_is_not_clan: result.cache_clan(_mo.CacheStatus.IS_NOT) return result
def test_make_or_raise_undef(self): """Test make_or_raise_undef() together with RaiseOnUndef.""" try: self.assertEqual(RaiseOnUndef.get_level(), 0) self.assertIs(make_or_raise_undef(), Undef()) RaiseOnUndef.set_level(1) self.assertRaises(UndefException, lambda: make_or_raise_undef()) self.assertIs(make_or_raise_undef(2), Undef()) RaiseOnUndef.set_level(2) self.assertRaises(UndefException, lambda: make_or_raise_undef(2)) RaiseOnUndef.reset() self.assertIs(make_or_raise_undef(2), Undef()) except: # Make sure RaiseOnUndef level gets reset. RaiseOnUndef.reset() raise
def transpose(multiclan: 'P(P(M x M) x N)', _checked=True) -> 'P(P(M x M) x N)': """Return a multiclan where all relations have their left and right components swapped. :return: The :term:`unary multi-extension` of :term:`transposition` from the :term:`algebra of relations` to the :term:`algebra of multiclans`, applied to ``multiclan``, or `Undef()` if ``multiclan`` is not a :term:`multiclan`. """ if _checked: if not is_member(multiclan): return _undef.make_or_raise_undef2(multiclan) else: assert is_member_or_undef(multiclan) if multiclan is _undef.Undef(): return _undef.make_or_raise_undef(2) result = _extension.unary_multi_extend(multiclan, _functools.partial( _relations.transpose, _checked=False), _checked=False) if not result.is_empty: result.cache_multiclan(_mo.CacheStatus.IS) result.cache_absolute(multiclan.cached_absolute) result.cache_functional(multiclan.cached_right_functional) result.cache_right_functional(multiclan.cached_functional) result.cache_reflexive(multiclan.cached_reflexive) result.cache_symmetric(multiclan.cached_symmetric) result.cache_transitive(multiclan.cached_transitive) result.cache_regular(multiclan.cached_right_regular) result.cache_right_regular(multiclan.cached_regular) return result
def cross_intersect(multiclan1: 'P(P(M x M) x N)', multiclan2: 'PP(M x M)', _checked=True) -> 'PP(M x M)': r"""Return the :term:`cross-intersection` of ``multiclan1`` and ``multiclan2``. :return: The :term:`binary multi-extension` of :term:`intersection` from the :term:`algebra of relations` (which inherits it from the :term:`algebra of sets`) to the :term:`algebra of multiclans` applied to ``multiclan1`` and ``multiclan2``, or `Undef()` if ``multiclan1`` or ``multiclan2`` are not :term:`multiclan`\s. """ if _checked: if not is_member(multiclan1): return _undef.make_or_raise_undef2(multiclan1) if not is_member(multiclan2): return _undef.make_or_raise_undef2(multiclan2) else: assert is_member_or_undef(multiclan1) assert is_member_or_undef(multiclan2) if multiclan1 is _undef.Undef() or multiclan2 is _undef.Undef(): return _undef.make_or_raise_undef(2) result = _extension.binary_multi_extend(multiclan1, multiclan2, _functools.partial( _sets.intersect, _checked=False), _checked=False) if not result.is_empty: result.cache_multiclan(_mo.CacheStatus.IS) if multiclan1.cached_is_functional or multiclan2.cached_is_functional: result.cache_functional(_mo.CacheStatus.IS) if multiclan1.cached_is_right_functional or multiclan2.cached_is_right_functional: result.cache_right_functional(_mo.CacheStatus.IS) return result
def compose(rel1: 'P(M x M)', rel2: 'P(M x M)', _checked=True) -> 'P(M x M)': r"""Return the composition of ``rel1`` with ``rel2``. :return: The :term:`binary extension` of :term:`composition` from the :term:`algebra of couplets` to the :term:`algebra of relations`, applied to the :term:`relation`\s ``rel1`` and ``rel2``, or `Undef()` if ``rel1`` or ``rel2`` are not relations. """ if _checked: if not is_member(rel1): return _undef.make_or_raise_undef2(rel1) if not is_member(rel2): return _undef.make_or_raise_undef2(rel2) else: assert is_member_or_undef(rel1) assert is_member_or_undef(rel2) if rel1 is _undef.Undef() or rel2 is _undef.Undef(): return _undef.make_or_raise_undef(2) result = _extension.binary_extend(rel1, rel2, _functools.partial(_couplets.compose, _checked=False), _checked=False) if not result.is_empty: result.cache_relation(_mo.CacheStatus.IS) if rel1.cached_is_absolute and rel2.cached_is_absolute: result.cache_absolute(_mo.CacheStatus.IS) if rel1.cached_is_functional and rel2.cached_is_functional: result.cache_functional(_mo.CacheStatus.IS) if rel1.cached_is_right_functional and rel2.cached_is_right_functional: result.cache_right_functional(_mo.CacheStatus.IS) return result
def demultify(multiset: 'P( M x N )', _checked=True) -> 'P( M )': """Return a :term:`set` based on ``multiset`` that contains all elements without multiples.""" if _checked: if not is_member(multiset): return _undef.make_or_raise_undef2(multiset) else: assert is_member_or_undef(multiset) if multiset is _undef.Undef(): return _undef.make_or_raise_undef(2) result = _mo.Set(multiset.data.keys(), direct_load=True) if not result.is_empty: result.cache_clan(multiset.cached_multiclan) if multiset.cached_is_multiclan: result.cache_absolute(multiset.cached_absolute) result.cache_functional(multiset.cached_functional) result.cache_right_functional(multiset.cached_right_functional) result.cache_reflexive(multiset.cached_reflexive) result.cache_symmetric(multiset.cached_symmetric) result.cache_transitive(multiset.cached_transitive) result.cache_regular(multiset.cached_regular) result.cache_right_regular(multiset.cached_right_regular) # We don't yet have a concept of multirelations (multisets of couplets). Because of this, # a multiset that is converted into a set may be a relation without us being able to know # this here. Because of this, the only flags we can propagate are multiclan flags. return result
def intersect(multiset1: 'P( M x N )', multiset2: 'P( M x N )', _checked=True) -> 'P( M x N )': """Return the multiset intersection of ``multiset1`` with ``multiset2``. :return: The :term:`multiset intersection` of ``multiset1`` and ``multiset2`` or `Undef()` if ``multiset1`` or ``multiset2`` are not instances of :class:`~.Multiset`. """ if _checked: if not is_member(multiset1): return _undef.make_or_raise_undef2(multiset1) if not is_member(multiset2): return _undef.make_or_raise_undef2(multiset2) else: assert is_member_or_undef(multiset1) assert is_member_or_undef(multiset2) if multiset1 is _undef.Undef() or multiset2 is _undef.Undef(): return _undef.make_or_raise_undef(2) values = multiset1.data & multiset2.data result = _mo.Multiset(values) if not result.is_empty: # Multiclan flags: if multiset1.cached_is_multiclan or multiset2.cached_is_multiclan: result.cache_multiclan(CacheStatus.IS) if multiset1.cached_is_absolute or multiset2.cached_is_absolute: result.cache_absolute(CacheStatus.IS) if multiset1.cached_is_functional or multiset2.cached_is_functional: result.cache_functional(CacheStatus.IS) if multiset1.cached_is_right_functional or multiset2.cached_is_right_functional: result.cache_right_functional(CacheStatus.IS) if multiset1.cached_is_regular or multiset2.cached_is_regular: result.cache_regular(CacheStatus.IS) if multiset1.cached_is_right_regular or multiset2.cached_is_right_regular: result.cache_right_regular(CacheStatus.IS) return result
def cross_right_functional_union(clan1: 'PP(M x M)', clan2: 'PP(M x M)', _checked=True) -> 'PP(M x M)': r"""Return the cross-right-functional union of ``clan1`` and ``clan2``. The :term:`cross-right-functional union` of two :term:`clan`\s is the :term:`cross-union` of these clans, but removing all resulting :term:`relation`\s that are not :term:`right-functional`. :return: The :term:`binary extension` of the :term:`right-functional union` from the :term:`algebra of relations` to the :term:`algebra of clans`, applied to ``clan1`` and ``clan2``, or `Undef()` if ``clan1`` or ``clan2`` are not :term:`clan`\s. """ if _checked: if not is_member(clan1): return _undef.make_or_raise_undef2(clan1) if not is_member(clan2): return _undef.make_or_raise_undef2(clan2) else: assert is_member_or_undef(clan1) assert is_member_or_undef(clan2) if clan1 is _undef.Undef() or clan2 is _undef.Undef(): return _undef.make_or_raise_undef(2) result = _extension.binary_extend( clan1, clan2, _functools.partial(_relations.right_functional_union, _checked=False), _checked=False) if not result.is_empty: result.cache_clan(_mo.CacheStatus.IS) result.cache_right_functional(_mo.CacheStatus.IS) if clan1.cached_is_not_functional or clan2.cached_is_not_functional: result.cache_functional(_mo.CacheStatus.IS_NOT) return result
def get_lefts(clan: 'PP(M x M)', _checked=True) -> 'P( M )': r"""Return the set of the left components of all couplets in all relations in ``clan``. :return: The :term:`union` of the :term:`left set`\s of all :term:`relation`\s in ``clan`` or `Undef()` if ``clan`` is not a :term:`clan`. """ if _checked: if not is_member(clan): return _undef.make_or_raise_undef2(clan) else: assert is_member_or_undef(clan) if clan is _undef.Undef(): return _undef.make_or_raise_undef(2) if clan.is_empty: # The left set of an empty set is the empty set return clan clan_itr = iter(clan) left_set = _relations.get_lefts(next(clan_itr), _checked=False) for rel in clan_itr: left_set = _sets.union(_relations.get_lefts(rel, _checked=False), left_set, _checked=False) if not left_set.is_empty: if clan.cached_is_absolute: left_set.cache_absolute(CacheStatus.IS) return left_set
def cross_intersect(clan1: 'PP(M x M)', clan2: 'PP(M x M)', _checked=True) -> 'PP(M x M)': r"""Return the cross-intersection of ``clan1`` and ``clan2``. The :term:`cross-intersection` of two :term:`clan`\s is a clan that contains the result of intersecting every :term:`relation` from one clan with every relation from the other clan. :return: The :term:`binary extension` of :term:`intersection` from the :term:`algebra of relations` (which inherits it from the :term:`algebra of sets`) to the :term:`algebra of clans` applied to ``clan1`` and ``clan2``, or `Undef()` if ``clan1`` or ``clan2`` are not :term:`clan`\s. """ if _checked: if not is_member(clan1): return _undef.make_or_raise_undef2(clan1) if not is_member(clan2): return _undef.make_or_raise_undef2(clan2) else: assert is_member_or_undef(clan1) assert is_member_or_undef(clan2) if clan1 is _undef.Undef() or clan2 is _undef.Undef(): return _undef.make_or_raise_undef(2) result = _extension.binary_extend(clan1, clan2, _functools.partial(_sets.intersect, _checked=False), _checked=False) if not result.is_empty: result.cache_clan(_mo.CacheStatus.IS) if clan1.cached_is_functional or clan2.cached_is_functional: result.cache_functional(_mo.CacheStatus.IS) if clan1.cached_is_right_functional or clan2.cached_is_right_functional: result.cache_right_functional(_mo.CacheStatus.IS) return result
def is_superset_of(set1: 'P( M )', set2: 'P( M )', _checked=True) -> bool: r"""Return whether ``set1`` is a superset of ``set2``. :return: ``True`` if ``set1`` is a :term:`superset` of ``set2``, ``False`` if not. Return `Undef()` if ``set1`` or ``set2`` are not :term:`set`\s (that is, instances of :class:`~.Set`). """ if _checked: if not is_member(set1): return _undef.make_or_raise_undef() if not is_member(set2): return _undef.make_or_raise_undef() else: assert is_member(set1) assert is_member(set2) return set1.data.issuperset(set2.data)
def get_rights(mclan: 'P(P(M x M) x N)', _checked=True) -> 'P( M )': r"""Return the set of the right components of all couplets in all relations in ``mclan``. :return: The :term:`union` of the :term:`right set`\s of all :term:`relation`\s in ``mclan`` or `Undef()` if ``mclan`` is not a :term:`multiclan`. """ if _checked: if not is_member(mclan): return _undef.make_or_raise_undef2(mclan) else: assert is_member_or_undef(mclan) if mclan is _undef.Undef(): return _undef.make_or_raise_undef(2) if mclan.is_empty: # The right set of an empty set is the empty set return mclan clan_itr = iter(mclan) right_set = _relations.get_rights(next(clan_itr), _checked=False) for rel in clan_itr: right_set = _sets.union(_relations.get_rights(rel, _checked=False), right_set, _checked=False) if not right_set.is_empty: if mclan.cached_is_absolute: right_set.cache_absolute(_mo.CacheStatus.IS) return right_set
def is_right_regular(mclan, _checked=True) -> bool: """Return whether ``mclan`` is right-regular. :return: ``True`` if ``mclan`` is :term:`right-regular`, ``False`` if not, or `Undef()` if ``mclan`` is not a :term:`multiclan`. """ if _checked: if not is_member(mclan): return _undef.make_or_raise_undef2(mclan) else: assert is_member_or_undef(mclan) if mclan is _undef.Undef(): return _undef.make_or_raise_undef(2) if mclan.cached_right_regular == _mo.CacheStatus.UNKNOWN: # The empty set is already handled in Set().__init__ via flags initialization. if mclan.cached_is_not_right_functional: mclan.cache_right_regular(_mo.CacheStatus.IS_NOT) return False itr = iter(mclan.data) rel = next(itr) if not _relations.is_right_functional(rel): mclan.cache_right_regular(_mo.CacheStatus.IS_NOT) return False right_set = rel.get_right_set() right_regular = all( _relations.is_right_functional(rel) and right_set == rel.get_right_set() for rel in itr) mclan.cache_regular(_mo.CacheStatus.from_bool(right_regular)) return mclan.cached_is_regular
def fill_lefts(rel: 'P(M x M)', renames: 'P(M x M)', _checked=True) -> 'P(M x M)': r"""Return the left components in ``rel`` that are missing in ``renames`` as a diagonal unioned with ``renames``. The purpose is to create a :term:`relation` that can be used with the :term:`composition` operation to change (rename) one or more :term:`left component`\s and leave the rest alone. :param rel: The :term:`relation` that provides the full :term:`left set`. :param renames: A relation where the :term:`right component`\s are meant to be :term:`composition` 'origins' and the :term:`left component`\s composition 'targets'. :return: A relation that contains all members of ``renames`` unioned with a :term:`diagonal` that consists of all left components in ``rel`` that are missing in ``renames``. """ if _checked: if not is_member(rel): return _undef.make_or_raise_undef2(rel) if not is_member(renames): return _undef.make_or_raise_undef2(renames) else: assert is_member_or_undef(rel) assert is_member_or_undef(renames) if rel is _undef.Undef() or renames is _undef.Undef(): return _undef.make_or_raise_undef(2) missing_lefts = _sets.minus(get_lefts(rel, _checked=False), get_rights(renames, _checked=False), _checked=False) diag_missing_lefts = diag(*missing_lefts, _checked=False) result = _sets.union(renames, diag_missing_lefts, _checked=False) assert result.cached_is_relation return result
def get_rights_for_left(mclan: 'P(P(M x M) x N)', left: '( M )', _checked=True) -> 'P(M x N)': """Return the multiset of the right components of all couplets in the multiclan ``mclan`` associated with the left component ``left``. :return: The :term:`right multiset` of the :term:`multiclan` ``mclan`` associated with the :term:`left component` ``left`` or `Undef()` if ``mclan`` is not a multiclan. """ if _checked: if not is_member(mclan): return _undef.make_or_raise_undef2(mclan) if left is _undef.Undef(): return _mo.Set() left = _mo.auto_convert(left) else: assert is_member_or_undef(mclan) assert _mo.is_mathobject_or_undef(left) if mclan is _undef.Undef(): return _undef.make_or_raise_undef(2) if left is _undef.Undef(): return _mo.Set() clan_itr = iter(mclan) rights = _sets.multify( _relations.get_rights_for_left(next(clan_itr), left, _checked=False)) for rel in clan_itr: rights = _multisets.add(_sets.multify( _relations.get_rights_for_left(rel, left, _checked=False)), rights, _checked=False) if not rights.is_empty: if mclan.cached_is_absolute: rights.cache_absolute(_mo.CacheStatus.IS) return rights
def multify(set_: 'P( M )', _checked=True) -> 'P( M x N )': """Return a :term:`multiset` based on ``set_`` where all multiples are set to 1.""" if _checked: if not is_member(set_): return _undef.make_or_raise_undef() else: assert is_member(set_) result = _mo.Multiset(set_.data, direct_load=True) if not result.is_empty: result.cache_multiclan(set_.cached_clan) if set_.cached_is_relation: # We don't yet have a concept of multirelations (multisets of couplets). This would be # handled here. pass elif set_.cached_is_clan: result.cache_absolute(set_.cached_absolute) result.cache_functional(set_.cached_functional) result.cache_right_functional(set_.cached_right_functional) result.cache_reflexive(set_.cached_reflexive) result.cache_symmetric(set_.cached_symmetric) result.cache_transitive(set_.cached_transitive) result.cache_regular(set_.cached_regular) result.cache_right_regular(set_.cached_right_regular) if set_.cached_is_not_relation and set_.cached_is_not_clan: # set_ is known to be a plain set. result.cache_absolute(set_.cached_absolute) result.cache_functional(_mo.CacheStatus.N_A) result.cache_right_functional(_mo.CacheStatus.N_A) result.cache_reflexive(_mo.CacheStatus.N_A) result.cache_symmetric(_mo.CacheStatus.N_A) result.cache_transitive(_mo.CacheStatus.N_A) result.cache_regular(_mo.CacheStatus.N_A) result.cache_right_regular(_mo.CacheStatus.N_A) return result
def compose(multiclan1: 'P(P(M x M) x N)', multiclan2: 'P(P(M x M) x N)', _checked=True) -> 'P(P(M x M) x N)': r"""Return the composition of ``multiclan1`` with ``multiclan2``. :return: The :term:`binary multi-extension` of :term:`composition` from the :term:`algebra of relations` to the :term:`algebra of multiclans`, applied to ``multiclan1`` and ``multiclan2``, or `Undef()` if ``multiclan1`` or ``multiclan2`` are not :term:`multiclan`\s. """ if _checked: if not is_member(multiclan1): return _undef.make_or_raise_undef2(multiclan1) if not is_member(multiclan2): return _undef.make_or_raise_undef2(multiclan2) else: assert is_member_or_undef(multiclan1) assert is_member_or_undef(multiclan2) if multiclan1 is _undef.Undef() or multiclan2 is _undef.Undef(): return _undef.make_or_raise_undef(2) result = _extension.binary_multi_extend(multiclan1, multiclan2, _functools.partial( _relations.compose, _checked=False), _checked=False) if not result.is_empty: result.cache_multiclan(_mo.CacheStatus.IS) if multiclan1.cached_is_absolute and multiclan2.cached_is_absolute: result.cache_absolute(_mo.CacheStatus.IS) if multiclan1.cached_is_functional and multiclan2.cached_is_functional: result.cache_functional(_mo.CacheStatus.IS) if multiclan1.cached_is_right_functional and multiclan2.cached_is_right_functional: result.cache_right_functional(_mo.CacheStatus.IS) return result
def get_rights_for_left(rel: 'P(M x M)', left: '( M )', _checked=True) -> 'P( M )': """Return the set of the right components of all couplets in the relation ``rel`` associated with the :term:`left component` ``left``. :return: The :term:`right set` of the :term:`relation` ``rel`` associated with the :term:`left component` or `Undef()` if ``rel`` is not a :term:`relation`. """ if _checked: if not is_member(rel): return _undef.make_or_raise_undef2(rel) if left is _undef.Undef(): return _mo.Set() left = _mo.auto_convert(left) else: assert is_member_or_undef(rel) assert _mo.is_mathobject_or_undef(left) if rel is _undef.Undef(): return _undef.make_or_raise_undef(2) if left is _undef.Undef(): return _mo.Set() result = _mo.Set((elem.right for elem in rel if elem.left == left), direct_load=True) if not result.is_empty: if rel.cached_is_absolute: result.cache_absolute(_mo.CacheStatus.IS) return result
def is_regular(clan, _checked=True) -> bool: """Return whether ``clan`` is (left-)regular. :return: ``True`` if ``clan`` is :term:`regular`, ``False`` if not, or `Undef()` if ``clan`` is not a :term:`clan`. """ if _checked: if not is_member(clan): return _undef.make_or_raise_undef() else: assert is_member(clan) if clan.cached_regular == _mo.CacheStatus.UNKNOWN: # The empty set is already handled in Set().__init__ via flags initialization. if clan.cached_is_not_functional: clan.cache_regular(_mo.CacheStatus.IS_NOT) return False itr = iter(clan) rel = next(itr) if not _relations.is_functional(rel): clan.cache_regular(_mo.CacheStatus.IS_NOT) return False left_set = rel.get_left_set() regular = all( _relations.is_functional(rel) and left_set == rel.get_left_set() for rel in itr) clan.cache_regular(_mo.CacheStatus.from_bool(regular)) return clan.cached_is_regular
def transpose(rel: 'P(M x M)', _checked=True) -> 'P(M x M)': """Return a relation where all couplets have their left and right components swapped. :return: The :term:`unary extension` of :term:`transposition` from the :term:`algebra of couplets` to the :term:`algebra of relations`, applied to the :term:`relation` ``rel``, or `Undef()` if ``rel`` is not a relation. """ if _checked: if not is_member(rel): return _undef.make_or_raise_undef2(rel) else: assert is_member_or_undef(rel) if rel is _undef.Undef(): return _undef.make_or_raise_undef(2) result = _extension.unary_extend(rel, _functools.partial( _couplets.transpose, _checked=False), _checked=False) if not result.is_empty: result.cache_relation(_mo.CacheStatus.IS) result.cache_absolute(rel.cached_absolute) result.cache_functional(rel.cached_right_functional) result.cache_right_functional(rel.cached_functional) result.cache_reflexive(rel.cached_reflexive) result.cache_symmetric(rel.cached_symmetric) result.cache_transitive(rel.cached_transitive) return result
def cross_functional_union(mclan1: 'P(P(M x M) x N)', mclan2: 'P(P(M x M) x N)', _checked=True) -> 'P(P(M x M) x N)': r"""Return the :term:`cross-functional union` of ``mclan1`` and ``mclan2``. :return: The :term:`binary multi-extension` of the :term:`functional union` from the :term:`algebra of relations` to the :term:`algebra of multiclans`, applied to ``mclan1`` and ``mclan2``, or `Undef()` if ``mclan1`` or ``mclan2`` are not :term:`multiclan`\s. """ if _checked: if not is_member(mclan1): return _undef.make_or_raise_undef2(mclan1) if not is_member(mclan2): return _undef.make_or_raise_undef2(mclan2) else: assert is_member_or_undef(mclan1) assert is_member_or_undef(mclan2) if mclan1 is _undef.Undef() or mclan2 is _undef.Undef(): return _undef.make_or_raise_undef(2) result = _extension.binary_multi_extend( mclan1, mclan2, _functools.partial(_relations.functional_union, _checked=False), _checked=False) if not result.is_empty: result.cache_multiclan(CacheStatus.IS) result.cache_functional(CacheStatus.IS) if mclan1.cached_is_not_right_functional or mclan2.cached_is_not_right_functional: result.cache_right_functional(CacheStatus.IS_NOT) return result
def unary_multi_extend(multiset: 'P( M x N )', op, _checked=True) -> 'P( M x N )': r"""Return the :term:`unary extension` of ``op`` from one :term:`algebra` to another algebra. For this extension, the elements of the extended algebra must be :term:`multiset`\s of the elements of the original algebra. :param multiset: A :term:`multiset` with elements on which ``op`` operates. :param op: A :term:`unary operation` that operates on the elements of ``multiset``. :return: A set that consists of the defined results of ``op`` when executed on the elements of ``multiset``, or `Undef()` if ``set1`` is not a :class:`~.Multiset`. """ if _checked: if not isinstance(multiset, _mo.Multiset): return _ud.make_or_raise_undef() else: assert multiset.is_multiset def _get_values(_multiset): return_count = _collections.Counter() for elem, multi in _multiset.data.items(): result = op(elem) if result is not _ud.Undef(): return_count[result] += multi return return_count return _mo.Multiset(_get_values(multiset))
def is_equivalence_relation(mo: _mo.MathObject, _checked: bool = True) -> bool: r"""Return whether ``mo`` is an :term:`equivalence relation` or `Undef()` if not applicable. Is implemented for :term:`relation`\s, :term:`clan`\s, :term:`multiclan`\s and :term:`set`\s of (sets of ...) clans. Is also defined (but not yet implemented) for any combination of sets or :term:`multiset`\s of relations. """ if _checked: if not isinstance(mo, _mo.MathObject): return _undef.make_or_raise_undef() # is_reflexive is the only one that is defined for couplets, so it must be evaluated last or it # may result in erroneous `False` returns. symmetric = is_symmetric(mo, _checked=False) if symmetric is _undef.Undef() or not symmetric: return symmetric transitive = is_transitive(mo, _checked=False) if transitive is _undef.Undef() or not transitive: return transitive reflexive = is_reflexive(mo, _checked=False) if reflexive is _undef.Undef() or not reflexive: return reflexive return True
def binary_extend(set1: 'P( M )', set2: 'P( M )', op, _checked=True) -> 'P( M )': r"""Return the :term:`binary extension` of ``op`` from one :term:`algebra` to another algebra. For this extension, the elements of the extended algebra must be :term:`set`\s of the elements of the original algebra. :param set1: A :term:`set` with elements on which ``op`` operates. :param set2: A set with elements on which ``op`` operates. :param op: A :term:`binary operation` that operates on the elements of ``set1`` and ``set2``. :return: A set that consists of the defined results of ``op`` when executed on all combinations of the elements of ``set1`` and ``set2``, or `Undef()` if either set is not a :class:`~.Set`. """ if _checked: if not _sets.is_member(set1): return _undef.make_or_raise_undef2(set1) if not _sets.is_member(set2): return _undef.make_or_raise_undef2(set2) else: assert _sets.is_member_or_undef(set1) assert _sets.is_member_or_undef(set2) if set1 is _undef.Undef() or set2 is _undef.Undef(): return _undef.make_or_raise_undef(2) def _get_values(_set1, _set2): for e1 in _set1: for e2 in _set2: result = op(e1, e2) if result is not _undef.Undef(): yield result return _mo.Set(_get_values(set1, set2), direct_load=True)
def get_rights_for_left(mclan: 'P(P(M x M) x N)', left: '( M )', _checked=True) -> 'P(M x N)': """Return the multiset of the right components of all couplets in the multiclan ``mclan`` associated with the left component ``left``. :return: The :term:`right multiset` of the :term:`multiclan` ``mclan`` associated with the :term:`left component` ``left`` or `Undef()` if ``mclan`` is not a multiclan. """ if _checked: if not is_member(mclan): return _undef.make_or_raise_undef2(mclan) if left is _undef.Undef(): return _mo.Set() left = _mo.auto_convert(left) else: assert is_member_or_undef(mclan) assert _mo.is_mathobject_or_undef(left) if mclan is _undef.Undef(): return _undef.make_or_raise_undef(2) if left is _undef.Undef(): return _mo.Set() clan_itr = iter(mclan) rights = _sets.multify(_relations.get_rights_for_left(next(clan_itr), left, _checked=False)) for rel in clan_itr: rights = _multisets.add( _sets.multify(_relations.get_rights_for_left(rel, left, _checked=False)), rights, _checked=False) if not rights.is_empty: if mclan.cached_is_absolute: rights.cache_absolute(_mo.CacheStatus.IS) return rights
def get_rights(mclan: 'P(P(M x M) x N)', _checked=True) -> 'P( M )': r"""Return the set of the right components of all couplets in all relations in ``mclan``. :return: The :term:`union` of the :term:`right set`\s of all :term:`relation`\s in ``mclan`` or `Undef()` if ``mclan`` is not a :term:`multiclan`. """ if _checked: if not is_member(mclan): return _undef.make_or_raise_undef2(mclan) else: assert is_member_or_undef(mclan) if mclan is _undef.Undef(): return _undef.make_or_raise_undef(2) if mclan.is_empty: # The right set of an empty set is the empty set return mclan clan_itr = iter(mclan) right_set = _relations.get_rights(next(clan_itr), _checked=False) for rel in clan_itr: right_set = _sets.union( _relations.get_rights(rel, _checked=False), right_set, _checked=False) if not right_set.is_empty: if mclan.cached_is_absolute: right_set.cache_absolute(_mo.CacheStatus.IS) return right_set
def is_right_regular(mo: _mo.MathObject, _checked: bool = True) -> bool: r"""Return whether ``mo`` is :term:`right-regular` or `Undef()` if not applicable. Is implemented for :term:`clan`\s, :term:`multiclan`\s and :term:`set`\s of (sets of ...) clans. Is also defined (but not yet implemented) for any combination of sets or :term:`multiset`\s of clans. """ # pylint: disable=too-many-return-statements if _checked: if not isinstance(mo, _mo.MathObject): return _undef.make_or_raise_undef() # Check cache status. if mo.cached_right_regular == _mo.CacheStatus.IS: return True if mo.cached_right_regular == _mo.CacheStatus.IS_NOT: return False if mo.cached_right_regular == _mo.CacheStatus.N_A: return _undef.make_or_raise_undef(2) # Check type (right-regular is only defined on Sets and Multisets) and algebra memberships. if not mo.is_set and not mo.is_multiset: mo.cache_right_regular(_mo.CacheStatus.N_A) return _undef.make_or_raise_undef(2) if _clans.is_member(mo): return _clans.is_right_regular(mo, _checked=False) if _multiclans.is_member(mo): return _multiclans.is_right_regular(mo, _checked=False) # Check higher (not yet defined) algebras. if mo.get_ground_set().get_powerset_level(_clans.get_ground_set()) > 0: mo_iter = iter(mo) elem1 = next(mo_iter) if not is_right_regular(elem1): mo.cache_right_regular(_mo.CacheStatus.IS_NOT) return False elem1_rights = elem1.get_rights() right_regular = all( is_right_regular(elem, _checked=False) and elem.get_rights() == elem1_rights for elem in mo_iter) mo.cache_right_regular(_mo.CacheStatus.from_bool(right_regular)) return mo.cached_is_right_regular # Nothing applied: 'right-regular' is not defined. mo.cache_right_regular(_mo.CacheStatus.N_A) return _undef.make_or_raise_undef(2)
def superstrict(set1: 'P( M )', set2: 'P( M )', _checked=True) -> 'P( M )': r"""Return ``set1`` if it is a superset of ``set2``, otherwise return `Undef()`. :return: Return the :term:`superstriction` of ``set1`` and ``set1``; that is, return ``set1`` if it is a :term:`superset` of ``set2`` or `Undef()` if not. Also return `Undef()` if ``set1`` or ``set2`` are not :term:`set`\s (that is, instances of :class:`~.Set`). """ # pylint: disable=too-many-branches if _checked: if not is_member(set1): return _undef.make_or_raise_undef2(set1) if not is_member(set2): return _undef.make_or_raise_undef2(set2) else: assert is_member_or_undef(set1) assert is_member_or_undef(set2) if set1 is _undef.Undef() or set2 is _undef.Undef(): return _undef.make_or_raise_undef(2) if not is_superset_of(set1, set2, _checked=False): return _undef.make_or_raise_undef(2) if not set1.is_empty: # Relation flags: if set1.cached_is_relation: if set2.cached_is_not_absolute: set1.cache_absolute(_mo.CacheStatus.IS_NOT) if set2.cached_is_not_functional: set1.cache_functional(_mo.CacheStatus.IS_NOT) if set2.cached_is_not_right_functional: set1.cache_right_functional(_mo.CacheStatus.IS_NOT) # Clan flags: if set1.cached_is_clan: if set2.cached_is_not_absolute: set1.cache_absolute(_mo.CacheStatus.IS_NOT) if set2.cached_is_not_functional: set1.cache_functional(_mo.CacheStatus.IS_NOT) if set2.cached_is_not_right_functional: set1.cache_right_functional(_mo.CacheStatus.IS_NOT) if set2.cached_is_not_regular: set1.cache_regular(_mo.CacheStatus.IS_NOT) if set2.cached_is_not_right_regular: set1.cache_right_regular(_mo.CacheStatus.IS_NOT) return set1
def is_reflexive(mo: _mo.MathObject, _checked: bool = True) -> bool: r"""Return whether ``mo`` is :term:`reflexive` or `Undef()` if not applicable. Is implemented for :term:`couplet`\s, :term:`relation`\s, :term:`clan`\s, :term:`multiclan`\s and :term:`set`\s of (sets of ...) clans. Is also defined (but not yet implemented) for any combination of sets or :term:`multiset`\s of relations. """ # pylint: disable=too-many-return-statements if _checked: if not isinstance(mo, _mo.MathObject): return _undef.make_or_raise_undef() # Check cache status. if mo.cached_reflexive == _mo.CacheStatus.IS: return True if mo.cached_reflexive == _mo.CacheStatus.IS_NOT: return False if mo.cached_reflexive == _mo.CacheStatus.N_A: return _undef.make_or_raise_undef(2) # Check types and algebra memberships. if _couplets.is_member(mo): return _couplets.is_reflexive(mo, _checked=False) if not mo.is_set and not mo.is_multiset: mo.cache_reflexive(_mo.CacheStatus.N_A) return _undef.make_or_raise_undef(2) if _relations.is_member(mo): return _relations.is_reflexive(mo, _checked=False) if _clans.is_member(mo): return _clans.is_reflexive(mo, _checked=False) if _multiclans.is_member(mo): return _multiclans.is_reflexive(mo, _checked=False) # Check higher (not yet defined) algebras. reflexive = _is_powerset_property(mo, _clans.get_ground_set(), is_reflexive) if reflexive is not _undef.Undef(): mo.cache_reflexive(_mo.CacheStatus.from_bool(reflexive)) return reflexive # Nothing applied: 'reflexive' is not defined. mo.cache_reflexive(_mo.CacheStatus.N_A) return _undef.make_or_raise_undef(2)
def get_right_set(self) -> 'P( M )': """Get the :term:`right set` for this :class:`Multiset`. Return `Undef()` if not applicable. .. todo:: Once multipowersets are fully implemented, see :meth:`~algebraixlib.mathobjects.set.Set.get_right_set`. """ if _multiclans().is_member(self): return _multiclans().get_rights(self, _checked=False) return _ud.make_or_raise_undef()
def __getitem__(self, left) -> 'P( M )': r"""With the syntax ``mo[left]``, return a set of rights associated with ``left``. :param left: The :term:`left component` of the :term:`couplet`\(s) of which the :term:`right component`\(s) are returned. :return: If ``self`` is a :term:`relation`, return a :term:`set` that contains the right(s) of the couplet(s) that have a left that matches ``left``. (This set may be empty if no couplet with the given left exists.) Return `Undef()` if ``self`` is not a relation. """ return _undef.make_or_raise_undef()