소스 검색

simpler plotting and manually assignable colours + line&marker type

Snej69420 5 달 전
부모
커밋
c2a70e3f0d
1개의 변경된 파일과 79개의 추가작업 그리고 141개의 파일을 삭제
  1. 79 141
      benchmarks/compare.py

+ 79 - 141
benchmarks/compare.py

@@ -5,79 +5,46 @@ import matplotlib.pyplot as plt
 from pathlib import Path
 from model_properties import model_properties
 
-MODEL_COLOR_FAMILIES = {
-    "LI": "Greys",
-    "HI": "Purples",
-    "HO": "Blues",
-    "HOmod": "Greens",
-
-    "dLI": "YlOrRd",
-    "dHI": "Oranges",
-    "dHO": "Reds",
-
-    "mdLI": "PuBu",
-    "mdHI": "YlOrBr",
-    "mdHO": "YlGn",
+# Dictionary mapping human-readable color names to styles
+PREDEFINED_STYLES = {
+    "blue": {"color": "tab:blue", "ls": "-", "marker": "o"},
+    "orange": {"color": "tab:orange", "ls": "--", "marker": "s"},
+    "green": {"color": "tab:green", "ls": "-.", "marker": "^"},
+    "red": {"color": "tab:red", "ls": ":", "marker": "D"},
+    "purple": {"color": "tab:purple", "ls": "-", "marker": "v"},
+    "brown": {"color": "tab:brown", "ls": "--", "marker": "X"},
 }
 
-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": "--"},
+SHORTHANDS = {
+    "b":  "blue",
+    "o":  "orange",
+    "g":  "green",
+    "r":  "red",
+    "p":  "purple",
+    "br": "brown"
 }
 
-def get_dynamic_colors(results, bw=False):
-    """
-    Generates dynamic, evenly spaced colormap values based on the number of simulators per model.
-    """
-    dynamic_colors = {}
+VALID_STYLE_INPUTS = list(PREDEFINED_STYLES.keys()) + list(SHORTHANDS.keys())
 
-    # Find all unique model types currently loaded in the results
-    model_types = set([m for _, _, m in results.values()])
+def get_style_config(user_input):
+    """Helper to resolve shorthands and handle case-insensitivity."""
+    normalized = user_input.lower()
+    key = SHORTHANDS.get(normalized, normalized)
+    return PREDEFINED_STYLES.get(key)
 
-    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"] = (
             df["simulation_time"]
             + df["creation_time"]
             + df["setup_time"]
     )
 
-    # Determine columns for grouping (exclude total_time itself from grouping if possible)
-    # Usually, you want to group by parameters to find outliers within those specific settings
     group_params = [c for c in ["depth", "width", "number"] if c in df.columns]
 
     if not group_params:
         return df
 
-    # Vectorized IQR calculation using transform
-    # transform('quantile') returns a Series aligned with the original DataFrame index
     q1 = df.groupby(group_params)["total_time"].transform(lambda x: x.quantile(0.25))
     q3 = df.groupby(group_params)["total_time"].transform(lambda x: x.quantile(0.75))
 
@@ -85,16 +52,12 @@ def clean_data(df):
     lower = q1 - 1.5 * iqr
     upper = q3 + 1.5 * iqr
 
-    # Single-pass boolean indexing
     return df[(df["total_time"] >= lower) & (df["total_time"] <= upper)]
 
+
 def get_spaced_indices(series, n_markers):
-    """
-    Returns a list of indices that are roughly equidistant in terms of value,
-    rather than index. This prevents marker clustering in dense data regions.
-    """
     if len(series) <= n_markers:
-        return None  # Plot all markers if we have few points
+        return None
 
     values = series.values
     min_v, max_v = values.min(), values.max()
@@ -102,35 +65,26 @@ def get_spaced_indices(series, n_markers):
     if min_v == max_v:
         return [0]
 
-    # Create target values evenly spaced across the range
     targets = np.linspace(min_v, max_v, n_markers)
-
     indices = []
-    # Find the index of the value closest to each target
-    # Since 'grouped' is sorted by param, values are sorted.
-    # We use searchsorted to find insertion points, then check neighbors.
     import bisect
-
     val_list = values.tolist()
 
     for t in targets:
-        # bisect finds the insertion point to maintain order
         idx = bisect.bisect_left(val_list, t)
-
-        # Check if the previous index is actually closer
         if idx > 0:
             if idx == len(val_list) or abs(val_list[idx - 1] - t) < abs(val_list[idx] - t):
                 idx = idx - 1
-
         if idx < len(val_list):
             indices.append(idx)
 
-    # Deduplicate and sort
     return sorted(list(set(indices)))
 
-def plot_time_vs_param(csv_files, param, fixed_param=None, fixed_value=None, max_value=0, o_path="", name="", bw=False):
-    results = {}
+
+def plot_time_vs_param(csv_files, styles, param, fixed_param=None, fixed_value=None, max_value=0, o_path="", name=""):
+    results = []
     models = []
+
     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"
@@ -142,8 +96,8 @@ def plot_time_vs_param(csv_files, param, fixed_param=None, fixed_value=None, max
             df = df[df[fixed_param] == fixed_value]
 
         df = df.copy()
-        if param == "#Models":
-            df["#Models"] = df.apply(
+        if param == "Models":
+            df["Models"] = df.apply(
                 lambda row: model_properties(
                     str(row["model_type"]),
                     int(row["width"]),
@@ -157,36 +111,26 @@ 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 = f"{model_type} ({sim_id})"
 
-        # Determine the label
-        sim_label = SIMULATOR_TRAITS.get(sim_id, {}).get("label", sim_id)
-        label = f"{model_type} ({sim_label})"
+        style_input = styles[i] if i < len(styles) else list(PREDEFINED_STYLES.keys())[i % 6]
+        style = get_style_config(style_input)
 
-        results[label] = grouped, sim_id, model_type
+        results.append((label, grouped, style))
 
-    if not name:
-        unique_models = "|".join(set(models))
-        final_name = f"{unique_models}_over_{param}"
-    else:
-        final_name = name + "_" + param
+    final_name = name + "_over_" + param if name else f"{'|'.join(set(models))}_over_{param}"
 
     plt.figure(figsize=(8, 6))
 
-    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")
-
+    for label, df, style in results:
         plt.plot(
             df[param],
             df["total_time"],
             label=label,
-            color=colour,
-            linestyle=lstyle,
-            marker=marker,
-            markersize=4,
+            color=style["color"],
+            linestyle=style["ls"],
+            marker=style["marker"],
+            markersize=5,
             markevery=3,
         )
 
@@ -199,31 +143,26 @@ def plot_time_vs_param(csv_files, param, fixed_param=None, fixed_value=None, max
     plt.legend()
     plt.grid(True, linestyle=':', alpha=0.6)
 
-    script_dir = Path(__file__).resolve().parent
-    output_dir = script_dir / "Plots"
-    if o_path:
-        output_dir = output_dir / o_path
-
+    output_dir = Path(__file__).resolve().parent / "Plots" / 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 = {}
+def plot_time_vs_models(csv_files, styles, o_path="", name=""):
+    Atomics = "Models"
+    results = []
     models = []
+
     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)
-
         df = clean_data(df)
         df = df.copy()
         df[Atomics] = df.apply(
@@ -237,55 +176,39 @@ 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 = f"{model_type} ({sim_id})"
 
-        # Determine the label
-        sim_label = SIMULATOR_TRAITS.get(sim_id, {}).get("label", sim_id)
-        label = f"{model_type} ({sim_label})"
+        style_input = styles[i] if i < len(styles) else list(PREDEFINED_STYLES.keys())[i % 6]
+        style = get_style_config(style_input)
 
-        results[label] = grouped, sim_id, model_type
+        results.append((label, grouped, style))
 
-    if not name:
-        unique_models = "|".join(set(models))
-        final_name = f"{unique_models}_over_{Atomics}"
-    else:
-        final_name = name + "_" + Atomics
+    final_name = name + "_over_" + Atomics if name else f"{'|'.join(set(models))}_over_{Atomics}"
 
     plt.figure(figsize=(8, 6))
 
-    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, style in results:
         plt.plot(
             df[Atomics],
             df["total_time"],
             label=label,
-            color=colour,
-            marker=marker,
+            color=style["color"],
+            marker=style["marker"],
             linestyle='',
-            markersize=3,
-            markeredgecolor='none',
-            alpha=0.6,
+            markersize=4,
+            alpha=0.7,
         )
 
-    title = f"Average Simulation Time vs {Atomics}"
-    plt.title(title)
+    plt.title(f"Average Simulation Time vs {Atomics}")
     plt.xlabel(Atomics)
     plt.ylabel("Average Simulation Time (s)")
     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 = Path(__file__).resolve().parent / "Plots" / 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}")
@@ -308,14 +231,26 @@ def main():
     # Subcommand: total (Atomic Models)
     parser_tot = subparsers.add_parser("total", help="Plot total time vs number of atomic models")
 
+    # Shared arguments for all subparsers
     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(
+            "-s", "--styles",
+            type=str,
+            nargs="+",
+            help=f"Space-separated list of colors matching CSV order. Options: {', '.join(VALID_STYLE_INPUTS)}"
+        )
         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()
 
+    # Default to cycling through styles if the user omits the -s flag
+    if not args.styles:
+        styles = list(PREDEFINED_STYLES.keys())[:len(args.csv_files)]
+    else:
+        styles = args.styles
+
     if args.command == "param":
         if args.width is not None:
             fixed_param, fixed_value = "width", args.width
@@ -323,14 +258,17 @@ def main():
         else:
             fixed_param, fixed_value = "depth", args.depth
             varying_param = "width"
-        plot_time_vs_param(args.csv_files, param=varying_param,
+
+        plot_time_vs_param(args.csv_files, styles, param=varying_param,
                            fixed_param=fixed_param, fixed_value=fixed_value,
-                           max_value=args.max, o_path=args.path, name=args.name, bw=args.bw)
+                           max_value=args.max, o_path=args.path, name=args.name)
+
     elif args.command == "number":
-        plot_time_vs_param(args.csv_files, param="number", o_path=args.path, name=args.name, bw=args.bw)
+        plot_time_vs_param(args.csv_files, styles, param="number", o_path=args.path, name=args.name)
+
     elif args.command == "total":
-        plot_time_vs_models(args.csv_files, o_path=args.path, name=args.name, bw=args.bw)
+        plot_time_vs_models(args.csv_files, styles, o_path=args.path, name=args.name)
 
 
 if __name__ == "__main__":
-    main()
+    main()