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.
- speclogician/agent/funcs.py +29 -0
- speclogician/cmd/agent_cmd.py +89 -0
- speclogician/cmd/data_cmd.py +24 -0
- speclogician/cmd/model_cmd.py +42 -0
- speclogician/cmd/overlay_cmd.py +30 -0
- speclogician/cmd/scenario_cmd.py +61 -0
- speclogician/cmd/state_cmd.py +52 -0
- speclogician/data/artifact.py +8 -50
- speclogician/data/container.py +18 -384
- speclogician/data/mapping.py +18 -17
- speclogician/data/refs.py +12 -11
- speclogician/data/reports.py +11 -0
- speclogician/data/traces.py +15 -6
- speclogician/llms/llmtools.py +102 -0
- speclogician/llms/overlay.py +264 -0
- speclogician/main.py +36 -102
- speclogician/modeling/__init__.py +0 -31
- speclogician/modeling/component.py +4 -60
- speclogician/modeling/conflict.py +5 -19
- speclogician/modeling/domain.py +93 -280
- speclogician/modeling/model.py +206 -0
- speclogician/modeling/predicates.py +20 -22
- speclogician/modeling/report.py +33 -0
- speclogician/modeling/scenario.py +119 -87
- speclogician/sl_cmd.py +76 -0
- speclogician/state/change.py +98 -378
- speclogician/state/state.py +183 -399
- speclogician/tui/box.tcss +10 -0
- speclogician/tui/tui.py +131 -0
- speclogician/utils/__init__.py +1 -70
- speclogician/utils/imx.py +195 -0
- speclogician/utils/load.py +25 -147
- speclogician/utils/prompt.md +1 -325
- speclogician-0.0.0.dev1.dist-info/METADATA +21 -0
- speclogician-0.0.0.dev1.dist-info/RECORD +43 -0
- speclogician/commands/__init__.py +0 -15
- speclogician/commands/cmd_ch.py +0 -616
- speclogician/commands/cmd_find.py +0 -256
- speclogician/commands/cmd_view.py +0 -202
- speclogician/commands/runner.py +0 -149
- speclogician/commands/utils.py +0 -101
- speclogician/demos/.DS_Store +0 -0
- speclogician/demos/cmd_demo.py +0 -278
- speclogician/demos/loader.py +0 -135
- speclogician/demos/model.py +0 -27
- speclogician/demos/runner.py +0 -51
- speclogician/logic/__init__.py +0 -11
- speclogician/logic/api/__init__.py +0 -29
- speclogician/logic/api/client.py +0 -606
- speclogician/logic/api/decomp.py +0 -67
- speclogician/logic/api/scenario.py +0 -102
- speclogician/logic/api/traces.py +0 -59
- speclogician/logic/lib/__init__.py +0 -19
- speclogician/logic/lib/complement.py +0 -107
- speclogician/logic/lib/domain_model.py +0 -59
- speclogician/logic/lib/predicates.py +0 -151
- speclogician/logic/lib/scenarios.py +0 -369
- speclogician/logic/lib/traces.py +0 -114
- speclogician/logic/lib/transitions.py +0 -104
- speclogician/logic/main.py +0 -246
- speclogician/logic/strings.py +0 -194
- speclogician/logic/utils.py +0 -135
- speclogician/modeling/complement.py +0 -104
- speclogician/modeling/spec.py +0 -306
- speclogician/modeling/spec_stats.py +0 -39
- speclogician/presentation/api.py +0 -244
- speclogician/presentation/builders/_links.py +0 -44
- speclogician/presentation/builders/container.py +0 -53
- speclogician/presentation/builders/data_artifact.py +0 -42
- speclogician/presentation/builders/domain.py +0 -54
- speclogician/presentation/builders/instances_list.py +0 -38
- speclogician/presentation/builders/predicate.py +0 -51
- speclogician/presentation/builders/recommendations.py +0 -41
- speclogician/presentation/builders/scenario.py +0 -41
- speclogician/presentation/builders/scenario_complement.py +0 -82
- speclogician/presentation/builders/smart_find.py +0 -39
- speclogician/presentation/builders/spec.py +0 -39
- speclogician/presentation/builders/state_diff.py +0 -150
- speclogician/presentation/builders/state_instance.py +0 -42
- speclogician/presentation/builders/state_instance_summary.py +0 -84
- speclogician/presentation/builders/trace.py +0 -58
- speclogician/presentation/ctx.py +0 -38
- speclogician/presentation/models/container.py +0 -44
- speclogician/presentation/models/data_artifact.py +0 -33
- speclogician/presentation/models/domain.py +0 -50
- speclogician/presentation/models/instances_list.py +0 -23
- speclogician/presentation/models/predicate.py +0 -60
- speclogician/presentation/models/recommendations.py +0 -34
- speclogician/presentation/models/scenario.py +0 -31
- speclogician/presentation/models/scenario_complement.py +0 -40
- speclogician/presentation/models/smart_find.py +0 -34
- speclogician/presentation/models/spec.py +0 -32
- speclogician/presentation/models/state_diff.py +0 -34
- speclogician/presentation/models/state_instance.py +0 -31
- speclogician/presentation/models/state_instance_summary.py +0 -102
- speclogician/presentation/models/trace.py +0 -42
- speclogician/presentation/preview/__init__.py +0 -13
- speclogician/presentation/preview/cli.py +0 -50
- speclogician/presentation/preview/fixtures/__init__.py +0 -205
- speclogician/presentation/preview/fixtures/artifact_container.py +0 -150
- speclogician/presentation/preview/fixtures/data_artifact.py +0 -144
- speclogician/presentation/preview/fixtures/domain_model.py +0 -162
- speclogician/presentation/preview/fixtures/instances_list.py +0 -162
- speclogician/presentation/preview/fixtures/predicate.py +0 -184
- speclogician/presentation/preview/fixtures/scenario.py +0 -84
- speclogician/presentation/preview/fixtures/scenario_complement.py +0 -81
- speclogician/presentation/preview/fixtures/smart_find.py +0 -140
- speclogician/presentation/preview/fixtures/spec.py +0 -95
- speclogician/presentation/preview/fixtures/state_diff.py +0 -158
- speclogician/presentation/preview/fixtures/state_instance.py +0 -128
- speclogician/presentation/preview/fixtures/state_instance_summary.py +0 -80
- speclogician/presentation/preview/fixtures/trace.py +0 -206
- speclogician/presentation/preview/registry.py +0 -42
- speclogician/presentation/renderers/__init__.py +0 -24
- speclogician/presentation/renderers/container.py +0 -136
- speclogician/presentation/renderers/data_artifact.py +0 -144
- speclogician/presentation/renderers/domain.py +0 -123
- speclogician/presentation/renderers/instances_list.py +0 -120
- speclogician/presentation/renderers/predicate.py +0 -180
- speclogician/presentation/renderers/recommendations.py +0 -90
- speclogician/presentation/renderers/scenario.py +0 -94
- speclogician/presentation/renderers/scenario_complement.py +0 -59
- speclogician/presentation/renderers/smart_find.py +0 -307
- speclogician/presentation/renderers/spec.py +0 -105
- speclogician/presentation/renderers/state_diff.py +0 -102
- speclogician/presentation/renderers/state_instance.py +0 -82
- speclogician/presentation/renderers/state_instance_summary.py +0 -143
- speclogician/presentation/renderers/trace.py +0 -122
- speclogician/shell/app.py +0 -170
- speclogician/shell/shell_ch.py +0 -263
- speclogician/shell/shell_view.py +0 -153
- speclogician/state/change_result.py +0 -32
- speclogician/state/diff.py +0 -191
- speclogician/state/inst.py +0 -574
- speclogician/state/recommendation.py +0 -13
- speclogician/state/recommender.py +0 -577
- speclogician/state/state_stats.py +0 -133
- speclogician/tui/__init__.py +0 -0
- speclogician/tui/app.py +0 -257
- speclogician/tui/app.tcss +0 -160
- speclogician/tui/demo.py +0 -45
- speclogician/tui/images/speclogician-full.png +0 -0
- speclogician/tui/images/speclogician-minimal.png +0 -0
- speclogician/tui/main_screen.py +0 -454
- speclogician/tui/splash_screen.py +0 -51
- speclogician/tui/stats_screen.py +0 -125
- speclogician/utils/testing.py +0 -151
- speclogician-0.0.0b1.dist-info/METADATA +0 -116
- speclogician-0.0.0b1.dist-info/RECORD +0 -139
- /speclogician/{presentation → agent}/__init__.py +0 -0
- /speclogician/{presentation/builders → cmd}/__init__.py +0 -0
- /speclogician/{presentation/models → llms}/__init__.py +0 -0
- {speclogician-0.0.0b1.dist-info → speclogician-0.0.0.dev1.dist-info}/WHEEL +0 -0
speclogician/modeling/domain.py
CHANGED
|
@@ -1,150 +1,61 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Imandra Inc.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
4
|
+
# domain.py
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
from
|
|
7
|
+
import re
|
|
8
|
+
from typing import List
|
|
9
|
+
from pydantic import BaseModel
|
|
9
10
|
|
|
10
|
-
from .
|
|
11
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
+
from dotenv import load_dotenv
|
|
20
|
+
load_dotenv("../.env")
|
|
106
21
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return s_pred
|
|
44
|
+
class PredicateType(Enum):
|
|
45
|
+
STATE = 1
|
|
46
|
+
ACTION = 2
|
|
132
47
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
48
|
+
class DomainModel(BaseModel):
|
|
49
|
+
"""
|
|
50
|
+
The domain model
|
|
51
|
+
"""
|
|
136
52
|
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
"""
|
|
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
|
|
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
|
|
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
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
+
Return a nice table we can visualize
|
|
346
141
|
"""
|
|
347
|
-
|
|
348
|
-
|
|
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
|