speclogician 0.0.0b1__py3-none-any.whl → 0.0.0.dev1__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.
Files changed (153) hide show
  1. speclogician/agent/funcs.py +29 -0
  2. speclogician/cmd/agent_cmd.py +89 -0
  3. speclogician/cmd/data_cmd.py +24 -0
  4. speclogician/cmd/model_cmd.py +42 -0
  5. speclogician/cmd/overlay_cmd.py +30 -0
  6. speclogician/cmd/scenario_cmd.py +61 -0
  7. speclogician/cmd/state_cmd.py +52 -0
  8. speclogician/data/artifact.py +8 -50
  9. speclogician/data/container.py +18 -384
  10. speclogician/data/mapping.py +18 -17
  11. speclogician/data/refs.py +12 -11
  12. speclogician/data/reports.py +11 -0
  13. speclogician/data/traces.py +15 -6
  14. speclogician/llms/llmtools.py +102 -0
  15. speclogician/llms/overlay.py +264 -0
  16. speclogician/main.py +36 -102
  17. speclogician/modeling/__init__.py +0 -31
  18. speclogician/modeling/component.py +4 -60
  19. speclogician/modeling/conflict.py +5 -19
  20. speclogician/modeling/domain.py +93 -280
  21. speclogician/modeling/model.py +206 -0
  22. speclogician/modeling/predicates.py +20 -22
  23. speclogician/modeling/report.py +33 -0
  24. speclogician/modeling/scenario.py +119 -87
  25. speclogician/sl_cmd.py +76 -0
  26. speclogician/state/change.py +98 -378
  27. speclogician/state/state.py +183 -399
  28. speclogician/tui/box.tcss +10 -0
  29. speclogician/tui/tui.py +131 -0
  30. speclogician/utils/__init__.py +1 -70
  31. speclogician/utils/imx.py +195 -0
  32. speclogician/utils/load.py +25 -147
  33. speclogician/utils/prompt.md +1 -325
  34. speclogician-0.0.0.dev1.dist-info/METADATA +21 -0
  35. speclogician-0.0.0.dev1.dist-info/RECORD +43 -0
  36. speclogician/commands/__init__.py +0 -15
  37. speclogician/commands/cmd_ch.py +0 -616
  38. speclogician/commands/cmd_find.py +0 -256
  39. speclogician/commands/cmd_view.py +0 -202
  40. speclogician/commands/runner.py +0 -149
  41. speclogician/commands/utils.py +0 -101
  42. speclogician/demos/.DS_Store +0 -0
  43. speclogician/demos/cmd_demo.py +0 -278
  44. speclogician/demos/loader.py +0 -135
  45. speclogician/demos/model.py +0 -27
  46. speclogician/demos/runner.py +0 -51
  47. speclogician/logic/__init__.py +0 -11
  48. speclogician/logic/api/__init__.py +0 -29
  49. speclogician/logic/api/client.py +0 -606
  50. speclogician/logic/api/decomp.py +0 -67
  51. speclogician/logic/api/scenario.py +0 -102
  52. speclogician/logic/api/traces.py +0 -59
  53. speclogician/logic/lib/__init__.py +0 -19
  54. speclogician/logic/lib/complement.py +0 -107
  55. speclogician/logic/lib/domain_model.py +0 -59
  56. speclogician/logic/lib/predicates.py +0 -151
  57. speclogician/logic/lib/scenarios.py +0 -369
  58. speclogician/logic/lib/traces.py +0 -114
  59. speclogician/logic/lib/transitions.py +0 -104
  60. speclogician/logic/main.py +0 -246
  61. speclogician/logic/strings.py +0 -194
  62. speclogician/logic/utils.py +0 -135
  63. speclogician/modeling/complement.py +0 -104
  64. speclogician/modeling/spec.py +0 -306
  65. speclogician/modeling/spec_stats.py +0 -39
  66. speclogician/presentation/api.py +0 -244
  67. speclogician/presentation/builders/_links.py +0 -44
  68. speclogician/presentation/builders/container.py +0 -53
  69. speclogician/presentation/builders/data_artifact.py +0 -42
  70. speclogician/presentation/builders/domain.py +0 -54
  71. speclogician/presentation/builders/instances_list.py +0 -38
  72. speclogician/presentation/builders/predicate.py +0 -51
  73. speclogician/presentation/builders/recommendations.py +0 -41
  74. speclogician/presentation/builders/scenario.py +0 -41
  75. speclogician/presentation/builders/scenario_complement.py +0 -82
  76. speclogician/presentation/builders/smart_find.py +0 -39
  77. speclogician/presentation/builders/spec.py +0 -39
  78. speclogician/presentation/builders/state_diff.py +0 -150
  79. speclogician/presentation/builders/state_instance.py +0 -42
  80. speclogician/presentation/builders/state_instance_summary.py +0 -84
  81. speclogician/presentation/builders/trace.py +0 -58
  82. speclogician/presentation/ctx.py +0 -38
  83. speclogician/presentation/models/container.py +0 -44
  84. speclogician/presentation/models/data_artifact.py +0 -33
  85. speclogician/presentation/models/domain.py +0 -50
  86. speclogician/presentation/models/instances_list.py +0 -23
  87. speclogician/presentation/models/predicate.py +0 -60
  88. speclogician/presentation/models/recommendations.py +0 -34
  89. speclogician/presentation/models/scenario.py +0 -31
  90. speclogician/presentation/models/scenario_complement.py +0 -40
  91. speclogician/presentation/models/smart_find.py +0 -34
  92. speclogician/presentation/models/spec.py +0 -32
  93. speclogician/presentation/models/state_diff.py +0 -34
  94. speclogician/presentation/models/state_instance.py +0 -31
  95. speclogician/presentation/models/state_instance_summary.py +0 -102
  96. speclogician/presentation/models/trace.py +0 -42
  97. speclogician/presentation/preview/__init__.py +0 -13
  98. speclogician/presentation/preview/cli.py +0 -50
  99. speclogician/presentation/preview/fixtures/__init__.py +0 -205
  100. speclogician/presentation/preview/fixtures/artifact_container.py +0 -150
  101. speclogician/presentation/preview/fixtures/data_artifact.py +0 -144
  102. speclogician/presentation/preview/fixtures/domain_model.py +0 -162
  103. speclogician/presentation/preview/fixtures/instances_list.py +0 -162
  104. speclogician/presentation/preview/fixtures/predicate.py +0 -184
  105. speclogician/presentation/preview/fixtures/scenario.py +0 -84
  106. speclogician/presentation/preview/fixtures/scenario_complement.py +0 -81
  107. speclogician/presentation/preview/fixtures/smart_find.py +0 -140
  108. speclogician/presentation/preview/fixtures/spec.py +0 -95
  109. speclogician/presentation/preview/fixtures/state_diff.py +0 -158
  110. speclogician/presentation/preview/fixtures/state_instance.py +0 -128
  111. speclogician/presentation/preview/fixtures/state_instance_summary.py +0 -80
  112. speclogician/presentation/preview/fixtures/trace.py +0 -206
  113. speclogician/presentation/preview/registry.py +0 -42
  114. speclogician/presentation/renderers/__init__.py +0 -24
  115. speclogician/presentation/renderers/container.py +0 -136
  116. speclogician/presentation/renderers/data_artifact.py +0 -144
  117. speclogician/presentation/renderers/domain.py +0 -123
  118. speclogician/presentation/renderers/instances_list.py +0 -120
  119. speclogician/presentation/renderers/predicate.py +0 -180
  120. speclogician/presentation/renderers/recommendations.py +0 -90
  121. speclogician/presentation/renderers/scenario.py +0 -94
  122. speclogician/presentation/renderers/scenario_complement.py +0 -59
  123. speclogician/presentation/renderers/smart_find.py +0 -307
  124. speclogician/presentation/renderers/spec.py +0 -105
  125. speclogician/presentation/renderers/state_diff.py +0 -102
  126. speclogician/presentation/renderers/state_instance.py +0 -82
  127. speclogician/presentation/renderers/state_instance_summary.py +0 -143
  128. speclogician/presentation/renderers/trace.py +0 -122
  129. speclogician/shell/app.py +0 -170
  130. speclogician/shell/shell_ch.py +0 -263
  131. speclogician/shell/shell_view.py +0 -153
  132. speclogician/state/change_result.py +0 -32
  133. speclogician/state/diff.py +0 -191
  134. speclogician/state/inst.py +0 -574
  135. speclogician/state/recommendation.py +0 -13
  136. speclogician/state/recommender.py +0 -577
  137. speclogician/state/state_stats.py +0 -133
  138. speclogician/tui/__init__.py +0 -0
  139. speclogician/tui/app.py +0 -257
  140. speclogician/tui/app.tcss +0 -160
  141. speclogician/tui/demo.py +0 -45
  142. speclogician/tui/images/speclogician-full.png +0 -0
  143. speclogician/tui/images/speclogician-minimal.png +0 -0
  144. speclogician/tui/main_screen.py +0 -454
  145. speclogician/tui/splash_screen.py +0 -51
  146. speclogician/tui/stats_screen.py +0 -125
  147. speclogician/utils/testing.py +0 -151
  148. speclogician-0.0.0b1.dist-info/METADATA +0 -116
  149. speclogician-0.0.0b1.dist-info/RECORD +0 -139
  150. /speclogician/{presentation → agent}/__init__.py +0 -0
  151. /speclogician/{presentation/builders → cmd}/__init__.py +0 -0
  152. /speclogician/{presentation/models → llms}/__init__.py +0 -0
  153. {speclogician-0.0.0b1.dist-info → speclogician-0.0.0.dev1.dist-info}/WHEEL +0 -0
@@ -1,150 +1,61 @@
1
1
  #
2
2
  # Imandra Inc.
3
3
  #
4
- # speclogician/modeling/domain.py
4
+ # domain.py
5
5
  #
6
6
 
7
- from typing import Sequence
8
- from pydantic import BaseModel, Field
7
+ import re
8
+ from typing import List
9
+ from pydantic import BaseModel
9
10
 
10
- from .predicates import (
11
- StatePredicate, ActionPredicate, Transition, PredicateType
12
- )
13
- from .__init__ import extract_declared_types
14
- from enum import StrEnum
15
-
16
- class BaseStatus(StrEnum):
17
- UNKNOWN = "unknown"
18
- INVALID_IML = "invalid_iml"
19
- VALID = "valid"
20
-
21
- class DomainModel(BaseModel):
22
- """
23
- The domain model (base model with state/action types + other auxillary functions,
24
- and predicates/transition functions)
25
- """
26
-
27
- base_status : BaseStatus = BaseStatus.UNKNOWN # IML status
28
- base_has_state : bool = False # Model contains `state` type definition
29
- base_has_action : bool = False # Model contains `action` type definition
30
- base : str = "" # Includes type definitions and other
31
-
32
- transitions : list[Transition] = Field(default_factory=list[Transition]) # 'state * action -> state' tansition functions
33
- state_preds : list[StatePredicate] = Field(default_factory=list[StatePredicate]) # 'state -> bool' predicates
34
- action_preds : list[ActionPredicate] = Field(default_factory=list[ActionPredicate]) # 'state * action -> bool' predicates
35
-
36
- # transitions
37
- num_trans_total : int = 0
38
- num_trans_matched : int = 0
39
- num_trans_valid_logic : int = 0
40
- num_trans_errored : int = 0
41
-
42
- # state-only predicates
43
- num_state_preds_total : int = 0
44
- num_state_preds_matched : int = 0
45
- num_state_preds_valid_logic : int = 0
46
- num_state_preds_errored : int = 0
47
-
48
- # state x action predicates
49
- num_action_preds_total : int = 0
50
- num_action_preds_matched : int = 0
51
- num_action_preds_valid_logic : int = 0
52
- num_action_preds_errored : int = 0
53
-
54
- # predicate totals
55
- num_preds_total : int = 0
56
- num_preds_matched : int = 0
57
- num_preds_valid_logic : int = 0
58
- num_preds_errored : int = 0
59
-
60
- def assert_has_predicates(
61
- self,
62
- *,
63
- given: Sequence[str],
64
- when: Sequence[str],
65
- ) -> None:
66
- """Ensure all predicates referenced by a scenario exist in the domain model.
67
-
68
- - `given` must reference state predicates (state -> bool)
69
- - `when` must reference action predicates (state -> action -> bool)
70
-
71
- Raises ValueError with a helpful message if anything is missing or mismatched.
72
- """
73
- missing_state: list[str] = []
74
- missing_action: list[str] = []
75
- wrong_kind_given: list[str] = []
76
- wrong_kind_when: list[str] = []
77
-
78
- for name in given:
79
- p = self.get_pred_by_name(name)
80
- if p is None:
81
- missing_state.append(name)
82
- elif p not in self.state_preds:
83
- # Exists, but it's an action predicate (or otherwise not a state pred)
84
- wrong_kind_given.append(name)
11
+ from rich.table import Table
12
+ from rich.syntax import Syntax
85
13
 
86
- for name in when:
87
- p = self.get_pred_by_name(name)
88
- if p is None:
89
- missing_action.append(name)
90
- elif p not in self.action_preds:
91
- # Exists, but it's a state predicate (or otherwise not an action pred)
92
- wrong_kind_when.append(name)
14
+ from .predicates import StatePredicate, ActionPredicate, Transition
15
+ from .report import DomainModelReport
93
16
 
94
- if missing_state or missing_action or wrong_kind_given or wrong_kind_when:
95
- parts: list[str] = []
96
- if missing_state:
97
- parts.append(f"missing state predicates (given): {missing_state}")
98
- if wrong_kind_given:
99
- parts.append(f"not state predicates (given): {wrong_kind_given}")
100
- if missing_action:
101
- parts.append(f"missing action predicates (when): {missing_action}")
102
- if wrong_kind_when:
103
- parts.append(f"not action predicates (when): {wrong_kind_when}")
17
+ from enum import Enum
104
18
 
105
- raise ValueError("Scenario references invalid predicates: " + "; ".join(parts))
19
+ from dotenv import load_dotenv
20
+ load_dotenv("../.env")
106
21
 
107
- def assert_has_transitions(self, *, then: Sequence[str]) -> None:
108
- """Ensure all transitions referenced by a scenario exist in the domain model.
109
-
110
- `then` is a list of transition function names.
111
-
112
- Raises ValueError with a helpful message if anything is missing.
113
- """
114
- missing: list[str] = []
115
-
116
- for name in then:
117
- t = self.get_trans_by_name(name)
118
- if t is None:
119
- missing.append(name)
22
+ IML_TYPE_REGEX = re.compile(
23
+ r"""
24
+ ^\s*type\s+ # 'type' keyword
25
+ (?P<name>[a-zA-Z_][a-zA-Z0-9_]*) # type name
26
+ \s*= # '=' sign
27
+ """,
28
+ re.MULTILINE | re.VERBOSE,
29
+ )
120
30
 
121
- if missing:
122
- raise ValueError(f"Scenario references unknown transitions (then): {missing}")
31
+ def strip_iml_comments(text: str) -> str:
32
+ """Remove OCaml-style (* ... *) and // comments."""
33
+ # Remove block comments
34
+ text = re.sub(r"\(\*.*?\*\)", "", text, flags=re.DOTALL)
35
+ # Remove line comments
36
+ text = re.sub(r"//.*", "", text)
37
+ return text
123
38
 
124
- def get_pred_by_name (self, name:str) -> StatePredicate|ActionPredicate|None:
125
- """ Return a predicate body for the specified name """
126
- s_pred = next((p for p in self.state_preds if p.name == name), None)
39
+ def extract_declared_types(iml_text: str) -> List[str]:
40
+ """Return all declared type names in an IML file."""
41
+ clean = strip_iml_comments(iml_text)
42
+ return [m.group("name") for m in IML_TYPE_REGEX.finditer(clean)]
127
43
 
128
- if s_pred is None:
129
- return next((p for p in self.action_preds if p.name == name), None)
130
- else:
131
- return s_pred
44
+ class PredicateType(Enum):
45
+ STATE = 1
46
+ ACTION = 2
132
47
 
133
- def get_trans_by_name (self, name:str) -> Transition|None:
134
- """ Return transition body for a specified name """
135
- return next((p for p in self.transitions if p.name == name), None)
48
+ class DomainModel(BaseModel):
49
+ """
50
+ The domain model
51
+ """
136
52
 
137
- def action_pred_names(self) -> list[str]:
138
- """ List of action predicate names """
139
- return list(map(lambda x: x.name, self.action_preds))
140
-
141
- def state_pred_names(self) -> list[str]:
142
- """ List of available state predicate names """
143
- return list(map(lambda x: x.name, self.state_preds))
53
+ base_status : str = "UNKOWN" # IML status
54
+ base : str = "" # Includes type definitions and other things...
144
55
 
145
- def transition_names(self) -> list[str]:
146
- """ List of available transitions """
147
- return list(map(lambda x: x.name, self.transitions))
56
+ transitions : list[Transition] = [] # 'state * action -> state' tansition functions
57
+ state_preds : list[StatePredicate] = [] # 'state -> bool' predicates
58
+ action_preds : list[ActionPredicate] = [] # 'state * action -> bool' predicates
148
59
 
149
60
  def pred_exists(self, name:str) -> bool:
150
61
  """
@@ -154,113 +65,11 @@ class DomainModel(BaseModel):
154
65
  return True
155
66
  return next ((p for p in self.action_preds if p.name == name), None) is not None
156
67
 
157
- def add_predicate(self, name: str, ptype: PredicateType, src_code: str) -> None:
158
- """Add a new predicate to the domain model."""
159
-
160
- # Enforce global uniqueness across all predicates
161
- if any(p.name == name for p in self.state_preds) or \
162
- any(p.name == name for p in self.action_preds):
163
- raise ValueError(f"Predicate '{name}' already exists in domain model")
164
-
165
- if ptype == PredicateType.STATE:
166
- pred = StatePredicate(
167
- name=name,
168
- src_code=src_code,
169
- )
170
- self.state_preds.append(pred)
171
-
172
- elif ptype == PredicateType.ACTION:
173
- pred = ActionPredicate(
174
- name=name,
175
- src_code=src_code,
176
- )
177
- self.action_preds.append(pred)
178
-
179
- else:
180
- raise ValueError(f"Attempting to add unknown type of predicate: {ptype}")
181
-
182
- def edit_predicate(self, name: str, src_code: str) -> None:
183
- """Edit a predicate by name and update last_updated."""
184
-
185
- matches = [p for p in self.state_preds if p.name == name] + \
186
- [p for p in self.action_preds if p.name == name]
187
-
188
- if not matches:
189
- raise ValueError(f"Predicate '{name}' not found")
190
-
191
- if len(matches) > 1:
192
- raise ValueError(f"Predicate '{name}' is ambiguous (found {len(matches)} matches)")
193
-
194
- pred = matches[0]
195
- pred.set_src_code(src_code)
196
-
197
- def rem_predicate(self, name: str) -> None:
198
- """Remove a predicate (state or action) by name."""
199
-
200
- removed = False
201
-
202
- # Remove from state predicates
203
- for i, p in enumerate(self.state_preds):
204
- if p.name == name:
205
- del self.state_preds[i]
206
- removed = True
207
- break
208
-
209
- # Remove from action predicates
210
- for i, p in enumerate(self.action_preds):
211
- if p.name == name:
212
- del self.action_preds[i]
213
- removed = True
214
- break
215
-
216
- if not removed:
217
- raise ValueError(f"Predicate '{name}' not found in domain model")
218
-
219
- def add_transition(self, name: str, src_code: str) -> None:
220
- """Add a new transition function to the domain model."""
221
-
222
- # Ensure uniqueness
223
- if any(t.name == name for t in self.transitions):
224
- raise ValueError(f"Transition '{name}' already exists in domain model")
225
-
226
- transition = Transition(
227
- name=name,
228
- src_code=src_code,
229
- )
230
-
231
- self.transitions.append(transition)
232
-
233
- def edit_transition(self, name: str, src_code: str) -> None:
234
- """Edit a transition function by name and update last_updated."""
235
- matches = [t for t in self.transitions if t.name == name]
236
-
237
- if not matches:
238
- raise ValueError(f"Transition '{name}' not found in domain model")
239
-
240
- if len(matches) > 1:
241
- raise ValueError(
242
- f"Transition name '{name}' is ambiguous "
243
- f"({len(matches)} transitions found)"
244
- )
245
-
246
- transition = matches[0]
247
- transition.set_src_code(src_code)
248
-
249
- def rem_transition(self, name: str) -> None:
250
- """Remove a transition function by name."""
251
-
252
- for i, t in enumerate(self.transitions):
253
- if t.name == name:
254
- del self.transitions[i]
255
- return
256
-
257
- raise ValueError(f"Transition '{name}' not found in domain model")
258
-
259
68
  def trans_exists(self, name:str) -> bool:
260
- """ Return True/False if transitions with specified name exists """
69
+ """ """
261
70
  return next ((t for t in self.transitions if t.name == name), None) is not None
262
71
 
263
- def state_specified (self) -> bool:
72
+ def state_specified(self) -> bool:
264
73
  """
265
74
  Return True if the `state` type is properly specified
266
75
  """
@@ -285,65 +94,69 @@ class DomainModel(BaseModel):
285
94
 
286
95
  def set_base (
287
96
  self,
288
- new_base : str) -> None:
97
+ new_base : str,
98
+ do_check : bool = True) -> None | DomainModelReport:
289
99
  """
290
- Update the base model
291
100
  """
292
101
  self.base = new_base
293
102
 
103
+ if do_check:
104
+ return self.report()
294
105
  return None
295
106
 
296
- def make_iml_model (
297
- self,
298
- state_pred_names : list[str],
299
- action_pred_names : list[str],
300
- transition_names : list[str]
301
- ) -> str:
302
- """
303
- Return base model with required predicates/transitions if needed
107
+ def report(self) -> DomainModelReport:
108
+ """
109
+ Generate a DomainModelReport
304
110
  """
305
111
 
306
- state_preds_str = ""
307
- for p in state_pred_names:
308
- pred = self.get_pred_by_name(p)
309
- if pred is None:
310
- continue
311
- state_preds_str += f"{pred.to_iml()}"
312
- if len(state_preds_str):
313
- state_preds_str = f"(* State predicates *)\n{state_preds_str}"
314
-
315
- action_preds_str = ""
316
- for p in action_pred_names:
317
- pred = self.get_pred_by_name(p)
318
- if pred is None:
319
- continue
320
- action_preds_str += f"\n{pred.to_iml()}\n"
321
- if len(action_preds_str):
322
- action_preds_str = f"(* Action predicates *)\n{action_preds_str}"
323
-
324
- trans_str = ""
325
- for t in transition_names:
326
- trans = self.get_trans_by_name(t)
327
- if trans is None:
328
- continue
329
- trans_str += f"\n{trans.to_iml()}\n"
330
- if len(trans_str):
331
- trans_str = f"(* Transitions *)\n{trans_str}"
112
+ return DomainModelReport()
332
113
 
114
+ def to_iml(self):
115
+ """
116
+ Create a consolidated IML model
117
+ """
118
+ state_predicates = "\n".join(map(lambda x: x.to_iml(), self.state_preds))
119
+ action_predicates = "\n".join(map(lambda x: x.to_iml(), self.action_preds))
120
+ transitions = "\n".join(map(lambda x: x.to_iml(), self.transitions))
121
+
333
122
  return f"""
334
123
  (* Domain Model *)
335
124
 
336
125
  (* Base *)
337
126
  {self.base}
338
- {state_preds_str}
339
- {action_preds_str}
340
- {trans_str}
341
- """.rstrip("\n")
342
127
 
343
- def to_iml(self):
128
+ (* State predicates *)
129
+ {state_predicates}
130
+
131
+ (* Action predicates *)
132
+ {action_predicates}
133
+
134
+ (* Transitions *)
135
+ {transitions}
136
+ """
137
+
138
+ def __rich__ (self):
344
139
  """
345
- Create a consolidated IML model with all predicates and transitions
140
+ Return a nice table we can visualize
346
141
  """
347
- return self.make_iml_model(
348
- self.state_pred_names(), self.action_pred_names(), self.transition_names()
349
- )
142
+ t = Table(title="Domain model")
143
+
144
+ t.add_column("Attribute", "Value")
145
+ t.add_row("Base", Syntax(self.base, "OCaml"))
146
+ t.add_row("State predicates", Syntax("\n".join(map(lambda x: x.to_iml(), self.state_preds)) , "OCaml"))
147
+ t.add_row("Action predicates", Syntax("\n".join(map(lambda x: x.to_iml(), self.action_preds)), "OCaml"))
148
+ t.add_row("Transitions", Syntax("\n".join(map(lambda x: x.to_iml(), self.transitions)) , "OCaml"))
149
+
150
+ return t
151
+
152
+ if __name__ == "__main__":
153
+
154
+ #
155
+ m = DomainModel(
156
+ base_status = "UNKNOWN"
157
+ )
158
+ m.set_base("type state = int\ntype action = int")
159
+
160
+ print (f"State is specified: {m.state_specified()}")
161
+ print (f"Action is specified: {m.action_specified()}")
162
+
@@ -0,0 +1,206 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # model.py
5
+ #
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from .report import (
10
+ ModelReport, DomainModelReport, ScenarioReport
11
+ )
12
+ from .domain import DomainModel
13
+ from .scenario import Scenario
14
+ from ..utils.imx import run_decomp
15
+
16
+ from imandrax_api_models import DecomposeRes, InstanceRes, VerifyRes
17
+
18
+ from ..state.change import (
19
+ ModelChange,
20
+ DomainModelBaseEdit,
21
+ PredicateAdd,
22
+ PredicateEdit,
23
+ PredicateRemove,
24
+ TransitionAdd,
25
+ TransitionEdit,
26
+ ScenarioAdd,
27
+ ScenarioEdit,
28
+ ScenarioRemoved
29
+ )
30
+
31
+ from ..utils import console
32
+
33
+ class PredicateDetails(BaseModel):
34
+ """
35
+ We use this to compute details on how the predicates are used, how to search for it, etc...
36
+ """
37
+
38
+ predicate : str # predicate itself
39
+ num_given : int # number of times it appears in given
40
+ num_when : int # number of times it appears in when
41
+ num_complements : int # number of times the complement appears
42
+
43
+ given_scenarios : list[Scenario] # Scenarios where the predicate appears in 'Given' clause
44
+ where_scenarios : list[Scenario] # Scenarios where the predicate appears in 'Where' clause
45
+ then_scenarios : list[Scenario] # Scenarios where the predicate appears in 'Then' clause
46
+
47
+ class ModelSummaryInfo(BaseModel):
48
+ """ """
49
+ domain_iml_status : str
50
+
51
+ preds_errored : int
52
+ preds_total : int
53
+
54
+ scenarios_total : int
55
+ scenarios_errored : int
56
+
57
+ class Model(BaseModel):
58
+ """
59
+ A model is a combination of Domain Model and list of scenarios
60
+ """
61
+
62
+ domain_model : DomainModel = DomainModel() # Domain model
63
+ scenarios : list[Scenario] = [] # The list of Scenarios
64
+
65
+ def info(self) -> ModelSummaryInfo:
66
+ """
67
+
68
+ """
69
+
70
+ preds_errored = 0
71
+ scenarios_errored = 0
72
+
73
+ msi = ModelSummaryInfo (
74
+ domain_iml_status=self.domain_model.base_status,
75
+
76
+ preds_total = len(self.domain_model.state_preds) + len(self.domain_model.action_preds),
77
+ preds_errored = preds_errored,
78
+
79
+ scenarios_total = len(self.scenarios),
80
+ scenarios_errored = scenarios_errored
81
+ )
82
+
83
+ return msi
84
+
85
+ def proc_change(self, change : ModelChange):
86
+ """
87
+ Process model change
88
+ """
89
+ if isinstance(change, DomainModelBaseEdit):
90
+ self.domain_model.set_base(change.new_base_src)
91
+ elif isinstance(change, PredicateAdd):
92
+ pass
93
+ elif isinstance(change, PredicateEdit):
94
+ pass
95
+ elif isinstance(change, PredicateRemove):
96
+ pass
97
+ elif isinstance(change, TransitionAdd):
98
+ pass
99
+ elif isinstance(change, TransitionEdit):
100
+ pass
101
+ elif isinstance(change, ScenarioAdd):
102
+ pass
103
+ elif isinstance(change, ScenarioEdit):
104
+ pass
105
+ elif isinstance(change, ScenarioRemoved):
106
+ pass
107
+ else:
108
+ pass
109
+
110
+ def scenario_model(self):
111
+ """
112
+ Synthesize a model from the scenarios
113
+ """
114
+
115
+ scenario_logic = ""
116
+ for s in self.scenarios:
117
+ scenario_logic += f"{s.full_model_to_iml()}\n"
118
+
119
+ model = f"""
120
+ let scenarios (s : state) (a : action) =
121
+ {scenario_logic} else
122
+ false
123
+ """
124
+ return model
125
+
126
+ def scenario_complement(self, json_out:bool = False) -> None|DecomposeRes:
127
+ """
128
+ Perform region decomp on the model
129
+ """
130
+
131
+ pred_str = ""
132
+
133
+ for s in self.scenarios:
134
+ pred_str += f"if (\n{s.pred_calls_to_iml()}\n) then true else\n"
135
+
136
+ decomp_query = f"""
137
+
138
+ let complement (s : state) (a : action) =
139
+ {pred_str}
140
+ false
141
+
142
+ let not_covered (s : state) (a : action) =
143
+ (complement s a) = false
144
+
145
+ let wrapper (s : state) (a : action) =
146
+ complement s a
147
+ [@@decomp top ~assuming:[%id not_covered] ()]
148
+ """
149
+
150
+ if not json_out:
151
+ with console.status(":robot: [bold]Computing regions..."):
152
+ result = run_decomp(self.domain_model.to_iml(), decomp_query)
153
+ else:
154
+ result = run_decomp(self.domain_model.to_iml(), decomp_query)
155
+
156
+
157
+ if not result.regions_str:
158
+ if not json_out:
159
+ console.print(":warning: Failed to compute regions...")
160
+ return None
161
+
162
+ if not json_out:
163
+ console.print(f":robot::rocket: [bold]Computed: {len(result.regions_str)} regions!")
164
+ for idx, region_str in enumerate(result.regions_str):
165
+ console.rule(f"[bold red]Region: {idx+1}")
166
+
167
+ if region_str.constraints_str:
168
+ console.print("Constraints:")
169
+ for c in region_str.constraints_str:
170
+ console.print (f" - {c}")
171
+
172
+ if region_str.model_str:
173
+ console.print("Sample model input:")
174
+ console.print(f" - {region_str.model_str}")
175
+ return None
176
+ else:
177
+ return result
178
+
179
+ def report(self) -> ModelReport:
180
+
181
+ dmodel_report = self.domain_model.report()
182
+
183
+ scenario_report = ScenarioReport(
184
+ # num_scenarios=len(self.)
185
+ num_scenarios = 0,
186
+ num_conflicted = 0,
187
+ num_failed= 0
188
+ )
189
+
190
+ return ModelReport(dmodel_report=dmodel_report, scenario_report=scenario_report)
191
+
192
+ def check_logic(self):
193
+ """
194
+ Check that the resulting logic is correct
195
+ """
196
+ pass
197
+
198
+ def rich_scenarios(self):
199
+ """
200
+ Return a table of scenarios
201
+ """
202
+ pass
203
+
204
+ def __rich__(self):
205
+ """ """
206
+ pass