From d177a8a3163a4fdd90b74457876e61548fc30f0d Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Fri, 19 Jun 2026 22:53:14 -0600 Subject: [PATCH] Add RawFile.resolve_index, dedup signal-index lookup in two tools tune_circuit and plot_waveform each hand-rolled exact case-insensitive name->row-index resolution -- a manual 3-way loop and a next(enumerate) generator respectively. Centralize as RawFile.resolve_index(), next to the existing get_variable() (which does partial match, so it can't be reused here without changing semantics). No behavior change: exact case-insensitive match and the not-found error paths (with available_signals) are preserved. Verified against the live LTspice path -- tune_circuit bandwidth and plot_waveform resolution unchanged. --- src/mcltspice/raw_parser.py | 13 +++++++++++++ src/mcltspice/server.py | 24 ++++++++---------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/mcltspice/raw_parser.py b/src/mcltspice/raw_parser.py index f9b8a27..13742b2 100644 --- a/src/mcltspice/raw_parser.py +++ b/src/mcltspice/raw_parser.py @@ -52,6 +52,19 @@ class RawFile: return arr return None + def resolve_index(self, name: str) -> int | None: + """Return a variable's data-row index by exact name (case-insensitive). + + Unlike get_variable (partial match, returns the data array), this does an + exact case-insensitive name match and returns the integer row index into + self.data, or None if there's no exact match. + """ + name_lower = name.lower() + for var in self.variables: + if var.name.lower() == name_lower: + return var.index + return None + def get_time(self, run: int | None = None) -> np.ndarray | None: """Get the time axis (for transient analysis).""" return self.get_variable("time", run=run) diff --git a/src/mcltspice/server.py b/src/mcltspice/server.py index acfb7c3..f2aef06 100644 --- a/src/mcltspice/server.py +++ b/src/mcltspice/server.py @@ -1272,17 +1272,10 @@ async def tune_circuit( if raw is None: return {"error": "No raw data from simulation", "params_used": effective_params} - # Find the signal variable - sig_idx = None - time_idx = None - freq_idx = None - for var in raw.variables: - if var.name.lower() == signal.lower(): - sig_idx = var.index - if var.name.lower() == "time": - time_idx = var.index - if var.name.lower() == "frequency": - freq_idx = var.index + # Find the signal variable (exact, case-insensitive) + sig_idx = raw.resolve_index(signal) + time_idx = raw.resolve_index("time") + freq_idx = raw.resolve_index("frequency") if sig_idx is None: available = [v.name for v in raw.variables] @@ -1513,19 +1506,18 @@ async def plot_waveform( signal_list = signals if signals else [signal] is_multi = len(signal_list) > 1 - # Resolve all signal names (case-insensitive) + # Resolve all signal names (exact, case-insensitive) sig_names = [v.name for v in raw_data.variables] sig_lower = {s.lower(): s for s in sig_names} resolved: list[tuple[str, int]] = [] # (actual_name, index) for s in signal_list: - actual = sig_lower.get(s.lower()) - if actual is None: + idx = raw_data.resolve_index(s) + if idx is None: return { "error": f"Signal '{s}' not found", "available_signals": sig_names, } - idx = next(i for i, v in enumerate(raw_data.variables) if v.name == actual) - resolved.append((actual, idx)) + resolved.append((sig_lower[s.lower()], idx)) # Get x-axis data (time or frequency) x_var = raw_data.variables[0]