From 47dea1ad2905652588eb5d187b26d945ff0ac965 Mon Sep 17 00:00:00 2001 From: cha <charles.paperman@univ-lille.fr> Date: Sat, 1 Jun 2024 15:34:41 +0200 Subject: [PATCH] Improvement into class method --- data/classes/algebras/commutative.typed.yaml | 2 +- pysemigroup/algebra/__init__.py | 8 +- pysemigroup/algebra/algebra_base.py | 94 ++++++++++++++------ pysemigroup/algebra/associativity.py | 92 ++++++++----------- pysemigroup/algebra/morphism.py | 44 +++++---- pysemigroup/algebra/with_identity.py | 57 ++---------- pysemigroup/datamodel/files.py | 44 +++------ 7 files changed, 156 insertions(+), 185 deletions(-) diff --git a/data/classes/algebras/commutative.typed.yaml b/data/classes/algebras/commutative.typed.yaml index 4f669b5..5810cb9 100644 --- a/data/classes/algebras/commutative.typed.yaml +++ b/data/classes/algebras/commutative.typed.yaml @@ -22,4 +22,4 @@ algo: def cond(G): return G.is_J_trivial() algo: ... - # Be default, uses the first appliable equation \ No newline at end of file + # Be default, uses the first appliable equation diff --git a/pysemigroup/algebra/__init__.py b/pysemigroup/algebra/__init__.py index cc6bf5c..d336978 100644 --- a/pysemigroup/algebra/__init__.py +++ b/pysemigroup/algebra/__init__.py @@ -1,7 +1,7 @@ from typing import Type, TypeVar from pysemigroup.algebra import associativity, inversible, with_identity -from pysemigroup.algebra.algebra_base import FreeAlgebraBase, FiniteAlgebraBase +from pysemigroup.algebra.algebra_base import FiniteAlgebraBase, FreeAlgebraBase __all__ = ["associativity", "with_identity", "inversible"] @@ -11,6 +11,6 @@ for A in (FreeAlgebraBase, FiniteAlgebraBase): local_scope[cls.__name__] = cls __all__.append(cls.__name__) -FiniteAlgebra : TypeVar = Type[FiniteAlgebraBase] -FreeAlgebra : TypeVar = Type[FreeAlgebraBase] -Algebra : TypeVar = FiniteAlgebra | FreeAlgebra +FiniteAlgebra: TypeVar = Type[FiniteAlgebraBase] +FreeAlgebra: TypeVar = Type[FreeAlgebraBase] +Algebra: TypeVar = FiniteAlgebra | FreeAlgebra diff --git a/pysemigroup/algebra/algebra_base.py b/pysemigroup/algebra/algebra_base.py index 460ea07..cae832d 100644 --- a/pysemigroup/algebra/algebra_base.py +++ b/pysemigroup/algebra/algebra_base.py @@ -2,6 +2,7 @@ from abc import ABC, abstractmethod, abstractproperty from collections.abc import Collection, Iterable from dataclasses import dataclass from itertools import product +from operator import mul from typing import ( Callable, Generic, @@ -13,6 +14,7 @@ from typing import ( TypeAlias, TypeVar, cast, + ClassVar ) import networkx as nx @@ -91,8 +93,9 @@ class Element(Generic[LabelType], Hashable): return f"{self.algebra}({self.arrow.label})" return f"{self.algebra}({self.arrow.source}, {self.arrow.label}, {self.arrow.target})" + class AlgebraBase(Generic[InputType, LabelType], ABC): - """ A Base class for Algebra """ + """A Base class for Algebra""" is_typed: bool = True is_free: bool = False @@ -113,7 +116,6 @@ class AlgebraBase(Generic[InputType, LabelType], ABC): @abstractmethod def element_factory(self, e: InputType) -> Element[LabelType]: ... - @abstractmethod def multiplication( self, @@ -122,7 +124,7 @@ class AlgebraBase(Generic[InputType, LabelType], ABC): ) -> Element[LabelType]: ... @abstractproperty - def type_graph(self) -> nx.DiGraph: ... + def type_graph(self) -> nx.MultiDiGraph: ... @property def objects(self) -> Iterable[Hashable]: @@ -139,12 +141,45 @@ class AlgebraBase(Generic[InputType, LabelType], ABC): raise ValueError(f"{type(self)} do not admit identity") @classmethod - @abstractmethod - def typed_version(cls) -> type: ... + def typed_version(cls) -> Type["AlgebraBase"]: + if cls.is_free: + for ocls in FreeAlgebraBase.subclasses: + if ocls.is_typed and ocls.__module__ == cls.__module__: + return ocls + else: + for ocls in FiniteAlgebraBase.subclasses: + if ocls.is_typed and ocls.__module__ == cls.__module__: + return ocls + assert False @classmethod - @abstractmethod - def untyped_version(cls) -> type: ... + def untyped_version(cls) -> Type["AlgebraBase"]: + if cls.is_free: + for ocls in FreeAlgebraBase.subclasses: + if not ocls.is_typed and ocls.__module__ == cls.__module__: + return ocls + else: + for ocls in FiniteAlgebraBase.subclasses: + if not ocls.is_typed and ocls.__module__ == cls.__module__: + return ocls + assert False + + @classmethod + def to_free(cls) -> Type["FreeAlgebraBase"]: + if cls.is_free: + return cls + for ocls in FreeAlgebraBase.subclasses: + if cls.is_typed == ocls.is_typed and cls.__module__ == ocls.__module__: + return ocls + assert False + + def to_finite(cls) -> Type["FiniteAlgebraBase"]: + if not cls.is_free: + return cls + for ocls in FiniteAlgebraBase.subclasses: + if cls.is_typed == ocls.is_typed and cls.__module__ == ocls.__module__: + return ocls + assert False @classmethod def to_typed(cls, el: Self): @@ -182,22 +217,22 @@ FreePath: TypeAlias = Arrow[FreePathLabel] class FreeAlgebraBase(Generic[InputType], AlgebraBase[InputType, FreePathLabel]): - """ A Base class for free algebra """ + """A Base class for free algebra""" - is_free = True - subclasses: list[Type[Self]] = [] + is_free : ClassVar[bool] = True + subclasses: ClassVar[list[Type[Self]]] = [] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) cls.subclasses.append(cls) @property - def type_graph(self) -> nx.DiGraph: + def type_graph(self) -> nx.MultiDiGraph: if hasattr(self, "_type_graph"): return self._type_graph - G: nx.DiGraph = nx.DiGraph() + G: nx.MultiDiGraph = nx.MultiDiGraph() G.add_edges_from((e.source, e.target, dict(e=None)) for e in self.generators) - self._type_graph: nx.DiGraph = G + self._type_graph: nx.MultiDiGraph = G return G def multiplication( @@ -213,9 +248,9 @@ class FreeAlgebraBase(Generic[InputType], AlgebraBase[InputType, FreePathLabel]) return Element(arrow, self) @abstractmethod - def deconstruct(self, e: Element[FreePathLabel]) -> tuple[Element[FreePathLabel],...]: - ... - + def deconstruct( + self, e: Element[FreePathLabel], + ) -> tuple[Element[FreePathLabel], ...]: ... def __iter__(self) -> Iterable[Element[FreePathLabel]]: raise NotImplementedError @@ -234,9 +269,10 @@ class FreeAlgebraBase(Generic[InputType], AlgebraBase[InputType, FreePathLabel]) class FiniteAlgebraBase(Sized, AlgebraBase[InputType, Hashable]): - """ A Base class for finite algebra """ - is_free = False - subclasses: list[Type[Self]] = [] + """A Base class for finite algebra""" + + is_free : ClassVar[bool] = False + subclasses: ClassVar[list[Type[Self]]] = [] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) @@ -249,9 +285,9 @@ class FiniteAlgebraBase(Sized, AlgebraBase[InputType, Hashable]): def __init__( self, generators: Iterable[InputType], - multiplication: Callable[[InputType, InputType], InputType], + multiplication: Callable[[InputType, InputType], InputType] = mul, ): - self._cache_element = {} + self._cache_element: dict[InputType, Element[Hashable]] = {} super().__init__(generators) self._base_multiplication = multiplication self._to_base = dict(self._to_base_generators) @@ -276,7 +312,7 @@ class FiniteAlgebraBase(Sized, AlgebraBase[InputType, Hashable]): continue remain.add(hg) self._representant[hg] = self._representant[new_element] + (g,) - self._left_cayley: nx.DiGraph = nx.DiGraph() + self._left_cayley: nx.MultiDiGraph = nx.MultiDiGraph() for x in elements: for g in self.generators: if g.is_compatible(x): @@ -307,7 +343,7 @@ class FiniteAlgebraBase(Sized, AlgebraBase[InputType, Hashable]): G: nx.MultiDiGraph = nx.MultiDiGraph() for e in self: G.add_edge(e.arrow.source, e.arrow.target, e=e) - self._type_graph: nx.DiGraph = G + self._type_graph: nx.MultiDiGraph = G return G def elements_between(self, t, u): @@ -347,10 +383,12 @@ class FiniteAlgebraBase(Sized, AlgebraBase[InputType, Hashable]): cls = type(self).meet_class(type(other)) self = cls.from_(self) other = cls.from_(other) - new_gen = ((g,h) for g in self.generators for h in other.generators) - def new_mul(x,y): - (x1,x2), (y1,y2) = x, y - return (x1*y1, x2*y2) + new_gen = ((g, h) for g in self.generators for h in other.generators) + + def new_mul(x, y): + (x1, x2), (y1, y2) = x, y + return (x1 * y1, x2 * y2) + if cls.has_identity: cls(new_gen, new_mul, (self.identity(), other.identity())) return cls(new_gen, new_mul) @@ -373,7 +411,7 @@ class FiniteAlgebraBase(Sized, AlgebraBase[InputType, Hashable]): return cls(**states) def sub_algebra_generated_by( - self, some_element: Iterable[Element[Hashable]] + self, some_element: Iterable[Element[Hashable]], ) -> Self: state = self.__get_state__() state["generators"] = some_element diff --git a/pysemigroup/algebra/associativity.py b/pysemigroup/algebra/associativity.py index 2ee8a8a..6ff92f6 100644 --- a/pysemigroup/algebra/associativity.py +++ b/pysemigroup/algebra/associativity.py @@ -1,7 +1,7 @@ from collections.abc import Sized from functools import cached_property from itertools import chain -from typing import Callable, Hashable, cast, Iterable +from typing import Callable, Hashable, Iterable, Type, cast from pysemigroup.algebra.algebra_base import ( Arrow, @@ -15,24 +15,12 @@ from pysemigroup.views import ElementView, ω class FiniteSemigroup(FiniteAlgebraBase[Hashable]): is_typed = False - + def element_factory(self, e: Hashable) -> Element: if e not in self._cache_element: self._cache_element[e] = Element(Arrow(e, 0, 0), self) return self._cache_element[e] - @classmethod - def typed_version(cls) -> type: - return FiniteSemigroupoid - - @classmethod - def untyped_version(cls) -> type: - return FiniteSemigroup - - @classmethod - def to_free(cls) -> type: - return FreeSemigroupoid - def compute_identity(self): for g in self.generators: if (g**ω).is_identity(): @@ -58,76 +46,68 @@ class FiniteSemigroupoid(FiniteAlgebraBase[Arrow], Sized): def consolidate(self) -> FiniteSemigroup: new_gen = chain(self.generators, (0,)) - def new_mul(x,y): - if x==0 or y==0 or not x.is_compatible(y): + + def new_mul(x, y): + if x == 0 or y == 0 or not x.is_compatible(y): return 0 - return x*y + return x * y + return self.untyped_version().from_(FiniteSemigroup(new_gen, new_mul)) - def idempotent_category(self) -> "FiniteCategory": - from pysemigroup.algebra.with_identity import FiniteCategory + def idempotent_category(self) -> Type["FiniteSemigroupoid"]: + FiniteCategory = self.to_untype() if self.has_inverse: return self - new_objects = [ (t,e.arrow.label) for t in self.type_graph for e in self.local_semigroup(t).idempotents ] - new_gen = set(( Arrow(e*x*f, (t,e), (u,f)) for (t,e) in new_objects for (u,f) in new_objects for x in self.elements_between(t,u) )) - new_id = ( Arrow(e, (t,e), (t,e)) for (t,e) in new_objects ) - def new_mul(x,y): - return Arrow(x.label*y.label, x.source, y.target) - return FiniteCategory(new_gen, new_mul, new_id) + new_objects = [ + (t, e.arrow.label) + for t in self.type_graph + for e in self.local_semigroup(t).idempotents + ] + new_gen = set( + + Arrow(e * x * f, (t, e), (u, f)) + for (t, e) in new_objects + for (u, f) in new_objects + for x in self.elements_between(t, u) - @classmethod - def to_free(cls) -> type: - return FreeSemigroupoid + ) + new_id = (Arrow(e, (t, e), (t, e)) for (t, e) in new_objects) - @classmethod - def typed_version(cls) -> type: - return FiniteSemigroupoid + def new_mul(x, y): + return Arrow(x.label * y.label, x.source, y.target) - @classmethod - def untyped_version(cls) -> type: - return FiniteSemigroup + return FiniteCategory(new_gen, new_mul, new_id) def local_semigroup(self, t: Hashable) -> FiniteSemigroup: multiplication = cast( - Callable[[Hashable, Hashable], Hashable], self.multiplication + Callable[[Hashable, Hashable], Hashable], self.multiplication, ) - return FiniteSemigroup(self.elements_between(t,t), multiplication) + return FiniteSemigroup(self.elements_between(t, t), multiplication) def iter_local_semigroups(self) -> Iterable[FiniteSemigroup]: for t in self.type_graph: yield self.local_semigroup(t) + class FreeSemigroup(FreeAlgebraBase[Hashable]): is_typed = False def element_factory(self, a: Hashable) -> Element[FreePathLabel]: return Element(Arrow((Arrow(a, 0, 0),), 0, 0), self) - def deconstruct(self, e: Element[FreePathLabel]) -> tuple[Element[FreePathLabel],...]: - return tuple(map(lambda g:self.element_factory(g.label), e.arrow.label)) - - @classmethod - def typed_version(cls) -> type: - return FreeSemigroupoid - - @classmethod - def untyped_version(cls) -> type: - return FreeSemigroup + def deconstruct( + self, e: Element[FreePathLabel], + ) -> tuple[Element[FreePathLabel], ...]: + return tuple(map(lambda g: self.element_factory(g.label), e.arrow.label)) class FreeSemigroupoid(FreeAlgebraBase[Arrow[Hashable]]): is_typed = True - @classmethod - def typed_version(cls) -> type: - return FreeSemigroupoid - - @classmethod - def untyped_version(cls) -> type: - return FreeSemigroup - def element_factory(self, a: Arrow[Hashable]) -> Element[FreePathLabel]: return Element(Arrow((a,), 0, 0), self) - def deconstruct(self, e: Element[FreePathLabel]) -> tuple[Element[FreePathLabel],...]: - return tuple(map(lambda g:self.element_factory(g), e.arrow.label)) + def deconstruct( + self, e: Element[FreePathLabel], + ) -> tuple[Element[FreePathLabel], ...]: + return tuple(map(lambda g: self.element_factory(g), e.arrow.label)) diff --git a/pysemigroup/algebra/morphism.py b/pysemigroup/algebra/morphism.py index c72c2f9..81de9e2 100644 --- a/pysemigroup/algebra/morphism.py +++ b/pysemigroup/algebra/morphism.py @@ -1,11 +1,14 @@ -from typing import Hashable, Mapping, Self, Iterable, Callable from functools import reduce -from pysemigroup.algebra.algebra_base import FiniteAlgebraBase, Element, AlgebraBase +from typing import Callable, Hashable, Iterable, Mapping, Self + +from pysemigroup.algebra.algebra_base import AlgebraBase, Element, FiniteAlgebraBase from pysemigroup.automata import DFA + def multiply(x: Iterable[Element]): it = iter(x) - return reduce(lambda e,f: e*f, it, next(iter(it))) + return reduce(lambda e, f: e * f, it, next(iter(it))) + class Morphism: def __init__( @@ -56,18 +59,24 @@ class Morphism: return type(self)(mapping, self.obj_mapping) + class Stamp: finite_algebra = None - def __init__(self, descr: dict[Hashable, Hashable], multiplication: Callable[[Hashable, Hashable], Hashable], *args): + def __init__( + self, + descr: dict[Hashable, Hashable], + multiplication: Callable[[Hashable, Hashable], Hashable], + *args, + ): M = self.finite_algebra(descr.values(), multiplication, *args) F = self.finite_algebra.to_free()(descr.keys()) - self.key_mapping = { a: F.element_factory(a) for a in descr } - self.key_mapping_rev = { r:a for a,r in descr.items() } - self.value_mapping = { v: M.element_factory(v) for v in descr.values() } - self.value_mapping_rev = { t:v for v,t in self.value_mapping.items() } + self.key_mapping = {a: F.element_factory(a) for a in descr} + self.key_mapping_rev = {r: a for a, r in descr.items()} + self.value_mapping = {v: M.element_factory(v) for v in descr.values()} + self.value_mapping_rev = {t: v for v, t in self.value_mapping.items()} - mapping = { self.key_mapping[v]: self.value_mapping[k] for v,k in descr.items()} + mapping = {self.key_mapping[v]: self.value_mapping[k] for v, k in descr.items()} self.morphism = Morphism.from_mapping(mapping) def __call__(self, u: tuple[Hashable, ...]) -> Hashable: @@ -79,15 +88,20 @@ class Stamp: def to_regular_language(self, P: Iterable[Hashable]): from pysemigroup.regular_language import RegularLanguage - transitions = ((source, self.key_mapping_rev[self.value_mapping_rev[g]], target) for (source, target, g) in self.codomain._right_cayley.edges(data="g")) + + transitions = ( + (source, self.key_mapping_rev[self.value_mapping_rev[g]], target) + for (source, target, g) in self.codomain._right_cayley.edges(data="g") + ) assert set(P).issubset(self.codomain) return RegularLanguage(DFA(transitions, self.codomain.identity, P)) - -l = locals() + + +loc = locals() __all__ = [] for alg in FiniteAlgebraBase.subclasses: name = f"{alg.__name__}Stamp" - l[name] = type(name, (Stamp,), dict(finite_algebra=alg)) + loc[name] = type(name, (Stamp,), dict(finite_algebra=alg)) __all__.append(alg) - -del l + +del loc diff --git a/pysemigroup/algebra/with_identity.py b/pysemigroup/algebra/with_identity.py index a98bd2f..1d19c44 100644 --- a/pysemigroup/algebra/with_identity.py +++ b/pysemigroup/algebra/with_identity.py @@ -1,10 +1,9 @@ -from typing import Hashable, Mapping, Iterable, Callable +from typing import Callable, Hashable, Iterable, Mapping from pysemigroup.algebra.algebra_base import Arrow, Element, FreePathLabel from pysemigroup.algebra.associativity import ( FiniteSemigroup, FiniteSemigroupoid, - FreePathLabel, FreeSemigroup, FreeSemigroupoid, ) @@ -17,14 +16,6 @@ class FreeMonoid(FreeSemigroup): assert t == 0 return Element(Arrow((), 0, 0), self) - @classmethod - def typed_version(cls) -> type: - return FreeCategory - - @classmethod - def untyped_version(cls) -> type: - return FreeMonoid - class FreeCategory(FreeSemigroupoid): has_identity = True @@ -33,14 +24,6 @@ class FreeCategory(FreeSemigroupoid): assert t in self.objects return Element(Arrow((), t, t), self) - @classmethod - def typed_version(cls) -> type: - return FreeCategory - - @classmethod - def untyped_version(cls) -> type: - return FreeMonoid - class FiniteMonoid(FiniteSemigroup): has_identity = True @@ -49,9 +32,9 @@ class FiniteMonoid(FiniteSemigroup): self, generators: Iterable[Hashable], multiplication: Callable[[Hashable, Hashable], Hashable], - identity: Hashable + identity: Hashable, ): - generators = tuple(generators) + generators = tuple(generators) super().__init__(generators, multiplication) self._to_base[self.element_factory(identity)] = identity self._base_identity = identity @@ -64,18 +47,6 @@ class FiniteMonoid(FiniteSemigroup): t["identity"] = self._base_identity return t - @classmethod - def typed_version(cls) -> type: - return FiniteCategory - - @classmethod - def untyped_version(cls) -> type: - return FiniteMonoid - - @classmethod - def to_free(cls) -> type: - return FreeMonoid - class FiniteCategory(FiniteSemigroupoid): has_identity = True @@ -84,17 +55,16 @@ class FiniteCategory(FiniteSemigroupoid): self, generators: Iterable[Arrow], multiplication: Callable[[Arrow, Arrow], Arrow], - identities: Iterable[Arrow] + identities: Iterable[Arrow], ): - super().__init__(generators, multiplication) self._base_identities = identities identities_dict: Mapping[Hashable, Arrow[Hashable]] = dict() for t in identities: assert t.source == t.target - d[t.source] = self._to_base[t] = self.element_factory(t) - - self._identities = identities_dict + identities_dict[t.source] = self._to_base[t] = self.element_factory(t) + + self._identities = identities_dict def identity(self, t: Hashable = 0) -> Element: ident = self._identities[t] @@ -106,22 +76,9 @@ class FiniteCategory(FiniteSemigroupoid): t["identities"] = self._base_identities return t - def local_monoid(self, t) -> FiniteMonoid: return FiniteMonoid( generators=self.type_graph[t][t], multiplication=self.multiplication, identity=self.identity(t), ) - - @classmethod - def typed_version(cls) -> type: - return FiniteCategory - - @classmethod - def untyped_version(cls) -> type: - return FiniteMonoid - - @classmethod - def to_free(cls) -> type: - return FreeCategory diff --git a/pysemigroup/datamodel/files.py b/pysemigroup/datamodel/files.py index fbb648f..b5ed7e3 100755 --- a/pysemigroup/datamodel/files.py +++ b/pysemigroup/datamodel/files.py @@ -17,6 +17,19 @@ InitTuple = Annotated[ Tuple[SomeType, ...], PlainValidator(lambda v: () if v is None else v) ] +PathToClassAlgebras = Annotated[ + Path, PlainValidator(lambda v: Path("classes/algebras/", v)) +] + +PathToClassLanguages = Annotated[ + Path, PlainValidator(lambda v: Path("classes/languages/", v)) +] +PathToClassLogic = Annotated[Path, PlainValidator(lambda v: Path("classes/logic/", v))] +PathToOperator = Annotated[Path, PlainValidator(lambda v: Path("operators/", v))] +PathToBinaryOperator = Annotated[ + Path, PlainValidator(lambda v: Path("binary_operators/", v)) +] +PathToPredicate = Annotated[Path, PlainValidator(lambda v: Path("predicates/", v))] class Monad(Enum): Semigroups = "Semigroups" @@ -32,55 +45,24 @@ class Monad(Enum): class CommentedField(BaseModel): comment: str | None = None - -PathToClassAlgebras = Annotated[ - Path, PlainValidator(lambda v: Path("classes/algebras/", v)) -] - - class PointerToClassAlgebras(CommentedField): pointer: PathToClassAlgebras - -PathToClassLanguages = Annotated[ - Path, PlainValidator(lambda v: Path("classes/languages/", v)) -] - - class PointerToClassLanguages(CommentedField): pointer: PathToClassLanguages - -PathToClassLogic = Annotated[Path, PlainValidator(lambda v: Path("classes/logic/", v))] - - class PointerToClassLogic(CommentedField): pointer: PathToClassLogic - -PathToOperator = Annotated[Path, PlainValidator(lambda v: Path("operators/", v))] - - class PointerToOperator(CommentedField): pointer: PathToOperator - -PathToBinaryOperator = Annotated[ - Path, PlainValidator(lambda v: Path("binary_operators/", v)) -] - - class PointerToBinaryOperator(CommentedField): pointer: PathToBinaryOperator - -PathToPredicate = Annotated[Path, PlainValidator(lambda v: Path("predicates/", v))] - - class PointerToPredicate(CommentedField): pointer: PathToPredicate - class PredicateValue(PointerToPredicate): value: bool -- GitLab