mean.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. import argparse
  2. from pathlib import Path
  3. import numpy as np
  4. import pandas as pd
  5. BASE_DIR = Path(__file__).resolve().parent
  6. ENGINES = {
  7. "agents": "Results/Agents/Time/{model}_{type}.csv",
  8. "pypdevs-minimal": "Results/PyPDEVS/Time/minimal/{model}_{type}.csv",
  9. "pypdevs-classic": "Results/PyPDEVS/Time/classic/{model}_{type}.csv",
  10. "pypdevs-parallel": "Results/PyPDEVS/Time/parallel/{model}_{type}.csv",
  11. "pypdevs-dynamic": "Results/PyPDEVS/Time/dynamic/{model}_{type}.csv",
  12. "pypdevs-dynamic-classic": "Results/PyPDEVS/Time/dynamic-classic/{model}_{type}.csv",
  13. }
  14. # Configured dimensions per model scenario
  15. MODEL_DIMENSIONS = {
  16. "LI": {"depth": 200, "width": 200},
  17. "HI": {"depth": 200, "width": 200},
  18. "HO": {"depth": 200, "width": 200},
  19. "HOmod": {"depth": 40, "width": 40},
  20. }
  21. MODELS = ["LI", "HI", "HO", "HOmod"]
  22. TRANSLATIONS = ["baseline", "scaled", "accessed"]
  23. def format_to_latex(mean, std):
  24. if pd.isna(mean) or pd.isna(std):
  25. return "-"
  26. if std == 0:
  27. return f"${mean:.3f} \\pm 0.000$"
  28. std_magnitude = int(np.floor(np.log10(abs(std))))
  29. decimal_places = -std_magnitude
  30. if decimal_places >= 0:
  31. return f"${mean:.{decimal_places}f} \\pm {std:.{decimal_places}f}$"
  32. else:
  33. return f"${round(mean, decimal_places):.0f} \\pm {round(std, decimal_places):.0f}$"
  34. def get_metrics(engine, model, data_type, custom_depth=None, custom_width=None):
  35. """Dynamically locates file, removes configuration-specific outliers using the IQR method
  36. from compare.py, and computes the final mean/std.
  37. """
  38. template = ENGINES.get(engine)
  39. if not template:
  40. return None, None
  41. file_path = BASE_DIR / template.format(model=model, type=data_type)
  42. if not file_path.exists():
  43. return None, None
  44. depth = (
  45. custom_depth
  46. if custom_depth is not None
  47. else MODEL_DIMENSIONS[model]["depth"]
  48. )
  49. width = (
  50. custom_width
  51. if custom_width is not None
  52. else MODEL_DIMENSIONS[model]["width"]
  53. )
  54. try:
  55. df = pd.read_csv(file_path)
  56. # 1. Compute total_time exactly like clean_data(df) in compare.py
  57. df["total_time"] = (
  58. df["simulation_time"] + df["creation_time"] + df["setup_time"]
  59. )
  60. # 2. Identify outlier thresholds per unique simulation grouping parameter context
  61. group_params = [
  62. c for c in ["depth", "width", "number"] if c in df.columns
  63. ]
  64. if group_params:
  65. q1 = df.groupby(group_params)["total_time"].transform(
  66. lambda x: x.quantile(0.25)
  67. )
  68. q3 = df.groupby(group_params)["total_time"].transform(
  69. lambda x: x.quantile(0.75)
  70. )
  71. iqr = q3 - q1
  72. lower = q1 - 1.5 * iqr
  73. upper = q3 + 1.5 * iqr
  74. # 3. Filter out rows matching outlier bounds
  75. df = df[(df["total_time"] >= lower) & (df["total_time"] <= upper)]
  76. # 4. Extract specific dimension rows for the requested table metric cell
  77. filtered_df = df[(df["depth"] == depth) & (df["width"] == width)]
  78. if filtered_df.empty:
  79. return None, None
  80. combined_time = filtered_df["total_time"]
  81. return combined_time.mean(), combined_time.std()
  82. except Exception:
  83. return None, None
  84. def get_dual_metrics(engine, model, data_type, custom_depth=None, custom_width=None):
  85. """
  86. Locates file, removes outliers using the IQR method based on total_time,
  87. and returns both the Total (sum) and Sim-only metrics.
  88. """
  89. template = ENGINES.get(engine)
  90. if not template:
  91. return (None, None), (None, None)
  92. file_path = BASE_DIR / template.format(model=model, type=data_type)
  93. if not file_path.exists():
  94. return (None, None), (None, None)
  95. depth = custom_depth if custom_depth is not None else MODEL_DIMENSIONS[model]["depth"]
  96. width = custom_width if custom_width is not None else MODEL_DIMENSIONS[model]["width"]
  97. try:
  98. df = pd.read_csv(file_path)
  99. df["total_time"] = df["simulation_time"] + df["creation_time"] + df["setup_time"]
  100. group_params = [c for c in ["depth", "width", "number"] if c in df.columns]
  101. if group_params:
  102. q1 = df.groupby(group_params)["total_time"].transform(lambda x: x.quantile(0.25))
  103. q3 = df.groupby(group_params)["total_time"].transform(lambda x: x.quantile(0.75))
  104. iqr = q3 - q1
  105. lower = q1 - 1.5 * iqr
  106. upper = q3 + 1.5 * iqr
  107. df = df[(df["total_time"] >= lower) & (df["total_time"] <= upper)]
  108. filtered_df = df[(df["depth"] == depth) & (df["width"] == width)]
  109. if filtered_df.empty:
  110. return (None, None), (None, None)
  111. tot_mean, tot_std = filtered_df["total_time"].mean(), filtered_df["total_time"].std()
  112. sim_mean, sim_std = filtered_df["simulation_time"].mean(), filtered_df["simulation_time"].std()
  113. return (tot_mean, tot_std), (sim_mean, sim_std)
  114. except Exception:
  115. return (None, None), (None, None)
  116. def handle_single(args):
  117. """Computes a single measurement point using manual overrides or rules if omitted."""
  118. depth = args.depth if args.depth is not None else MODEL_DIMENSIONS[args.model]["depth"]
  119. width = args.width if args.width is not None else MODEL_DIMENSIONS[args.model]["width"]
  120. mean_val, std_val = get_metrics(
  121. args.engine, args.model, args.type, custom_depth=depth, custom_width=width
  122. )
  123. if mean_val is None:
  124. print(f"❌ Data matching filters (Depth={depth}, Width={width}) could not be resolved.")
  125. return
  126. print(f"--- Matrix Point Verification ---")
  127. print(f"Target: {args.engine} | {args.model}_{args.type}.csv")
  128. print(f"Filter: Depth={depth} | Width={width}")
  129. print(f"Raw Mean: {mean_val:.6f} | Raw Std: {std_val:.6f}")
  130. print(f"LaTeX Output: {format_to_latex(mean_val, std_val)}")
  131. def handle_comparison(args):
  132. """Generates the engine cross-comparison performance matrix table with fixed dimensions."""
  133. print("Generating Engine Performance Matrix Table...\n")
  134. table_data = {}
  135. for engine in ENGINES.keys():
  136. row_cells = []
  137. for model in MODELS:
  138. mean_val, std_val = get_metrics(engine, model, "baseline")
  139. row_cells.append(format_to_latex(mean_val, std_val))
  140. table_data[engine] = row_cells
  141. labels = {
  142. "pypdevs-minimal": "PyPDEVS (m)",
  143. "pypdevs-classic": "PyPDEVS (c)",
  144. "pypdevs-parallel": "PyPDEVS (p)",
  145. "pypdevs-dynamic": "PyPDEVS (d)",
  146. "pypdevs-dynamic-classic": "PyPDEVS (dc)",
  147. "agents": "Agents.jl",
  148. }
  149. latex_table = (
  150. "\\begin{table}[htbp]\n"
  151. " \\centering\n"
  152. " \\begin{tabular}{lcccc}\n"
  153. " \\toprule\n"
  154. " \\textbf{Engine} & {\\textbf{LI (200x200)}} & {\\textbf{HI (200x200)}} & {\\textbf{HO (200x200)}} & {\\textbf{HOmod (40x40)}} \\\\\n"
  155. " \\midrule\n"
  156. )
  157. for eng in ENGINES.keys():
  158. print(eng)
  159. latex_table += f" {labels[eng]} & {' & '.join(table_data[eng])} \\\\\n"
  160. latex_table += (
  161. " \\bottomrule\n"
  162. " \\end{tabular}\n"
  163. " \\caption{Performance comparison of different DEVS simulation engines in seconds.}\n"
  164. " \\label{tab:devstone-performance}\n"
  165. "\\end{table}"
  166. )
  167. print(latex_table)
  168. def handle_devstone_comparison(args):
  169. """Generates the dual-metric comparison table (Total vs. Sim)."""
  170. print("Generating Dual-Metric Engine Comparison Table...\n")
  171. labels = {
  172. "pypdevs-minimal": "PyPDEVS (m)",
  173. "pypdevs-classic": "PyPDEVS (c)",
  174. "pypdevs-parallel": "PyPDEVS (p)",
  175. "pypdevs-dynamic": "PyPDEVS (d)",
  176. "pypdevs-dynamic-classic": "PyPDEVS (dc)",
  177. "agents": "Agents.jl",
  178. }
  179. latex_table = (
  180. "\\begin{table}[htbp]\n"
  181. " \\centering\n"
  182. " \\begin{tabular}{llcccc}\n"
  183. " \\toprule\n"
  184. " \\textbf{Engine} & \\textbf{Phase} & {\\textbf{LI (200x200)}} & {\\textbf{HI (200x200)}} & {\\textbf{HO (200x200)}} & {\\textbf{HOmod (40x40)}} \\\\\n"
  185. " \\midrule\n"
  186. )
  187. for eng in ENGINES.keys():
  188. tot_cells = []
  189. sim_cells = []
  190. for model in MODELS:
  191. tot_metrics, sim_metrics = get_dual_metrics(eng, model, "baseline")
  192. if tot_metrics[0] is not None:
  193. tot_cells.append(format_to_latex(tot_metrics[0], tot_metrics[1]))
  194. sim_cells.append(format_to_latex(sim_metrics[0], sim_metrics[1]))
  195. else:
  196. tot_cells.append("-")
  197. sim_cells.append("-")
  198. latex_table += f" \\multirow{{2}}{{*}}{{{labels[eng]}}} & Total & {' & '.join(tot_cells)} \\\\\n"
  199. latex_table += f" & Sim. Only & {' & '.join(sim_cells)} \\\\\n"
  200. latex_table += " \\midrule\n"
  201. latex_table = latex_table.rstrip("\\midrule\n") + "\\bottomrule\n"
  202. latex_table += (
  203. " \\end{tabular}\n"
  204. " \\caption{Comparison of total execution time versus isolated simulation execution time in seconds.}\n"
  205. " \\label{tab:devstone-dual-comparison}\n"
  206. "\\end{table}"
  207. )
  208. print(latex_table)
  209. def handle_translation(args):
  210. """Generates two separate cross-functional Translation strategy tables with fixed dimensions."""
  211. print("Generating Comparative Translation Function Tables...\n")
  212. engine_targets = ["pypdevs-parallel", "agents"]
  213. engine_labels = {
  214. "pypdevs-parallel": "PyPDEVS Parallel",
  215. "agents": "Agents.jl",
  216. }
  217. labels = {
  218. "baseline": "Identity (Baseline)",
  219. "scaled": "Scaled ",
  220. "accessed": "Accessed ",
  221. }
  222. for engine in engine_targets:
  223. print(f"% --- TRANSLATION TABLE FOR {engine_labels[engine].upper()} ---")
  224. latex_table = (
  225. "\\begin{table}[htbp]\n"
  226. " \\centering\n"
  227. " \\begin{tabular}{lcccc}\n"
  228. " \\toprule\n"
  229. " \\textbf{Translation Function} & {\\textbf{LI (200x200)}} & {\\textbf{HI (200x200)}} & {\\textbf{HO (200x200)}} & {\\textbf{HOmod (40x40)}} \\\\\n"
  230. " \\midrule\n"
  231. )
  232. for tx in TRANSLATIONS:
  233. row_cells = []
  234. for model in MODELS:
  235. mean_val, std_val = get_metrics(engine, model, tx)
  236. row_cells.append(format_to_latex(mean_val, std_val))
  237. latex_table += f" {labels[tx]} & {' & '.join(row_cells)} \\\\\n"
  238. latex_table += (
  239. " \\bottomrule\n"
  240. " \\end{tabular}\n"
  241. f" \\caption{{Performance comparison of translation functions under {engine_labels[engine]} in seconds.}}\n"
  242. f" \\label{{tab:translation-performance-{engine.replace('-', '_')}}}\n"
  243. "\\end{table}\n"
  244. )
  245. print(latex_table)
  246. def main():
  247. parser = argparse.ArgumentParser(
  248. description="DEVS Benchmarking Performance Statistics Aggregator CLI"
  249. )
  250. subparsers = parser.add_subparsers(dest="command", required=True, help="Available actions")
  251. p_single = subparsers.add_parser("single", help="Calculate point result for individual matrix logs.")
  252. p_single.add_argument("-e", "--engine", choices=["agents", "pypdevs-min", "pypdevs-parallel"], required=True)
  253. p_single.add_argument("-m", "--model", choices=MODELS, required=True)
  254. p_single.add_argument("-t", "--type", choices=TRANSLATIONS, default="baseline")
  255. p_single.add_argument("-d", "--depth", type=int, default=None, help="Force override depth constraint")
  256. p_single.add_argument("-w", "--width", type=int, default=None, help="Force override width constraint")
  257. subparsers.add_parser("comparison", help="Generate Engine Comparison Table (Fixed sizes automatically applied).")
  258. subparsers.add_parser("devstone-comparison", help="Generate Engine Comparison Table showing Total and Sim times separately.")
  259. subparsers.add_parser("translation", help="Generate Translation Strategy Tables (Fixed sizes automatically applied).")
  260. args = parser.parse_args()
  261. if args.command == "single":
  262. handle_single(args)
  263. elif args.command == "comparison":
  264. handle_comparison(args)
  265. elif args.command == "devstone-comparison":
  266. handle_devstone_comparison(args)
  267. elif args.command == "translation":
  268. handle_translation(args)
  269. if __name__ == "__main__":
  270. main()