from __future__ import annotations from typing import Dict, Optional, Tuple import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import numpy as np from .cubic_curves import ( EPS33_ROW, LATERAL_ROW, MODE_TO_ROW, default_x_for_mode, evaluate_cubic_no_intercept, fit_cubic_no_intercept, ) def _set_axis_limits( ax: plt.Axes, y: np.ndarray, *, force_min: Optional[float] = None, force_max: Optional[float] = None, min_floor: Optional[float] = None, max_ceiling: Optional[float] = None, pad_frac: float = 0.2, ) -> None: y = np.asarray(y, dtype=np.float32).reshape(-1) if y.size == 0: return y_min = float(np.min(y)) y_max = float(np.max(y)) if force_min is not None: y_min = min(y_min, float(force_min)) if force_max is not None: y_max = max(y_max, float(force_max)) if min_floor is not None: y_min = min(y_min, float(min_floor)) if max_ceiling is not None: y_max = max(y_max, float(max_ceiling)) span = y_max - y_min pad = span * pad_frac if span > 0 else max(abs(y_max), abs(y_min), 1.0) * 0.1 bottom = y_min - pad top = y_max + pad if force_min is not None: bottom = float(force_min) if force_max is not None: top = float(force_max) ax.set_ylim(bottom, top) def plot_single_curve(coeffs: np.ndarray, mode: str, title: str, ylabel: str) -> plt.Figure: x = default_x_for_mode(mode) y = evaluate_cubic_no_intercept(coeffs, x) fig, ax = plt.subplots(figsize=(3.2, 2.5)) ax.plot(x, y, color="k", linewidth=2) ax.set_title(title) ax.set_xlabel("strain") ax.set_ylabel(ylabel) ax.grid(True, alpha=0.25) fig.tight_layout() return fig def plot_required_curves(cond_coeffs_7x3: np.ndarray) -> plt.Figure: cond = np.asarray(cond_coeffs_7x3, dtype=np.float32).reshape(7, 3) fig, axes = plt.subplots(3, 3, figsize=(14, 12)) modes = ["11", "22", "12"] for col, mode in enumerate(modes): x = default_x_for_mode(mode) stress_row = MODE_TO_ROW[mode] axes[0, col].plot(x, evaluate_cubic_no_intercept(cond[stress_row], x), color="k", linewidth=2) axes[0, col].set_title(f"Mode {mode} stress") axes[0, col].set_xlabel("strain") axes[0, col].set_ylabel("stress (MPa)") axes[0, col].grid(True, alpha=0.25) if mode in ("11", "22"): lat_row = int(LATERAL_ROW[mode]) e33_row = int(EPS33_ROW[mode]) axes[1, col].plot(x, evaluate_cubic_no_intercept(cond[lat_row], x), color="k", linewidth=2) axes[1, col].set_title(f"Mode {mode} lateral") axes[1, col].set_xlabel("strain") axes[1, col].set_ylabel("lateral strain") axes[1, col].grid(True, alpha=0.25) axes[2, col].plot(x, evaluate_cubic_no_intercept(cond[e33_row], x), color="k", linewidth=2) axes[2, col].set_title(f"Mode {mode} eps33") axes[2, col].set_xlabel("strain") axes[2, col].set_ylabel("eps33") axes[2, col].grid(True, alpha=0.25) else: axes[1, col].axis("off") axes[2, col].axis("off") fig.tight_layout() return fig def plot_required_curves_from_series(curves: Dict[str, np.ndarray]) -> plt.Figure: fig, axes = plt.subplots(3, 3, figsize=(14, 12)) axes[0, 0].plot(curves["eps11"], curves["sig11_mpa"], color="k", linewidth=2) axes[0, 0].set_title("Mode 11 stress") axes[0, 0].set_xlabel("strain") axes[0, 0].set_ylabel("stress (MPa)") axes[0, 0].grid(True, alpha=0.25) _set_axis_limits(axes[0, 0], curves["sig11_mpa"]) axes[1, 0].plot(curves["eps11"], curves["eps22_from_eps11"], color="k", linewidth=2) axes[1, 0].set_title("Mode 11 lateral") axes[1, 0].set_xlabel("strain") axes[1, 0].set_ylabel("lateral strain") axes[1, 0].grid(True, alpha=0.25) _set_axis_limits(axes[1, 0], curves["eps22_from_eps11"], min_floor=-0.01, max_ceiling=0.0, pad_frac=0.0) axes[2, 0].plot(curves["eps11"], curves["eps33_from_eps11"], color="k", linewidth=2) axes[2, 0].set_title("Mode 11 eps33") axes[2, 0].set_xlabel("strain") axes[2, 0].set_ylabel("eps33") axes[2, 0].grid(True, alpha=0.25) _set_axis_limits(axes[2, 0], curves["eps33_from_eps11"]) axes[0, 1].plot(curves["eps22"], curves["sig22_mpa"], color="k", linewidth=2) axes[0, 1].set_title("Mode 22 stress") axes[0, 1].set_xlabel("strain") axes[0, 1].set_ylabel("stress (MPa)") axes[0, 1].grid(True, alpha=0.25) _set_axis_limits(axes[0, 1], curves["sig22_mpa"]) axes[1, 1].plot(curves["eps22"], curves["eps11_from_eps22"], color="k", linewidth=2) axes[1, 1].set_title("Mode 22 lateral") axes[1, 1].set_xlabel("strain") axes[1, 1].set_ylabel("lateral strain") axes[1, 1].grid(True, alpha=0.25) _set_axis_limits(axes[1, 1], curves["eps11_from_eps22"], min_floor=-0.01, max_ceiling=0.0, pad_frac=0.0) axes[2, 1].plot(curves["eps22"], curves["eps33_from_eps22"], color="k", linewidth=2) axes[2, 1].set_title("Mode 22 eps33") axes[2, 1].set_xlabel("strain") axes[2, 1].set_ylabel("eps33") axes[2, 1].grid(True, alpha=0.25) _set_axis_limits(axes[2, 1], curves["eps33_from_eps22"]) axes[0, 2].plot(curves["eps12"], curves["sig12_mpa"], color="k", linewidth=2) axes[0, 2].set_title("Mode 12 stress") axes[0, 2].set_xlabel("strain") axes[0, 2].set_ylabel("stress (MPa)") axes[0, 2].grid(True, alpha=0.25) _set_axis_limits(axes[0, 2], curves["sig12_mpa"]) axes[1, 2].axis("off") axes[2, 2].axis("off") fig.tight_layout() return fig def plot_condition_and_simulations_v5( cond_coeffs_7x3: np.ndarray, simulations_by_design: Dict[int, Dict[str, Dict[str, np.ndarray]]], ) -> plt.Figure: cond = np.asarray(cond_coeffs_7x3, dtype=np.float32).reshape(7, 3) fig, axes = plt.subplots(3, 3, figsize=(14, 12)) modes = ["11", "22", "12"] colors = ["b", "c", "m", "g", "r"] for col, mode in enumerate(modes): x_cond = default_x_for_mode(mode) stress_ys = [evaluate_cubic_no_intercept(cond[MODE_TO_ROW[mode]], x_cond)] lateral_ys = [] eps33_ys = [] x_min, x_max = None, None for _, sim in sorted(simulations_by_design.items()): if mode in sim: x = sim[mode]["strain"] x_min = float(np.min(x)) x_max = float(np.max(x)) break stress_row = MODE_TO_ROW[mode] axes[0, col].plot( x_cond, evaluate_cubic_no_intercept(cond[stress_row], x_cond), color="k", linewidth=2, label="Condition", ) if mode in ("11", "22"): lat_row = int(LATERAL_ROW[mode]) e33_row = int(EPS33_ROW[mode]) lateral_ys.append(evaluate_cubic_no_intercept(cond[lat_row], x_cond)) eps33_ys.append(evaluate_cubic_no_intercept(cond[e33_row], x_cond)) axes[1, col].plot( x_cond, lateral_ys[-1], color="k", linewidth=2, label="Condition", ) axes[2, col].plot( x_cond, eps33_ys[-1], color="k", linewidth=2, label="Condition", ) else: axes[1, col].axis("off") axes[2, col].axis("off") for idx, (design_idx, sim) in enumerate(sorted(simulations_by_design.items(), key=lambda kv: kv[0])): if mode not in sim: continue color = colors[idx % len(colors)] data = sim[mode] strain = np.asarray(data["strain"], dtype=np.float32) stress = np.asarray(data["stress"], dtype=np.float32) stress_ys.append(stress) axes[0, col].plot([], [], color=color, linewidth=2, label=f"Gen {design_idx}") axes[0, col].scatter(strain, stress, color=color, s=22, alpha=0.85, label="_nolegend_") stress_coeffs = fit_cubic_no_intercept(strain, stress, degree=3) if stress_coeffs is not None: x_sim = np.linspace(float(np.min(strain)), float(np.max(strain)), 200, dtype=np.float32) axes[0, col].plot(x_sim, evaluate_cubic_no_intercept(stress_coeffs, x_sim), color=color, linewidth=2, alpha=0.9, label="_nolegend_") if mode in ("11", "22") and data.get("lateral") is not None: lateral = np.asarray(data["lateral"], dtype=np.float32) lateral_ys.append(lateral) axes[1, col].plot([], [], color=color, linewidth=2, label=f"Gen {design_idx}") axes[1, col].scatter(strain, lateral, color=color, s=22, alpha=0.85, label="_nolegend_") lat_coeffs = fit_cubic_no_intercept(strain, lateral, degree=3) if lat_coeffs is not None: axes[1, col].plot(x_sim, evaluate_cubic_no_intercept(lat_coeffs, x_sim), color=color, linewidth=2, alpha=0.9, label="_nolegend_") if mode in ("11", "22") and data.get("eps33") is not None: eps33 = np.asarray(data["eps33"], dtype=np.float32) eps33_ys.append(eps33) axes[2, col].plot([], [], color=color, linewidth=2, label=f"Gen {design_idx}") axes[2, col].scatter(strain, eps33, color=color, s=22, alpha=0.85, label="_nolegend_") e33_coeffs = fit_cubic_no_intercept(strain, eps33, degree=3) if e33_coeffs is not None: axes[2, col].plot(x_sim, evaluate_cubic_no_intercept(e33_coeffs, x_sim), color=color, linewidth=2, alpha=0.9, label="_nolegend_") if x_min is None or x_max is None or x_max <= x_min: x_min = float(np.min(x_cond)) x_max = float(np.max(x_cond)) else: x_min = min(float(np.min(x_cond)), x_min) x_max = max(float(np.max(x_cond)), x_max) axes[0, col].set_xlim(x_min, x_max) if mode in ("11", "22"): axes[1, col].set_xlim(x_min, x_max) axes[2, col].set_xlim(x_min, x_max) axes[0, col].set_title(f"Mode {mode} stress") axes[0, col].set_xlabel("strain") axes[0, col].set_ylabel("stress (MPa)") axes[0, col].grid(True, alpha=0.25) axes[0, col].legend(fontsize=8) _set_axis_limits(axes[0, col], np.concatenate(stress_ys)) if mode in ("11", "22"): axes[1, col].set_title(f"Mode {mode} lateral") axes[1, col].set_xlabel("strain") axes[1, col].set_ylabel("lateral strain") axes[1, col].grid(True, alpha=0.25) axes[1, col].legend(fontsize=8) _set_axis_limits(axes[1, col], np.concatenate(lateral_ys), min_floor=-0.01, max_ceiling=0.0, pad_frac=0.0) axes[2, col].set_title(f"Mode {mode} eps33") axes[2, col].set_xlabel("strain") axes[2, col].set_ylabel("eps33") axes[2, col].grid(True, alpha=0.25) axes[2, col].legend(fontsize=8) _set_axis_limits(axes[2, col], np.concatenate(eps33_ys)) fig.tight_layout() return fig