mcmas.fmtk

mcmas.fmtk:

A growing formal methods toolkit. This is focused on abstract base classes (pydantic models) that are intended to be useful for describing things like "specifications" and "simulations" in general. Classes like ispl.Specification and ispl.Simulation extend these, but the hopefully the base classes are more reusable.

This might be reused to wrap other kinds of formalisms like:

  • Other model-checkers (.. Alloy lang?)
  • SAT & SMT Provers (.. Z3?)
  • Constraints, Discrete-events, Systems Simulations (.. SimPy / CPMpy?)
  • Games & Protocols, etc (.. py-mfglib?)
  1"""
  2mcmas.fmtk:
  3
  4A growing formal methods toolkit. This is focused on abstract base classes
  5(pydantic models) that are intended to be useful for describing things like
  6"specifications" and "simulations" in general. Classes like ispl.Specification
  7and ispl.Simulation extend these, but the hopefully the base classes are more
  8reusable.
  9
 10This might be reused to wrap other kinds of formalisms like:
 11
 12* Other model-checkers (.. Alloy lang?)
 13* SAT & SMT Provers (.. Z3?)
 14* Constraints, Discrete-events, Systems Simulations (.. SimPy / CPMpy?)
 15* Games & Protocols, etc (.. py-mfglib?)
 16"""
 17
 18import typing
 19
 20import pydantic
 21from pydantic import Field
 22
 23from mcmas import util
 24from mcmas.typing import PathType
 25
 26LOGGER = util.get_logger(__name__)
 27
 28
 29class SpecificationMetadata(pydantic.BaseModel):
 30    """
 31    Metadata for this Specification.
 32    """
 33
 34    file: PathType = Field(
 35        description="File associated with this specification", default=None
 36    )
 37    engine: str = Field(
 38        description="The engine that will be used for this specification",
 39        default="mcmas",
 40    )
 41    parser: str = Field(
 42        description="The parser that will be used for this specification",
 43        default="mcmas.parser",
 44    )
 45
 46
 47SpecificationMetadataType = typing.Union[SpecificationMetadata, None]
 48
 49
 50class SpecificationFragment(pydantic.BaseModel):
 51    """
 52    A Fragment of a Specification.
 53
 54    NB: Fragments are incomplete by definition.
 55    """
 56
 57    logger: typing.ClassVar = LOGGER
 58    Metadata: typing.ClassVar = SpecificationMetadata
 59    metadata: SpecificationMetadata = Field(
 60        description=(
 61            "Known metadata about this Specification.\n\n"
 62            "(Updated if/when the specification is analyzed or simulated)"
 63        ),
 64        default=SpecificationMetadata(),
 65        exclude=True,
 66    )
 67
 68    @classmethod
 69    def get_trivial(kls, **kwargs):
 70        """
 71        Subclassers must implement this.
 72
 73        Returns the trivial fragment for this type.
 74        """
 75        raise NotImplementedError(f"{kls}")
 76
 77    @classmethod
 78    def load_from_source(kls, txt) -> typing.Self:
 79        """
 80        Subclassers must implement this.
 81
 82        Creates spec-fragment from raw source-code.
 83        """
 84        raise NotImplementedError(f"{kls}")
 85
 86    def model_dump_source(self) -> str:
 87        """
 88        Subclassers must implement this.
 89
 90        Dumps raw source-code for this fragment.
 91        """
 92        raise NotImplementedError(f"{self}")
 93
 94    @property
 95    def valid(self):
 96        """
 97        True if this agent is valid, i.e. ready to run and not a
 98        fragment.
 99        """
100        return not any(
101            [self.__class__.__name__ == "SpecificationFragment", self.advice]
102        )
103
104    concrete = valid
105
106    @property
107    def advice(self) -> list:
108        """
109        Returns a list of any problems with this object.
110
111        See also `valid` property
112        """
113        required = getattr(self.__class__, "REQUIRED", [])
114        if not required:
115            LOGGER.warning(
116                f"no attrs listed as required for {self}; advice unavailable"
117            )
118        return [
119            f"MISSING-REQUIRED {f} from {self.__class__.__name__}"
120            for f in required
121            if not bool(getattr(self, f))
122        ] + self.local_advice
123
124    @property
125    def local_advice(self) -> list:
126        """
127        Subclassers must implement this.
128        """
129        return []
130
131    def __add__(self, other):
132        """
133        Subclassers must implement this.
134        """
135        raise TypeError(f"Cannot add {self} and {other}")
136
137    def __and__(self, other):
138        """
139        Subclassers must implement this.
140        """
141        raise TypeError(f"Cannot `and` {self} and {other}")
142
143    def __or__(self, other):
144        """
145        Subclassers must implement this.
146        """
147        raise TypeError(f"Cannot `or` {self} and {other}")
148
149
150Fragment = SpecificationFragment
151
152
153class Specification(SpecificationFragment):
154    """
155    Pydantic models for a Specification.
156    """
157
158
159#####################
160from mcmas import typing
161
162
163class AnalysisMeta(pydantic.BaseModel):
164    """
165    Metadata for a Specification Analysis.
166    """
167
168    formula_index: int = Field(default=-1)
169    base_complexity: float = Field(default=0.0)
170    nesting_coefficient: int = Field(default=1)
171    raw_score: float = Field(default=0.0)
172
173
174class SymbolMetadata(pydantic.BaseModel):
175    """
176    Result of running ISPL Analysis.
177
178    This extracts details about symbols, namespaces, etc.
179    """
180
181    actions: typing.SymbolList2 = Field(
182        default=[],
183        description="Actions list",
184    )
185    agents: typing.SymbolList2 = Field(
186        default=[],
187        description="Agents list",
188    )
189    vars: typing.SymbolList2 = Field(
190        default=[],
191        description="Var list",
192    )
193
194
195#########################
196
197
198class SpecificationAnalysis(pydantic.BaseModel):
199    """
200    Base class for all Analysis results.
201    """
LOGGER = <Logger mcmas.fmtk (INFO)>
class SpecificationMetadata(pydantic.main.BaseModel):
30class SpecificationMetadata(pydantic.BaseModel):
31    """
32    Metadata for this Specification.
33    """
34
35    file: PathType = Field(
36        description="File associated with this specification", default=None
37    )
38    engine: str = Field(
39        description="The engine that will be used for this specification",
40        default="mcmas",
41    )
42    parser: str = Field(
43        description="The parser that will be used for this specification",
44        default="mcmas.parser",
45    )

Metadata for this Specification.

file: Annotated[Union[str, NoneType, pathlib.Path], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]
engine: str
parser: str
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

SpecificationMetadataType = typing.Optional[SpecificationMetadata]
class SpecificationFragment(pydantic.main.BaseModel):
 51class SpecificationFragment(pydantic.BaseModel):
 52    """
 53    A Fragment of a Specification.
 54
 55    NB: Fragments are incomplete by definition.
 56    """
 57
 58    logger: typing.ClassVar = LOGGER
 59    Metadata: typing.ClassVar = SpecificationMetadata
 60    metadata: SpecificationMetadata = Field(
 61        description=(
 62            "Known metadata about this Specification.\n\n"
 63            "(Updated if/when the specification is analyzed or simulated)"
 64        ),
 65        default=SpecificationMetadata(),
 66        exclude=True,
 67    )
 68
 69    @classmethod
 70    def get_trivial(kls, **kwargs):
 71        """
 72        Subclassers must implement this.
 73
 74        Returns the trivial fragment for this type.
 75        """
 76        raise NotImplementedError(f"{kls}")
 77
 78    @classmethod
 79    def load_from_source(kls, txt) -> typing.Self:
 80        """
 81        Subclassers must implement this.
 82
 83        Creates spec-fragment from raw source-code.
 84        """
 85        raise NotImplementedError(f"{kls}")
 86
 87    def model_dump_source(self) -> str:
 88        """
 89        Subclassers must implement this.
 90
 91        Dumps raw source-code for this fragment.
 92        """
 93        raise NotImplementedError(f"{self}")
 94
 95    @property
 96    def valid(self):
 97        """
 98        True if this agent is valid, i.e. ready to run and not a
 99        fragment.
100        """
101        return not any(
102            [self.__class__.__name__ == "SpecificationFragment", self.advice]
103        )
104
105    concrete = valid
106
107    @property
108    def advice(self) -> list:
109        """
110        Returns a list of any problems with this object.
111
112        See also `valid` property
113        """
114        required = getattr(self.__class__, "REQUIRED", [])
115        if not required:
116            LOGGER.warning(
117                f"no attrs listed as required for {self}; advice unavailable"
118            )
119        return [
120            f"MISSING-REQUIRED {f} from {self.__class__.__name__}"
121            for f in required
122            if not bool(getattr(self, f))
123        ] + self.local_advice
124
125    @property
126    def local_advice(self) -> list:
127        """
128        Subclassers must implement this.
129        """
130        return []
131
132    def __add__(self, other):
133        """
134        Subclassers must implement this.
135        """
136        raise TypeError(f"Cannot add {self} and {other}")
137
138    def __and__(self, other):
139        """
140        Subclassers must implement this.
141        """
142        raise TypeError(f"Cannot `and` {self} and {other}")
143
144    def __or__(self, other):
145        """
146        Subclassers must implement this.
147        """
148        raise TypeError(f"Cannot `or` {self} and {other}")

A Fragment of a Specification.

NB: Fragments are incomplete by definition.

logger: ClassVar = <Logger mcmas.fmtk (INFO)>
Metadata: ClassVar = <class 'SpecificationMetadata'>
@classmethod
def get_trivial(kls, **kwargs):
69    @classmethod
70    def get_trivial(kls, **kwargs):
71        """
72        Subclassers must implement this.
73
74        Returns the trivial fragment for this type.
75        """
76        raise NotImplementedError(f"{kls}")

Subclassers must implement this.

Returns the trivial fragment for this type.

@classmethod
def load_from_source(kls, txt) -> Self:
78    @classmethod
79    def load_from_source(kls, txt) -> typing.Self:
80        """
81        Subclassers must implement this.
82
83        Creates spec-fragment from raw source-code.
84        """
85        raise NotImplementedError(f"{kls}")

Subclassers must implement this.

Creates spec-fragment from raw source-code.

def model_dump_source(self) -> str:
87    def model_dump_source(self) -> str:
88        """
89        Subclassers must implement this.
90
91        Dumps raw source-code for this fragment.
92        """
93        raise NotImplementedError(f"{self}")

Subclassers must implement this.

Dumps raw source-code for this fragment.

valid
 95    @property
 96    def valid(self):
 97        """
 98        True if this agent is valid, i.e. ready to run and not a
 99        fragment.
100        """
101        return not any(
102            [self.__class__.__name__ == "SpecificationFragment", self.advice]
103        )

True if this agent is valid, i.e. ready to run and not a fragment.

concrete
 95    @property
 96    def valid(self):
 97        """
 98        True if this agent is valid, i.e. ready to run and not a
 99        fragment.
100        """
101        return not any(
102            [self.__class__.__name__ == "SpecificationFragment", self.advice]
103        )

True if this agent is valid, i.e. ready to run and not a fragment.

advice: list
107    @property
108    def advice(self) -> list:
109        """
110        Returns a list of any problems with this object.
111
112        See also `valid` property
113        """
114        required = getattr(self.__class__, "REQUIRED", [])
115        if not required:
116            LOGGER.warning(
117                f"no attrs listed as required for {self}; advice unavailable"
118            )
119        return [
120            f"MISSING-REQUIRED {f} from {self.__class__.__name__}"
121            for f in required
122            if not bool(getattr(self, f))
123        ] + self.local_advice

Returns a list of any problems with this object.

See also valid property

local_advice: list
125    @property
126    def local_advice(self) -> list:
127        """
128        Subclassers must implement this.
129        """
130        return []

Subclassers must implement this.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Fragment = <class 'SpecificationFragment'>
class Specification(SpecificationFragment):
154class Specification(SpecificationFragment):
155    """
156    Pydantic models for a Specification.
157    """

Pydantic models for a Specification.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class AnalysisMeta(pydantic.main.BaseModel):
164class AnalysisMeta(pydantic.BaseModel):
165    """
166    Metadata for a Specification Analysis.
167    """
168
169    formula_index: int = Field(default=-1)
170    base_complexity: float = Field(default=0.0)
171    nesting_coefficient: int = Field(default=1)
172    raw_score: float = Field(default=0.0)

Metadata for a Specification Analysis.

formula_index: int
base_complexity: float
nesting_coefficient: int
raw_score: float
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class SymbolMetadata(pydantic.main.BaseModel):
175class SymbolMetadata(pydantic.BaseModel):
176    """
177    Result of running ISPL Analysis.
178
179    This extracts details about symbols, namespaces, etc.
180    """
181
182    actions: typing.SymbolList2 = Field(
183        default=[],
184        description="Actions list",
185    )
186    agents: typing.SymbolList2 = Field(
187        default=[],
188        description="Agents list",
189    )
190    vars: typing.SymbolList2 = Field(
191        default=[],
192        description="Var list",
193    )

Result of running ISPL Analysis.

This extracts details about symbols, namespaces, etc.

actions: Annotated[List[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], PlainSerializer(func=<function <lambda> at 0x7fd124775bc0>, return_type=PydanticUndefined, when_used='always')]
agents: Annotated[List[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], PlainSerializer(func=<function <lambda> at 0x7fd124775bc0>, return_type=PydanticUndefined, when_used='always')]
vars: Annotated[List[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], PlainSerializer(func=<function <lambda> at 0x7fd124775bc0>, return_type=PydanticUndefined, when_used='always')]
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class SpecificationAnalysis(pydantic.main.BaseModel):
199class SpecificationAnalysis(pydantic.BaseModel):
200    """
201    Base class for all Analysis results.
202    """

Base class for all Analysis results.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].