"""Tab 3 -- Constraint-aware hardware optimizer via cryosim. Builds all Gradio components (left control panel + right results panel) and wires up the click handler. Must be called inside an active ``gr.TabItem`` context. """ from __future__ import annotations import traceback import gradio as gr import pandas as pd import cryosim from cryosim.calibration.params import PARAM_NAMES, get_nominal_values from murphy_unified.theme import metric_html, engine_banner # -- Helpers ----------------------------------------------------------------- def _compute_improvement(target: str, base: float, opt: float) -> tuple[float, bool]: """Return (signed_improvement_pct, is_better). For max_ targets (higher = better), improvement = (opt/base - 1)*100. For min_ targets (lower = better), improvement = (1 - opt/base)*100. Returns (0.0, False) when baseline is 0 (degenerate). """ if base == 0: return 0.0, False pct = (opt / base - 1) * 100.0 is_min = target.startswith("min_") signed = -pct if is_min else pct return signed, signed > 0 # -- Click handler ----------------------------------------------------------- def run_optimize( hardware: str, target: str, Pexit: float, speed: float, maxiter: float, c_metric: str, c_op: str, c_val: float, ): """Run constrained optimisation and return (metrics_html, table_df).""" try: # Build constraints dict constraints: dict[str, str] = {} if c_metric: constraints[c_metric] = f"{c_op}{c_val}" # Baseline prediction baseline = cryosim.predict(hardware=hardware, Pexit=Pexit, speed=speed) # Optimise opt = cryosim.optimize( hardware=hardware, target=target, speed=speed, Pexit=Pexit, constraints=constraints, maxiter=int(maxiter), ) # Parameter comparison table nominal = get_nominal_values(hardware) rows = [] for name, nom, opt_val in zip(PARAM_NAMES, nominal, opt.optimal_values): pct = (opt_val / nom - 1) * 100 if abs(nom) > 1e-12 else 0 rows.append({ "Parameter": name, "Nominal": f"{nom:.6f}", "Optimized": f"{opt_val:.6f}", "Change %": f"{pct:+.1f}%", }) table_df = pd.DataFrame(rows) # Metric cards status_accent = "green" if opt.constraints_satisfied else "red" # Baseline extraction: the baseline struct carries attrs per-target. _BASE_ATTRS = { "max_mdot": "mdot_kgpm", "min_kWh": "kWh_extend", "min_Tc_peak": "Tc_peak_K", "max_mass_eff": "mass_eff", } base_attr = _BASE_ATTRS.get(target, "mdot_kgpm") baseline_val = getattr(baseline, base_attr, 0) or 0 opt_val_scalar = opt.target_value improvement, is_better = _compute_improvement( target, float(baseline_val), float(opt_val_scalar) ) metrics = ( '
' + metric_html("BASELINE", f"{baseline_val:.4f}") + metric_html("OPTIMIZED", f"{opt_val_scalar:.4f}", "", "green") + metric_html( "IMPROVEMENT", f"{improvement:+.1f}", "%", "green" if improvement > 0 else "amber", ) + metric_html( "CONSTRAINTS", "OK" if opt.constraints_satisfied else "VIOLATED", "", status_accent, ) + "
" + '
' + metric_html("EVALUATIONS", f"{opt.n_evals}") + metric_html("CONVERGED", "YES" if opt.converged else "NO") + "
" ) return metrics, table_df except Exception: err = traceback.format_exc() err_html = ( f'
' f"Optimizer failed:\n{err}
" ) return err_html, pd.DataFrame() # -- Builder ----------------------------------------------------------------- def build_optimizer_tab(): """Create all Gradio components for the Optimizer tab and wire events. Must be called inside an active ``gr.TabItem(...)`` context manager. """ with gr.Row(): # -- Left panel -- controls ------------------------------------------- with gr.Column(scale=1, min_width=280): gr.HTML(engine_banner("ENGINE: EULER (optimizer default)")) hw_dd = gr.Dropdown( choices=["old_icv", "new_icv"], value="old_icv", label="Hardware Config", ) tgt_dd = gr.Dropdown( choices=["max_mdot", "max_mass_eff", "min_kWh", "min_Tc_peak"], value="max_mdot", label="Objective", ) pexit_sl = gr.Slider( minimum=50, maximum=900, value=500, step=10, label="Exit Pressure [barg]", ) speed_sl = gr.Slider( minimum=0.1, maximum=1.0, value=0.65, step=0.05, label="Speed Fraction", ) maxiter_sl = gr.Slider( minimum=3, maximum=50, value=10, step=1, label="Max Iterations", ) gr.Markdown("**Constraint (optional):**") c_metric_dd = gr.Dropdown( choices=["", "Tc_peak_K", "mass_eff", "kWh_extend", "pc_peak_barg"], value="", label="Metric", ) c_op_dd = gr.Dropdown( choices=["<", ">", "<=", ">="], value="<", label="Operator", ) c_val_num = gr.Number(value=200, label="Threshold") run_btn = gr.Button("OPTIMIZE", variant="primary") # -- Right panel -- results ------------------------------------------- with gr.Column(scale=3): metrics_out = gr.HTML(label="Metrics") table_out = gr.Dataframe(label="Parameter Changes") # -- Wire click event ----------------------------------------------------- run_btn.click( fn=run_optimize, inputs=[ hw_dd, tgt_dd, pexit_sl, speed_sl, maxiter_sl, c_metric_dd, c_op_dd, c_val_num, ], outputs=[metrics_out, table_out], )