umaudemc 0.12.2__py3-none-any.whl → 0.13.1__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.
- umaudemc/__init__.py +1 -1
- umaudemc/__main__.py +5 -0
- umaudemc/api.py +1 -1
- umaudemc/backend/_spot.py +7 -8
- umaudemc/command/graph.py +30 -2
- umaudemc/common.py +0 -24
- umaudemc/counterprint.py +12 -4
- umaudemc/data/smcview.js +42 -29
- umaudemc/formatter.py +20 -25
- umaudemc/grapher.py +6 -5
- umaudemc/gtk.py +88 -499
- umaudemc/kleene.py +156 -0
- umaudemc/opsem.py +1 -1
- umaudemc/probabilistic.py +12 -9
- umaudemc/pyslang.py +320 -186
- umaudemc/simulators.py +3 -2
- umaudemc/statistical.py +1 -0
- umaudemc/webui.py +31 -33
- umaudemc/wrappers.py +44 -2
- {umaudemc-0.12.2.dist-info → umaudemc-0.13.1.dist-info}/METADATA +1 -1
- {umaudemc-0.12.2.dist-info → umaudemc-0.13.1.dist-info}/RECORD +25 -24
- {umaudemc-0.12.2.dist-info → umaudemc-0.13.1.dist-info}/WHEEL +1 -1
- {umaudemc-0.12.2.dist-info → umaudemc-0.13.1.dist-info}/LICENSE +0 -0
- {umaudemc-0.12.2.dist-info → umaudemc-0.13.1.dist-info}/entry_points.txt +0 -0
- {umaudemc-0.12.2.dist-info → umaudemc-0.13.1.dist-info}/top_level.txt +0 -0
umaudemc/pyslang.py
CHANGED
|
@@ -19,7 +19,7 @@ class Instruction:
|
|
|
19
19
|
POP = 0 # no arguments
|
|
20
20
|
JUMP = 1 # sequence of addresses
|
|
21
21
|
STRAT = 2 # strategy (at the object level, for potential optimizations only)
|
|
22
|
-
RLAPP = 3 # (label,
|
|
22
|
+
RLAPP = 3 # (label, initial substitution, whether it is applied on top)
|
|
23
23
|
TEST = 4 # (match type, pattern, condition)
|
|
24
24
|
CHOICE = 5 # sequence of pairs of term (weight) and address
|
|
25
25
|
CALL = 6 # (call term, index of a named strategy within the program, whether the call is tail)
|
|
@@ -34,10 +34,11 @@ class Instruction:
|
|
|
34
34
|
WMATCHREW = 15 # like MATCHREW + (weight)
|
|
35
35
|
ONE = 16 # no arguments
|
|
36
36
|
CHECKPOINT = 17 # no arguments
|
|
37
|
+
KLEENE = 18 # (address of where the iteration begins, whether it enters or leaves)
|
|
37
38
|
|
|
38
39
|
NAMES = ['POP', 'JUMP', 'STRAT', 'RLAPP', 'TEST', 'CHOICE', 'CALL', 'SUBSEARCH',
|
|
39
40
|
'NOFAIL', 'MATCHREW', 'NEXTSUBTERM', 'RWCSTART', 'RWCNEXT', 'NOTIFY',
|
|
40
|
-
'SAMPLE', 'WMATCHREW', 'ONE', 'CHECKPOINT']
|
|
41
|
+
'SAMPLE', 'WMATCHREW', 'ONE', 'CHECKPOINT', 'KLEENE']
|
|
41
42
|
|
|
42
43
|
def __init__(self, itype, extra=None):
|
|
43
44
|
self.type = itype
|
|
@@ -209,7 +210,7 @@ class ListWalker:
|
|
|
209
210
|
class StratCompiler:
|
|
210
211
|
"""Compiler of strategy expressions"""
|
|
211
212
|
|
|
212
|
-
def __init__(self, m, ml, use_notify=False, ignore_one=False):
|
|
213
|
+
def __init__(self, m, ml, use_notify=False, use_kleene=False, ignore_one=False):
|
|
213
214
|
# Metalevel module
|
|
214
215
|
self.ml = ml
|
|
215
216
|
# Object-level module where the strategy lives
|
|
@@ -219,6 +220,8 @@ class StratCompiler:
|
|
|
219
220
|
self.use_notify = use_notify
|
|
220
221
|
# Whether the one combinator is ignored or not
|
|
221
222
|
self.use_one = not ignore_one
|
|
223
|
+
# Whether to delimit iterations with the Kleene instruction
|
|
224
|
+
self.use_kleene = use_kleene
|
|
222
225
|
|
|
223
226
|
# Table of strategy combinators
|
|
224
227
|
self.op_table = {}
|
|
@@ -453,22 +456,34 @@ class StratCompiler:
|
|
|
453
456
|
def ne_iter(self, s, p, tail):
|
|
454
457
|
initial_pc = p.pc
|
|
455
458
|
|
|
459
|
+
if self.use_kleene:
|
|
460
|
+
p.append(Instruction.KLEENE, (initial_pc, True))
|
|
461
|
+
|
|
456
462
|
# The non-empty iteration executes the body and then jumps
|
|
457
463
|
# to the next instruction or to the body again
|
|
458
464
|
self.generate(next(s.arguments()), p, False)
|
|
459
465
|
|
|
460
466
|
p.append(Instruction.JUMP, (p.pc + 1, initial_pc))
|
|
461
467
|
|
|
468
|
+
if self.use_kleene:
|
|
469
|
+
p.append(Instruction.KLEENE, (initial_pc, False))
|
|
470
|
+
|
|
462
471
|
def iter(self, s, p, tail):
|
|
463
472
|
# The empty iteration does the same with an additional initial jump
|
|
464
473
|
initial_jump = p.append(Instruction.JUMP)
|
|
465
474
|
initial_pc = p.pc
|
|
466
475
|
|
|
476
|
+
if self.use_kleene:
|
|
477
|
+
p.append(Instruction.KLEENE, (initial_pc, True))
|
|
478
|
+
|
|
467
479
|
self.generate(next(s.arguments()), p, False)
|
|
468
480
|
|
|
469
481
|
initial_jump.extra = (p.pc + 1, initial_pc)
|
|
470
482
|
p.append(Instruction.JUMP, (p.pc + 1, initial_pc))
|
|
471
483
|
|
|
484
|
+
if self.use_kleene:
|
|
485
|
+
p.append(Instruction.KLEENE, (initial_pc, False))
|
|
486
|
+
|
|
472
487
|
def cond(self, s, p, tail):
|
|
473
488
|
condition, true_branch, false_branch = s.arguments()
|
|
474
489
|
|
|
@@ -907,7 +922,7 @@ class StratCompiler:
|
|
|
907
922
|
visited.add(next_state)
|
|
908
923
|
dfs_stack.append(next_state)
|
|
909
924
|
|
|
910
|
-
# The reachable field has been calculated
|
|
925
|
+
# The reachable field has been calculated assuming that calls do not apply rewrites,
|
|
911
926
|
# but we can consider they do if every definition for it applies a rewrite
|
|
912
927
|
|
|
913
928
|
# has_rewrite_strats holds for each strategy the definitions that do not apply a rewrite
|
|
@@ -979,7 +994,7 @@ class StratCompiler:
|
|
|
979
994
|
jump_over_notify = [] # Jumps that should be added to skip notifies
|
|
980
995
|
# Blocks that carry a notify to the block in the key
|
|
981
996
|
notifiers = {}
|
|
982
|
-
# Blocks that jump to
|
|
997
|
+
# Blocks that jump to the block in the key without address
|
|
983
998
|
not_notifiers = {}
|
|
984
999
|
|
|
985
1000
|
# Backward jumps are always non-notifying
|
|
@@ -1198,6 +1213,13 @@ class StackNode:
|
|
|
1198
1213
|
|
|
1199
1214
|
self.seen[(pc, term, self.venv)] = value
|
|
1200
1215
|
|
|
1216
|
+
def __eq__(self, other):
|
|
1217
|
+
"""Deep comparison of stack nodes"""
|
|
1218
|
+
return self.pc == other.pc and self.venv == other.venv and self.parent == other.parent
|
|
1219
|
+
|
|
1220
|
+
def __hash__(self):
|
|
1221
|
+
return hash((self.pc, self.venv, self.parent))
|
|
1222
|
+
|
|
1201
1223
|
def __repr__(self):
|
|
1202
1224
|
return f'StackNode({self.venv}, {self.pc})'
|
|
1203
1225
|
|
|
@@ -1215,6 +1237,12 @@ class RwcNode(StackNode):
|
|
|
1215
1237
|
# Matching context
|
|
1216
1238
|
self.context = context
|
|
1217
1239
|
|
|
1240
|
+
def __eq__(self, other):
|
|
1241
|
+
return self is other
|
|
1242
|
+
|
|
1243
|
+
def __hash__(self):
|
|
1244
|
+
return id(self)
|
|
1245
|
+
|
|
1218
1246
|
def __repr__(self):
|
|
1219
1247
|
return f'RwcNode({self.venv}, index={self.index})'
|
|
1220
1248
|
|
|
@@ -1239,6 +1267,13 @@ class SubtermNode(StackNode):
|
|
|
1239
1267
|
pending=self.pending[1:], done={**self.done, **{var: value}},
|
|
1240
1268
|
seen=self.seen.fork((pc, value)))
|
|
1241
1269
|
|
|
1270
|
+
def __eq__(self, other):
|
|
1271
|
+
return type(other) is type(self) and self.context == other.context and \
|
|
1272
|
+
self.done == other.done and len(self.pending) == len(other.pending)
|
|
1273
|
+
|
|
1274
|
+
def __hash__(self):
|
|
1275
|
+
return hash((self.context, self.donde, len(self.pending), super()))
|
|
1276
|
+
|
|
1242
1277
|
def __repr__(self):
|
|
1243
1278
|
return f'SubtermNode({self.venv}, {self.context}, {self.done}, {self.pending})'
|
|
1244
1279
|
|
|
@@ -1353,12 +1388,17 @@ class StratRunner:
|
|
|
1353
1388
|
Instruction.WMATCHREW: self.wmatchrew,
|
|
1354
1389
|
Instruction.ONE: self.one,
|
|
1355
1390
|
Instruction.CHECKPOINT: self.checkpoint,
|
|
1391
|
+
Instruction.KLEENE: self.kleene,
|
|
1356
1392
|
}
|
|
1357
1393
|
|
|
1394
|
+
# Number of rewrites
|
|
1395
|
+
self.nr_rewrites = 0
|
|
1396
|
+
|
|
1358
1397
|
def reset(self, term):
|
|
1359
1398
|
"""Reset the runner with a given initial term"""
|
|
1360
1399
|
|
|
1361
1400
|
self.current_state = self.state_class(term, 0, self.stack_root)
|
|
1401
|
+
self.nr_rewrites = 0
|
|
1362
1402
|
self.pending.clear()
|
|
1363
1403
|
|
|
1364
1404
|
def dump_state(self):
|
|
@@ -1481,7 +1521,7 @@ class StratRunner:
|
|
|
1481
1521
|
initial_subs = {var: stack.venv.instantiate(value) for var, value in initial_subs.items()}
|
|
1482
1522
|
|
|
1483
1523
|
for value in initial_subs.values():
|
|
1484
|
-
value.reduce()
|
|
1524
|
+
self.nr_rewrites += value.reduce()
|
|
1485
1525
|
|
|
1486
1526
|
initial_subs = maude.Substitution(initial_subs)
|
|
1487
1527
|
|
|
@@ -1489,10 +1529,15 @@ class StratRunner:
|
|
|
1489
1529
|
initial_subs,
|
|
1490
1530
|
maxDepth=-1 if top else maude.UNBOUNDED)
|
|
1491
1531
|
|
|
1532
|
+
def reduced(self, t):
|
|
1533
|
+
"""Return the given term reduced and count the rewrites"""
|
|
1534
|
+
self.nr_rewrites += t.reduce() + 1
|
|
1535
|
+
return t
|
|
1536
|
+
|
|
1492
1537
|
def rlapp(self, args, stack):
|
|
1493
1538
|
"""Apply a rule"""
|
|
1494
1539
|
|
|
1495
|
-
self.pending += [self.current_state.copy(term=
|
|
1540
|
+
self.pending += [self.current_state.copy(term=self.reduced(t))
|
|
1496
1541
|
for t, *_ in self.get_rewrites(args, stack)]
|
|
1497
1542
|
|
|
1498
1543
|
self.next_pending()
|
|
@@ -1550,8 +1595,7 @@ class StratRunner:
|
|
|
1550
1595
|
except ValueError:
|
|
1551
1596
|
self.next_pending()
|
|
1552
1597
|
|
|
1553
|
-
|
|
1554
|
-
def compute_weights(weights, venv):
|
|
1598
|
+
def compute_weights(self, weights, venv):
|
|
1555
1599
|
"""Obtain the weights of a choice"""
|
|
1556
1600
|
|
|
1557
1601
|
# Instantiate and reduce the weights
|
|
@@ -1559,7 +1603,7 @@ class StratRunner:
|
|
|
1559
1603
|
weights = [venv.instantiate(w) for w in weights]
|
|
1560
1604
|
|
|
1561
1605
|
for w in weights:
|
|
1562
|
-
w.reduce()
|
|
1606
|
+
self.nr_rewrites += w.reduce()
|
|
1563
1607
|
|
|
1564
1608
|
# Convert the weights to Python's float
|
|
1565
1609
|
# (unreduced weights will be silently converted to zero)
|
|
@@ -1578,7 +1622,7 @@ class StratRunner:
|
|
|
1578
1622
|
# Instantiate the call term with the variable context
|
|
1579
1623
|
if stack.venv:
|
|
1580
1624
|
call = stack.venv.instantiate(call)
|
|
1581
|
-
call.reduce()
|
|
1625
|
+
self.nr_rewrites += call.reduce()
|
|
1582
1626
|
|
|
1583
1627
|
# Match the call term with every definition
|
|
1584
1628
|
pending = []
|
|
@@ -1661,7 +1705,7 @@ class StratRunner:
|
|
|
1661
1705
|
|
|
1662
1706
|
# Instantiated weight
|
|
1663
1707
|
this_weight = merged_subs.instantiate(weight)
|
|
1664
|
-
this_weight.reduce()
|
|
1708
|
+
self.nr_rewrites += this_weight.reduce()
|
|
1665
1709
|
|
|
1666
1710
|
match_data.append((merged_subs, context))
|
|
1667
1711
|
weights.append(float(this_weight))
|
|
@@ -1683,7 +1727,7 @@ class StratRunner:
|
|
|
1683
1727
|
|
|
1684
1728
|
# Start evaluating the first subterm
|
|
1685
1729
|
self.current_state = self.current_state.copy(term=merged_subs.instantiate(variables[0]),
|
|
1686
|
-
|
|
1730
|
+
stack=new_stack)
|
|
1687
1731
|
|
|
1688
1732
|
@staticmethod
|
|
1689
1733
|
def process_match(venv, match, ctx, pattern, variables):
|
|
@@ -1739,7 +1783,7 @@ class StratRunner:
|
|
|
1739
1783
|
initial_subs = {var: stack.venv.instantiate(value) for var, value in initial_subs.items()}
|
|
1740
1784
|
|
|
1741
1785
|
for value in initial_subs.values():
|
|
1742
|
-
value.reduce()
|
|
1786
|
+
self.nr_rewrites += value.reduce()
|
|
1743
1787
|
|
|
1744
1788
|
initial_subs_obj = maude.Substitution(initial_subs)
|
|
1745
1789
|
|
|
@@ -1758,7 +1802,7 @@ class StratRunner:
|
|
|
1758
1802
|
new_stack = RwcNode(parent=stack, index=k, subs=subs, context=(ctx(hole), hole))
|
|
1759
1803
|
# Start rewriting the left-hand side of the first condition fragment
|
|
1760
1804
|
new_term = subs.instantiate(rwc_lhs)
|
|
1761
|
-
new_term.reduce()
|
|
1805
|
+
self.nr_rewrites += new_term.reduce()
|
|
1762
1806
|
self.pending.append(self.current_state.copy(term=new_term, stack=new_stack))
|
|
1763
1807
|
|
|
1764
1808
|
self.next_pending()
|
|
@@ -1781,12 +1825,12 @@ class StratRunner:
|
|
|
1781
1825
|
if is_final:
|
|
1782
1826
|
context, hole = stack.context
|
|
1783
1827
|
new_term = maude.Substitution({hole: subs.instantiate(rwc_lhs)}).instantiate(context)
|
|
1784
|
-
new_term.reduce()
|
|
1828
|
+
self.nr_rewrites += new_term.reduce()
|
|
1785
1829
|
self.pending.append(self.current_state.copy(term=new_term, stack=stack.parent))
|
|
1786
1830
|
else:
|
|
1787
1831
|
new_stack = RwcNode(parent=stack.parent, index=stack.index, subs=subs, context=stack.context)
|
|
1788
1832
|
new_term = subs.instantiate(rwc_lhs)
|
|
1789
|
-
new_term.reduce()
|
|
1833
|
+
self.nr_rewrites += new_term.reduce()
|
|
1790
1834
|
self.pending.append(self.current_state.copy(term=new_term, stack=new_stack))
|
|
1791
1835
|
|
|
1792
1836
|
self.next_pending()
|
|
@@ -1801,7 +1845,7 @@ class StratRunner:
|
|
|
1801
1845
|
|
|
1802
1846
|
# Reduce them
|
|
1803
1847
|
for arg in dargs:
|
|
1804
|
-
arg.reduce()
|
|
1848
|
+
self.nr_rewrites += arg.reduce()
|
|
1805
1849
|
|
|
1806
1850
|
dargs = [float(arg) for arg in dargs]
|
|
1807
1851
|
|
|
@@ -1825,6 +1869,12 @@ class StratRunner:
|
|
|
1825
1869
|
# Do nothing (subclasses will)
|
|
1826
1870
|
self.current_state.pc += 1
|
|
1827
1871
|
|
|
1872
|
+
def kleene(self, args, stack):
|
|
1873
|
+
"""Keep track of iterations"""
|
|
1874
|
+
|
|
1875
|
+
# Do nothing (subclasses will)
|
|
1876
|
+
self.current_state.pc += 1
|
|
1877
|
+
|
|
1828
1878
|
|
|
1829
1879
|
def rebuild_term(term, stack, args):
|
|
1830
1880
|
"""Rebuild the term within nested matchrews"""
|
|
@@ -1848,7 +1898,7 @@ def rebuild_term(term, stack, args):
|
|
|
1848
1898
|
subs = maude.Substitution({
|
|
1849
1899
|
# ...the subterms that have already been rewritten, and...
|
|
1850
1900
|
**mrew_stack.done,
|
|
1851
|
-
#
|
|
1901
|
+
# ...the current and the pending subterms.
|
|
1852
1902
|
**dict(zip(pending_vars, [new_term] + mrew_stack.pending))
|
|
1853
1903
|
})
|
|
1854
1904
|
# Rebuild the subterm
|
|
@@ -1882,6 +1932,35 @@ class GraphExecutionState(StratRunner.State):
|
|
|
1882
1932
|
self.extra if extra is None else extra,
|
|
1883
1933
|
)
|
|
1884
1934
|
|
|
1935
|
+
def reset_extra(self):
|
|
1936
|
+
"""Reset the extra field"""
|
|
1937
|
+
pass # subclasses will
|
|
1938
|
+
|
|
1939
|
+
|
|
1940
|
+
class GraphState:
|
|
1941
|
+
"""State of the rewrite graph"""
|
|
1942
|
+
|
|
1943
|
+
def __init__(self, term):
|
|
1944
|
+
self.term = term
|
|
1945
|
+
# Set of graph states that follow by a rewrite
|
|
1946
|
+
self.children = set()
|
|
1947
|
+
# Temporary information about the last rewrite
|
|
1948
|
+
self.last_rewrite = None
|
|
1949
|
+
self.actions = {}
|
|
1950
|
+
self.solution = False
|
|
1951
|
+
self.valid = True
|
|
1952
|
+
# Each graph state has its own list of pending execution states
|
|
1953
|
+
self.pending = []
|
|
1954
|
+
# Subsearches for which a solution is reachable from here
|
|
1955
|
+
self.solve_subsearch = set()
|
|
1956
|
+
|
|
1957
|
+
@property
|
|
1958
|
+
def has_children(self):
|
|
1959
|
+
return self.children
|
|
1960
|
+
|
|
1961
|
+
def __repr__(self):
|
|
1962
|
+
return f'GraphState({self.term}, {self.children}, {self.solution})'
|
|
1963
|
+
|
|
1885
1964
|
|
|
1886
1965
|
class BadProbStrategy(Exception):
|
|
1887
1966
|
"""Bad probabilitistic strategy that cannot engender MDPs or DTMCs"""
|
|
@@ -1906,40 +1985,14 @@ class SubsearchNode(StackNode):
|
|
|
1906
1985
|
return f'SubsearchNode(id={self.subsearch_id}, dfs_base={self.dfs_base})'
|
|
1907
1986
|
|
|
1908
1987
|
|
|
1909
|
-
class
|
|
1910
|
-
"""Runner that extracts a
|
|
1911
|
-
|
|
1912
|
-
class GraphState:
|
|
1913
|
-
"""State of the graph
|
|
1914
|
-
|
|
1915
|
-
In addition to the actual states of the graph, there may be temporary fake
|
|
1916
|
-
states used to gather the evolutions of the branches of a choice. They are
|
|
1917
|
-
characterized by a None value in their term attribute.
|
|
1918
|
-
"""
|
|
1919
|
-
|
|
1920
|
-
def __init__(self, term):
|
|
1921
|
-
self.term = term
|
|
1922
|
-
# Set of graph states that follow by a rewrite
|
|
1923
|
-
self.children = set()
|
|
1924
|
-
# Set of choice alternatives (each an association of
|
|
1925
|
-
# weights or probabilities to graph states)
|
|
1926
|
-
self.child_choices = set()
|
|
1927
|
-
# Temporary information about the last rewrite
|
|
1928
|
-
self.last_rewrite = None
|
|
1929
|
-
self.actions = {}
|
|
1930
|
-
self.solution = False
|
|
1931
|
-
self.valid = True
|
|
1932
|
-
# Each graph state has its own list of pending execution states
|
|
1933
|
-
self.pending = []
|
|
1934
|
-
# Subsearches for which a solution is reachable from here
|
|
1935
|
-
self.solve_subsearch = set()
|
|
1936
|
-
|
|
1937
|
-
def __repr__(self):
|
|
1938
|
-
return f'GraphState({self.term}, {self.children}, {self.child_choices}, {self.solution})'
|
|
1988
|
+
class GraphRunner(StratRunner):
|
|
1989
|
+
"""Runner that extracts a graph"""
|
|
1939
1990
|
|
|
1940
|
-
def __init__(self, program, term):
|
|
1941
|
-
super().__init__(program, term, state_class=
|
|
1942
|
-
|
|
1991
|
+
def __init__(self, program, term, state_class=GraphExecutionState, graph_state_class=GraphState):
|
|
1992
|
+
super().__init__(program, term, state_class=state_class, seen_class=dict)
|
|
1993
|
+
# Class of the states of the graph
|
|
1994
|
+
self.graph_state_class = graph_state_class
|
|
1995
|
+
self.root_node = graph_state_class(term)
|
|
1943
1996
|
self.dfs_stack = [self.root_node]
|
|
1944
1997
|
|
|
1945
1998
|
# A dictionary from execution states to graph states: the latter
|
|
@@ -1951,59 +2004,13 @@ class MarkovRunner(StratRunner):
|
|
|
1951
2004
|
# different sucessor can be reached)
|
|
1952
2005
|
self.solution_states = {}
|
|
1953
2006
|
|
|
1954
|
-
def resolve_choice(self, choice, actions):
|
|
1955
|
-
"""Resolve a choice with weights into probabilities"""
|
|
1956
|
-
|
|
1957
|
-
# While choice is a sequence of (weight, state), new_choice is
|
|
1958
|
-
# a dictionary from state to probability
|
|
1959
|
-
new_choice = {}
|
|
1960
|
-
|
|
1961
|
-
for w, s in choice:
|
|
1962
|
-
if not s.valid:
|
|
1963
|
-
continue
|
|
1964
|
-
|
|
1965
|
-
# If s is not a fake state
|
|
1966
|
-
if s.term is not None:
|
|
1967
|
-
old_w = new_choice.get(s, 0.0)
|
|
1968
|
-
new_choice[s] = old_w + w
|
|
1969
|
-
|
|
1970
|
-
else:
|
|
1971
|
-
# Nondeteterministic and choice alternatives
|
|
1972
|
-
nd_opts, ch_opts = len(s.children), len(s.child_choices)
|
|
1973
|
-
|
|
1974
|
-
if nd_opts + ch_opts > 1:
|
|
1975
|
-
raise BadProbStrategy
|
|
1976
|
-
|
|
1977
|
-
# Copy the action information
|
|
1978
|
-
for target, info in s.actions.items():
|
|
1979
|
-
old_info = actions.get(target)
|
|
1980
|
-
|
|
1981
|
-
if old_info:
|
|
1982
|
-
old_info.extend(info)
|
|
1983
|
-
else:
|
|
1984
|
-
actions[target] = info
|
|
1985
|
-
|
|
1986
|
-
if nd_opts:
|
|
1987
|
-
s, = s.children
|
|
1988
|
-
old_w = new_choice.get(s, 0.0)
|
|
1989
|
-
new_choice[s] = old_w + w
|
|
1990
|
-
|
|
1991
|
-
if ch_opts:
|
|
1992
|
-
child_choice, = s.child_choices
|
|
1993
|
-
total_cw = sum(child_choice.values())
|
|
1994
|
-
for cs, cw in child_choice.items():
|
|
1995
|
-
old_w = new_choice.get(cs, 0.0)
|
|
1996
|
-
new_choice[cs] = old_w + cw * w / total_cw
|
|
1997
|
-
|
|
1998
|
-
return new_choice
|
|
1999
|
-
|
|
2000
2007
|
def get_solution_state(self, term):
|
|
2001
2008
|
"""Get the solution state for the given term"""
|
|
2002
2009
|
|
|
2003
2010
|
solution_state = self.solution_states.get(term)
|
|
2004
2011
|
|
|
2005
2012
|
if not solution_state:
|
|
2006
|
-
solution_state = self.
|
|
2013
|
+
solution_state = self.graph_state_class(term)
|
|
2007
2014
|
self.solution_states[term] = solution_state
|
|
2008
2015
|
|
|
2009
2016
|
return solution_state
|
|
@@ -2028,7 +2035,7 @@ class MarkovRunner(StratRunner):
|
|
|
2028
2035
|
return True
|
|
2029
2036
|
|
|
2030
2037
|
# This execution state is linked to a fake choice-generated graph state
|
|
2031
|
-
# that should be
|
|
2038
|
+
# that should be pushed to the DFS stack
|
|
2032
2039
|
elif push_state:
|
|
2033
2040
|
self.dfs_stack.append(push_state)
|
|
2034
2041
|
self.pending = push_state.pending
|
|
@@ -2038,37 +2045,24 @@ class MarkovRunner(StratRunner):
|
|
|
2038
2045
|
return True
|
|
2039
2046
|
|
|
2040
2047
|
# Check whether the graph state is valid
|
|
2041
|
-
graph_state.valid = graph_state.solution or graph_state.
|
|
2048
|
+
graph_state.valid = graph_state.solution or graph_state.has_children
|
|
2042
2049
|
|
|
2043
2050
|
# Link a solution state if there are children
|
|
2044
2051
|
# (these states are needed to explicit the self-loop for the stuttering
|
|
2045
2052
|
# extension of finite traces without introducing spurious executions)
|
|
2046
|
-
if graph_state.solution and
|
|
2053
|
+
if graph_state.solution and graph_state.has_children and \
|
|
2047
2054
|
graph_state not in graph_state.children:
|
|
2048
2055
|
solution_state = self.get_solution_state(graph_state.term)
|
|
2049
2056
|
graph_state.children.add(solution_state)
|
|
2050
2057
|
graph_state.actions[solution_state] = None
|
|
2051
2058
|
|
|
2052
|
-
|
|
2053
|
-
new_choices = []
|
|
2054
|
-
|
|
2055
|
-
for choice in graph_state.child_choices:
|
|
2056
|
-
resolved_choice = self.resolve_choice(choice, graph_state.actions)
|
|
2057
|
-
|
|
2058
|
-
if len(resolved_choice) == 1:
|
|
2059
|
-
child, = resolved_choice.keys()
|
|
2060
|
-
graph_state.children.add(child)
|
|
2061
|
-
|
|
2062
|
-
elif resolved_choice:
|
|
2063
|
-
new_choices.append(resolved_choice)
|
|
2064
|
-
|
|
2065
|
-
graph_state.child_choices = tuple(new_choices)
|
|
2059
|
+
self._on_state_exhausted(graph_state)
|
|
2066
2060
|
|
|
2067
2061
|
self.dfs_stack.pop()
|
|
2068
2062
|
|
|
2069
2063
|
if self.dfs_stack:
|
|
2070
2064
|
dfs_top = self.dfs_stack[-1]
|
|
2071
|
-
self.pending =
|
|
2065
|
+
self.pending = self.dfs_stack[-1].pending
|
|
2072
2066
|
|
|
2073
2067
|
# Add graph_state as a child if it is valid
|
|
2074
2068
|
if graph_state.valid and graph_state.term is not None:
|
|
@@ -2080,6 +2074,9 @@ class MarkovRunner(StratRunner):
|
|
|
2080
2074
|
self.current_state = None
|
|
2081
2075
|
return False
|
|
2082
2076
|
|
|
2077
|
+
def _on_state_exhausted(self, graph_state):
|
|
2078
|
+
pass # nothing to do here
|
|
2079
|
+
|
|
2083
2080
|
def pop(self, _, stack):
|
|
2084
2081
|
"""Return from a strategy call or similar construct"""
|
|
2085
2082
|
|
|
@@ -2095,9 +2092,9 @@ class MarkovRunner(StratRunner):
|
|
|
2095
2092
|
else:
|
|
2096
2093
|
graph_state = self.dfs_stack[-1]
|
|
2097
2094
|
|
|
2098
|
-
# Fake states
|
|
2099
|
-
# solution state as a child (this is also
|
|
2100
|
-
# marked states unless they do not have successors)
|
|
2095
|
+
# Fake states (used in the subclass MarkovRunner) are not marked
|
|
2096
|
+
# as solutions but added a solution state as a child (this is also
|
|
2097
|
+
# done later to marked states unless they do not have successors)
|
|
2101
2098
|
if graph_state.term is None:
|
|
2102
2099
|
graph_state.children.add(self.get_solution_state(self.current_state.term))
|
|
2103
2100
|
else:
|
|
@@ -2105,6 +2102,202 @@ class MarkovRunner(StratRunner):
|
|
|
2105
2102
|
|
|
2106
2103
|
self.next_pending()
|
|
2107
2104
|
|
|
2105
|
+
def notify(self, args, stack):
|
|
2106
|
+
"""Record a transition in the graph"""
|
|
2107
|
+
|
|
2108
|
+
state = self.current_state
|
|
2109
|
+
state.pc += 1
|
|
2110
|
+
|
|
2111
|
+
# Check whether this state has already been visited
|
|
2112
|
+
successor = stack.already_seen_table(state.pc, state.term)
|
|
2113
|
+
|
|
2114
|
+
# This is a new state, so add it to the graph
|
|
2115
|
+
if successor is None:
|
|
2116
|
+
new_term = rebuild_term(state.term, stack, args)
|
|
2117
|
+
|
|
2118
|
+
new_state = self.graph_state_class(new_term)
|
|
2119
|
+
stack.add_to_seen_table(state.pc, state.term, new_state)
|
|
2120
|
+
self.dfs_stack.append(new_state)
|
|
2121
|
+
self.pending = new_state.pending
|
|
2122
|
+
|
|
2123
|
+
# The extra information for the last rewrite is recorded
|
|
2124
|
+
self.dfs_stack[-2].last_rewrite = state.extra
|
|
2125
|
+
state.reset_extra()
|
|
2126
|
+
|
|
2127
|
+
else:
|
|
2128
|
+
if successor.valid:
|
|
2129
|
+
dfs_top = self.dfs_stack[-1]
|
|
2130
|
+
dfs_top.children.add(successor)
|
|
2131
|
+
dfs_top.actions.setdefault(successor, []).append(state.extra)
|
|
2132
|
+
|
|
2133
|
+
state.reset_extra()
|
|
2134
|
+
|
|
2135
|
+
# Handle the deactivation of the negative branch of
|
|
2136
|
+
# a subsearch when this state leaded to a solution
|
|
2137
|
+
to_be_solved = len(successor.solve_subsearch)
|
|
2138
|
+
|
|
2139
|
+
while stack and to_be_solved:
|
|
2140
|
+
subsearch_id = getattr(stack, 'subsearch_id', None)
|
|
2141
|
+
if subsearch_id in successor.solve_subsearch:
|
|
2142
|
+
stack.pc = None
|
|
2143
|
+
to_be_solved -= 1
|
|
2144
|
+
stack = stack.parent
|
|
2145
|
+
|
|
2146
|
+
self.next_pending()
|
|
2147
|
+
|
|
2148
|
+
def checkpoint(self, args, stack):
|
|
2149
|
+
"""Check whether this state has been visited and interrupt the execution"""
|
|
2150
|
+
|
|
2151
|
+
if stack.already_seen_table(self.current_state.pc, self.dfs_stack[-1]):
|
|
2152
|
+
self.next_pending()
|
|
2153
|
+
|
|
2154
|
+
else:
|
|
2155
|
+
stack.add_to_seen_table(self.current_state.pc, self.dfs_stack[-1], True)
|
|
2156
|
+
self.current_state.pc += 1
|
|
2157
|
+
|
|
2158
|
+
def subsearch(self, args, stack):
|
|
2159
|
+
"""Start a subsearch by pushing a stack node with continuation"""
|
|
2160
|
+
|
|
2161
|
+
# The same as StratRunner.subsearch, but forking the seen set. Moreover,
|
|
2162
|
+
# the size of the DFS stack is recorded to allow finding out which states
|
|
2163
|
+
# lead to solutions within the search (identified by the current PC)
|
|
2164
|
+
subsearch_stack = SubsearchNode(parent=stack, pc=args,
|
|
2165
|
+
seen=stack.seen.fork(self.current_state.pc),
|
|
2166
|
+
dfs_base=len(self.dfs_stack),
|
|
2167
|
+
subsearch_id=self.current_state.pc)
|
|
2168
|
+
self.current_state = self.current_state.copy(stack=subsearch_stack)
|
|
2169
|
+
|
|
2170
|
+
# Exactly the same as StratRunner.subsearch
|
|
2171
|
+
self.pending.append(self.current_state.copy(pc=args, stack=subsearch_stack, conditional=True))
|
|
2172
|
+
|
|
2173
|
+
def nofail(self, args, stack):
|
|
2174
|
+
"""Pop the stack and discard the continuation on failure"""
|
|
2175
|
+
|
|
2176
|
+
# Annotate the graph states with the current subsearch,
|
|
2177
|
+
# for which they provide a solution
|
|
2178
|
+
for node in self.dfs_stack[stack.dfs_base:]:
|
|
2179
|
+
node.solve_subsearch.add(stack.subsearch_id)
|
|
2180
|
+
|
|
2181
|
+
super().nofail(args, stack)
|
|
2182
|
+
|
|
2183
|
+
def rlapp(self, args, stack):
|
|
2184
|
+
"""Apply a rule"""
|
|
2185
|
+
|
|
2186
|
+
# Like the parent implementation, but the rule is recorded
|
|
2187
|
+
self.pending += [self.current_state.copy(term=self.reduced(t), extra=rl)
|
|
2188
|
+
for t, _, _, rl in self.get_rewrites(args, stack)]
|
|
2189
|
+
|
|
2190
|
+
self.next_pending()
|
|
2191
|
+
|
|
2192
|
+
def run(self):
|
|
2193
|
+
"""Run the strategy to generate the graph"""
|
|
2194
|
+
|
|
2195
|
+
self.solution = None
|
|
2196
|
+
self.pending = self.root_node.pending
|
|
2197
|
+
|
|
2198
|
+
while self.current_state:
|
|
2199
|
+
state = self.current_state
|
|
2200
|
+
|
|
2201
|
+
# The instruction to be executed
|
|
2202
|
+
inst = self.code[state.pc]
|
|
2203
|
+
|
|
2204
|
+
self.handlers[inst.type](inst.extra, state.stack)
|
|
2205
|
+
|
|
2206
|
+
return self.root_node, self.nr_rewrites
|
|
2207
|
+
|
|
2208
|
+
|
|
2209
|
+
class MarkovRunner(GraphRunner):
|
|
2210
|
+
"""Runner that extracts a DTMC or MDP"""
|
|
2211
|
+
|
|
2212
|
+
class GraphState(GraphState):
|
|
2213
|
+
"""State of the graph
|
|
2214
|
+
|
|
2215
|
+
In addition to the actual states of the graph, there may be temporary fake
|
|
2216
|
+
states used to gather the evolutions of the branches of a choice. They are
|
|
2217
|
+
characterized by a None value in their term attribute.
|
|
2218
|
+
"""
|
|
2219
|
+
|
|
2220
|
+
def __init__(self, term):
|
|
2221
|
+
super().__init__(term)
|
|
2222
|
+
|
|
2223
|
+
# Set of choice alternatives (each an association of
|
|
2224
|
+
# weights or probabilities to graph states)
|
|
2225
|
+
self.child_choices = set()
|
|
2226
|
+
|
|
2227
|
+
@property
|
|
2228
|
+
def has_children(self):
|
|
2229
|
+
return self.children or self.child_choices
|
|
2230
|
+
|
|
2231
|
+
def __repr__(self):
|
|
2232
|
+
return f'GraphState({self.term}, {self.children}, {self.child_choices}, {self.solution})'
|
|
2233
|
+
|
|
2234
|
+
def __init__(self, program, term):
|
|
2235
|
+
super().__init__(program, term, graph_state_class=self.GraphState)
|
|
2236
|
+
|
|
2237
|
+
def resolve_choice(self, choice, actions):
|
|
2238
|
+
"""Resolve a choice with weights into probabilities"""
|
|
2239
|
+
|
|
2240
|
+
# While choice is a sequence of (weight, state), new_choice is
|
|
2241
|
+
# a dictionary from state to probability
|
|
2242
|
+
new_choice = {}
|
|
2243
|
+
|
|
2244
|
+
for w, s in choice:
|
|
2245
|
+
if not s.valid:
|
|
2246
|
+
continue
|
|
2247
|
+
|
|
2248
|
+
# If s is not a fake state
|
|
2249
|
+
if s.term is not None:
|
|
2250
|
+
old_w = new_choice.get(s, 0.0)
|
|
2251
|
+
new_choice[s] = old_w + w
|
|
2252
|
+
|
|
2253
|
+
else:
|
|
2254
|
+
# Nondeterministic and choice alternatives
|
|
2255
|
+
nd_opts, ch_opts = len(s.children), len(s.child_choices)
|
|
2256
|
+
|
|
2257
|
+
if nd_opts + ch_opts > 1:
|
|
2258
|
+
raise BadProbStrategy
|
|
2259
|
+
|
|
2260
|
+
# Copy the action information
|
|
2261
|
+
for target, info in s.actions.items():
|
|
2262
|
+
old_info = actions.get(target)
|
|
2263
|
+
|
|
2264
|
+
if old_info:
|
|
2265
|
+
old_info.extend(info)
|
|
2266
|
+
else:
|
|
2267
|
+
actions[target] = info
|
|
2268
|
+
|
|
2269
|
+
if nd_opts:
|
|
2270
|
+
s, = s.children
|
|
2271
|
+
old_w = new_choice.get(s, 0.0)
|
|
2272
|
+
new_choice[s] = old_w + w
|
|
2273
|
+
|
|
2274
|
+
if ch_opts:
|
|
2275
|
+
child_choice, = s.child_choices
|
|
2276
|
+
total_cw = sum(child_choice.values())
|
|
2277
|
+
for cs, cw in child_choice.items():
|
|
2278
|
+
old_w = new_choice.get(cs, 0.0)
|
|
2279
|
+
new_choice[cs] = old_w + cw * w / total_cw
|
|
2280
|
+
|
|
2281
|
+
return new_choice
|
|
2282
|
+
|
|
2283
|
+
def _on_state_exhausted(self, graph_state):
|
|
2284
|
+
"""When a graph state is exhausted"""
|
|
2285
|
+
|
|
2286
|
+
# Adjust the probabilities of the choice operators
|
|
2287
|
+
new_choices = []
|
|
2288
|
+
|
|
2289
|
+
for choice in graph_state.child_choices:
|
|
2290
|
+
resolved_choice = self.resolve_choice(choice, graph_state.actions)
|
|
2291
|
+
|
|
2292
|
+
if len(resolved_choice) == 1:
|
|
2293
|
+
child, = resolved_choice.keys()
|
|
2294
|
+
graph_state.children.add(child)
|
|
2295
|
+
|
|
2296
|
+
elif resolved_choice:
|
|
2297
|
+
new_choices.append(resolved_choice)
|
|
2298
|
+
|
|
2299
|
+
graph_state.child_choices = tuple(new_choices)
|
|
2300
|
+
|
|
2108
2301
|
def choice(self, args, stack):
|
|
2109
2302
|
"""A choice node"""
|
|
2110
2303
|
|
|
@@ -2161,7 +2354,7 @@ class MarkovRunner(StratRunner):
|
|
|
2161
2354
|
|
|
2162
2355
|
# Instantiated weight
|
|
2163
2356
|
this_weight = merged_subs.instantiate(weight)
|
|
2164
|
-
this_weight.reduce()
|
|
2357
|
+
self.nr_rewrites += this_weight.reduce()
|
|
2165
2358
|
this_weight = float(this_weight)
|
|
2166
2359
|
|
|
2167
2360
|
if this_weight > 0.0:
|
|
@@ -2242,65 +2435,6 @@ class MarkovRunner(StratRunner):
|
|
|
2242
2435
|
|
|
2243
2436
|
self.next_pending()
|
|
2244
2437
|
|
|
2245
|
-
def checkpoint(self, args, stack):
|
|
2246
|
-
"""Check whether this state has been visited and interrupt the execution"""
|
|
2247
|
-
|
|
2248
|
-
if stack.already_seen_table(self.current_state.pc, self.dfs_stack[-1]):
|
|
2249
|
-
self.next_pending()
|
|
2250
|
-
|
|
2251
|
-
else:
|
|
2252
|
-
stack.add_to_seen_table(self.current_state.pc, self.dfs_stack[-1], True)
|
|
2253
|
-
self.current_state.pc += 1
|
|
2254
|
-
|
|
2255
|
-
def subsearch(self, args, stack):
|
|
2256
|
-
"""Start a subsearch by pushing a stack node with continuation"""
|
|
2257
|
-
|
|
2258
|
-
# The same as StratRunner.subsearch, but forking the seen set. Moreover,
|
|
2259
|
-
# the size of the DFS stack is recorded to allow finding out which states
|
|
2260
|
-
# lead to solutions within the search (identified by the current PC)
|
|
2261
|
-
subsearch_stack = SubsearchNode(parent=stack, pc=args,
|
|
2262
|
-
seen=stack.seen.fork(self.current_state.pc),
|
|
2263
|
-
dfs_base=len(self.dfs_stack),
|
|
2264
|
-
subsearch_id=self.current_state.pc)
|
|
2265
|
-
self.current_state = self.current_state.copy(stack=subsearch_stack)
|
|
2266
|
-
|
|
2267
|
-
# Exactly the same as StratRunner.subsearch
|
|
2268
|
-
self.pending.append(self.current_state.copy(pc=args, stack=subsearch_stack, conditional=True))
|
|
2269
|
-
|
|
2270
|
-
def nofail(self, args, stack):
|
|
2271
|
-
"""Pop the stack and discard the continuation on failure"""
|
|
2272
|
-
|
|
2273
|
-
# Annotate the graph states with the current subsearch,
|
|
2274
|
-
# for which they provides a solution
|
|
2275
|
-
for node in self.dfs_stack[stack.dfs_base:]:
|
|
2276
|
-
node.solve_subsearch.add(stack.subsearch_id)
|
|
2277
|
-
|
|
2278
|
-
super().nofail(args, stack)
|
|
2279
|
-
|
|
2280
|
-
def rlapp(self, args, stack):
|
|
2281
|
-
"""Apply a rule"""
|
|
2282
|
-
|
|
2283
|
-
self.pending += [self.current_state.copy(term=(t.reduce(), t)[1], extra=rl)
|
|
2284
|
-
for t, _, _, rl in self.get_rewrites(args, stack)]
|
|
2285
|
-
|
|
2286
|
-
self.next_pending()
|
|
2287
|
-
|
|
2288
|
-
def run(self):
|
|
2289
|
-
"""Run the strategy to generate the graph"""
|
|
2290
|
-
|
|
2291
|
-
self.solution = None
|
|
2292
|
-
self.pending = self.root_node.pending
|
|
2293
|
-
|
|
2294
|
-
while self.current_state:
|
|
2295
|
-
state = self.current_state
|
|
2296
|
-
|
|
2297
|
-
# The instruction to be executed
|
|
2298
|
-
inst = self.code[state.pc]
|
|
2299
|
-
|
|
2300
|
-
self.handlers[inst.type](inst.extra, state.stack)
|
|
2301
|
-
|
|
2302
|
-
return self.root_node
|
|
2303
|
-
|
|
2304
2438
|
|
|
2305
2439
|
class MetadataRunner(MarkovRunner):
|
|
2306
2440
|
"""Runner for the metadata assignment method with non-ground weights"""
|
|
@@ -2319,7 +2453,7 @@ class MetadataRunner(MarkovRunner):
|
|
|
2319
2453
|
|
|
2320
2454
|
for t, sb, _, rl in self.get_rewrites(args, stack):
|
|
2321
2455
|
# Term.apply does not normalize output terms
|
|
2322
|
-
t.reduce()
|
|
2456
|
+
self.nr_rewrites += t.reduce() + 1
|
|
2323
2457
|
|
|
2324
2458
|
# Evaluate the metadata weight
|
|
2325
2459
|
weight = self.stmt_map.get(rl, 1.0)
|
|
@@ -2327,7 +2461,7 @@ class MetadataRunner(MarkovRunner):
|
|
|
2327
2461
|
# If it is not a literal, we should reduce the term
|
|
2328
2462
|
if not isinstance(weight, float):
|
|
2329
2463
|
weight = sb.instantiate(weight)
|
|
2330
|
-
weight.reduce()
|
|
2464
|
+
self.nr_rewrites += weight.reduce()
|
|
2331
2465
|
weight = float(weight)
|
|
2332
2466
|
|
|
2333
2467
|
self.pending.append(self.current_state.copy(term=t, extra=(rl, weight)))
|
|
@@ -2338,7 +2472,7 @@ class MetadataRunner(MarkovRunner):
|
|
|
2338
2472
|
class RandomRunner(StratRunner):
|
|
2339
2473
|
"""Runner that resolves every choice locally at random without backtracking.
|
|
2340
2474
|
|
|
2341
|
-
Instead of solutions of the strategy, the run method returns the
|
|
2475
|
+
Instead of solutions of the strategy, the run method returns the successive steps
|
|
2342
2476
|
of a single random rewriting path. Hence, conditionals do no work with this runner,
|
|
2343
2477
|
since exploration is not exhaustive, and failed executions are not discarded."""
|
|
2344
2478
|
|
|
@@ -2355,7 +2489,7 @@ class RandomRunner(StratRunner):
|
|
|
2355
2489
|
subsearches, trivial_subsearch = [], False
|
|
2356
2490
|
|
|
2357
2491
|
for k, inst in enumerate(self.code.inst):
|
|
2358
|
-
# Jumps with multiple
|
|
2492
|
+
# Jumps with multiple destinations
|
|
2359
2493
|
if inst.type == Instruction.JUMP and len(inst.extra) > 1:
|
|
2360
2494
|
inst.extra = inst.extra[:1]
|
|
2361
2495
|
usermsgs.print_warning(f'Unquantified nondeterminism detected in {current_name}. '
|
|
@@ -2396,9 +2530,9 @@ class RandomRunner(StratRunner):
|
|
|
2396
2530
|
return False
|
|
2397
2531
|
|
|
2398
2532
|
# Otherwise, a pending state is chosen at random and all other pending
|
|
2399
|
-
# states are discarded. Indeed, pending states are not actually pending but
|
|
2533
|
+
# states are discarded. Indeed, pending states are not actually pending, but
|
|
2400
2534
|
# they have just been generated by a nondeterministic instruction (rule
|
|
2401
|
-
# application, call, matchrew...) before calling this method.
|
|
2535
|
+
# application, call, matchrew...) before calling this method. Reimplementing
|
|
2402
2536
|
# these instructions is avoided by this trick.
|
|
2403
2537
|
self.current_state = random.choice(self.pending)
|
|
2404
2538
|
self.pending.clear()
|