umaudemc 0.16.0__py3-none-any.whl → 0.17.0__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 CHANGED
@@ -1 +1 @@
1
- __version__ = '0.16.0'
1
+ __version__ = '0.17.0'
umaudemc/__main__.py CHANGED
@@ -318,6 +318,7 @@ def build_parser():
318
318
  )
319
319
  parser_scheck.add_argument(
320
320
  '-D',
321
+ metavar='name=value',
321
322
  action='append',
322
323
  help='Define a constant to be used in QuaTEx expressions'
323
324
  )
@@ -19,16 +19,16 @@ def show_results(program, nsims, qdata):
19
19
  qdata_it = iter(qdata)
20
20
  q = next(qdata_it, None)
21
21
 
22
- for k, (fname, line, column, params) in enumerate(program.query_locations):
22
+ for k, query in enumerate(program.queries):
23
23
  # Print the query name and location only if there are many
24
24
  if program.nqueries > 1:
25
25
  # If the number of simulation is lower for this query
26
26
  sim_detail = f' ({q.n} simulations)' if q.n != nsims else ''
27
27
 
28
- print(f'Query {k + 1} ({fname}:{line}:{column}){sim_detail}')
28
+ print(f'Query {k + 1} ({query.filename}:{query.line}:{query.column}){sim_detail}')
29
29
 
30
30
  # For parametric queries, we show the result for every value
31
- var = params[0] if params else None
31
+ var = query.parameters[0] if query.parameters else None
32
32
 
33
33
  while q and q.query == k:
34
34
  if var:
@@ -93,10 +93,10 @@ def plot_results(program, qdata):
93
93
  return
94
94
 
95
95
  for k, xs, ys, rs in results:
96
- line, column, _ = program.query_locations[k]
96
+ query = program.queries[k]
97
97
 
98
98
  # Plot the mean
99
- p = plt.plot(xs, ys, label=f'{line}:{column}')
99
+ p = plt.plot(xs, ys, label=f'{query.line}:{query.column}')
100
100
  # Plot the confidence interval
101
101
  plt.fill_between(xs, [y - r for y, r in zip(ys, rs)],
102
102
  [y + r for y, r in zip(ys, rs)],
@@ -96,8 +96,8 @@ class Worker:
96
96
  # (delta, its second argument, does not matter because
97
97
  # convergence is not evaluated by the worker)
98
98
  qdata = [QueryData(k, 1.0, idict)
99
- for k, qinfo in enumerate(program.query_locations)
100
- for idict in make_parameter_dicts(qinfo[3])]
99
+ for k, qinfo in enumerate(program.queries)
100
+ for idict, _ in make_parameter_dicts(qinfo, 1.0)]
101
101
 
102
102
  sums = array('d', [0.0] * len(qdata))
103
103
  sum_sq = array('d', [0.0] * len(qdata))
umaudemc/distributed.py CHANGED
@@ -277,9 +277,9 @@ def distributed_check(args, initial_data, min_sim, max_sim, program, constants,
277
277
  ibuffer = array('i')
278
278
 
279
279
  # Query data
280
- qdata = [QueryData(k, args.delta, idict)
281
- for k, qinfo in enumerate(program.query_locations)
282
- for idict in make_parameter_dicts(qinfo[3])]
280
+ qdata = [QueryData(k, delta, idict)
281
+ for k, qinfo in enumerate(program.queries)
282
+ for idict, delta in make_parameter_dicts(qinfo, args.delta)]
283
283
  nqueries = len(qdata)
284
284
  num_sims = 0
285
285
 
umaudemc/quatex.py CHANGED
@@ -8,6 +8,19 @@ import os
8
8
  from . import usermsgs
9
9
 
10
10
 
11
+ class QuaTExQuery:
12
+ """QuaTeX query information"""
13
+
14
+ def __init__(self, fname, line, column, expr, parameters, delta):
15
+ self.filename = fname # file cointing the query
16
+ self.line = line # query location
17
+ self.column = column
18
+ self.parameters = parameters # parameters as (variable, start, step, end)
19
+
20
+ self.expr = expr # query expression (only needed for compilation)
21
+ self.delta = delta # delta value
22
+
23
+
11
24
  class QuaTExProgram:
12
25
  """Compiled QuaTEx program"""
13
26
 
@@ -23,7 +36,7 @@ class QuaTExProgram:
23
36
  self.nqueries = len(slots) - ndefs
24
37
 
25
38
  # Query information (file name, line, column, and parameters)
26
- self.query_locations = qinfo
39
+ self.queries = qinfo
27
40
 
28
41
 
29
42
  class QuaTExLexer:
@@ -171,6 +184,7 @@ class QuaTExLexer:
171
184
  self.ltype = self.LT_STRING
172
185
  self._capture_string()
173
186
 
187
+ # Names cannot start with a number
174
188
  elif c.isalpha() or c == '$':
175
189
  self.ltype = self.LT_NAME
176
190
  self._capture(self._is_name)
@@ -211,9 +225,9 @@ class QuaTExLexer:
211
225
  class QuaTExParser:
212
226
  """Parser for QuaTEx"""
213
227
 
214
- PS_IFC = 0 # condition
215
- PS_IFT = 1 # true branch
216
- PS_IFF = 2 # negative branch
228
+ PS_IF_COND = 0 # condition
229
+ PS_IF_THEN = 1 # true branch
230
+ PS_IF_ELSE = 2 # negative branch
217
231
  PS_PAREN = 3 # parenthesis
218
232
  PS_ARITH = 4 # completing an arithmetic expression
219
233
  PS_CALLARGS = 5 # call arguments
@@ -223,6 +237,7 @@ class QuaTExParser:
223
237
  BINOPS_PREC = (4, 4, 3, 3, 3, 11, 12, 7, 7, 6, 6, 6, 6)
224
238
  BINOPS_AST = (ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod, ast.And, ast.Or, ast.Eq,
225
239
  ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)
240
+ # Kind of binary operator (0 = BinOp, 1 = BoolOp, 2 = Compare)
226
241
  BINOPS_CMP = (0, ) * 5 + (1, ) * 2 + (2, ) * 6
227
242
  # Unary operator and its precedence (as in C)
228
243
  UNARY_OPS = ('!', )
@@ -238,13 +253,14 @@ class QuaTExParser:
238
253
  self.pending_lexers = []
239
254
  self.seen_files = set() if filename.startswith('<') else {os.path.realpath(filename)}
240
255
 
241
- # Parameters of the current function
242
- self.fvars = []
243
- # Whether the variables that may occur
244
- # in an expression are known
256
+ # Whether the variables that may occur in an expression are known
245
257
  self.known_vars = True
258
+ # Parameters of the current function if self.known_vars
259
+ # or variables found in the last processed expression otherwise
260
+ self.fvars = []
246
261
  # Constants defined outside
247
262
  self.constants = {} if constants is None else constants
263
+ self.pending_constants = []
248
264
 
249
265
  # State stack for parsing expressions
250
266
  self.stack = []
@@ -253,7 +269,7 @@ class QuaTExParser:
253
269
  # Whether parsing errors have been encountered
254
270
  self.ok = True
255
271
 
256
- # Compilation slot indices for each number
272
+ # Compilation slot indices for each element
257
273
  self.fslots = {}
258
274
  self.calls = []
259
275
  self.observations = []
@@ -284,13 +300,29 @@ class QuaTExParser:
284
300
 
285
301
  return True
286
302
 
303
+ def _expect_any(self, *options):
304
+ """Check whether any of the given strings is the next token"""
305
+
306
+ atoken = self.lexer.get_token()
307
+ options_str = ', '.join(f'"{opt}"' for opt in options)
308
+
309
+ if atoken is None:
310
+ self._eprint(f'unexpected end of file where any of {options_str} is required.')
311
+ return None
312
+
313
+ if atoken not in options:
314
+ self._eprint(f'unexpected token "{atoken}" where any of {options_str} is required.')
315
+ return None
316
+
317
+ return atoken
318
+
287
319
  def _in_state(self, state):
288
320
  """Check whether the parser is in the given state"""
289
321
 
290
322
  return self.stack and self.stack[-1] == state
291
323
 
292
324
  def _next_token(self):
293
- """Get the next token without potential file exhaustion"""
325
+ """Get the next token dealing with file exhaustion"""
294
326
 
295
327
  token = self.lexer.get_token()
296
328
 
@@ -332,40 +364,89 @@ class QuaTExParser:
332
364
  self._eprint(f'unexpected token "{var_name}" where a variable name is required.')
333
365
  return (None, ) * 3
334
366
 
367
+ if not self._expect(','):
368
+ return (None, ) * 3
369
+
335
370
  # Parameter specification (name, initial value, step, last value)
336
371
  spec = [var_name]
337
372
 
338
- # Parse the initial value, step, and last value
339
- for _ in range(3):
340
- if not self._expect(','):
341
- return (None, ) * 3
373
+ # Make a copy of the expression variables, since we continue in
374
+ # known variables mode with no variables allowed
375
+ expr_vars = tuple(self.fvars)
376
+ self.fvars.clear()
342
377
 
343
- token = self.lexer.get_token()
378
+ # Parse the initial value, step, and last value
379
+ for k in range(3):
380
+ expr = self._parse_constexpr(',' if k < 2 else ')')
344
381
 
345
- if self.lexer.ltype != self.lexer.LT_NUMBER:
346
- self._eprint(f'unexpected token "{token}" where a number is required.')
382
+ if expr is None:
347
383
  return (None, ) * 3
348
384
 
349
- spec.append(float(token))
350
-
351
- # Check the closing parenthesis
352
- if not self._expect(')'):
353
- return (None, ) * 3
385
+ spec.append(float(expr))
354
386
 
355
387
  # Check whether the variables in the expressions are the parameter
356
- for var, line, column in self.fvars:
388
+ for var, line, column in expr_vars:
357
389
  if var != var_name:
358
390
  self._eprint(f'unknown variable "{var}".', line=line, column=column)
359
391
  self.ok = False
360
392
 
393
+ return tuple(spec)
394
+
395
+ def _parse_delta(self, parameter):
396
+ """Parse the delta annotation"""
397
+
398
+ if not self._expect('delta', '='):
399
+ return None
400
+
401
+ # Only the parameter can appear as free variable in the expression
361
402
  self.fvars.clear()
362
403
 
363
- return tuple(spec)
404
+ if parameter:
405
+ self.fvars.append(parameter[0])
406
+
407
+ if (delta := self._parse_expr(';', constexpr=True)) is None:
408
+ return None
364
409
 
365
- def _parse_expr(self, end_token, inside_def=False):
410
+ # Prepare delta as a function on the parameter (if any)
411
+ args = [ast.arg(parameter[0])] if parameter else []
412
+
413
+ delta_fn = ast.Expression(ast.Lambda(ast.arguments(args=args), delta))
414
+ ast.fix_missing_locations(delta_fn)
415
+
416
+ return eval(compile(delta_fn, '<delta>', 'eval'), {}, {})
417
+
418
+ def _parse_constexpr(self, end_token):
419
+ """Parse a constant expression as a number"""
420
+
421
+ # Soft errors, like unbound variables, are hard here because
422
+ # we run the code to obtain a value for the expression
423
+ old_ok, self.ok = self.ok, True
424
+
425
+ if (expr := self._parse_expr(end_token, constexpr=True)) is None:
426
+ return None
427
+
428
+ elif not self.ok:
429
+ return 0.0 # dummy value to keep running
430
+
431
+ self.ok = old_ok
432
+
433
+ # Evaluate the expression
434
+ expr = ast.Expression(expr)
435
+ ast.fix_missing_locations(expr)
436
+
437
+ result = eval(compile(expr, self.lexer.filename, 'eval'), {}, {})
438
+
439
+ # Check whether it is a number
440
+ if not isinstance(result, (int, float)):
441
+ self._eprint(f'"{result}" obtained while a number is expected.')
442
+ return None
443
+
444
+ return result
445
+
446
+ def _parse_expr(self, end_token, constexpr=False):
366
447
  """Parse an expression"""
367
448
 
368
- # Current expression
449
+ # Current expression (as a Python AST node)
369
450
  current = None
370
451
  # Number of nested conditions in the current position
371
452
  inside_cond = 0
@@ -400,13 +481,13 @@ class QuaTExParser:
400
481
  self._eprint('misplaced "if" keyword.')
401
482
  return None
402
483
 
403
- self.stack.append(self.PS_IFC)
484
+ self.stack.append(self.PS_IF_COND)
404
485
  arg_stack.append([])
405
486
  inside_cond += 1
406
487
 
407
488
  elif token == 'then':
408
- if current and self._in_state(self.PS_IFC):
409
- self.stack[-1] = self.PS_IFT
489
+ if current and self._in_state(self.PS_IF_COND):
490
+ self.stack[-1] = self.PS_IF_THEN
410
491
  arg_stack[-1].append(current)
411
492
  current = None
412
493
  inside_cond -= 1
@@ -415,8 +496,8 @@ class QuaTExParser:
415
496
  return None
416
497
 
417
498
  elif token == 'else':
418
- if current and self._in_state(self.PS_IFT):
419
- self.stack[-1] = self.PS_IFF
499
+ if current and self._in_state(self.PS_IF_THEN):
500
+ self.stack[-1] = self.PS_IF_ELSE
420
501
  arg_stack[-1].append(current)
421
502
  current = None
422
503
  else:
@@ -424,7 +505,7 @@ class QuaTExParser:
424
505
  return None
425
506
 
426
507
  elif token == 'fi':
427
- if current and self._in_state(self.PS_IFF):
508
+ if current and self._in_state(self.PS_IF_ELSE):
428
509
  arg_stack[-1].append(current)
429
510
  current = ast.IfExp(*arg_stack[-1])
430
511
  arg_stack.pop()
@@ -442,6 +523,10 @@ class QuaTExParser:
442
523
  self._eprint('the next operator # cannot be used in conditions or call arguments.')
443
524
  self.ok = False
444
525
 
526
+ elif constexpr:
527
+ self._eprint('the next operator # cannot be used in constant contexts.')
528
+ self.ok = False
529
+
445
530
  # A function call should follow
446
531
  token = self.lexer.get_token()
447
532
  line, column = self.lexer.sline, self.lexer.scolumn
@@ -455,7 +540,8 @@ class QuaTExParser:
455
540
  inside_next = True
456
541
  call_name, call_line, call_column = token, line, column
457
542
 
458
- elif token == 'discard' and not (inside_cond or call_name):
543
+ # discard is a soft keyword
544
+ elif token == 'discard' and not (inside_cond or call_name or constexpr):
459
545
  if current:
460
546
  self._eprint('misplaced discard keyword.')
461
547
  return None
@@ -491,10 +577,15 @@ class QuaTExParser:
491
577
  self._eprint(f'argument missing after a comma in a call to "{call_name}".')
492
578
  self.ok = False
493
579
 
580
+ # Call are internally represented as tuples that indicate whether
581
+ # the call is preceded by next (i.e. to be evaluated after a step)
582
+ # and which code slot contains the callee definition
494
583
  slot = self.fslots.setdefault(call_name, len(self.fslots))
495
584
  current = ast.Tuple([ast.Constant(inside_next), ast.Constant(slot), *args], ast.Load())
496
- current.custom_loc = (call_line, call_column)
497
- self.calls.append((call_name, call_line, call_column, len(args)))
585
+ # We keep the source location for error reporting (using the attributes
586
+ # lineno and col_offset of the AST node only here is not possible)
587
+ current.custom_loc = dict(fname=self.lexer.filename, line=call_line, column=call_column)
588
+ self.calls.append((call_name, self.lexer.filename, call_line, call_column, len(args)))
498
589
  inside_next = False
499
590
  call_name = None
500
591
 
@@ -549,6 +640,10 @@ class QuaTExParser:
549
640
  self._eprint(f'"{token}" is called in a condition or call argument, but this is not allowed.')
550
641
  return None
551
642
 
643
+ if constexpr:
644
+ self._eprint(f'"{token}" is called in a constant context, but calls are not allowed.')
645
+ return None
646
+
552
647
  self.stack.append(self.PS_CALLARGS)
553
648
  arg_stack.append([])
554
649
  call_name = token
@@ -558,17 +653,16 @@ class QuaTExParser:
558
653
  else:
559
654
  current = ast.Name(token, ast.Load())
560
655
 
561
- if not self.known_vars:
656
+ # Constants (from the command line) start with $, but variables can also
657
+ if token.startswith('$') and (value := self.constants.get(token[1:])) is not None:
658
+ current = ast.Constant(value)
659
+
660
+ elif not self.known_vars:
562
661
  self.fvars.append((token, line, column))
563
662
 
564
663
  elif token not in self.fvars:
565
- # The variable is an externally-defined constant
566
- if token.startswith('$') and (value := self.constants.get(token[1:])) is not None:
567
- current = ast.Constant(value)
568
-
569
- else:
570
- self._eprint(f'unknown variable "{token}".', line=line, column=column)
571
- self.ok = False
664
+ self._eprint(f'unknown variable "{token}".', line=line, column=column)
665
+ self.ok = False
572
666
 
573
667
 
574
668
  # We continue with the peeked token
@@ -694,22 +788,29 @@ class QuaTExParser:
694
788
  if parameter:
695
789
  self.known_vars = False
696
790
 
697
- expr = self._parse_expr(']')
791
+ if not (expr := self._parse_expr(']')):
792
+ return False
698
793
 
699
794
  self.known_vars = True
700
795
 
701
796
  # Parse parameter specification in parametric queries
702
797
  parameter = self._parse_parameter() if parameter else None
798
+ delta = None
703
799
 
704
- if not expr or not self._expect(';'):
800
+ # Check whether there is a "with delta = <number>" prefix
801
+ if (token := self._expect_any('with', ';')) is None:
705
802
  return False
706
803
 
804
+ if token == 'with':
805
+ if (delta := self._parse_delta(parameter)) is None:
806
+ return False
807
+
707
808
  # Ignore parameterized expressions with empty range
708
809
  if parameter and parameter[1] > parameter[3]:
709
810
  usermsgs.print_warning_loc(self.lexer.filename, line, column,
710
811
  'ignoring parametric query with empty range.')
711
812
  else:
712
- self.queries.append((self.lexer.filename, line, column, expr, parameter))
813
+ self.queries.append(QuaTExQuery(self.lexer.filename, line, column, expr, parameter, delta))
713
814
 
714
815
  # Function definition -- <name> ( <args> ) = <expr> ;
715
816
  else:
@@ -744,7 +845,7 @@ class QuaTExParser:
744
845
  if not self._expect('='):
745
846
  return False
746
847
 
747
- expr = self._parse_expr(';', inside_def=True)
848
+ expr = self._parse_expr(';')
748
849
 
749
850
  if not expr:
750
851
  return False
@@ -763,8 +864,7 @@ class QuaTExParser:
763
864
  expr, tail_pos = pending.pop()
764
865
 
765
866
  if isinstance(expr, ast.Tuple) and not tail_pos:
766
- self._eprint('non-tail calls are not allowed.',
767
- line=expr.custom_loc[0], column=expr.custom_loc[1])
867
+ self._eprint('non-tail calls are not allowed.', **expr.custom_loc)
768
868
  return False
769
869
 
770
870
  elif isinstance(expr, ast.UnaryOp):
@@ -799,17 +899,17 @@ class QuaTExParser:
799
899
  arities[name] = len(args)
800
900
 
801
901
  # Check whether all calls are well-defined
802
- for name, line, column, arity in self.calls:
902
+ for name, fname, line, column, arity in self.calls:
803
903
  def_arity = arities.get(name)
804
904
 
805
905
  if def_arity is None:
806
906
  self._eprint(f'call to undefined function "{name}".',
807
- line=line, column=column)
907
+ line=line, column=column, fname=fname)
808
908
  ok = False
809
909
 
810
910
  elif arity != def_arity:
811
911
  self._eprint(f'wrong number of arguments in a call to "{name}" ({arity} given, but {def_arity} expected).',
812
- line=line, column=column)
912
+ line=line, column=column, fname=fname)
813
913
  ok = False
814
914
 
815
915
  # Check all calls are tail in expression
@@ -817,8 +917,8 @@ class QuaTExParser:
817
917
  if not self._check_tail(expr):
818
918
  ok = False
819
919
 
820
- for _, _, _, expr, _ in self.queries:
821
- if not self._check_tail(expr):
920
+ for query in self.queries:
921
+ if not self._check_tail(query.expr):
822
922
  ok = False
823
923
 
824
924
  if not ok:
@@ -848,22 +948,24 @@ class QuaTExParser:
848
948
  line=line, column=column)
849
949
  ok = False
850
950
 
851
- for k, (fname, line, column, expr, _) in enumerate(self.queries):
951
+ for k, query in enumerate(self.queries):
852
952
  try:
853
- expr = ast.Expression(expr)
953
+ expr = ast.Expression(query.expr)
854
954
  ast.fix_missing_locations(expr)
855
- slots[used_defs + k] = compile(expr, filename=f'query{fname}:{line}:{column}', mode='eval')
955
+ slots[used_defs + k] = compile(expr, filename=f'query{query.filename}:{query.line}:{query.column}', mode='eval')
856
956
 
857
957
  except TypeError:
858
958
  self._eprint('this query cannot cannot be compiled.',
859
- line=line, column=column, fname=fname)
959
+ line=query.line, column=query.column, fname=query.fname)
860
960
  ok = False
861
961
 
962
+ # No longer needed
963
+ query.expr = None
964
+
862
965
  if not ok:
863
966
  return None
864
967
 
865
- return QuaTExProgram(slots, varnames, len(self.fslots),
866
- tuple((fname, line, column, params) for fname, line, column, _, params in self.queries))
968
+ return QuaTExProgram(slots, varnames, len(self.fslots), self.queries)
867
969
 
868
970
 
869
971
  def parse_quatex(input_file, filename='<string>', legacy=False, constants=None):
umaudemc/simulators.py CHANGED
@@ -109,6 +109,42 @@ class StrategyStepSimulator(BaseSimulator):
109
109
  self.step += 1
110
110
 
111
111
 
112
+ class RuleStepSimulator(BaseSimulator):
113
+ """Simulator where rule application (potentially using random) is the step"""
114
+
115
+ def __init__(self, initial):
116
+ super().__init__(initial)
117
+
118
+ # See the PMaude simulator to an explanation for why we need that
119
+ self.random = None
120
+
121
+ if nat_kind := self.module.findSort('Nat').kind():
122
+ if random := self.module.findSymbol('random', (nat_kind,), nat_kind):
123
+ self.random = random(self.module.parseTerm('1', nat_kind))
124
+
125
+ def restart(self):
126
+ """Restart simulator"""
127
+
128
+ super().restart()
129
+
130
+ # PMaude uses Maude's random symbol, which is memoryless
131
+ # and deterministic for a fixed seed, so we need a new seed
132
+ if self.random:
133
+ self.random.copy().reduce()
134
+ maude.setRandomSeed(random.getrandbits(31))
135
+
136
+ def next_step(self):
137
+ """Perform a step of the simulation"""
138
+
139
+ # Application of any executable rule
140
+ # (assumming it is deterministic)
141
+ next_state, *_ = next(self.state.apply(None), (None,))
142
+
143
+ if next_state is not None:
144
+ self.state = next_state
145
+ self.step += 1
146
+
147
+
112
148
  def all_children(graph, state):
113
149
  """All children of a state in a graph"""
114
150
 
@@ -279,8 +315,8 @@ class PMaudeSimulator(BaseSimulator):
279
315
  self.state.rewrite()
280
316
 
281
317
  # Try to find Maude's random symbol for calculating random(1). If the
282
- # PMaude specification only reduces random(0), even after resetting
283
- # the random seed for a new simulation, it will take the same value
318
+ # PMaude specification only reduces random(0), even after resetting the
319
+ # random seed for a new simulation, it will take the same cached value
284
320
  self.random = self.module.findSymbol('random', (nat_kind,), nat_kind)
285
321
 
286
322
  if self.random:
@@ -473,12 +509,15 @@ def get_simulator(method, data):
473
509
  method = 'step' if data.strategy else 'uniform'
474
510
 
475
511
  # Check whether a strategy is provided for methods that require it
476
- if not data.strategy and method in ('step', 'strategy-fast', 'strategy'):
512
+ if not data.strategy and method in ('strategy-fast', 'strategy'):
477
513
  usermsgs.print_error(f'No strategy is provided for the {method} assignment method.')
478
514
  return None
479
515
 
480
516
  if method == 'step':
481
- return StrategyStepSimulator(data.term, data.strategy)
517
+ if data.strategy:
518
+ return StrategyStepSimulator(data.term, data.strategy)
519
+ else:
520
+ return RuleStepSimulator(data.term)
482
521
 
483
522
  if method == 'strategy-fast':
484
523
  return StrategyPathSimulator(data.module, data.term, data.strategy)
umaudemc/statistical.py CHANGED
@@ -120,17 +120,17 @@ class QueryData:
120
120
  self.discarded = 0
121
121
 
122
122
 
123
- def make_parameter_dicts(qinfo):
123
+ def make_parameter_dicts(qinfo, delta):
124
124
  """Make the initial variable mapping for the parameters of a query"""
125
125
 
126
- if qinfo is None:
127
- yield {}
126
+ if qinfo.parameters is None:
127
+ yield {}, (qinfo.delta() if qinfo.delta else delta)
128
128
 
129
129
  else:
130
- var, x, step, end = qinfo
130
+ var, x, step, end = qinfo.parameters
131
131
 
132
132
  while x <= end:
133
- yield {var: x}
133
+ yield {var: x}, (qinfo.delta(x) if qinfo.delta else delta)
134
134
  x += step
135
135
 
136
136
 
@@ -148,14 +148,18 @@ def check_interval(qdata, num_sims, min_sim, alpha, quantile, verbose):
148
148
  elif query.n == 0:
149
149
  converged = False
150
150
  continue
151
+ # A single execution
152
+ elif query.n == 1:
153
+ query.mu, query.s, query.h = query.sum, 0.0, 0.0
154
+ # General case
155
+ else:
156
+ # The radius encloses the confidence level in the reference
157
+ # distribution for calculating confidence intervals
158
+ tinv = quantile(query.n - 1, 1 - alpha / 2) / math.sqrt(query.n)
151
159
 
152
- # The radius encloses the confidence level in the reference
153
- # distribution for calculating confidence intervals
154
- tinv = quantile(query.n - 1, 1 - alpha / 2) / math.sqrt(query.n)
155
-
156
- query.mu = query.sum / query.n
157
- query.s = math.sqrt(max(query.sum_sq - query.sum * query.mu, 0.0) / (query.n - 1))
158
- query.h = query.s * tinv
160
+ query.mu = query.sum / query.n
161
+ query.s = math.sqrt(max(query.sum_sq - query.sum * query.mu, 0.0) / (query.n - 1))
162
+ query.h = query.s * tinv
159
163
 
160
164
  if query.h <= query.delta and query.n >= min_sim:
161
165
  query.converged = True
@@ -328,9 +332,9 @@ def qdata_to_dict(num_sims, qdata, program):
328
332
  qdata_it = iter(qdata)
329
333
  q = next(qdata_it, None)
330
334
 
331
- for k, (fname, line, column, params) in enumerate(program.query_locations):
335
+ for k, query in enumerate(program.queries):
332
336
  # For parametric queries, we return an array of values
333
- if params:
337
+ if query.parameters:
334
338
  mean, std, radius, count, discarded = [], [], [], [], []
335
339
 
336
340
  while q and q.query == k:
@@ -342,13 +346,15 @@ def qdata_to_dict(num_sims, qdata, program):
342
346
  q = next(qdata_it, None)
343
347
 
344
348
  # We also write information about the parameter
345
- param_info = {'params': [dict(name=params[0], start=params[1], step=params[2], stop=params[3])]}
349
+ param_info = {'params': [dict(zip(('name', 'start', 'step', 'stop'), query.parameters))]}
346
350
 
347
351
  else:
348
352
  mean, std, radius, count, discarded = q.mu, q.s, q.h, q.n, q.discarded
353
+ q = next(qdata_it, None)
349
354
  param_info = {}
350
355
 
351
- queries.append(dict(mean=mean, std=std, radius=radius, file=fname, line=line, column=column,
356
+ queries.append(dict(mean=mean, std=std, radius=radius,
357
+ file=query.filename, line=query.line, column=query.column,
352
358
  nsims=count, discarded=discarded, **param_info))
353
359
 
354
360
  return dict(nsims=num_sims, queries=queries)
@@ -370,9 +376,9 @@ def check(program, simulator, seed, alpha, delta, block, min_sim, max_sim, jobs,
370
376
 
371
377
  # Each query maintains some data like the sum of the outcomes
372
378
  # and the sum of their squares
373
- qdata = [QueryData(k, delta, idict)
374
- for k, qinfo in enumerate(program.query_locations)
375
- for idict in make_parameter_dicts(qinfo[3])]
379
+ qdata = [QueryData(k, dt, idict)
380
+ for k, qinfo in enumerate(program.queries)
381
+ for idict, dt in make_parameter_dicts(qinfo, delta)]
376
382
 
377
383
  # Run the simulations
378
384
  if jobs == 1 and num_sims != 1:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: umaudemc
3
- Version: 0.16.0
3
+ Version: 0.17.0
4
4
  Summary: Unified Maude model-checking utility
5
5
  Author-email: ningit <ningit@users.noreply.github.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -1,10 +1,10 @@
1
- umaudemc/__init__.py,sha256=ZkVXSbnNkhhpmMRr5ur6FqBcUYuqHyK0KUV5Je_XFn8,23
2
- umaudemc/__main__.py,sha256=zmgS0amNTdNZ4i2fhg860uLechFnBthvPNFBbFgUSJc,15039
1
+ umaudemc/__init__.py,sha256=ctD9pjqBvASXR0DHHzalDZFaQsnMJWDpTalYrvY3e_Y,23
2
+ umaudemc/__main__.py,sha256=LgKeZWi1JRrEclPS3asyZziErEJyldQvlZTtTt_tcyc,15063
3
3
  umaudemc/api.py,sha256=naZ5edEbvx-S-NU29yAAJtqglfYnSAYVS2RJNyxJMQQ,19893
4
4
  umaudemc/backends.py,sha256=mzJkALYwcKPInT0lBiRsCxJSewKvx5j_akQsqWN1Ezo,4590
5
5
  umaudemc/common.py,sha256=UcIf7hTpP2qjcT9u_9-UcYR0nNeosx1xRZW7wsuT2bE,7305
6
6
  umaudemc/counterprint.py,sha256=vVqM_UjGRk_xeftFxBGI5m6cQXV7mf8KvbQ_fvAvSQk,9226
7
- umaudemc/distributed.py,sha256=CljCg0VzLG7pDsDb_q1Lc95OVVjrU02cadkHRz9O8qY,9112
7
+ umaudemc/distributed.py,sha256=aCvU1TaM5SZhAtWahclyANc1I0YXj8SuPXO_VjaqDdo,9115
8
8
  umaudemc/formatter.py,sha256=nbQlIsR5Xv18OEcpJdnTDGqO9xGL_amvBGFMU2OmheU,6026
9
9
  umaudemc/formulae.py,sha256=jZPPDhjgsb7cs5rWvitiQoO0fd8JIlK98at2SN-LzVE,12156
10
10
  umaudemc/grapher.py,sha256=K1chKNNlEzQvfOsiFmRPJmd9OpxRIrg6OyiMW6gqOCU,4348
@@ -15,10 +15,10 @@ umaudemc/mproc.py,sha256=9X5pTb3Z3XHcdOo8ynH7I5RZQpjzm9xr4IBbEtaglUE,11766
15
15
  umaudemc/opsem.py,sha256=Xfdi9QGy-vcpmQ9ni8lBDAlKNw-fCRzYr6wnPbv6m1s,9448
16
16
  umaudemc/probabilistic.py,sha256=MNvFeEd84-OYedSnyksZB87UckPfwizVNJepCItgRy8,29306
17
17
  umaudemc/pyslang.py,sha256=ABSXYUQO2TmDq8EZ3EpVZV8NecZ0p0gERlSvLUIVAm8,87970
18
- umaudemc/quatex.py,sha256=Je5g16Tzb1t9NtHPSww0W6wUTIrHPdQCOJN-bTliOnQ,23888
18
+ umaudemc/quatex.py,sha256=7n7ugusjFUyO5jqKeb6-O8hdH4vj2fgnc-V_Z41w-40,27271
19
19
  umaudemc/resources.py,sha256=qKqvgLYTJVtsQHQMXFObyCLTo6-fssQeu_mG7tvVyD0,932
20
- umaudemc/simulators.py,sha256=ZGDpQjFj2Sv4GLq-NGVBMH78cFiG45KFPKfAfH1ds9w,13283
21
- umaudemc/statistical.py,sha256=FY3yXvv9NRiwYOQdwLDRf4WTXG1QupGJU8KwdHQhJyo,10534
20
+ umaudemc/simulators.py,sha256=K8NFgwvvTgj1lueksnWOSc-7bo6VxS1_CA9g-6l5umc,14367
21
+ umaudemc/statistical.py,sha256=aogqx2JCur9Lw9ZfOI6jaggwLcVTflDSs_Mn7yCmOGE,10815
22
22
  umaudemc/terminal.py,sha256=B4GWLyW4Sdymgoavj418y4TI4MnWqNu3JS4BBoSYeTc,1037
23
23
  umaudemc/usermsgs.py,sha256=h4VPxljyKidEI8vpPcToKJA6mcLu9PtMkIh6vH3rDuA,719
24
24
  umaudemc/webui.py,sha256=XlDV87tOOdcclHp2_oerrvHwRmCZdqAR4PppqeZm47A,11072
@@ -40,8 +40,8 @@ umaudemc/command/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
40
40
  umaudemc/command/check.py,sha256=PyaPDMw5OnPxSIZ10U4we0b5tTrjnYKAtAeQkJh2uLE,12031
41
41
  umaudemc/command/graph.py,sha256=JqGzESC2sn-LBh2sqctrij03ItzwDO808s2qkNKUle0,6112
42
42
  umaudemc/command/pcheck.py,sha256=eV4e4GcOHanP4hcIhMKd5Js22_ONac6kYj70FXun3mY,7274
43
- umaudemc/command/scheck.py,sha256=wByVmANax4-Jw3S6MHbXevYDiVP81HIhMk1M7yZwuMs,6205
44
- umaudemc/command/sworker.py,sha256=rTfGbIRvXV7cVEmlTwkKrP9tfZN0ESNJKtVLnIVCOMs,4654
43
+ umaudemc/command/scheck.py,sha256=mmAzCdGdIHgKbsyEi0uW-jcj9X0MSc-EEkEPR-EiB8U,6208
44
+ umaudemc/command/sworker.py,sha256=1icakFDO_qSr4ga-5Cu6eDfLirjLPJLNtO7OkbwTUQQ,4651
45
45
  umaudemc/command/test.py,sha256=Ru21JXNF61F5N5jayjwxp8okIjOAvuZuAlV_5ltQ-GU,37088
46
46
  umaudemc/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  umaudemc/data/opsem.maude,sha256=geDP3_RMgtS1rRmYOybJDCXn_-dyHHxg0JxfYg1ftv0,27929
@@ -52,9 +52,9 @@ umaudemc/data/smcgraph.js,sha256=iCNQNmsuGdL_GLnqVhGDisediFtedxw3C24rxSiQwx8,667
52
52
  umaudemc/data/smcview.css,sha256=ExFqrMkSeaf8VxFrJXflyCsRW3FTwbv78q0Hoo2UVrM,3833
53
53
  umaudemc/data/smcview.js,sha256=_fHum1DRU1mhco-9-c6KqTLgiC5u_cCUf61jIK7wcIQ,14509
54
54
  umaudemc/data/templog.maude,sha256=TZ-66hVWoG6gp7gJpS6FsQn7dpBTLrr76bKo-UfHGcA,9161
55
- umaudemc-0.16.0.dist-info/licenses/LICENSE,sha256=MrEGL32oSWfnAZ0Bq4BZNcqnq3Mhp87Q4w6-deXfFnA,17992
56
- umaudemc-0.16.0.dist-info/METADATA,sha256=53-pCZwFfBsUPOyA_ocXL1TauD09WrY6NL574Sf1LbQ,1654
57
- umaudemc-0.16.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
58
- umaudemc-0.16.0.dist-info/entry_points.txt,sha256=8rYRlLkn4orZtAoujDSeol1t_UFBrK0bfjmLTNv9B44,52
59
- umaudemc-0.16.0.dist-info/top_level.txt,sha256=Yo_CF78HLGBSblk3890qLcx6XZ17zHCbGcT9iG8sfMw,9
60
- umaudemc-0.16.0.dist-info/RECORD,,
55
+ umaudemc-0.17.0.dist-info/licenses/LICENSE,sha256=MrEGL32oSWfnAZ0Bq4BZNcqnq3Mhp87Q4w6-deXfFnA,17992
56
+ umaudemc-0.17.0.dist-info/METADATA,sha256=HMhQv0StUz5ODf2H8vanIt_FnkK6p7BDZd-nkc8o0Fg,1654
57
+ umaudemc-0.17.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
58
+ umaudemc-0.17.0.dist-info/entry_points.txt,sha256=8rYRlLkn4orZtAoujDSeol1t_UFBrK0bfjmLTNv9B44,52
59
+ umaudemc-0.17.0.dist-info/top_level.txt,sha256=Yo_CF78HLGBSblk3890qLcx6XZ17zHCbGcT9iG8sfMw,9
60
+ umaudemc-0.17.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5