mvlogics 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mvlogics/__init__.py ADDED
@@ -0,0 +1,104 @@
1
+ from .base import Fraction, Decimal, _AllLogicMeta, _FakeProtocolMeta, _DecimalLogicMeta, _RationalLogicMeta, _FastEnumLogicMeta, _SubmoduleMeta, _singleton_new, REQUIRED_ATTRS, FORBIDDEN, ALL_LOGICS, ALL_LOGICS_TUPLE, ALL_METHODS, FAKE_PROTOCOLS, FAKE_PROTOCOLS_TUPLE, RECOMMENDED_METHODS, MIXIN_METHODS, EXTENSION_METHODS, _all as __all__
2
+ def is_logic(typ): return isinstance(typ, _AllLogicMeta)
3
+ def is_logic_member(obj): return is_logic(type(obj))
4
+ def is_builtin_logic(typ): return typ.__name__ in ALL_LOGICS and typ.__module__ == __name__
5
+ def is_builtin_logic_member(obj): return is_builtin_logic(type(obj))
6
+ def convert(member, cls):
7
+ if isinstance(member, type): cls, member = member, cls
8
+ return member.convert_to(cls)
9
+ class Unit(metaclass=_AllLogicMeta):
10
+ __new__ = _singleton_new; box = diamond = __invert__ = lambda self: self; __and__ = __or__ = implies = gullibility = consensus = lambda self, _, /: self
11
+ def __bool__(self): raise TypeError('cannot convert Unit to bool')
12
+ def __repr__(self): return 'Unit.T'
13
+ def normalized(self): return Fraction(1)
14
+ @classmethod
15
+ def from_normalized(cls, val):
16
+ if val == Fraction(1): return cls()
17
+ raise ValueError(f'could not construct instance from val {val}')
18
+ def __reduce__(self): return type(self), ()
19
+ Unit.T = Unit()
20
+ class Boolean(metaclass=_FastEnumLogicMeta):
21
+ members = {'F': False, 'T': True}
22
+ box = diamond = lambda self: self
23
+ def __and__(self, other, /): return self and other
24
+ def __or__(self, other, /): return self or other
25
+ def __invert__(self): return type(self)(not self)
26
+ def __reduce__(self): return type(self), (self.value,)
27
+ K3, LP = map(lambda n, g: _FastEnumLogicMeta(n, (), {'members': {'F': -1, 'I': 0, 'T': 1}, '__and__': lambda self, other, /: min(self, other), '__or__': lambda self, other, /: max(self, other), '__invert__': lambda self: type(self)(-self.value), '__bool__': lambda self: self.value >= (n == 'K'), 'gullibility': g, 'consensus': lambda self, other, /: self if self is other else other if self is (I := type(self).I) else self if other is I else I, 'normalized': lambda self: Fraction(self.value+1, 2), 'from_normalized': classmethod(lambda cls, val: cls(int(val*2-1)))}), ('K3', 'LP'), (lambda self, other, /: self if self is other else type(self).I, lambda self, other, /: self if self is other else other if self is (I := type(self).I) else self if other is I else type(self).F))
28
+ class BI3(metaclass=_FastEnumLogicMeta):
29
+ members = {'F': -1, 'I': 0, 'T': 1}
30
+ def __and__(self, other, /): return I if (I := __class__.I) in (self, other) else type(self)(min(self.value, other.value))
31
+ def __or__(self, other, /): return I if (I := __class__.I) in (self, other) else type(self)(max(self.value, other.value))
32
+ def __invert__(self): return type(self)(-self.value)
33
+ def implies(self, other, /): return I if (I := __class__.I) in (self, other) else __class__.F if self is (T := __class__.T) and other is __class__.F else T
34
+ def gullibility(self, other, /): return self if self is other else __class__.I
35
+ def consensus(self, other, /): return self if len(s := {self, other}) == 1 else s.discard(__class__.I) or (s.pop() if len(s) == 1 else __class__.I)
36
+ def normalized(self): return Fraction(self.value+1, 2)
37
+ @classmethod
38
+ def from_normalized(cls, val): return cls(int(val*2-1))
39
+ def __reduce__(self): return type(self), (self.value,)
40
+ class RM3(metaclass=_FastEnumLogicMeta):
41
+ members = {'F': -1, 'B': 0, 'T': 1}
42
+ def __and__(self, other, /): return type(self)(min(self.value, other.value))
43
+ def __or__(self, other, /): return type(self)(max(self.value, other.value))
44
+ def __invert__(self): return type(self)(-self.value)
45
+ def implies(self, other, /): return self if self is other else T if (self is (F := __class__.F))^(other is (T := __class__.T)) else F
46
+ def normalized(self): return Fraction(self.value+1, 2)
47
+ @classmethod
48
+ def from_normalized(cls, val): return cls(int(val*2-1))
49
+ def __reduce__(self): return type(self), (self.value,)
50
+ def _check_names(n: tuple[str, ...], k: int|None, p: str):
51
+ if k is None: k = len(n)
52
+ elif n: raise ValueError('do not pass k with names')
53
+ if k < 2: raise ValueError('k should be >= 2')
54
+ return k, iter(n or map((p+'%s').__mod__, range(k)))
55
+ def _to_members(K: int, N): return {x: Fraction(i, K-1) for i, x in enumerate(N)}
56
+ def gödel_logic(*names, k=None, prefix='x_', clsname=None): K, N = _check_names(names, k, prefix); return _FastEnumLogicMeta(clsname or f'G{K}', (), {'members': _to_members(K, N), '__and__': lambda self, other, /: type(self)(min(self.value, other.value)), '__or__': lambda self, other, /: type(self)(max(self.value, other.value)), '__invert__': lambda self: type(self)(self.value == 0), 'implies': lambda self, other, /: type(self)(1) if self.value <= other.value else other, '__doc__': f"Godel's {K}-valued logic. See https://plato.stanford.edu/entries/logic-manyvalued/#GodLog."})
57
+ G3, SmT = gödel_logic('F', 'NF', 'T'), gödel_logic('F', 'NF', 'T', clsname='SmT')
58
+ def łukasiewicz_logic(*names, k=None, prefix='x_', clsname=None): K, N = _check_names(names, k, prefix); return _FastEnumLogicMeta(clsname or f'L{K}', (), {'members': _to_members(K, N), 'implies': (f := lambda self, other, /: type(self)(1+min(0, other.value-self.value))), '__invert__': lambda self: type(self)(1-self.value), '__and__': lambda self, other, /: f(f(self, other), other), '__or__': lambda self, other, /: ~(~self&~other), 'strong_disjunction': lambda self, other, /: f(~self, other), 'strong_conjunction': lambda self, other, /: ~f(self, ~other), 'diamond': (d := lambda self: f(~self, self)), 'box': lambda self: ~d(~self), 'doubtful': lambda self: self.iff(~self)})
59
+ L3 = łukasiewicz_logic('F', 'U', 'T')
60
+ def post_logic(*names, k=None, prefix='x_', clsname=None): K, N = _check_names(names, k, prefix); U = Fraction(1, K-1); return _FastEnumLogicMeta(clsname or f'P{K}', (), {'members': _to_members(K, N), '__invert__': lambda self: type(self)(v-U if (v := self.value) else 1), '__and__': lambda self, other, /: type(self)(min(self.value, other.value)), '__or__': lambda self, other, /: type(self)(max(self.value, other.value))})
61
+ P3 = post_logic('F', 'U', 'T')
62
+ class B4(metaclass=_FastEnumLogicMeta):
63
+ members = {'F': 0, 'N': 1, 'B': 2, 'T': 3}
64
+ def __invert__(self): return self if self in (__class__.B, __class__.N) else type(self)(3-self.value)
65
+ def __bool__(self): return self.value >= 2
66
+ def __and__(self, other, /):
67
+ if (F := __class__.F) in (self, other): return F
68
+ if self is (T := __class__.T): return other
69
+ if other is T: return self
70
+ return self if self is other else F
71
+ def __or__(self, other, /):
72
+ if (T := __class__.T) in (self, other): return T
73
+ if self is (F := __class__.F): return other
74
+ if other is F: return self
75
+ return self if self is other else T
76
+ def implies(self, other, /): return ~self|other
77
+ def gullibility(self, other, /): return self if self is other else __class__.N
78
+ def consensus(self, other, /): return self if len(s := {self, other}) == 1 else s.discard(__class__.N) or (s.pop() if len(s) == 1 else __class__.B)
79
+ def normalized(self): return Fraction(self.value, 3)
80
+ @classmethod
81
+ def from_normalized(cls, val): return cls(int(val*3))
82
+ Π, Π_aleph_0 = map(lambda name, meta: meta(name, (), {'__and__': lambda self, other, /: type(self)(self.value*other.value), '__or__': lambda self, other, /: type(self)(min(self.value, other.value)), '__invert__': lambda self: self.implies(type(self).F), 'implies': lambda self, other, /: type(self)(b/a if (a := self.value) > (b := other.value) else 1)}), ('Π', 'Π_aleph_0'), (_DecimalLogicMeta, _RationalLogicMeta))
83
+ G_inf, G_aleph_0 = map(lambda name, meta: meta(name, (), {'__and__': lambda self, other, /: type(self)(self.value*other.value), '__or__': lambda self, other, /: type(self)(min(self.value, other.value)), '__invert__': lambda self: type(self)(self.value == 0), 'implies': lambda self, other, /: type(self).T if self.value <= other.value else other}), ('G_inf', 'G_aleph_0'), (_DecimalLogicMeta, _RationalLogicMeta))
84
+ L_inf, L_aleph_0 = map(lambda name, meta: meta(name, (), {'implies': lambda self, other, /: type(self)(1+min(0, other.value-self.value)), '__invert__': lambda self: type(self)(1-self.value), '__and__': lambda self, other, /: self.implies(other).implies(other), '__or__': lambda self, other, /: ~(~self&~other), 'strong_disjunction': lambda self, other, /: (~self).implies(other), 'strong_conjunction': lambda self, other, /: ~self.implies(~other), 'diamond': lambda self: (~self).implies(self), 'box': lambda self: ~(~self).diamond(), 'doubtful': lambda self: self.iff(~self)}), ('L_inf', 'L_aleph_0'), (_DecimalLogicMeta, _RationalLogicMeta))
85
+ def t_norm_logic(*, name=None, rational=False, **k):
86
+ def dec(strong_conjunctionf):
87
+ x, y, z = map(Decimal, ('0.3', '0.7', '0.5'))
88
+ if strong_conjunctionf(x, y) != strong_conjunctionf(y, x): raise ValueError('t-norm must be commutative')
89
+ if strong_conjunctionf(x, Decimal(1)) != x: raise ValueError('degree 1 must be neutral element')
90
+ if strong_conjunctionf(Decimal('0.2'), z) > strong_conjunctionf(x, z): raise ValueError('t-norm must be non-decreasing')
91
+ def implies(self, other, /):
92
+ f = Decimal('0.001').__mul__
93
+ if self is (_ := type(self)).F: return _.T
94
+ s, u, v = 0, self.value, other.value
95
+ for i in range(1001):
96
+ if strong_conjunctionf(u, z := f(i)) <= v: s = z
97
+ return type(self)(s)
98
+ return (_RationalLogicMeta if rational else _DecimalLogicMeta)(name or strong_conjunctionf.__name__, (), {'strong_conjunction': lambda self, other, /: type(self)(strong_conjunctionf(self.value, other.value)), 'implies': implies, '__invert__': lambda self: self.implies(self.F), '__and__': lambda self, other: type(self)(min(self.value, other.value)), '__or__': lambda self, other, /: type(self)(max(self.value, other.value))}|k)
99
+ return dec
100
+ def logic_from_implication(*, name=None, rational=False, **k): return lambda impliesf: (_RationalLogicMeta if rational else _DecimalLogicMeta)(name or impliesf.__name__, (), {'implies': impliesf, '__invert__': lambda self: self.implies(self.F), '__and__': lambda self, other: type(self)(min(self.value, other.value)), '__or__': lambda self, other, /: type(self)(max(self.value, other.value))}|k)
101
+ decimal_t_norm_logic, rational_t_norm_logic, decimal_logic_from_implication, rational_logic_from_implication = t_norm_logic(), t_norm_logic(rational=True), logic_from_implication(), logic_from_implication(rational=True)
102
+ NP, NP_aleph_0 = t_norm_logic(name='NP')(f := lambda u, v: min(u, v) if u+v > 1 else Decimal()), t_norm_logic(name='NP_aleph_0', rational=True)(f)
103
+ class protocols(metaclass=_SubmoduleMeta, _supermodule_default_name_='logics'): __all__, __dir__ = FAKE_PROTOCOLS_TUPLE, (lambda: FAKE_PROTOCOLS_TUPLE)
104
+ for _ in FAKE_PROTOCOLS_TUPLE: setattr(protocols, _, _FakeProtocolMeta(_))
mvlogics/__init__.pyi ADDED
@@ -0,0 +1,186 @@
1
+ '''Implementations of various logics, not necessarily with two truth values. A logic is a propositional calculus, with an example being Aristotle's
2
+ logical calculus, where the only truth values are T and F and the law of the excluded middle holds. Logics do not support subclassing, analogously
3
+ to the built-in type bool. Members of all classes in this module are lazily generated to avoid significant memory overhead.'''
4
+ from typing import final, overload, Self, ClassVar, NoReturn, TypeGuard, Callable, Literal, Any
5
+ from fractions import Fraction
6
+ from decimal import Decimal
7
+ from .protocols import MemberlessLogicBase, LogicBase, StrictLogicBase, GödelLogic, ŁukasiewiczLogic, PostLogic, DecimalLogicBase, RationalLogicBase, TNormLogic, RationalTNormLogic, _L
8
+ REQUIRED_ATTRS: frozenset[str]
9
+ RECOMMENDED_METHODS: frozenset[str]
10
+ MIXIN_METHODS: frozenset[str]
11
+ EXTENSION_METHODS: frozenset[str]
12
+ FORBIDDEN: frozenset[str]
13
+ ALL_METHODS: frozenset[str]
14
+ FAKE_PROTOCOLS: frozenset[str]
15
+ ALL_LOGICS: frozenset[str]
16
+ FAKE_PROTOCOLS_TUPLE: tuple[str, ...]
17
+ ALL_LOGICS_TUPLE: tuple[str, ...]
18
+ __all__: tuple[str, ...]
19
+ def convert(member: MemberlessLogicBase, cls: type[_L]) -> _L: ...
20
+ def is_logic(typ: type) -> TypeGuard[type[MemberlessLogicBase[Any]]]: ...
21
+ def is_logic_member(obj: object) -> TypeGuard[MemberlessLogicBase[Any]]: ...
22
+ def is_builtin_logic(typ: type) -> TypeGuard[type[LogicBase[Any]|Unit]]: ...
23
+ def is_builtin_logic_member(obj: object) -> TypeGuard[LogicBase[Any]|Unit]: ...
24
+ @final
25
+ class Unit(MemberlessLogicBase):
26
+ '''An implementation of a single-valued logic. May be useful as a base case in various recursive operations.'''
27
+ T: ClassVar[Self]
28
+ def __new__(cls) -> Self: ...
29
+ def __and__(self, other: Self, /): ...
30
+ def __or__(self, other: Self, /) -> Self: ...
31
+ def __invert__(self) -> Self: ...
32
+ def box(self) -> Self: ...
33
+ def diamond(self) -> Self: ...
34
+ def gullibility(self, other: Self, /) -> Self: ...
35
+ def consensus(self, other: Self, /) -> Self: ...
36
+ def __bool__(self) -> NoReturn: ...
37
+ @final
38
+ class Boolean(LogicBase[bool]):
39
+ '''Boolean or Aristotelian logic, where the law of the excluded middle holds.
40
+ Actually just a wrapper around the built-in class bool which satisfies the interface.'''
41
+ T: ClassVar[Self]
42
+ F: ClassVar[Self]
43
+ def __and__(self, other: Self, /) -> Self: ...
44
+ def __or__(self, other: Self, /) -> Self: ...
45
+ def __invert__(self) -> Self: ...
46
+ def box(self) -> Self: ...
47
+ def diamond(self) -> Self: ...
48
+ @final
49
+ class K3(StrictLogicBase[int]):
50
+ '''Kleene's strong logic of indeterminancy (K_3), in which besides the truth values T and F, a new 'indeterminate' truth value I is introduced.
51
+ T is the only designated truth value.'''
52
+ T: ClassVar[Self]
53
+ I: ClassVar[Self]
54
+ F: ClassVar[Self]
55
+ def __and__(self, other: Self, /) -> Self: ...
56
+ def __or__(self, other: Self, /) -> Self: ...
57
+ def __invert__(self) -> Self: ...
58
+ def gullibility(self, other: Self, /): ...
59
+ def consensus(self, other: Self, /) -> Self: ...
60
+ @final
61
+ class LP(StrictLogicBase[int]):
62
+ '''Priest's 'logic of paradox' (P_3), provisionally named LP because P_3 refers to ternary Post logic here. The truth tables are equivalent to those of Kleene's, except I is also designated as a truth value. The gullibility operation is thus not as well-defined as in K_3, and in the case that one operand is T and the other F, the arbitrary convention to return F is chosen.'''
63
+ T: ClassVar[Self]
64
+ I: ClassVar[Self]
65
+ F: ClassVar[Self]
66
+ def __and__(self, other: Self, /) -> Self: ...
67
+ def __or__(self, other: Self, /) -> Self: ...
68
+ def __invert__(self) -> Self: ...
69
+ def gullibility(self, other: Self, /): ...
70
+ def consensus(self, other: Self, /) -> Self: ...
71
+ @final
72
+ class BI3(StrictLogicBase[int]):
73
+ '''Dmitry Bochvar's internal three-value logic, also known as Kleene's weak three-value logic. The indeterminate truth value is contagious in the sense that it propagates as the result of any operation that involves it.'''
74
+ T: ClassVar[Self]
75
+ I: ClassVar[Self]
76
+ F: ClassVar[Self]
77
+ def __and__(self, other: Self, /) -> Self: ...
78
+ def __or__(self, other: Self, /) -> Self: ...
79
+ def __invert__(self) -> Self: ...
80
+ def gullibility(self, other: Self, /): ...
81
+ def consensus(self, other: Self, /) -> Self: ...
82
+ @final
83
+ class RM3(LogicBase[int]):
84
+ '''The R-mingle 3 logic, whose significance lies in its material implication implementation. The axiom of weakening does not hold in RM3.'''
85
+ T: ClassVar[Self]
86
+ B: ClassVar[Self]
87
+ F: ClassVar[Self]
88
+ def __and__(self, other: Self, /) -> Self: ...
89
+ def __or__(self, other: Self, /) -> Self: ...
90
+ def __invert__(self) -> Self: ...
91
+ @final
92
+ class B4(StrictLogicBase[int]):
93
+ '''Belnap's logic B_4, a combination of K_3 and P_3, such that the overdetermined I is called B for 'both', and the underdetermined N for 'neither' The truth tables are derived accordingly.'''
94
+ T: ClassVar[Self]
95
+ B: ClassVar[Self]
96
+ N: ClassVar[Self]
97
+ F: ClassVar[Self]
98
+ def __and__(self, other: Self, /) -> Self: ...
99
+ def __or__(self, other: Self, /) -> Self: ...
100
+ def __invert__(self) -> Self: ...
101
+ def gullibility(self, other: Self, /): ...
102
+ def consensus(self, other: Self, /) -> Self: ...
103
+ @final
104
+ class SmT(GödelLogic):
105
+ '''Smetanov logic, also known as the logic of here and there and Gödel G3 logic. Introduced by Arend Heyting in 1930 to study intuitionistic logic, it is a three-valued intermediate logic where the intermediate value can be understood as 'not false'.'''
106
+ T: ClassVar[Self]
107
+ NF: ClassVar[Self]
108
+ F: ClassVar[Self]
109
+ @final
110
+ class G3(GödelLogic):
111
+ '''A three-valued logic of Gödel's, also known as Smetanov logic and the logic of here and there. Belongs to a larger family of 'Gödel' logics G_k with truth values 0, 1/(k-1), 2/(k-1), ..., (k-2)/(k-1), 1, with 1 designated as a 'true' truth value.'''
112
+ T: ClassVar[Self]
113
+ NF: ClassVar[Self]
114
+ F: ClassVar[Self]
115
+ @final
116
+ class L3(ŁukasiewiczLogic):
117
+ '''Jan Łukasiewicz's three-valued logic; the truth values for 0, 1/2, 1 are called F, U and T respectively.'''
118
+ T: ClassVar[Self]
119
+ U: ClassVar[Self]
120
+ F: ClassVar[Self]
121
+ @final
122
+ class P3(PostLogic):
123
+ '''Ternary Post logic, with a cyclic implementation of the negation operator.'''
124
+ T: ClassVar[Self]
125
+ U: ClassVar[Self]
126
+ F: ClassVar[Self]
127
+ @final
128
+ class Π(DecimalLogicBase): '''Product logic, with truth values between 0 and 1. For precision, please pass in a string (e.g. '3.14159' instead of 3.14159) to the constructor; however, passing an int, float or decimal.Decimal instance is also acceptable.'''
129
+ @final
130
+ class G_inf(DecimalLogicBase): '''Gödel's infinite-valued logic, with real numbers between 0 and 1 as truth values.'''
131
+ @final
132
+ class L_inf(DecimalLogicBase):
133
+ '''Łukasiewicz's infinite-valued logic, with real numbers between 0 and 1 as truth values.'''
134
+ def strong_disjunction(self, other: Self, /) -> Self: ...
135
+ def strong_conjunction(self, other: Self, /) -> Self: ...
136
+ def diamond(self) -> Self: ...
137
+ def box(self) -> Self: ...
138
+ def doubtful(self) -> Self: ...
139
+ @final
140
+ class NP(TNormLogic): '''A t-norm logic with the nilpotent minimum norm as the implication operator.'''
141
+ @final
142
+ class Π_aleph_0(RationalLogicBase): '''Product logic, with rational numbers between 0 and 1 as truth values. Pass in what Fraction takes to the constructor.'''
143
+ @final
144
+ class G_aleph_0(RationalLogicBase): '''Gödel's infinite-valued logic, with rational numbers between 0 and 1 as truth values.'''
145
+ @final
146
+ class L_aleph_0(RationalLogicBase):
147
+ '''Łukasiewicz's infinite-valued logic, with rational numbers between 0 and 1 as truth values.'''
148
+ def strong_disjunction(self, other: Self, /) -> Self: ...
149
+ def strong_conjunction(self, other: Self, /) -> Self: ...
150
+ def diamond(self) -> Self: ...
151
+ def box(self) -> Self: ...
152
+ def doubtful(self) -> Self: ...
153
+ @final
154
+ class NP_aleph_0(RationalTNormLogic): '''A t-norm logic with the nilpotent minimum norm as the implication operator and rational truth values only.'''
155
+ @overload
156
+ def gödel_logic(name_1: str, name_2: str, /, *names: str, clsname: str|None=...) -> type[GödelLogic]: '''Returns a Gödel logic class with name clsname and truth values of names name_1 (0), name_2 (1/(k-1)), ..., name_(k-1) ((k-2)/(k-1)), name_k (1).'''
157
+ @overload
158
+ def gödel_logic(*, k: int, prefix: str='x_', clsname: str|None=...) -> type[GödelLogic]: ...
159
+ @overload
160
+ def łukasiewicz_logic(name_1: str, name_2: str, /, *names: str, clsname: str|None=...) -> type[ŁukasiewiczLogic]: '''Returns a Łukasiewicz logic class with name clsname and truth values of names name_1 (0), name_2 (1/(k-1)), ..., name_(k-1) ((k-2)/(k-1)), name_k (1).'''
161
+ @overload
162
+ def łukasiewicz_logic(*, k: int, prefix: str='x_', clsname: str|None=...) -> type[ŁukasiewiczLogic]: ...
163
+ @overload
164
+ def post_logic(name_1: str, name_2: str, /, *names: str, clsname: str|None=...) -> type[PostLogic]: '''Returns a Postlogic class with name `clsname` and truth values of names `name_1` (0), `name_2` (1/(k-1)), ..., `name_(k-1)` ((k-2)/(k-1)), `name_k` (1).'''
165
+ @overload
166
+ def post_logic(*, k: int, prefix: str='x_', clsname: str|None=...) -> type[PostLogic]: ...
167
+ @overload
168
+ def t_norm_logic(*, name: str, rational: Literal[False]=False, **additional: Any) -> Callable[[Callable[[Decimal, Decimal], Decimal]], type[TNormLogic]]: '''A decorator factory whose products return t-norm logic classes given the strong conjunction operator, given whether the members should be constrained to rational numbers.'''
169
+ @overload
170
+ def t_norm_logic(*, name: str, rational: Literal[True], **additional: Any) -> Callable[[Callable[[Fraction, Fraction], Fraction]], type[RationalTNormLogic]]: ...
171
+ @overload
172
+ def t_norm_logic(*, rational: Literal[False]=False, **additional: Any) -> Callable[[Callable[[Decimal, Decimal], Decimal]], type[TNormLogic]]: ...
173
+ @overload
174
+ def t_norm_logic(*, rational: Literal[True], **additional: Any) -> Callable[[Callable[[Fraction, Fraction], Fraction]], type[RationalTNormLogic]]: ...
175
+ @overload
176
+ def logic_from_implication(*, name: str, rational: Literal[False]=False, **additional: Any) -> Callable[[Callable[[Decimal, Decimal], Decimal]], type[DecimalLogicBase]]: '''A decorator factory whose products return logic classes given the implication operator only, given whether the members should be constrained to rational numbers.'''
177
+ @overload
178
+ def logic_from_implication(*, name: str, rational: Literal[True], **additional: Any) -> Callable[[Callable[[Fraction, Fraction], Fraction]], type[RationalLogicBase]]: ...
179
+ @overload
180
+ def logic_from_implication(*, rational: Literal[False]=False, **additional: Any) -> Callable[[Callable[[Decimal, Decimal], Decimal]], type[DecimalLogicBase]]: ...
181
+ @overload
182
+ def logic_from_implication(*, rational: Literal[True], **additional: Any) -> Callable[[Callable[[Fraction, Fraction], Fraction]], type[RationalLogicBase]]: ...
183
+ def decimal_t_norm_logic(strong_conjunctionf: Callable[[Decimal, Decimal], Decimal]) -> type[TNormLogic]: '''Version of t_norm_logic specific to decimals.'''
184
+ def rational_t_norm_logic(strong_conjunctionf: Callable[[Fraction, Fraction], Fraction]) -> type[RationalTNormLogic]: '''Version of t_norm_logic specific to fractions.'''
185
+ def decimal_logic_from_implication(impliesf: Callable[[Decimal, Decimal], Decimal]) -> type[DecimalLogicBase]: '''Version of logic_from_implication specific to decimals.'''
186
+ def rational_logic_from_implication(impliesf: Callable[[Fraction, Fraction], Fraction]) -> type[RationalLogicBase]: '''Version of logic_from_implication specific to fractions.'''
mvlogics/base.py ADDED
@@ -0,0 +1,108 @@
1
+ import sys
2
+ from weakref import WeakKeyDictionary
3
+ from fractions import Fraction
4
+ from decimal import Decimal, getcontext
5
+ def _weak_cache(name=None):
6
+ def dec(f):
7
+ def g(self, _=WeakKeyDictionary()):
8
+ if (r := _.get(self, s := _SlowEnumLogicMeta.MemberContainer._NOT_GENERATED)) is s: _[self] = r = f(self)
9
+ return r
10
+ g.__name__ = name or f.__name__; return g
11
+ return dec
12
+ _normalized_cache, _repr_cache = map(_weak_cache, ('normalized', '__repr__'))
13
+ REQUIRED_ATTRS, FORBIDDEN = map(frozenset, (('__and__', '__or__', '__invert__'), ('__new__', 'value')))
14
+ (_singleton_new := lambda cls: _singleton_new.cache.get(cls, False) or object.__new__(cls)).cache, getcontext().prec = WeakKeyDictionary(), 100
15
+ _all, ALL_METHODS, ALL_LOGICS, FAKE_PROTOCOLS = ('ALL_METHODS', 'FORBIDDEN', 'ALL_LOGICS', 'ALL_LOGICS_TUPLE', 'FAKE_PROTOCOLS', 'FAKE_PROTOCOLS_TUPLE', 'REQUIRED_ATTRS', 'RECOMMENDED_METHODS', 'MIXIN_METHODS', 'EXTENSION_METHODS', 'protocols', 'is_logic', 'is_logic_member', 'is_builtin_logic', 'is_builtin_logic_member', 'convert', 'gödel_logic', 'łukasiewicz_logic', 'post_logic', 't_norm_logic', 'logic_from_implication', 'decimal_t_norm_logic', 'rational_t_norm_logic', 'decimal_logic_from_implication', 'rational_logic_from_implication', *(ALL_LOGICS_TUPLE := ('Unit', 'Boolean', 'K3', 'LP', 'BI3', 'RM3', 'G3', 'SmT', 'L3', 'P3', 'B4', 'Π', 'Π_aleph_0', 'G_inf', 'G_aleph_0', 'L_inf', 'L_aleph_0', 'NP', 'NP_aleph_0'))), REQUIRED_ATTRS|(RECOMMENDED_METHODS := frozenset(('implies', '__bool__', 'gullibility', 'consensus')))|(MIXIN_METHODS := frozenset(('iff', 'implies', '__pos__', '__neg__', '__xor__', 'value', 'nand', 'nor', 'xnor', 'abjunction', 'converse_implies', 'converse_abjunction')))|(EXTENSION_METHODS := frozenset(('strong_disjunction', 'strong_conjunction', 'diamond', 'box', 'doubtful'))), frozenset(ALL_LOGICS_TUPLE), frozenset(FAKE_PROTOCOLS_TUPLE := ('MemberlessLogicBase', 'AbstractLogicBase', 'LogicBase', 'InfiniteLogicBase', 'DecimalLogicBase', 'RationalLogicBase', 'StrictLogicBase', 'GödelLogic', 'ŁukasiewiczLogic', 'PostLogic', 'TNormLogic', 'RationalTNormLogic'))
16
+ class _SubmoduleMeta(type):
17
+ def __new__(mcls, name, bases, namespace, /, *, _supermodule_default_name_='<unknown>'):
18
+ if bases: raise TypeError('Submodule cannot inherit from other classes')
19
+ try: name = f'{sys._getframemodulename(1) or _supermodule_default_name_}.{name}'
20
+ except AttributeError: name = f'{_supermodule_default_name_}.{name}'
21
+ sys.modules[name] = r = type(sys)(name, namespace.pop('__doc__', None)); return r
22
+ class _AllLogicMeta(type):
23
+ __defaults, __repr__ = None, _repr_cache(lambda cls: f'logics.{cls.__name__}')
24
+ def __new__(mcls, name, bases, namespace, /, **k):
25
+ if bases: raise ValueError('logics cannot inherit from anything')
26
+ if not REQUIRED_ATTRS.issubset(namespace): raise TypeError('missing methods')
27
+ return super().__new__(mcls, name, bases, mcls.__default_factory__()|namespace, **k)
28
+ @classmethod
29
+ def __default_factory__(mcls):
30
+ if (r := mcls.__defaults) is None: mcls.__defaults = r = {'__xor__': lambda self, other, /: (self&~other)|(other&~self), 'implies': lambda self, other, /: ~self|other, 'iff': (g := lambda self, other: self.implies(other)&other.implies(self)), 'nand': lambda self, other, /: ~(self&other), 'nor': lambda self, other, /: ~(self|other), 'xnor': g, 'abjunction': lambda self, other, /: ~self.implies(other), 'converse_implies': lambda self, other, /: self|~other, 'converse_abjunction': lambda self, other, /: other&~self, '__bool__': lambda self: self.value == 1, '__pos__': lambda self: self, 'normalized': _normalized_cache(lambda self: Fraction(self.value)), 'from_normalized': classmethod(lambda cls, val: cls(val)), 'from_logic_member': classmethod(lambda cls, member: cls.from_normalized(member.normalized())), 'convert_to': lambda self, cls: cls.from_normalized(self.normalized())}
31
+ return r
32
+ class _InfLogicMetaBase(_AllLogicMeta):
33
+ @property
34
+ def T(cls): return cls.from_normalized(Fraction(1))
35
+ @property
36
+ def F(cls): return cls.from_normalized(Fraction())
37
+ verum, falsum = T, F
38
+ @classmethod
39
+ def _ret_new_and_value(mcls): raise NotImplementedError('do not use this metaclass; inherit from it and implement this method or use either _DecimalLogicMeta or _RationalLogicMeta')
40
+ @classmethod
41
+ def __default_factory__(mcls): return _AllLogicMeta.__default_factory__()|{'members': {}, '__repr__': _repr_cache(lambda self: f'{type(self).__name__}({self.value})')}|mcls._ret_new_and_value()
42
+ class _DecimalLogicMeta(_InfLogicMetaBase):
43
+ @classmethod
44
+ def _ret_new_and_value(mcls):
45
+ _ = WeakKeyDictionary()
46
+ def __new__(cls, val='0', /):
47
+ if (v := (c := cls.members).get(d := Decimal(val), None)) is None: c[d] = v = object.__new__(cls); _[v] = d
48
+ return v
49
+ return {'__new__': __new__, 'value': property(_.__getitem__)}
50
+ class _RationalLogicMeta(_InfLogicMetaBase):
51
+ @classmethod
52
+ def _ret_new_and_value(mcls):
53
+ _ = WeakKeyDictionary()
54
+ def __new__(cls, /, *a):
55
+ if (v := (c := cls.members).get(f := Fraction(*a), None)) is None: c[f] = v = object.__new__(cls); _[v] = f
56
+ return v
57
+ return {'__new__': __new__, 'value': property(_.__getitem__)}
58
+ class _SlowEnumLogicMeta(_AllLogicMeta):
59
+ class MemberContainer:
60
+ __cache, __cache2, _NOT_GENERATED = WeakKeyDictionary(), WeakKeyDictionary(), type('NotGenerated', (), {'__new__': _singleton_new, '__repr__': lambda _, /: '<not generated>'})()
61
+ def __init__(self, values): self.__values, self.value_from_name, self.name_from_value, self.names = dict.fromkeys(values.values(), self._NOT_GENERATED), values.__getitem__, {v: k for k, v in values.items()}.__getitem__, tuple(values.keys())
62
+ def generate(self, value):
63
+ if (x := self.__values[value]) is self._NOT_GENERATED: self.__values[value] = x = object.__new__(self.typ); self.__cache[x], self.__cache2[x] = value, self.name_from_value(value)
64
+ return x
65
+ def from_name(self, name):
66
+ if (x := self.__values[v := self.value_from_name(name)]) is self._NOT_GENERATED: self.__values[v] = x = object.__new__(self.typ); self.__cache[x], self.__cache2[x] = v, name
67
+ return x
68
+ def member_values(self): yield from self.__values.keys()
69
+ def __set_name__(self, owner, name, /): assert name == 'members'; self.typ = owner
70
+ @_repr_cache
71
+ def __repr__(self): return f'({', '.join(map(lambda i: str(self.generate(i)), self.__values))})'
72
+ def __iter__(self): yield from map(self.generate, self.__values)
73
+ def __getattr__(cls, name, /):
74
+ if name in cls.members.names: super().__setattr__(name, r := cls(name)); return r
75
+ raise AttributeError(f'class {cls.__name__} has no attribute {name!r}')
76
+ def __setattr__(cls, name, value, /):
77
+ if name in cls.members.names: raise TypeError(f'attribute {name!r} is read-only')
78
+ super().__setattr__(name, value)
79
+ def __new__(mcls, name, bases, namespace, /, **k):
80
+ if FORBIDDEN.intersection(namespace): raise TypeError('attempted to override forbidden attributes')
81
+ c = mcls.MemberContainer(namespace['members']); return super().__new__(mcls, name, bases, {'__repr__': _repr_cache(lambda self: f'{type(self).__name__}.{self.name}'), 'value': property(c._MemberContainer__cache.__getitem__), 'name': property(c._MemberContainer__cache2.__getitem__), '__neg__': namespace['__invert__']}|namespace|{'members': c, '__new__': lambda cls, v, /: c.from_name(v) if isinstance(v, str) else c.generate(v)}, **k)
82
+ class _FastEnumLogicMeta(_SlowEnumLogicMeta):
83
+ class MemberContainer(_SlowEnumLogicMeta.MemberContainer):
84
+ @_repr_cache
85
+ def __repr__(self): return f'({(n := self.typ.__name__)}.{f', {n}.'.join(self.names)})'
86
+ class _FakeProtocolMeta(type):
87
+ _cache, _meta_map, _requirement_map, _prefix_map, _mcont_allowed = [None]*12, (_AllLogicMeta, _AllLogicMeta, _SlowEnumLogicMeta, _InfLogicMetaBase, _DecimalLogicMeta, _RationalLogicMeta, _SlowEnumLogicMeta, _FastEnumLogicMeta, _FastEnumLogicMeta, _FastEnumLogicMeta, _DecimalLogicMeta, _RationalLogicMeta), tuple(map(frozenset, ((), ('values',), ('members', 'values'), (), (), (), ('gullibility', 'consensus'), (), ('strong_disjunction', 'strong_conjunction', 'diamond', 'box', 'doubtful'), (), ('strong_conjunction',), ('strong_conjunction',)))), ('', '', '', '', '', '', '', 'G', 'L', 'P', '', ''), frozenset((2, 6, 7, 8, 9))
88
+ def __new__(mcls, name, bases=(), namespace={}, /, **k):
89
+ try: r = (c := mcls._cache)[i := FAKE_PROTOCOLS_TUPLE.index(name)]
90
+ except ValueError: raise TypeError(f'cannot create protocol class with name {name!r}') from None
91
+ def __init_subclass__(_, /): raise TypeError(f'cannot inherit from protocol class {name!r}')
92
+ def __new__(*_): raise TypeError(f'cannot create instance of protocol {name!r}')
93
+ namespace['__init_subclass__'], namespace['__new__'] = __init_subclass__, __new__
94
+ if r is None: c[i] = r = super().__new__(mcls, name, bases, namespace, **k); r._idx, r._meth_cache = i, {}
95
+ return r
96
+ def __getattr__(cls, name, /):
97
+ if name == 'MemberContainer' and cls._idx in cls._mcont_allowed:
98
+ class MemberContainer(_FastEnumLogicMeta.MemberContainer):
99
+ def __new__(cls, values): raise TypeError(f'cannot instantiate MemberContainer with values {values!r}')
100
+ def __init_subclass__(cls): raise TypeError('cannot subclass MemberContainer')
101
+ cls.MemberContainer = MemberContainer; return MemberContainer
102
+ if name in ALL_METHODS:
103
+ if (r := (c := cls._meth_cache).get(name)) is None: exec('@property\n'*(name == 'value')+f'def {name}(*_): raise NotImplementedError("method {name!r} of protocol {cls.__name__!r} is abstract")', locals=c); r = c[name]
104
+ return r
105
+ raise AttributeError(f'class {cls.__name__!r} has no attribute {name!r}')
106
+ def __init_subclass__(mcls, /, **_): raise TypeError('cannot subclass _FakeProtocolMeta')
107
+ def __instancecheck__(cls, instance): return cls.__subclasscheck__(type(instance))
108
+ def __subclasscheck__(cls, sub): return isinstance(sub, cls._meta_map[i := cls._idx]) and sub.__name__.startswith(cls._prefix_map[i]) and (cls._requirement_map[i]|MIXIN_METHODS).issubset(sub.__dict__)
mvlogics/protocols.pyi ADDED
@@ -0,0 +1,89 @@
1
+ from typing import final, overload, Final, Generic, TypeVar, Self, Any, Generator, ClassVar, Sequence, TYPE_CHECKING
2
+ from fractions import Fraction
3
+ from decimal import Decimal
4
+ if TYPE_CHECKING:
5
+ _T = TypeVar('_T')
6
+ _L = TypeVar('_L', bound=MemberlessLogicBase)
7
+ _R = TypeVar('_R', bound=LogicBase)
8
+ class MemberlessLogicBase(Generic[_T]):
9
+ @classmethod
10
+ def from_normalized(cls, val: Fraction) -> Self: ...
11
+ @classmethod
12
+ def from_logic_member(cls, member: MemberlessLogicBase[Any]) -> Self: ...
13
+ def __and__(self, other: Self, /) -> Self: ...
14
+ def __or__(self, other: Self, /) -> Self: ...
15
+ def __invert__(self) -> Self: ...
16
+ def __pos__(self) -> Self: ...
17
+ def __neg__(self) -> Self: ...
18
+ def __xor__(self) -> Self: ...
19
+ def implies(self, other: Self, /) -> Self: ...
20
+ def iff(self, other: Self, /) -> Self: ...
21
+ def nor(self, other: Self, /) -> Self: ...
22
+ def nand(self, other: Self, /) -> Self: ...
23
+ def xnor(self, other: Self, /) -> Self: ...
24
+ def abjunction(self, other: Self, /) -> Self: ...
25
+ def converse_implies(self, other: Self, /) -> Self: ...
26
+ def converse_abjunction(self, other: Self, /) -> Self: ...
27
+ def normalized(self) -> Fraction: ...
28
+ def convert_to(self, cls: type[_L]) -> _L: ...
29
+ class AbstractLogicBase(MemberlessLogicBase[_T]):
30
+ @property
31
+ def value(self) -> _T: ...
32
+ def __new__(cls, v: _T, /) -> Self: ...
33
+ class LogicBase(AbstractLogicBase[_T]):
34
+ @final
35
+ class MemberContainer(Generic[_R]):
36
+ @property
37
+ def names(self) -> tuple[str, ...]: '''Is not a property at runtime.'''
38
+ def __init__(self, values: dict[str, _T]): ...
39
+ def generate(self, value: _T) -> _R: ...
40
+ def from_name(self, name: str) -> _R: ...
41
+ def value_from_name(self, name: str, /) -> _T: ...
42
+ def name_from_value(self, value: _T, /) -> str: ...
43
+ def member_values(self) -> Generator[_T, None, None]: ...
44
+ def __set_name__(self, owner: type, name: str, /) -> None: ...
45
+ def __iter__(self) -> Generator[_R, None, None]: ...
46
+ members: Final[MemberContainer[Self]]
47
+ class InfiniteLogicBase(AbstractLogicBase[_T]):
48
+ T: ClassVar[Self]
49
+ F: ClassVar[Self]
50
+ verum: ClassVar[Self]
51
+ falsum: ClassVar[Self]
52
+ members: Final[dict[_T, Self]]
53
+ class DecimalLogicBase(InfiniteLogicBase[Decimal]):
54
+ @overload
55
+ def __new__(cls, v: Decimal, /) -> Self: ...
56
+ @overload
57
+ def __new__(cls, v: float, /) -> Self: ...
58
+ @overload
59
+ def __new__(cls, v: str, /) -> Self: ...
60
+ @overload
61
+ def __new__(cls, v: tuple[int, Sequence[int], int], /) -> Self: ...
62
+ class RationalLogicBase(InfiniteLogicBase[Fraction]):
63
+ @overload
64
+ def __new__(cls, num: int, /) -> Self: ...
65
+ @overload
66
+ def __new__(cls, arg: str, /) -> Self: ...
67
+ @overload
68
+ def __new__(cls, num: float, /) -> Self: ...
69
+ @overload
70
+ def __new__(cls, num: Fraction, /) -> Self: ...
71
+ @overload
72
+ def __new__(cls, num: Decimal, /) -> Self: ...
73
+ @overload
74
+ def __new__(cls, num: int, dem: int, /) -> Self: ...
75
+ class StrictLogicBase(LogicBase[_T]):
76
+ def gullibility(self, other: Self, /) -> Self: ...
77
+ def consensus(self, other: Self, /) -> Self: ...
78
+ class GödelLogic(LogicBase[Fraction]): ...
79
+ class ŁukasiewiczLogic(LogicBase[Fraction]):
80
+ def strong_disjunction(self, other: Self, /) -> Self: ...
81
+ def strong_conjunction(self, other: Self, /) -> Self: ...
82
+ def diamond(self) -> Self: ...
83
+ def box(self) -> Self: ...
84
+ def doubtful(self) -> Self: ...
85
+ class PostLogic(LogicBase[Fraction]): ...
86
+ class TNormLogic(DecimalLogicBase):
87
+ def strong_conjunction(self, other: Self, /) -> Self: ...
88
+ class RationalTNormLogic(RationalLogicBase):
89
+ def strong_conjunction(self, other: Self, /) -> Self: ...
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: mvlogics
3
+ Version: 0.9.0
4
+ Summary: Multi-valued logics.
5
+ Author-email: Jonathan Dung <jonathandung@yahoo.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/jonathandung/mvlogics
8
+ Project-URL: Repository, https://github.com/jonathandung/mvlogics.git
9
+ Keywords: mathematical,object-oriented
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Provides-Extra: dev
16
+ Requires-Dist: black; extra == "dev"
17
+ Requires-Dist: flake8; extra == "dev"
18
+ Dynamic: license-file
19
+
20
+ # mvlogics
@@ -0,0 +1,9 @@
1
+ mvlogics/__init__.py,sha256=X112Qek4azgNyP6gAuNjgRckWOT8CUFd0cmC7a6htyc,11157
2
+ mvlogics/__init__.pyi,sha256=0y95DYMI4TgYRRcL1gz4nikevsq73plv95Zk4iBItIk,11802
3
+ mvlogics/base.py,sha256=YldQA3vClpPMhGcuZQDSJToce7hTyuhMl3qWPFsGnvE,10553
4
+ mvlogics/protocols.pyi,sha256=vb1E7-LvYTY_9L2FZM46WBdmtIhWpgUkMc1Z3fnNXI0,3850
5
+ mvlogics-0.9.0.dist-info/licenses/LICENSE,sha256=DIVhmXv1QFIoykQd8zmb-hEQ4vDvDBi3oteSatFMF-I,1089
6
+ mvlogics-0.9.0.dist-info/METADATA,sha256=2uXUL3bBbKANPJe9r5b3ARE4NoR_ixm1YfMdFnCSKHw,661
7
+ mvlogics-0.9.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
8
+ mvlogics-0.9.0.dist-info/top_level.txt,sha256=MEu4A_SLEh1sNkT89LhUa6Of2MzOCMowEKfFJjpeR_E,9
9
+ mvlogics-0.9.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jonathan Dung
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ mvlogics