testPyPDEVS.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. #!/usr/bin/env python3
  2. import subprocess
  3. import csv
  4. import re
  5. from argparse import ArgumentParser, Action
  6. from itertools import product
  7. from datetime import datetime
  8. MODEL_TYPES = ("LI", "HI", "HO", "HOmod")
  9. DS_MODEL_TYPES = ("LI2HI", "HI2LI", "LI-HI", "dLI", "dHI")
  10. GEN_METHODS = ("start", "uniform", "end")
  11. INT_CYCLES = 0
  12. EXT_CYCLES = 0
  13. PyPDEVS = "./pythonpdevs/main.py"
  14. MinimalPyPDEVS = "./pythonpdevs-minimal/main.py"
  15. # Regex to capture times from script output
  16. TIME_PATTERN = re.compile(
  17. r"Model creation time: ([\d.eE+-]+).*"
  18. r"Engine setup time: ([\d.eE+-]+).*"
  19. r"Simulation time: ([\d.eE+-]+)",
  20. re.S
  21. )
  22. def run_devstone(model_type, depth, width, minimal=False, classic=False, dynamic=False, gen_mode="uniform"):
  23. """Runs the devstone_driver.py script and returns timing results as floats."""
  24. SCRIPT_PATH = PyPDEVS
  25. if minimal:
  26. SCRIPT_PATH = MinimalPyPDEVS
  27. cmd = [
  28. "python3", SCRIPT_PATH,
  29. "-m", model_type,
  30. "-d", str(depth),
  31. "-w", str(width),
  32. "-i", str(INT_CYCLES),
  33. "-e", str(EXT_CYCLES),
  34. ]
  35. if dynamic and not minimal:
  36. cmd.extend(["-D"])
  37. cmd.extend(["-g", gen_mode])
  38. if classic and not minimal:
  39. cmd.extend(["-C"])
  40. result = subprocess.run(cmd, capture_output=True, text=True)
  41. output = result.stdout.strip()
  42. print(output)
  43. errors = result.stderr.strip()
  44. match = TIME_PATTERN.search(output)
  45. if match:
  46. creation, setup, sim = map(float, match.groups())
  47. return creation, setup, sim, output
  48. print("Failed to run: ", cmd)
  49. print(output)
  50. print(errors)
  51. return None, None, None, output
  52. class EnsureDS(Action):
  53. def __call__(self, parser, namespace, values, option_string=None):
  54. if not getattr(namespace, "dynamic", False):
  55. parser.error("--dynamic-models (-dm) can only be used with --dynamic (-D)")
  56. setattr(namespace, self.dest, values)
  57. def parse_args():
  58. parser = ArgumentParser()
  59. parser.add_argument("-p", "--path", type=str, default="./Results-PyPDEVS/", help="Path to the results directory")
  60. parser.add_argument("-f", '--filename', type=str, default="", help="Name of the output file")
  61. parser.add_argument("-M", "--minimal", action="store_true", default=False, help="Uses the minimal DEVS kernel. (Less features => less overhead)")
  62. parser.add_argument("-C", "--classic", action="store_true", default=False, help="Selects the Classic DEVS Simulator")
  63. parser.add_argument("-D", "--dynamic", action="store_true", default=False, help="Enables Dynamic Structure DEVS")
  64. parser.add_argument("-g", "--gen-mode", choices=GEN_METHODS, default="uniform", help="Determines the spread of model creation.")
  65. parser.add_argument("-ms", "--model-structure", nargs="+", default=MODEL_TYPES+DS_MODEL_TYPES, choices=MODEL_TYPES+DS_MODEL_TYPES, help="Select one or more model structures")
  66. parser.add_argument("-md", "--max-depth", default=8, type=int, help="Max depth for the generated models")
  67. parser.add_argument("-ds", "--depth-stepsize", default=1, type=int, help="Determines for which depths a model is generated.")
  68. parser.add_argument("-mw", "--max-width", default=8, type=int, help="Max width for the generated models")
  69. parser.add_argument("-ws", "--width-stepsize", default=1, type=int, help="Determines for which depths a model is generated.")
  70. parser.add_argument("-r", "--repetitions", default=1, type=int, help="The number of times a simulation is repeated")
  71. return parser.parse_args()
  72. if __name__ == "__main__":
  73. args = parse_args()
  74. print(args)
  75. d = (args.max_depth, args.depth_stepsize)
  76. w = (args.max_width, args.width_stepsize)
  77. if d[0] % d[1] != 0:
  78. raise RuntimeError("Invalid depth stepsize.")
  79. if w[0] % w[1] != 0:
  80. raise RuntimeError("Invalid width stepsize.")
  81. fieldnames = ["model_type", "depth", "width", "run",
  82. "creation_time", "setup_time", "simulation_time", "generator_mode"]
  83. for model_type in args.model_structure:
  84. model_filename = f"{args.path}{model_type}"
  85. if args.filename:
  86. model_filename += f"_{args.filename}"
  87. else:
  88. model_filename += ".csv"
  89. with open(model_filename, "w", newline="") as csvfile:
  90. writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
  91. writer.writeheader()
  92. for depth in range(d[1], d[0] + 1, d[1]):
  93. for width in range(w[1], w[0] + 1, w[1]):
  94. for run in range(args.repetitions):
  95. creation, setup, sim, output = run_devstone(
  96. model_type, depth, width,
  97. args.minimal, args.classic, args.dynamic, args.gen_mode
  98. )
  99. if creation is not None:
  100. writer.writerow({
  101. "model_type": model_type,
  102. "depth": depth,
  103. "width": width,
  104. "run": run,
  105. "creation_time": creation,
  106. "setup_time": setup,
  107. "simulation_time": sim,
  108. "generator_mode": args.gen_mode
  109. })
  110. csvfile.flush()
  111. print(f"✅ Saved {model_type} results to {model_filename}")