From c71b3bbb4c2fd9f320a17e5539ca6b1a4b584e54 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Sat, 20 Jun 2026 00:45:24 -0600 Subject: [PATCH] Extract template registries into templates.py The two inline registries (_TEMPLATES, _ASC_TEMPLATES) plus their ~30 generator-function imports were pure data living in server.py. Move them to a dedicated templates.py as NETLIST_TEMPLATES / ASC_TEMPLATES, and add resolve_template() (netlist-first precedence) + all_template_names() to replace the '.get() or .get()' dual-lookup dance that tune_circuit used. server.py: 3257 -> 2949 lines. Registries verified byte-identical to the originals (keys, params, descriptions, func identities); netlist-first precedence for names in both registries preserved; live tune_circuit / create_from_template / generate_schematic unchanged. --- src/mcltspice/server.py | 356 +++---------------------------------- src/mcltspice/templates.py | 315 ++++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+), 332 deletions(-) create mode 100644 src/mcltspice/templates.py diff --git a/src/mcltspice/server.py b/src/mcltspice/server.py index f2aef06..0637859 100644 --- a/src/mcltspice/server.py +++ b/src/mcltspice/server.py @@ -21,51 +21,6 @@ import numpy as np from fastmcp import FastMCP from fastmcp.prompts import Message -from .asc_generator import ( - generate_boost_converter as generate_boost_converter_asc, -) -from .asc_generator import ( - generate_buck_converter as generate_buck_converter_asc, -) -from .asc_generator import ( - generate_colpitts_oscillator as generate_colpitts_asc, -) -from .asc_generator import ( - generate_common_emitter_amp as generate_ce_amp_asc, -) -from .asc_generator import ( - generate_current_mirror as generate_current_mirror_asc, -) -from .asc_generator import ( - generate_differential_amp as generate_diff_amp_asc, -) -from .asc_generator import ( - generate_h_bridge as generate_h_bridge_asc, -) -from .asc_generator import ( - generate_instrumentation_amp as generate_inamp_asc, -) -from .asc_generator import ( - generate_inverting_amp, -) -from .asc_generator import ( - generate_ldo_regulator as generate_ldo_asc, -) -from .asc_generator import ( - generate_non_inverting_amp as generate_noninv_amp_asc, -) -from .asc_generator import ( - generate_rc_lowpass as generate_rc_lowpass_asc, -) -from .asc_generator import ( - generate_sallen_key_lowpass as generate_sallen_key_asc, -) -from .asc_generator import ( - generate_transimpedance_amp as generate_tia_asc, -) -from .asc_generator import ( - generate_voltage_divider as generate_voltage_divider_asc, -) from .batch import ( run_monte_carlo, run_parameter_sweep, @@ -82,24 +37,7 @@ from .drc import run_drc as _run_drc from .log_parser import parse_log from .models import search_models as _search_models from .models import search_subcircuits as _search_subcircuits -from .netlist import ( - Netlist, - boost_converter, - buck_converter, - colpitts_oscillator, - common_emitter_amplifier, - current_mirror, - differential_amplifier, - h_bridge, - instrumentation_amplifier, - inverting_amplifier, - ldo_regulator, - non_inverting_amplifier, - rc_lowpass, - sallen_key_lowpass, - transimpedance_amplifier, - voltage_divider, -) +from .netlist import Netlist from .noise_analysis import ( compute_noise_metrics, compute_spot_noise, @@ -122,6 +60,12 @@ from .spicebook import ( ) from .stability import compute_stability_metrics from .svg_plot import plot_bode, plot_spectrum, plot_timeseries, plot_timeseries_multi +from .templates import ( + ASC_TEMPLATES, + NETLIST_TEMPLATES, + all_template_names, + resolve_template, +) from .touchstone import parse_touchstone, s_param_to_db from .waveform_expr import WaveformCalculator from .waveform_math import ( @@ -1200,7 +1144,7 @@ async def tune_circuit( Args: template: Template name (from list_templates). Works with both - netlist templates (_TEMPLATES) and schematic templates (_ASC_TEMPLATES). + netlist templates (NETLIST_TEMPLATES) and schematic templates (ASC_TEMPLATES). params: Component value overrides (e.g., {"r": "2.2k", "c": "47n"}). Use list_templates to see available parameters. targets: Performance targets to check against. Each key is a metric name, @@ -1213,14 +1157,12 @@ async def tune_circuit( Returns: Dict with metrics, target comparison, and tuning suggestions. """ - # --- 1. Look up template --- - tmpl = _TEMPLATES.get(template) - is_netlist = tmpl is not None - if tmpl is None: - tmpl = _ASC_TEMPLATES.get(template) - if tmpl is None: - names = sorted(set(list(_TEMPLATES.keys()) + list(_ASC_TEMPLATES.keys()))) + # --- 1. Look up template (netlist registry first) --- + found = resolve_template(template) + if found is None: + names = all_template_names() return {"error": f"Unknown template '{template}'. Available: {', '.join(names)}"} + tmpl, is_netlist = found # --- 2. Build effective params --- effective_params: dict[str, str] = dict(tmpl["params"]) @@ -1773,103 +1715,6 @@ async def monte_carlo( # ============================================================================ -_ASC_TEMPLATES: dict[str, dict] = { - "rc_lowpass": { - "func": generate_rc_lowpass_asc, - "description": "RC lowpass filter with AC analysis", - "params": {"r": "1k", "c": "100n"}, - }, - "voltage_divider": { - "func": generate_voltage_divider_asc, - "description": "Resistive voltage divider with .op analysis", - "params": {"r1": "10k", "r2": "10k", "vin": "5"}, - }, - "inverting_amp": { - "func": generate_inverting_amp, - "description": "Inverting op-amp amplifier (gain = -Rf/Rin)", - "params": {"rin": "10k", "rf": "100k"}, - }, - "non_inverting_amp": { - "func": generate_noninv_amp_asc, - "description": "Non-inverting op-amp amplifier (gain = 1 + Rf/Rin)", - "params": {"rin": "10k", "rf": "100k"}, - }, - "common_emitter_amp": { - "func": generate_ce_amp_asc, - "description": "Common-emitter BJT amplifier with voltage divider bias", - "params": { - "rc": "2.2k", "rb1": "56k", "rb2": "12k", "re": "1k", - "cc_in": "10u", "cc_out": "10u", "ce": "47u", - "vcc": "12", "bjt_model": "2N2222", - }, - }, - "colpitts_oscillator": { - "func": generate_colpitts_asc, - "description": "Colpitts oscillator with LC tank and BJT", - "params": { - "ind": "1u", "c1": "100p", "c2": "100p", - "rb": "47k", "rc": "1k", "re": "470", - "vcc": "12", "bjt_model": "2N2222", - }, - }, - "differential_amp": { - "func": generate_diff_amp_asc, - "description": "Differential amplifier with op-amp", - "params": {"r1": "10k", "r2": "10k", "r3": "10k", "r4": "10k"}, - }, - "buck_converter": { - "func": generate_buck_converter_asc, - "description": "Buck (step-down) converter with NMOS switch", - "params": { - "ind": "10u", "c_out": "100u", "r_load": "10", - "v_in": "12", "duty_cycle": "0.5", "freq": "100k", - "mosfet_model": "IRF540N", "diode_model": "1N5819", - }, - }, - "ldo_regulator": { - "func": generate_ldo_asc, - "description": "LDO voltage regulator with PMOS pass transistor", - "params": { - "r1": "10k", "r2": "10k", "pass_transistor": "IRF9540N", - "v_in": "8", "v_ref": "2.5", - }, - }, - "h_bridge": { - "func": generate_h_bridge_asc, - "description": "H-bridge motor driver with 4 NMOS transistors", - "params": {"v_supply": "12", "r_load": "10", "mosfet_model": "IRF540N"}, - }, - "sallen_key_lowpass": { - "func": generate_sallen_key_asc, - "description": "Sallen-Key lowpass filter (unity gain, 2nd order)", - "params": {"r1": "10k", "r2": "10k", "c1": "10n", "c2": "10n"}, - }, - "boost_converter": { - "func": generate_boost_converter_asc, - "description": "Boost (step-up) converter with NMOS switch", - "params": { - "ind": "10u", "c_out": "100u", "r_load": "50", - "v_in": "5", "duty_cycle": "0.5", "freq": "100k", - }, - }, - "instrumentation_amp": { - "func": generate_inamp_asc, - "description": "3-opamp instrumentation amplifier", - "params": {"r1": "10k", "r2": "10k", "r3": "10k", "r_gain": "10k"}, - }, - "current_mirror": { - "func": generate_current_mirror_asc, - "description": "BJT current mirror with reference and load", - "params": {"r_ref": "10k", "r_load": "1k", "vcc": "12"}, - }, - "transimpedance_amp": { - "func": generate_tia_asc, - "description": "Transimpedance amplifier (current to voltage)", - "params": {"rf": "100k", "cf": "1p", "i_source": "1u"}, - }, -} - - @mcp.tool() def generate_schematic( template: str, @@ -1894,11 +1739,11 @@ def generate_schematic( Use list_templates to see available parameters for each template. output_path: Where to save the .asc file (None = auto in /tmp) """ - if template not in _ASC_TEMPLATES: - names = ", ".join(sorted(_ASC_TEMPLATES)) + if template not in ASC_TEMPLATES: + names = ", ".join(sorted(ASC_TEMPLATES)) return {"error": f"Unknown template '{template}'. Available: {names}"} - entry = _ASC_TEMPLATES[template] + entry = ASC_TEMPLATES[template] call_params: dict[str, str | float] = {} if params: @@ -2143,159 +1988,6 @@ def create_netlist( # CIRCUIT TEMPLATE TOOLS # ============================================================================ -# Registry of netlist templates with parameter metadata -_TEMPLATES: dict[str, dict] = { - "voltage_divider": { - "func": voltage_divider, - "description": "Resistive voltage divider with .op or custom analysis", - "params": {"v_in": "5", "r1": "10k", "r2": "10k", "sim_type": "op"}, - }, - "rc_lowpass": { - "func": rc_lowpass, - "description": "RC lowpass filter with AC sweep", - "params": {"r": "1k", "c": "100n", "f_start": "1", "f_stop": "1meg"}, - }, - "inverting_amplifier": { - "func": inverting_amplifier, - "description": "Inverting op-amp (gain = -Rf/Rin), +/-15V supply", - "params": {"r_in": "10k", "r_f": "100k", "opamp_model": "LT1001"}, - }, - "non_inverting_amplifier": { - "func": non_inverting_amplifier, - "description": "Non-inverting op-amp (gain = 1 + Rf/Rin), +/-15V supply", - "params": {"r_in": "10k", "r_f": "100k", "opamp_model": "LT1001"}, - }, - "differential_amplifier": { - "func": differential_amplifier, - "description": "Diff amp: Vout = (R2/R1)*(V2-V1), +/-15V supply", - "params": { - "r1": "10k", - "r2": "10k", - "r3": "10k", - "r4": "10k", - "opamp_model": "LT1001", - }, - }, - "common_emitter_amplifier": { - "func": common_emitter_amplifier, - "description": "BJT common-emitter with voltage divider bias", - "params": { - "rc": "2.2k", - "rb1": "56k", - "rb2": "12k", - "re": "1k", - "cc1": "10u", - "cc2": "10u", - "ce": "47u", - "vcc": "12", - "bjt_model": "2N2222", - }, - }, - "buck_converter": { - "func": buck_converter, - "description": "Step-down DC-DC converter with MOSFET switch", - "params": { - "ind": "10u", - "c_out": "100u", - "r_load": "10", - "v_in": "12", - "duty_cycle": "0.5", - "freq": "100k", - "mosfet_model": "IRF540N", - "diode_model": "1N5819", - }, - }, - "ldo_regulator": { - "func": ldo_regulator, - "description": "LDO regulator: Vout = Vref * (1 + R1/R2)", - "params": { - "opamp_model": "LT1001", - "r1": "10k", - "r2": "10k", - "pass_transistor": "IRF9540N", - "v_in": "8", - "v_ref": "2.5", - }, - }, - "colpitts_oscillator": { - "func": colpitts_oscillator, - "description": "LC oscillator: f ~ 1/(2pi*sqrt(L*Cseries))", - "params": { - "ind": "1u", - "c1": "100p", - "c2": "100p", - "rb": "47k", - "rc": "1k", - "re": "470", - "vcc": "12", - "bjt_model": "2N2222", - }, - }, - "h_bridge": { - "func": h_bridge, - "description": "4-MOSFET H-bridge motor driver with dead time", - "params": { - "v_supply": "12", - "r_load": "10", - "mosfet_model": "IRF540N", - }, - }, - "sallen_key_lowpass": { - "func": sallen_key_lowpass, - "description": "Sallen-Key lowpass filter (unity gain, 2nd order)", - "params": { - "r1": "10k", - "r2": "10k", - "c1": "10n", - "c2": "10n", - "opamp_model": "LT1001", - }, - }, - "boost_converter": { - "func": boost_converter, - "description": "Step-up DC-DC converter with MOSFET switch", - "params": { - "ind": "10u", - "c_out": "100u", - "r_load": "50", - "v_in": "5", - "duty_cycle": "0.5", - "freq": "100k", - "mosfet_model": "IRF540N", - "diode_model": "1N5819", - }, - }, - "instrumentation_amplifier": { - "func": instrumentation_amplifier, - "description": "3-opamp instrumentation amp: gain = 1 + 2*R1/R_gain", - "params": { - "r1": "10k", - "r2": "10k", - "r3": "10k", - "r_gain": "10k", - }, - }, - "current_mirror": { - "func": current_mirror, - "description": "BJT current mirror with reference resistor", - "params": { - "r_ref": "10k", - "r_load": "1k", - "vcc": "12", - "bjt_model": "2N2222", - }, - }, - "transimpedance_amplifier": { - "func": transimpedance_amplifier, - "description": "TIA: converts input current to output voltage (Vout = -Iph * Rf)", - "params": { - "rf": "100k", - "cf": "1p", - "i_source": "1u", - }, - }, -} - @mcp.tool() def create_from_template( @@ -2329,13 +2021,13 @@ def create_from_template( params: Optional dict of parameter overrides (all values as strings) output_path: Where to save .cir file (None = auto in /tmp) """ - template = _TEMPLATES.get(template_name) + template = NETLIST_TEMPLATES.get(template_name) if template is None: return { "error": f"Unknown template '{template_name}'", "available_templates": [ {"name": k, "description": v["description"], "params": v["params"]} - for k, v in _TEMPLATES.items() + for k, v in NETLIST_TEMPLATES.items() ], } @@ -2386,9 +2078,9 @@ def list_templates() -> dict: "description": info["description"], "params": info["params"], } - for name, info in _TEMPLATES.items() + for name, info in NETLIST_TEMPLATES.items() ], - "total_count": len(_TEMPLATES), + "total_count": len(NETLIST_TEMPLATES), } @@ -2639,11 +2331,11 @@ def resource_templates() -> str: templates = { "netlist_templates": [ {"name": name, "description": info["description"], "params": info["params"]} - for name, info in _TEMPLATES.items() + for name, info in NETLIST_TEMPLATES.items() ], "schematic_templates": [ {"name": name, "description": info["description"], "params": info["params"]} - for name, info in _ASC_TEMPLATES.items() + for name, info in ASC_TEMPLATES.items() ], } return json.dumps(templates, indent=2) @@ -2653,8 +2345,8 @@ def resource_templates() -> str: def resource_template_detail(name: str) -> str: """Detailed information about a specific circuit template.""" # Check both registries - netlist_info = _TEMPLATES.get(name) - asc_info = _ASC_TEMPLATES.get(name) + netlist_info = NETLIST_TEMPLATES.get(name) + asc_info = ASC_TEMPLATES.get(name) if not netlist_info and not asc_info: return json.dumps({"error": f"Template '{name}' not found"}) diff --git a/src/mcltspice/templates.py b/src/mcltspice/templates.py new file mode 100644 index 0000000..e86fc71 --- /dev/null +++ b/src/mcltspice/templates.py @@ -0,0 +1,315 @@ +"""Circuit template registries. + +Two registries map template names to builder functions and default params: + +- NETLIST_TEMPLATES -> .netlist builders, producing SPICE .cir netlists +- ASC_TEMPLATES -> .asc_generator builders, producing graphical schematics + +Some names appear in both (e.g. rc_lowpass). resolve_template() applies a +netlist-first precedence so a name available in both resolves to its netlist +form, matching the historical lookup order in tune_circuit. +""" + +from .asc_generator import ( + generate_boost_converter, + generate_buck_converter, + generate_colpitts_oscillator, + generate_common_emitter_amp, + generate_current_mirror, + generate_differential_amp, + generate_h_bridge, + generate_instrumentation_amp, + generate_inverting_amp, + generate_ldo_regulator, + generate_non_inverting_amp, + generate_rc_lowpass, + generate_sallen_key_lowpass, + generate_transimpedance_amp, + generate_voltage_divider, +) +from .netlist import ( + boost_converter, + buck_converter, + colpitts_oscillator, + common_emitter_amplifier, + current_mirror, + differential_amplifier, + h_bridge, + instrumentation_amplifier, + inverting_amplifier, + ldo_regulator, + non_inverting_amplifier, + rc_lowpass, + sallen_key_lowpass, + transimpedance_amplifier, + voltage_divider, +) + +# Graphical .asc schematic templates (generate_schematic, tune_circuit fallback) +ASC_TEMPLATES: dict[str, dict] = { + "rc_lowpass": { + "func": generate_rc_lowpass, + "description": "RC lowpass filter with AC analysis", + "params": {"r": "1k", "c": "100n"}, + }, + "voltage_divider": { + "func": generate_voltage_divider, + "description": "Resistive voltage divider with .op analysis", + "params": {"r1": "10k", "r2": "10k", "vin": "5"}, + }, + "inverting_amp": { + "func": generate_inverting_amp, + "description": "Inverting op-amp amplifier (gain = -Rf/Rin)", + "params": {"rin": "10k", "rf": "100k"}, + }, + "non_inverting_amp": { + "func": generate_non_inverting_amp, + "description": "Non-inverting op-amp amplifier (gain = 1 + Rf/Rin)", + "params": {"rin": "10k", "rf": "100k"}, + }, + "common_emitter_amp": { + "func": generate_common_emitter_amp, + "description": "Common-emitter BJT amplifier with voltage divider bias", + "params": { + "rc": "2.2k", "rb1": "56k", "rb2": "12k", "re": "1k", + "cc_in": "10u", "cc_out": "10u", "ce": "47u", + "vcc": "12", "bjt_model": "2N2222", + }, + }, + "colpitts_oscillator": { + "func": generate_colpitts_oscillator, + "description": "Colpitts oscillator with LC tank and BJT", + "params": { + "ind": "1u", "c1": "100p", "c2": "100p", + "rb": "47k", "rc": "1k", "re": "470", + "vcc": "12", "bjt_model": "2N2222", + }, + }, + "differential_amp": { + "func": generate_differential_amp, + "description": "Differential amplifier with op-amp", + "params": {"r1": "10k", "r2": "10k", "r3": "10k", "r4": "10k"}, + }, + "buck_converter": { + "func": generate_buck_converter, + "description": "Buck (step-down) converter with NMOS switch", + "params": { + "ind": "10u", "c_out": "100u", "r_load": "10", + "v_in": "12", "duty_cycle": "0.5", "freq": "100k", + "mosfet_model": "IRF540N", "diode_model": "1N5819", + }, + }, + "ldo_regulator": { + "func": generate_ldo_regulator, + "description": "LDO voltage regulator with PMOS pass transistor", + "params": { + "r1": "10k", "r2": "10k", "pass_transistor": "IRF9540N", + "v_in": "8", "v_ref": "2.5", + }, + }, + "h_bridge": { + "func": generate_h_bridge, + "description": "H-bridge motor driver with 4 NMOS transistors", + "params": {"v_supply": "12", "r_load": "10", "mosfet_model": "IRF540N"}, + }, + "sallen_key_lowpass": { + "func": generate_sallen_key_lowpass, + "description": "Sallen-Key lowpass filter (unity gain, 2nd order)", + "params": {"r1": "10k", "r2": "10k", "c1": "10n", "c2": "10n"}, + }, + "boost_converter": { + "func": generate_boost_converter, + "description": "Boost (step-up) converter with NMOS switch", + "params": { + "ind": "10u", "c_out": "100u", "r_load": "50", + "v_in": "5", "duty_cycle": "0.5", "freq": "100k", + }, + }, + "instrumentation_amp": { + "func": generate_instrumentation_amp, + "description": "3-opamp instrumentation amplifier", + "params": {"r1": "10k", "r2": "10k", "r3": "10k", "r_gain": "10k"}, + }, + "current_mirror": { + "func": generate_current_mirror, + "description": "BJT current mirror with reference and load", + "params": {"r_ref": "10k", "r_load": "1k", "vcc": "12"}, + }, + "transimpedance_amp": { + "func": generate_transimpedance_amp, + "description": "Transimpedance amplifier (current to voltage)", + "params": {"rf": "100k", "cf": "1p", "i_source": "1u"}, + }, +} + +# SPICE .cir netlist templates (create_from_template, list_templates, tune_circuit) +NETLIST_TEMPLATES: dict[str, dict] = { + "voltage_divider": { + "func": voltage_divider, + "description": "Resistive voltage divider with .op or custom analysis", + "params": {"v_in": "5", "r1": "10k", "r2": "10k", "sim_type": "op"}, + }, + "rc_lowpass": { + "func": rc_lowpass, + "description": "RC lowpass filter with AC sweep", + "params": {"r": "1k", "c": "100n", "f_start": "1", "f_stop": "1meg"}, + }, + "inverting_amplifier": { + "func": inverting_amplifier, + "description": "Inverting op-amp (gain = -Rf/Rin), +/-15V supply", + "params": {"r_in": "10k", "r_f": "100k", "opamp_model": "LT1001"}, + }, + "non_inverting_amplifier": { + "func": non_inverting_amplifier, + "description": "Non-inverting op-amp (gain = 1 + Rf/Rin), +/-15V supply", + "params": {"r_in": "10k", "r_f": "100k", "opamp_model": "LT1001"}, + }, + "differential_amplifier": { + "func": differential_amplifier, + "description": "Diff amp: Vout = (R2/R1)*(V2-V1), +/-15V supply", + "params": { + "r1": "10k", + "r2": "10k", + "r3": "10k", + "r4": "10k", + "opamp_model": "LT1001", + }, + }, + "common_emitter_amplifier": { + "func": common_emitter_amplifier, + "description": "BJT common-emitter with voltage divider bias", + "params": { + "rc": "2.2k", + "rb1": "56k", + "rb2": "12k", + "re": "1k", + "cc1": "10u", + "cc2": "10u", + "ce": "47u", + "vcc": "12", + "bjt_model": "2N2222", + }, + }, + "buck_converter": { + "func": buck_converter, + "description": "Step-down DC-DC converter with MOSFET switch", + "params": { + "ind": "10u", + "c_out": "100u", + "r_load": "10", + "v_in": "12", + "duty_cycle": "0.5", + "freq": "100k", + "mosfet_model": "IRF540N", + "diode_model": "1N5819", + }, + }, + "ldo_regulator": { + "func": ldo_regulator, + "description": "LDO regulator: Vout = Vref * (1 + R1/R2)", + "params": { + "opamp_model": "LT1001", + "r1": "10k", + "r2": "10k", + "pass_transistor": "IRF9540N", + "v_in": "8", + "v_ref": "2.5", + }, + }, + "colpitts_oscillator": { + "func": colpitts_oscillator, + "description": "LC oscillator: f ~ 1/(2pi*sqrt(L*Cseries))", + "params": { + "ind": "1u", + "c1": "100p", + "c2": "100p", + "rb": "47k", + "rc": "1k", + "re": "470", + "vcc": "12", + "bjt_model": "2N2222", + }, + }, + "h_bridge": { + "func": h_bridge, + "description": "4-MOSFET H-bridge motor driver with dead time", + "params": { + "v_supply": "12", + "r_load": "10", + "mosfet_model": "IRF540N", + }, + }, + "sallen_key_lowpass": { + "func": sallen_key_lowpass, + "description": "Sallen-Key lowpass filter (unity gain, 2nd order)", + "params": { + "r1": "10k", + "r2": "10k", + "c1": "10n", + "c2": "10n", + "opamp_model": "LT1001", + }, + }, + "boost_converter": { + "func": boost_converter, + "description": "Step-up DC-DC converter with MOSFET switch", + "params": { + "ind": "10u", + "c_out": "100u", + "r_load": "50", + "v_in": "5", + "duty_cycle": "0.5", + "freq": "100k", + "mosfet_model": "IRF540N", + "diode_model": "1N5819", + }, + }, + "instrumentation_amplifier": { + "func": instrumentation_amplifier, + "description": "3-opamp instrumentation amp: gain = 1 + 2*R1/R_gain", + "params": { + "r1": "10k", + "r2": "10k", + "r3": "10k", + "r_gain": "10k", + }, + }, + "current_mirror": { + "func": current_mirror, + "description": "BJT current mirror with reference resistor", + "params": { + "r_ref": "10k", + "r_load": "1k", + "vcc": "12", + "bjt_model": "2N2222", + }, + }, + "transimpedance_amplifier": { + "func": transimpedance_amplifier, + "description": "TIA: converts input current to output voltage (Vout = -Iph * Rf)", + "params": { + "rf": "100k", + "cf": "1p", + "i_source": "1u", + }, + }, +} + + +def resolve_template(name: str) -> tuple[dict, bool] | None: + """Resolve a template name to (entry, is_netlist), netlist registry first. + + Returns None if the name is in neither registry. Netlist-first precedence + preserves the historical lookup order: a name present in both registries + (e.g. "rc_lowpass") resolves to its netlist form. + """ + if name in NETLIST_TEMPLATES: + return NETLIST_TEMPLATES[name], True + if name in ASC_TEMPLATES: + return ASC_TEMPLATES[name], False + return None + + +def all_template_names() -> list[str]: + """Sorted union of every template name across both registries.""" + return sorted(set(NETLIST_TEMPLATES) | set(ASC_TEMPLATES))