mcmas.logic

  1"""
  2mcmas.logic.
  3"""
  4
  5import sympy
  6from pydantic_core import core_schema
  7from sympy import Eq as _Eq
  8
  9# from sympy.logic.boolalg import BooleanFunction
 10from sympy.printing.str import StrPrinter
 11
 12# from sympy import And, Or, Not, Eq, Lt, Gt
 13# from sympy.abc import *
 14# from sympy.printing.str import StrPrinter
 15
 16
 17class Function(sympy.Function):
 18    """
 19    
 20    """
 21
 22
 23class And(Function):
 24    """
 25    
 26    """
 27
 28    def __str__(self):
 29        """
 30        
 31        """
 32        return " and ".join([str(x) for x in self._sorted_args])
 33
 34
 35class Or(Function):
 36    """
 37    
 38    """
 39
 40    def __str__(self):
 41        """
 42        
 43        """
 44        return " or ".join([str(x) for x in self._sorted_args])
 45
 46
 47class Grouping(Function):
 48    """
 49    
 50    """
 51
 52    def __str__(self):
 53        """
 54        
 55        """
 56        tmp = " ".join([str(x) for x in self._sorted_args])
 57        return f"({tmp})"
 58
 59
 60class If(Function):
 61    """
 62    
 63    """
 64
 65    def __str__(self):
 66        """
 67        
 68        """
 69        if len(self._sorted_args) == 1:
 70            return f"if {self._sorted_args[0]}"
 71        else:
 72            assert len(self._sorted_args) == 2, "multiclause if not supported yet"
 73            return f"{self._sorted_args[0]} if {self._sorted_args[1]}"
 74
 75
 76class Eq(_Eq):
 77    """
 78    
 79    """
 80
 81    def __str__(expr):
 82        """
 83        
 84        """
 85        return f"{expr.lhs}={expr.rhs}"
 86
 87
 88Equal = Eq
 89
 90from mcmas import util
 91
 92LOGGER = util.get_logger(__name__)
 93
 94
 95class Symbol(sympy.core.symbol.Symbol):
 96    """
 97    
 98    """
 99
100    def __getitem__(self, key):
101        """
102        
103        """
104        if isinstance(key, (str,)):
105            return [self.__class__(k) for k in key.split(" ")]
106        else:
107            raise NotImplementedError()
108
109    @classmethod
110    def __get_pydantic_core_schema__(
111        cls,
112        source_type,  # noqa
113        handler,  # noqa
114    ) -> core_schema.CoreSchema:
115        """
116        Return a Pydantic core schema for Symbol validation.
117
118        This schema accepts: 1. Existing Symbol instances (passthrough)
119        2. String names to create new Symbols 3. Dictionaries with
120        'name' and optional symbol properties
121        """
122
123        def validate_symbol(value) -> "Symbol":
124            """
125            Validate and convert input to Symbol.
126            """
127            if isinstance(value, sympy.core.symbol.Symbol):
128                # If it's already a Symbol, return as-is (or convert to our class)
129                if isinstance(value, cls):
130                    return value
131                else:
132                    # Convert base Symbol to our extended Symbol
133                    return cls(value.name, **value.assumptions0)
134            elif isinstance(value, str):
135                # Create Symbol from string name
136                return cls(value)
137            elif isinstance(value, dict):
138                # Create Symbol from dictionary
139                if "name" not in value:
140                    raise ValueError("Dictionary must contain 'name' field")
141                name = value["name"]
142                # Extract symbol assumptions (like real=True, positive=True, etc.)
143                assumptions = {k: v for k, v in value.items() if k != "name"}
144                return cls(name, **assumptions)
145            else:
146                raise ValueError(
147                    f"Cannot convert {type(value).__name__} to Symbol. "
148                    f"Expected Symbol, str, or dict with 'name' field."
149                )
150
151        # Create the core schema (simplified version without custom serialization)
152        return core_schema.no_info_plain_validator_function(validate_symbol)
153
154    def __matmul__(self, other):
155        """
156        
157        """
158        return Eq(self, other)
159
160    def __eq__(self, other):
161        """
162        
163        """
164        LOGGER.critical(f"[{self},{other}]")
165        # return f"{self}={other}"
166        if isinstance(other, str):
167            # Compare with symbol name
168            return self.name == other
169        elif isinstance(other, (int, float)):
170            return False  # Symbols typically don't equal numbers
171        return super().__eq__(other)
172
173    def __ne__(self, other):
174        """
175        
176        """
177        return not self.__eq__(other)
178
179    def __hash__(self):
180        """
181        
182        """
183        # Maintain the parent's hash behavior
184        return super().__hash__()
185
186    def __and__(self, other):
187        """
188        
189        """
190        return self.__class__(f"{self.name} and {other}")
191
192    def __or__(self, other):
193        """
194        
195        """
196        return self.__class__(f"{self.name} or {other}")
197
198    def __rshift__(self, other):
199        """
200        
201        """
202        # return If(self, other)
203        return self.__class__(f"{self.name} if {other}")
204
205    def __imul__(self, other):
206        """
207        
208        """
209        return self.__class__(f"{self.name}={other}")
210
211    def __add__(self, other):
212        """
213        
214        """
215        return self.__class__(f"{self.name}+{other}")
216
217    def __lt__(self, other):
218        """
219        
220        """
221        return self.__class__(f"{self.name}<{other}")
222
223    def __lte__(self, other):
224        """
225        
226        """
227        return self.__class__(f"{self.name}<={other}")
228
229    def __gt__(self, other):
230        """
231        
232        """
233        return self.__class__(f"{self.name}>{other}")
234
235    def __gte__(self, other):
236        return self.__class__(f"{self.name}>={other}")
237
238    def __call__(self, other):
239        return self.__class__(f"{self.name}({other})")
240
241    def __getattribute__(self, name):
242        """
243        
244        """
245        try:
246            attr = super().__getattribute__(name)
247            # If it exists and is callable, return it
248            if callable(attr):
249                return attr
250            # If it exists and is not callable, return it
251            return attr
252        except AttributeError:
253            # Only create new symbols for names that don't exist as attributes
254            # and don't start with underscore (to avoid internal methods)
255            if not name.startswith("_"):
256                current_name = super().__getattribute__("name")
257                if current_name:
258                    new_name = f"{current_name}.{name}"
259                else:
260                    new_name = f"{name}"
261                return self.__class__(new_name)
262            # If it starts with underscore, re-raise the AttributeError
263            raise
264
265
266class CustomStrPrinter(StrPrinter):
267    """
268    
269    """
270
271    def _print_Equality(self, expr):
272        """
273        
274        """
275        return f"{expr.lhs}={expr.rhs}"
276
277
278printer = CustomStrPrinter()
279sympy2ispl = printer.doprint
280
281symbols = Symbol("")
282types = Symbol("")
283types.bool = types.boolean = types.boolean
284true = symbols.true
285false = symbols.false
286Environment = symbols.Environment
287
288# Temporal Operators / CTL
289# EF φ	There exists a path where eventually φ holds
290# AF φ	On all paths, eventually φ holds (φ is inevitable)
291# EG φ	There exists a path where φ holds forever
292# AG φ	On all paths, φ holds globally (φ is always true)
293AF = symbols.AF
294AG = symbols.AG
295EF = symbols.EF
296EG = symbols.EG
297
298# Epistemic	Operators
299# K(i, φ)	Agent i knows φ
300# CK(G, φ)  Group Knowledge; φ is common knowledge in group G
301K = symbols.K
302CK = symbols.CK
303
304# ATL <<G >>F φ	Group G can enforce φ eventually
305from .complexity import analyzer  # noqa
class Function(sympy.core.function.Function):
18class Function(sympy.Function):
19    """
20    
21    """
default_assumptions: ClassVar[sympy.core.assumptions.StdFactKB] = {}
class And(Function):
24class And(Function):
25    """
26    
27    """
28
29    def __str__(self):
30        """
31        
32        """
33        return " and ".join([str(x) for x in self._sorted_args])
default_assumptions: ClassVar[sympy.core.assumptions.StdFactKB] = {}
class Or(Function):
36class Or(Function):
37    """
38    
39    """
40
41    def __str__(self):
42        """
43        
44        """
45        return " or ".join([str(x) for x in self._sorted_args])
default_assumptions: ClassVar[sympy.core.assumptions.StdFactKB] = {}
class Grouping(Function):
48class Grouping(Function):
49    """
50    
51    """
52
53    def __str__(self):
54        """
55        
56        """
57        tmp = " ".join([str(x) for x in self._sorted_args])
58        return f"({tmp})"
default_assumptions: ClassVar[sympy.core.assumptions.StdFactKB] = {}
class If(Function):
61class If(Function):
62    """
63    
64    """
65
66    def __str__(self):
67        """
68        
69        """
70        if len(self._sorted_args) == 1:
71            return f"if {self._sorted_args[0]}"
72        else:
73            assert len(self._sorted_args) == 2, "multiclause if not supported yet"
74            return f"{self._sorted_args[0]} if {self._sorted_args[1]}"
default_assumptions: ClassVar[sympy.core.assumptions.StdFactKB] = {}
class Eq(sympy.core.relational.Equality):
77class Eq(_Eq):
78    """
79    
80    """
81
82    def __str__(expr):
83        """
84        
85        """
86        return f"{expr.lhs}={expr.rhs}"
default_assumptions: ClassVar[sympy.core.assumptions.StdFactKB] = {}
Equal = <class 'Eq'>
LOGGER = <Logger mcmas.logic (INFO)>
class Symbol(sympy.core.symbol.Symbol):
 96class Symbol(sympy.core.symbol.Symbol):
 97    """
 98    
 99    """
100
101    def __getitem__(self, key):
102        """
103        
104        """
105        if isinstance(key, (str,)):
106            return [self.__class__(k) for k in key.split(" ")]
107        else:
108            raise NotImplementedError()
109
110    @classmethod
111    def __get_pydantic_core_schema__(
112        cls,
113        source_type,  # noqa
114        handler,  # noqa
115    ) -> core_schema.CoreSchema:
116        """
117        Return a Pydantic core schema for Symbol validation.
118
119        This schema accepts: 1. Existing Symbol instances (passthrough)
120        2. String names to create new Symbols 3. Dictionaries with
121        'name' and optional symbol properties
122        """
123
124        def validate_symbol(value) -> "Symbol":
125            """
126            Validate and convert input to Symbol.
127            """
128            if isinstance(value, sympy.core.symbol.Symbol):
129                # If it's already a Symbol, return as-is (or convert to our class)
130                if isinstance(value, cls):
131                    return value
132                else:
133                    # Convert base Symbol to our extended Symbol
134                    return cls(value.name, **value.assumptions0)
135            elif isinstance(value, str):
136                # Create Symbol from string name
137                return cls(value)
138            elif isinstance(value, dict):
139                # Create Symbol from dictionary
140                if "name" not in value:
141                    raise ValueError("Dictionary must contain 'name' field")
142                name = value["name"]
143                # Extract symbol assumptions (like real=True, positive=True, etc.)
144                assumptions = {k: v for k, v in value.items() if k != "name"}
145                return cls(name, **assumptions)
146            else:
147                raise ValueError(
148                    f"Cannot convert {type(value).__name__} to Symbol. "
149                    f"Expected Symbol, str, or dict with 'name' field."
150                )
151
152        # Create the core schema (simplified version without custom serialization)
153        return core_schema.no_info_plain_validator_function(validate_symbol)
154
155    def __matmul__(self, other):
156        """
157        
158        """
159        return Eq(self, other)
160
161    def __eq__(self, other):
162        """
163        
164        """
165        LOGGER.critical(f"[{self},{other}]")
166        # return f"{self}={other}"
167        if isinstance(other, str):
168            # Compare with symbol name
169            return self.name == other
170        elif isinstance(other, (int, float)):
171            return False  # Symbols typically don't equal numbers
172        return super().__eq__(other)
173
174    def __ne__(self, other):
175        """
176        
177        """
178        return not self.__eq__(other)
179
180    def __hash__(self):
181        """
182        
183        """
184        # Maintain the parent's hash behavior
185        return super().__hash__()
186
187    def __and__(self, other):
188        """
189        
190        """
191        return self.__class__(f"{self.name} and {other}")
192
193    def __or__(self, other):
194        """
195        
196        """
197        return self.__class__(f"{self.name} or {other}")
198
199    def __rshift__(self, other):
200        """
201        
202        """
203        # return If(self, other)
204        return self.__class__(f"{self.name} if {other}")
205
206    def __imul__(self, other):
207        """
208        
209        """
210        return self.__class__(f"{self.name}={other}")
211
212    def __add__(self, other):
213        """
214        
215        """
216        return self.__class__(f"{self.name}+{other}")
217
218    def __lt__(self, other):
219        """
220        
221        """
222        return self.__class__(f"{self.name}<{other}")
223
224    def __lte__(self, other):
225        """
226        
227        """
228        return self.__class__(f"{self.name}<={other}")
229
230    def __gt__(self, other):
231        """
232        
233        """
234        return self.__class__(f"{self.name}>{other}")
235
236    def __gte__(self, other):
237        return self.__class__(f"{self.name}>={other}")
238
239    def __call__(self, other):
240        return self.__class__(f"{self.name}({other})")
241
242    def __getattribute__(self, name):
243        """
244        
245        """
246        try:
247            attr = super().__getattribute__(name)
248            # If it exists and is callable, return it
249            if callable(attr):
250                return attr
251            # If it exists and is not callable, return it
252            return attr
253        except AttributeError:
254            # Only create new symbols for names that don't exist as attributes
255            # and don't start with underscore (to avoid internal methods)
256            if not name.startswith("_"):
257                current_name = super().__getattribute__("name")
258                if current_name:
259                    new_name = f"{current_name}.{name}"
260                else:
261                    new_name = f"{name}"
262                return self.__class__(new_name)
263            # If it starts with underscore, re-raise the AttributeError
264            raise
Symbol(name, **assumptions)
326    def __new__(cls, name, **assumptions):
327        """Symbols are identified by name and assumptions::
328
329        >>> from sympy import Symbol
330        >>> Symbol("x") == Symbol("x")
331        True
332        >>> Symbol("x", real=True) == Symbol("x", real=False)
333        False
334
335        """
336        cls._sanitize(assumptions, cls)
337        return Symbol.__xnew_cached_(cls, name, **assumptions)

Symbols are identified by name and assumptions::

>>> from sympy import Symbol
>>> Symbol("x") == Symbol("x")
True
>>> Symbol("x", real=True) == Symbol("x", real=False)
False
default_assumptions: ClassVar[sympy.core.assumptions.StdFactKB] = {}
class CustomStrPrinter(sympy.printing.str.StrPrinter):
267class CustomStrPrinter(StrPrinter):
268    """
269    
270    """
271
272    def _print_Equality(self, expr):
273        """
274        
275        """
276        return f"{expr.lhs}={expr.rhs}"
printer = <CustomStrPrinter object>
def sympy2ispl(expr):
292    def doprint(self, expr):
293        """Returns printer's representation for expr (as a string)"""
294        return self._str(self._print(expr))

Returns printer's representation for expr (as a string)

symbols
types
true = true
false = false
Environment = Environment
AF = AF
AG = AG
EF = EF
EG = EG
K = K
CK = CK