|
|
@@ -1,22 +1,66 @@
|
|
|
-#!/usr/bin/env python3
|
|
|
-import os
|
|
|
import argparse
|
|
|
import numpy as np
|
|
|
import pandas as pd
|
|
|
import matplotlib.pyplot as plt
|
|
|
from pathlib import Path
|
|
|
-from itertools import cycle
|
|
|
from model_properties import model_properties
|
|
|
|
|
|
-SIMULATOR_MAPPING = {
|
|
|
- "minimal": "(m)",
|
|
|
- "classic": "(c)",
|
|
|
- "parallel": "(p)",
|
|
|
- "dynamic": "(d)",
|
|
|
- "dynamic-classic": "(dc)",
|
|
|
- "dynamic-old": "(do)"
|
|
|
+MODEL_COLOR_FAMILIES = {
|
|
|
+ "LI": "Greys",
|
|
|
+ "HI": "Purples",
|
|
|
+ "HO": "Blues",
|
|
|
+ "HOmod": "Greens",
|
|
|
+
|
|
|
+ "dLI": "YlOrRd",
|
|
|
+ "dHI": "Oranges",
|
|
|
+ "dHO": "Reds",
|
|
|
+
|
|
|
+ "mdLI": "PuBu",
|
|
|
+ "mdHI": "YlOrBr",
|
|
|
+ "mdHO": "YlGn",
|
|
|
}
|
|
|
|
|
|
+SIMULATOR_TRAITS = {
|
|
|
+ "agents": {"label": "Agents", "shade_idx": 0, "marker": "^", "ls": "-"}, # Darkest, Triangle, Solid
|
|
|
+ "pypdevs-parallel": {"label": "PyPDEVS (p)", "shade_idx": 1, "marker": "s", "ls": "-."},
|
|
|
+ "pypdevs-minimal": {"label": "PyPDEVS (m)", "shade_idx": 2, "marker": "o", "ls": "--"},
|
|
|
+ "pypdevs-classic": {"label": "PyPDEVS (c)", "shade_idx": 3, "marker": "D", "ls": ":"},
|
|
|
+ "pypdevs-dynamic": {"label": "PyPDEVS (d)", "shade_idx": 1, "marker": "v", "ls": "-"},
|
|
|
+ "pypdevs-dynamic-classic": {"label": "PyPDEVS (dc)", "shade_idx": 2, "marker": "p", "ls": "--"},
|
|
|
+}
|
|
|
+
|
|
|
+def get_dynamic_colors(results, bw=False):
|
|
|
+ """
|
|
|
+ Generates dynamic, evenly spaced colormap values based on the number of simulators per model.
|
|
|
+ """
|
|
|
+ dynamic_colors = {}
|
|
|
+
|
|
|
+ # Find all unique model types currently loaded in the results
|
|
|
+ model_types = set([m for _, _, m in results.values()])
|
|
|
+
|
|
|
+ for m_type in model_types:
|
|
|
+ # Find all simulators for this specific model type
|
|
|
+ sims = list(set([s for _, s, m in results.values() if m == m_type]))
|
|
|
+
|
|
|
+ # Sort them by their predefined shade_idx so 'agents' always gets the darkest color
|
|
|
+ sims.sort(key=lambda x: SIMULATOR_TRAITS.get(x, {}).get("shade_idx", 0))
|
|
|
+
|
|
|
+ cmap_name = MODEL_COLOR_FAMILIES.get(m_type, "Greys")
|
|
|
+ cmap = plt.get_cmap(cmap_name)
|
|
|
+
|
|
|
+ # Matplotlib colormaps go from 0.0 (white/lightest) to 1.0 (black/darkest).
|
|
|
+ # We sample between 0.9 and 0.4 so the colors don't become invisible on a white background.
|
|
|
+ if len(sims) == 1:
|
|
|
+ spacings = [0.8]
|
|
|
+ else:
|
|
|
+ spacings = np.linspace(0.9, 0.4, len(sims))
|
|
|
+
|
|
|
+ # Assign the calculated color to the specific (model, simulator) combo
|
|
|
+ for sim, space in zip(sims, spacings):
|
|
|
+ dynamic_colors[(m_type, sim)] = 'black' if bw else cmap(space)
|
|
|
+
|
|
|
+ return dynamic_colors
|
|
|
+
|
|
|
def clean_data(df):
|
|
|
# Vectorized addition for total_time
|
|
|
df["total_time"] = (
|
|
|
@@ -41,7 +85,7 @@ def clean_data(df):
|
|
|
lower = q1 - 1.5 * iqr
|
|
|
upper = q3 + 1.5 * iqr
|
|
|
|
|
|
- # Single-pass boolean indexing (extremely fast)
|
|
|
+ # Single-pass boolean indexing
|
|
|
return df[(df["total_time"] >= lower) & (df["total_time"] <= upper)]
|
|
|
|
|
|
def get_spaced_indices(series, n_markers):
|
|
|
@@ -87,11 +131,11 @@ def get_spaced_indices(series, n_markers):
|
|
|
def plot_time_vs_param(csv_files, param, fixed_param=None, fixed_value=None, max_value=0, o_path="", name="", bw=False):
|
|
|
results = {}
|
|
|
models = []
|
|
|
- for path in csv_files:
|
|
|
+ for i, path in enumerate(csv_files):
|
|
|
df = pd.read_csv(path)
|
|
|
-
|
|
|
- if "model_type" in df.columns:
|
|
|
- models.append(path.stem)
|
|
|
+ sim_id = df["simulator"].iloc[0] if "simulator" in df.columns else "None"
|
|
|
+ model_type = df["model_type"].iloc[0] if "model_type" in df.columns else "None"
|
|
|
+ models.append(model_type)
|
|
|
|
|
|
df = clean_data(df)
|
|
|
if fixed_param and fixed_value is not None:
|
|
|
@@ -113,8 +157,12 @@ def plot_time_vs_param(csv_files, param, fixed_param=None, fixed_value=None, max
|
|
|
df = df[df[param] <= max_value]
|
|
|
|
|
|
grouped = df.groupby(param, as_index=False)["total_time"].mean()
|
|
|
- label = path.stem + " " + SIMULATOR_MAPPING[path.parent.name]
|
|
|
- results[label] = grouped
|
|
|
+
|
|
|
+ # Determine the label
|
|
|
+ sim_label = SIMULATOR_TRAITS.get(sim_id, {}).get("label", sim_id)
|
|
|
+ label = f"{model_type} ({sim_label})"
|
|
|
+
|
|
|
+ results[label] = grouped, sim_id, model_type
|
|
|
|
|
|
if not name:
|
|
|
unique_models = "|".join(set(models))
|
|
|
@@ -124,30 +172,22 @@ def plot_time_vs_param(csv_files, param, fixed_param=None, fixed_value=None, max
|
|
|
|
|
|
plt.figure(figsize=(8, 6))
|
|
|
|
|
|
- # Setup styling for accessibility
|
|
|
- # Reference: http://msdl.uantwerpen.be/publications/colourInPublications
|
|
|
- colors = plt.rcParams['axes.prop_cycle'].by_key()['color'] if not bw else ['black']
|
|
|
- line_styles = ['-', '--', '-.', ':']
|
|
|
- markers = ['o', 's', '^', 'D', 'v', 'p', '*', 'x']
|
|
|
-
|
|
|
- style_cycle = cycle(zip(cycle(colors), cycle(line_styles), cycle(markers)))
|
|
|
-
|
|
|
- for label, df in results.items():
|
|
|
- color, lstyle, marker = next(style_cycle)
|
|
|
-
|
|
|
- mark = 1
|
|
|
- if param == "#Models":
|
|
|
- mark = get_spaced_indices(df[param], 20)
|
|
|
+ dynamic_colors = get_dynamic_colors(results, bw)
|
|
|
+ for label, (df, sim_id, model_type) in results.items():
|
|
|
+ sim_style = SIMULATOR_TRAITS.get(sim_id, {})
|
|
|
+ marker = sim_style.get("marker", "x")
|
|
|
+ lstyle = sim_style.get("ls", "-")
|
|
|
+ colour = dynamic_colors.get((model_type, sim_id), "#7f7f7f")
|
|
|
|
|
|
plt.plot(
|
|
|
df[param],
|
|
|
df["total_time"],
|
|
|
label=label,
|
|
|
- color=color,
|
|
|
+ color=colour,
|
|
|
linestyle=lstyle,
|
|
|
marker=marker,
|
|
|
- markersize=6,
|
|
|
- markevery=mark,
|
|
|
+ markersize=4,
|
|
|
+ markevery=3,
|
|
|
)
|
|
|
|
|
|
title = f"Average Simulation Time vs {param.capitalize()}"
|
|
|
@@ -159,25 +199,27 @@ def plot_time_vs_param(csv_files, param, fixed_param=None, fixed_value=None, max
|
|
|
plt.legend()
|
|
|
plt.grid(True, linestyle=':', alpha=0.6)
|
|
|
|
|
|
- # Set directory relative to script parent
|
|
|
script_dir = Path(__file__).resolve().parent
|
|
|
output_dir = script_dir / "Plots"
|
|
|
if o_path:
|
|
|
output_dir = output_dir / o_path
|
|
|
|
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
-
|
|
|
output_file = output_dir / f"{final_name}.png"
|
|
|
plt.tight_layout()
|
|
|
plt.savefig(output_file, dpi=300)
|
|
|
print(f"Saved plot as {output_file}")
|
|
|
|
|
|
+
|
|
|
def plot_time_vs_models(csv_files, o_path="", name="", bw=False):
|
|
|
Atomics = "#Models"
|
|
|
results = {}
|
|
|
models = []
|
|
|
- for path in csv_files:
|
|
|
+ for i, path in enumerate(csv_files):
|
|
|
df = pd.read_csv(path)
|
|
|
+ sim_id = df["simulator"].iloc[0] if "simulator" in df.columns else "None"
|
|
|
+ model_type = df["model_type"].iloc[0] if "model_type" in df.columns else "None"
|
|
|
+ models.append(model_type)
|
|
|
|
|
|
if "model_type" in df.columns:
|
|
|
models.append(path.stem)
|
|
|
@@ -195,8 +237,12 @@ def plot_time_vs_models(csv_files, o_path="", name="", bw=False):
|
|
|
df = df.sort_values(by=Atomics)
|
|
|
|
|
|
grouped = df.groupby(Atomics, as_index=False)["total_time"].mean()
|
|
|
- label = path.stem + " " + SIMULATOR_MAPPING[path.parent.name]
|
|
|
- results[label] = grouped
|
|
|
+
|
|
|
+ # Determine the label
|
|
|
+ sim_label = SIMULATOR_TRAITS.get(sim_id, {}).get("label", sim_id)
|
|
|
+ label = f"{model_type} ({sim_label})"
|
|
|
+
|
|
|
+ results[label] = grouped, sim_id, model_type
|
|
|
|
|
|
if not name:
|
|
|
unique_models = "|".join(set(models))
|
|
|
@@ -206,48 +252,24 @@ def plot_time_vs_models(csv_files, o_path="", name="", bw=False):
|
|
|
|
|
|
plt.figure(figsize=(8, 6))
|
|
|
|
|
|
- # Setup styling for accessibility
|
|
|
- # Reference: http://msdl.uantwerpen.be/publications/colourInPublications
|
|
|
- colors = plt.rcParams['axes.prop_cycle'].by_key()['color'] if not bw else ['black']
|
|
|
- line_styles = ['-', '--', '-.', ':']
|
|
|
- markers = ['o', 's', '^', 'D', 'v', 'p', '*', 'x']
|
|
|
-
|
|
|
- style_cycle = cycle(zip(cycle(colors), cycle(line_styles), cycle(markers)))
|
|
|
+ dynamic_colors = get_dynamic_colors(results, bw)
|
|
|
+ for label, (df, sim_id, model_type) in results.items():
|
|
|
+ sim_style = SIMULATOR_TRAITS.get(sim_id, {})
|
|
|
+ marker = sim_style.get("marker", "x")
|
|
|
+ colour = dynamic_colors.get((model_type, sim_id), "#7f7f7f")
|
|
|
|
|
|
- for label, df in results.items():
|
|
|
- color, lstyle, marker = next(style_cycle)
|
|
|
-
|
|
|
- plt.scatter(
|
|
|
+ plt.plot(
|
|
|
df[Atomics],
|
|
|
df["total_time"],
|
|
|
- label=label, # Keep label here for the legend
|
|
|
- color=color,
|
|
|
+ label=label,
|
|
|
+ color=colour,
|
|
|
marker=marker,
|
|
|
- s=15, # Smaller markers to reduce clutter
|
|
|
- alpha=0.5, # Transparency to show point density
|
|
|
- edgecolors='none' # Remove border for cleaner look
|
|
|
+ linestyle='',
|
|
|
+ markersize=3,
|
|
|
+ markeredgecolor='none',
|
|
|
+ alpha=0.6,
|
|
|
)
|
|
|
|
|
|
- # 2. ADD A TRENDLINE (Polynomial Fit)
|
|
|
- # This draws the smooth curve you want, representing the scaling law (O(n) or O(n^2))
|
|
|
- # if len(df) > 1:
|
|
|
- # # Fit a 2nd degree polynomial (y = Ax^2 + Bx + C)
|
|
|
- # # Use '1' if you know it's strictly linear, '2' covers most simulation curves
|
|
|
- # z = np.polyfit(df[Atomics], df["total_time"], 2)
|
|
|
- # p = np.poly1d(z)
|
|
|
- #
|
|
|
- # # Generate smooth x-axis points for the line
|
|
|
- # x_trend = np.linspace(df[Atomics].min(), df[Atomics].max(), 100)
|
|
|
- #
|
|
|
- # plt.plot(
|
|
|
- # x_trend,
|
|
|
- # p(x_trend),
|
|
|
- # color=color,
|
|
|
- # linestyle='--', # Dashed line distinguishes trend from raw data
|
|
|
- # linewidth=2,
|
|
|
- # alpha=0.9
|
|
|
- # )
|
|
|
-
|
|
|
title = f"Average Simulation Time vs {Atomics}"
|
|
|
plt.title(title)
|
|
|
plt.xlabel(Atomics)
|
|
|
@@ -275,10 +297,6 @@ def main():
|
|
|
|
|
|
# Subcommand: time-vs-depth/width
|
|
|
parser_tvparam = subparsers.add_parser("param", help="Plot total time vs depth or width")
|
|
|
- parser_tvparam.add_argument("--bw", action="store_true", help="Black and white / Colorblind friendly mode")
|
|
|
- parser_tvparam.add_argument("csv_files", nargs="+", type=Path, help="CSV files to process")
|
|
|
- parser_tvparam.add_argument("-p", "--path", type=str, default="", help="the subdirectory where the plots will be stored")
|
|
|
- parser_tvparam.add_argument("-n", "--name", type=str, help="Name of output file")
|
|
|
group = parser_tvparam.add_mutually_exclusive_group(required=True)
|
|
|
group.add_argument("-w", "--width", type=int, help="Fixed width value")
|
|
|
group.add_argument("-d", "--depth", type=int, help="Fixed depth value")
|
|
|
@@ -286,17 +304,15 @@ def main():
|
|
|
|
|
|
# Subcommand: time-vs-number
|
|
|
parser_tvn = subparsers.add_parser("number", help="Plot total time vs number")
|
|
|
- parser_tvn.add_argument("--bw", action="store_true", help="Black and white / Colorblind friendly mode")
|
|
|
- parser_tvn.add_argument("csv_files", nargs="+", type=Path, help="CSV files to process")
|
|
|
- parser_tvn.add_argument("-p", "--path", type=str, default="", help="the subdirectory where the plots will be stored")
|
|
|
- parser_tvn.add_argument("-n", "--name", type=str, help="Name of output file")
|
|
|
|
|
|
# Subcommand: total (Atomic Models)
|
|
|
parser_tot = subparsers.add_parser("total", help="Plot total time vs number of atomic models")
|
|
|
- parser_tot.add_argument("--bw", action="store_true", help="Black and white / Colorblind friendly mode")
|
|
|
- parser_tot.add_argument("csv_files", nargs="+", type=Path, help="CSV files to process")
|
|
|
- parser_tot.add_argument("-p", "--path", type=str, default="", help="the subdirectory where the plots will be stored")
|
|
|
- parser_tot.add_argument("-n", "--name", type=str, help="Name of output file")
|
|
|
+
|
|
|
+ for subp in [parser_tvparam, parser_tvn, parser_tot]:
|
|
|
+ subp.add_argument("--bw", action="store_true", help="Black and white / Colourblind friendly mode")
|
|
|
+ subp.add_argument("csv_files", nargs="+", type=Path, help="CSV files to process")
|
|
|
+ subp.add_argument("-p", "--path", type=str, default="", help="Subdirectory for storing plots")
|
|
|
+ subp.add_argument("-n", "--name", type=str, help="Name of output file")
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
@@ -315,5 +331,6 @@ def main():
|
|
|
elif args.command == "total":
|
|
|
plot_time_vs_models(args.csv_files, o_path=args.path, name=args.name, bw=args.bw)
|
|
|
|
|
|
+
|
|
|
if __name__ == "__main__":
|
|
|
- main()
|
|
|
+ main()
|