simulator.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import abc
  2. import random
  3. import math
  4. import functools
  5. import sys
  6. from typing import Callable, Generator
  7. from framework.conformance import Conformance, render_conformance_check_result
  8. from concrete_syntax.common import indent
  9. from concrete_syntax.textual_od.renderer import render_od
  10. from transformation.cloner import clone_od
  11. from api.od import ODAPI
  12. class DecisionMaker:
  13. @abc.abstractmethod
  14. def __call__(self, actions):
  15. pass
  16. class RandomDecisionMaker(DecisionMaker):
  17. def __init__(self, seed=0, verbose=True):
  18. self.seed = seed
  19. self.r = random.Random(seed)
  20. def __str__(self):
  21. return f"RandomDecisionMaker(seed={self.seed})"
  22. def __call__(self, actions):
  23. arr = [action for descr, action in actions]
  24. if len(arr) == 0:
  25. return
  26. i = math.floor(self.r.random()*len(arr))
  27. return arr[i]
  28. class InteractiveDecisionMaker(DecisionMaker):
  29. # auto_proceed: whether to prompt if there is only one enabled action
  30. def __init__(self, msg="Select action:", auto_proceed=False):
  31. self.msg = msg
  32. self.auto_proceed = auto_proceed
  33. def __str__(self):
  34. return f"InteractiveDecisionMaker()"
  35. def __call__(self, actions):
  36. arr = []
  37. for i, (key, result) in enumerate(actions):
  38. print(f" {chr(97+i)}. {key}")
  39. arr.append(result)
  40. if len(arr) == 0:
  41. return
  42. if len(arr) == 1 and self.auto_proceed:
  43. return arr[0]
  44. def __choose():
  45. sys.stdout.write(f"{self.msg} ")
  46. try:
  47. raw = input()
  48. choice = ord(raw)-97 # may raise ValueError
  49. if choice >= 0 and choice < len(arr):
  50. return arr[choice]
  51. except (ValueError, TypeError):
  52. pass
  53. print("Invalid option")
  54. return __choose()
  55. return __choose()
  56. class MinimalSimulator:
  57. def __init__(self,
  58. action_generator: Callable[[any], Generator[any, None, None]],
  59. decision_maker: DecisionMaker = RandomDecisionMaker(seed=0),
  60. # Returns 'None' to keep running, or a string to end simulation
  61. # Can also have side effects, such as rendering the model, and performing a conformance check.
  62. # BTW, Simulation will always end when there are no more enabled actions.
  63. termination_condition=lambda model: None,
  64. verbose=True,
  65. ):
  66. self.action_generator = action_generator
  67. self.decision_maker = decision_maker
  68. self.termination_condition = termination_condition
  69. self.verbose = verbose
  70. def _print(self, *args):
  71. if self.verbose:
  72. print(*args)
  73. # Run simulation until termination condition satisfied
  74. def run(self, model):
  75. self._print("Start simulation")
  76. self._print(f"Decision maker: {self.decision_maker}")
  77. step_counter = 0
  78. while step_counter < 10:
  79. termination_reason = self.termination_condition(model)
  80. if termination_reason != None:
  81. self._print(f"Termination condition satisfied.\nReason: {termination_reason}.")
  82. break
  83. chosen_action = self.decision_maker(self.action_generator(model))
  84. if chosen_action == None:
  85. self._print(f"No enabled actions.")
  86. break
  87. (model, msgs) = chosen_action()
  88. self._print(indent('\n'.join(f"▸ {msg}" for msg in msgs), 4))
  89. step_counter += 1
  90. self._print(f"Executed {step_counter} steps.")
  91. return model