Przeglądaj źródła

auto colour assignment

Snej69420 5 miesięcy temu
rodzic
commit
f0023cd11e
1 zmienionych plików z 105 dodań i 88 usunięć
  1. 105 88
      benchmarks/compare.py

+ 105 - 88
benchmarks/compare.py

@@ -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()