Time-Domain System Equivalent logoTime-Domain System EquivalentLinear dynamics, solved faster.Discuss Integration

Adapter Circuit

Circuit-domain preparation workflows from netlist or spectrum inputs into Builder-ready artifacts.

Use this chapter when your starting point is a circuit description rather than Builder-ready H or FRF data. It shows how Adapter Circuit turns netlists and RAW cases into matrices, source-side waveforms, probes, and Builder-ready handoff data.

Use this chapter for workflow meaning and integration choices. If you only need exact command flags, jump to CLI Reference. If you only need to check whether your input deck is inside the supported subset, jump to Element Reference.

Read this chapter in three passes if you need to:

  • get one common task working from the CLI
  • embed the adapter through the C API
  • tune advanced behavior such as solver policy, planning, or performance

Start with the shortest supported path that matches your question. If your goal is a qualified .pack with minimal glue, jump to the workflow path first. If your goal is circuit visibility, start with the CLI matrix / probe flows. Drop to the lower-level C API only when you need explicit phase control or intermediate artifacts.

For most integrations, Adapter Circuit falls into one of these roles:

Integration goalRecommended starting surface
netlist or RAW -> .pack as fast as possibleworkflow API
inspect matrices or probes before building a packCLI matrix / probe path
embed circuit compilation and sweeps inside host codecompile-then-compute C API

What Adapter Circuit Owns

Imagine you have a circuit — maybe a SPICE netlist from your PDK, maybe a PSS/E RAW case from a grid study, maybe a custom deck with frequency-dependent NPORT elements. You need to get that circuit into TDSE so Builder can produce a runtime pack. But Builder doesn't understand circuits. It understands frequency-domain matrices and time-domain source waveforms.

Adapter Circuit bridges that gap. It takes circuit descriptions, compiles them into a solvable form, and produces the outputs Builder needs: Y and Z matrices over frequency, VOC and ISC time sequences, and probe data for validation. It also handles RAW import — converting PSS/E power-system cases into circuit netlists.

Circuit Input        Adapter Circuit       Builder            Runtime
(netlist, RAW)  →    Y/Z, VOC/ISC,   →    .pack file   →    step loop
                     probe outputs

The separation between layers is deliberate:

  • Adapter Circuit owns circuit parsing, solve orchestration, and circuit-domain validation. It knows about nodes, branches, and MNA stamping. It does not know about Builder packs or Runtime step loops.
  • Builder converts validated frequency-domain data into a runtime pack. It knows about rational fitting and passivity enforcement. It does not know about circuits.
  • Runtime executes the pack against a host solver. It knows about time stepping and conductance matrices. It does not know about circuits or fitting.

This layering means you can swap any piece independently. Use a different circuit tool to produce Y matrices? Feed them directly to Builder. Want to use the circuit solver without Builder? Adapter Circuit's probe and series commands give you standalone access.

When to Use It

You should reach for Adapter Circuit when your source data begins as:

  • A SPICE-like netlist (.cir, .sp, or text)
  • A PSS/E RAW case file that needs conversion
  • A circuit deck containing NPORT elements that reference Touchstone files (.ynp, .znp, .y2p, .s2p, etc.)

The typical workflow is: compile a netlist, run one or more compute operations, then hand the results to Builder. But you can also use Adapter Circuit standalone — for circuit validation, for exploring frequency response, or for generating probe data.

What It Produces

Every Adapter Circuit operation falls into one of these categories:

  • Frequency-domain matrices: Y(jω) or Z(jω) over a user-specified frequency grid. These are the primary input to Builder's h_from_spectrum.
  • Source-side time sequences: Open-circuit voltage (VOC) or short-circuit current (ISC) waveforms. These define the Norton/Thevenin equivalents that drive the TDSE model in a host solver.
  • Probe outputs: Internal node voltages or branch currents, in either AC (frequency sweep) or transient (time-domain) form. Probes let you inspect what's happening inside the circuit without extracting full port matrices.
  • Diagnostics: Structured data describing parser behavior, solver backend selection, matrix conditioning, and policy decisions. Every result struct carries a diagnostics block.

Header Map

Most users only need tdse_adapter_circuit.h. Pull in narrower headers only when you are using those features directly:

HeaderPurpose
tdse_adapter_circuit.hUmbrella include — pulls in everything
tdse_adapter_circuit/common.hHandle lifecycle, error codes, core data types
tdse_adapter_circuit/compile.hNetlist compilation, compiled info queries
tdse_adapter_circuit/compute.hMatrix sweeps, port series, probes, region preparation
tdse_adapter_circuit/raw.hRAW import and conversion (PSS/E → netlist)
tdse_adapter_circuit/policy.hSolver policy and backend selection
tdse_adapter_circuit/diagnostics.hDiagnostics, policy tracing, solver statistics
tdse_adapter_circuit/planning.hAdaptive sweep planning and tail-vs-nfreq analysis
tdse_adapter_circuit/seq.hSequence network import (.seq files) and fault analysis
tdse_adapter_circuit/init.hStruct initializers, convenience helpers, macros
tdse_adapter_circuit.hppC++ wrapper with RAII and exception-based errors
tdse_workflow.hHigh-level workflows (e.g. tdse_workflow_netlist_to_pack)

For most integration work, treat this table as a lookup aid rather than required reading.

Quick Start

Before diving into the full API, here is the shortest path from a netlist to a frequency-domain matrix. If you have a SPICE file called my_circuit.cir and you want its Y matrix at DC plus four positive frequencies:

tdse adapter circuit matrix \
  --netlist-kind file --netlist my_circuit.cir \
  --matrix y --ports "1,0" \
  --w0-radps 0 --dw-radps 100 --nfreq 5 \
  --out-data matrix.csv --json-out -

This single command compiles the netlist, solves the circuit at each frequency, extracts the port admittance, and writes the results to matrix.csv. The --json-out - flag prints a machine-readable envelope to stdout — you will want this in any automated workflow.

The output CSV contains one row per frequency, with the real and imaginary parts of each Y matrix element in interleaved order: re(Y11), im(Y11), re(Y12), im(Y12), ....

For runnable examples of every CLI command and C API pattern, see the Examples Guide.

Choose Your Path

Use this table before reading deeper:

If you need to...Read firstThen use
turn a netlist or RAW case into a .pack with minimum glueRecommended Workflow API PathExamples Guide
get a Y or Z matrix for BuilderCommon CLI TasksBuilder Handoff
convert a PSS/E RAW caseRAW ImportBuilder Handoff
inspect internal circuit behaviorprobe — Observe Internal Voltages or CurrentsDiagnostics
embed the adapter in host codeCore C API TasksEmbedding in a Host Solver (MNA)
tune backend choice or sweep strategySolver PolicyAdvanced Tuning And Performance

For exhaustive CLI flags and machine-readable CLI outputs, use CLI Reference. This chapter keeps the command examples and the adapter-side meaning, not every CLI contract detail.

Common End-To-End Paths

Most users do not need this whole chapter at once. They need one of these short paths:

Starting pointShort path
SPICE-like netlist -> Builder packmatrix -> Builder handoff -> Runtime create
PSS/E RAW case -> Builder packraw-to-netlist -> matrix -> Builder handoff
netlist -> validation data onlyprobe or series -> diagnostics review

If your goal is a .pack file, stay focused on matrix generation and Builder handoff first. Probe and series workflows are more useful when you are validating the circuit model or debugging a mismatch.

Minimal Integration Decision

Before writing code, decide which side of the boundary needs to own intermediate artifacts:

  • if the host only needs a qualified .pack, use the workflow path
  • if the host must archive or inspect Y/Z, VOC/ISC, or probe outputs, use Adapter directly
  • if the host must control every phase boundary, use compile-then-compute and then hand off to Builder explicitly

If your real goal is "turn this netlist or RAW case into a qualified .pack with as little hand-stitched glue as possible," prefer the workflow API before you build your own Adapter -> Builder orchestration.

The public workflow surface covers three common starting points:

  • tdse_workflow_netlist_to_pack(...)
  • tdse_workflow_raw_to_pack(...)
  • tdse_workflow_yz_matrix_to_pack(...)

Why this path is valuable:

  • it keeps Builder planning inputs in one request object
  • it records whether the grid or sweep plan was auto-derived
  • it returns passivity and round-trip verification in the same result
  • it gives you one status surface that still preserves underlying Adapter, Builder, Runtime, and N-port outcomes

Use the workflow API when:

  • you want the shortest supported path from circuit input to .pack
  • your host does not need to intercept every intermediate matrix artifact
  • you want one qualification result that already includes pack-quality signals

Drop down to manual Adapter + Builder handoff only when you need to:

  • archive intermediate Y or Z matrices explicitly
  • inject a custom Builder correction or tail-processing sequence outside the workflow defaults
  • debug a mismatch by isolating compile, sweep, Builder conversion, and Runtime verification as separate phases

For a runnable public example, start with workflow_cpp_quickstart in the Examples Guide.

Workflow ownership split:

  • workflow / Adapter / Builder own circuit compilation, planning, and pack construction
  • the host owns source-file selection, workflow parameters, artifact archival, and the later Runtime embedding choice

Mental Model

Before you start writing code, it helps to understand the two fundamental patterns in Adapter Circuit.

The compile-then-compute pattern. You compile a netlist once, which gives you a handle — an opaque object that holds the parsed and analyzed circuit. That handle is then reused across as many compute calls as you need. Compilation is the expensive step (parsing, topology analysis, MNA construction). Compute calls are relatively cheap, and you can run them with different parameters — different frequency grids, different ports, different time steps — against the same handle.

compile_from_netlist(&req, &result)  →  handle

    ├── compute_port_fsweep(handle, ...)   →  Y or Z matrix
    ├── compute_port_series(handle, ...)   →  VOC or ISC time series
    ├── compute_probes(handle, ...)        →  internal voltage/current
    └── compute_port_fsweep(handle, ...)   →  another sweep, same handle

The two-output pattern. Every Adapter Circuit function that produces data follows a two-call pattern. First call: pass NULL for the output buffer and zero for its length — the result tells you how much space you need. Second call: allocate and pass the real buffer. This avoids guessing buffer sizes. The _init() functions and the sizing pass handle the details for you.

Common CLI Tasks

The CLI is the fastest way to validate a netlist, explore a circuit, or generate Builder-facing artifacts. The sections below explain the intent of each command family. Use CLI Reference when you need exhaustive flag or output-field detail.

All adapter circuit commands share two conventions: --json-out <path|-> for machine-readable output, and --netlist-kind <file|text|stdin> for specifying how the netlist is provided.

If you are writing a script, always use --json-out - (stdout) and parse the JSON envelope. The human-readable output is for interactive use only — its format may change between versions.

caps — Check Available Backends

Before you run any computation, you need to know what solver backends are available on your machine. The caps command answers this: it prints a table of every backend the SDK was built with, and whether each one is usable right now.

tdse adapter circuit caps --out-caps - --json-out -

This is the first thing to run on a new machine or after upgrading the SDK. A backend might show as unavailable because a required library is missing (CUDA, MKL), because the GPU driver is outdated, or because the backend was not compiled into this build.

FlagMeaning
--out-caps <path|->Write backend capability table (default: stdout)
--out-build-features <path|->Write build-time feature flags as JSON
--json-out <path|->Machine-readable output envelope

matrix — Compute Y or Z over Frequency

This is the workhorse command. You have a netlist and a set of ports, and you want the admittance (Y) or impedance (Z) matrix at each frequency in a grid. The output goes directly to Builder — the port order you specify here must match the port order you use in h_from_spectrum.

tdse adapter circuit matrix \
  --netlist-kind file --netlist my_circuit.cir \
  --matrix y --ports "1,0;2,0" \
  --w0-radps 0 --dw-radps 100 --nfreq 5 \
  --out-data matrix.csv --json-out -

The frequency grid is defined by three numbers: a start frequency w0 in rad/s, a step dw in rad/s, and a count nfreq. The grid covers w0, w0 + dw, w0 + 2*dw, ..., w0 + (nfreq-1)*dw. For a DC start use --w0-radps 0.

FlagMeaningDefault
--netlist-kind file|text|stdinHow the netlist is provided(required)
--netlist <path_or_text>Netlist source (omit for stdin)
--matrix y|zAdmittance (Y) or impedance (Z)(required)
--ports "p,n;p,n;..."Port definitions by numeric node index. String names in C API(required)
--w0-radpsStart frequency in rad/s. Use 0 for DC(required)
--dw-radpsFrequency step in rad/s(required)
--nfreqNumber of frequency points(required)
--dc-policyDC frequency resolution policyexact_dc_then_fallback
--dc-extrapolate-points <N>Points used for DC extrapolation4
--out-data <path|->Output CSV file path

DC policy values:

Solving at exactly zero frequency (DC) is harder than it sounds. Inductors become shorts, capacitors become opens, and the MNA matrix can become singular. These policies control how the solver handles ω = 0:

CLI valueEnum constantDescription
regularized_exact_dcW0_REGULARIZED_EXACT_DCAdds small conductance to regularize inductors at DC
extrapolate_from_positiveW0_EXTRAPOLATE_FROM_POSITIVESkips DC, extrapolates from positive-frequency data
exact_dc_then_fallbackW0_EXACT_DC_THEN_FALLBACKExact DC first; regularization fallback if singular

The default (exact_dc_then_fallback) is the right choice for most circuits. Switch to extrapolate_from_positive if your circuit has no DC path to ground (floating nets, ideal transformers) and regularization isn't helping.

series — Compute VOC or ISC Time Sequences

The matrix command gives you frequency-domain behavior. But to drive a time-domain simulation, you often need source waveforms — the open-circuit voltage or short-circuit current seen at each port as a function of time. That's what series computes.

tdse adapter circuit series \
  --netlist-kind file --netlist my_circuit.cir \
  --series voc --ports "1,0" \
  --method transient --dt 1e-4 --steps 64 \
  --out-data series.csv --json-out -

The output is a time series: one row per time step, one column per port. The time at step k is t0 + k * dt.

FlagMeaningDefault
--series voc|iscOpen-circuit voltage or short-circuit current(required)
--ports "p,n;p,n;..."Port definitions(required)
--method transient|ifft|toneSynthesis method(required)
--dtTime step in seconds(required)
--stepsNumber of time steps(required)
--nfft <N>FFT size for ifft method
--t0 <t0>Start time offset0
--out-data <path|->Output CSV file path

Choosing a synthesis method:

The method you pick depends on your circuit and what accuracy you need:

MethodWhen to use
transientGeneral-purpose time-domain solver. Handles nonlinear devices and switching
ifftFrequency-domain sources, linear circuit. Faster than transient. Requires nfft ≥ 2*(nfreq-1)
toneSingle-frequency steady-state analysis. Use for AC steady-state verification, not for transient simulation

If you are unsure, start with transient. It is slower but always correct. Once you have validated results, you can experiment with ifft for speed.

probe — Observe Internal Voltages or Currents

Port matrices tell you what happens at the terminals. But often you need to see inside the circuit — the voltage at an internal node, the current through a particular branch. Probes let you instrument the circuit and extract those values without modifying the netlist.

# AC probe — frequency sweep of internal quantities
tdse adapter circuit probe \
  --domain ac --netlist-kind file --netlist my_circuit.cir \
  --observe "v(1);v(2,0)" --observe-source argument \
  --ac-excitation small_signal \
  --sweep-kind lin --fstart-hz 50 --fstop-hz 5000 --sweep-points 100 \
  --out-data probe.csv --json-out -

# Transient probe — time-domain waveform of internal quantities
tdse adapter circuit probe \
  --domain transient --netlist-kind file --netlist my_circuit.cir \
  --observe "v(1);i(r1)" --observe-source argument \
  --method transient --dt 1e-4 --steps 64 \
  --out-data probe.csv --json-out -

Probes work in both AC and transient domains. In the AC domain, you get frequency sweeps of complex voltages and currents. In the transient domain, you get time-domain waveforms. The probe expressions use a simple syntax:

ExpressionMeaning
v(n)Voltage at node n relative to ground
v(p,n)Voltage between nodes p and n
i(r1)Current through element named r1
i(vsrc)Current through independent source vsrc
FlagMeaningDefault
--domain ac|transientAC small-signal or transient time-domain(required)
--observe "v(n);v(p,n);i(element);..."Probe expressions, semicolon-separated
--observe-source argument|netlist|argument_or_netlistProbe definition sourceargument_or_netlist
--ac-excitation small_signal|legacy_tonesAC excitation modesmall_signal
--sweep-kind from_netlist|lin|dec|oct|listFrequency sweep type for ACfrom_netlist
--sweep-points <N>Number of points for lin/dec/oct sweeps
--fstart-hz <f0>Start frequency in Hz
--fstop-hz <f1>Stop frequency in Hz
--freq-list-hz <f0,f1,...>Explicit frequency list for list sweep
--method transient|ifft|toneSynthesis method (transient probes)(required)
--dt, --stepsTime step and count (transient probes)(required)
--nfft <N>FFT size (transient)
--t0 <t0>Start time offset (transient)0

The --observe-source flag is worth understanding. When set to argument, the CLI uses the probes you specify with --observe. When set to netlist, it reads .probe directives from the netlist itself. The default argument_or_netlist checks both: if you pass --observe it uses those; otherwise it falls back to netlist probes.

When to Use Which Command

If you are new to Adapter Circuit, this table is your cheat sheet:

You want to...Use
Check what solvers are available on this machinecaps
Get Y or Z over frequency for Builder handoffmatrix
Get VOC or ISC time-domain waveforms for a host solverseries
Observe internal voltages or branch currents for validationprobe

RAW Import

Not every circuit starts as a SPICE netlist. In power systems, the starting point is often a PSS/E RAW case file — a grid description with buses, generators, branches, loads, and transformers. RAW Import converts these files into circuit netlists that Adapter Circuit can compile.

The conversion process reads the RAW file, builds an equivalent circuit model for each power-system element, stitches them together, and writes out a SPICE netlist. Base- frequency validation runs automatically — the generated netlist is solved at the nominal system frequency and the resulting bus voltages and branch flows are compared against the original RAW data.

CLI

tdse adapter circuit raw-to-netlist \
  --raw-kind file --raw case.raw \
  --out-netlist converted.cir \
  --unit pu --transformer-model ideal_tap_series --load-model shunt \
  --include-generator-sources 1 --json-out -

The three model choices — transformer, load, and generator — control how faithfully the netlist reproduces the RAW case behavior:

FlagMeaningDefault
--raw-kind file|text|stdinHow the RAW input is provided(required)
--raw <path_or_text>RAW source(required)
--out-netlist <path|->Output netlist path
--out-report-json <path|->Output validation report
--unit pu|siPer-unit or SI outputpu
--transformer-model series_only|ideal_tap_series|tap_split_shuntEquivalent circuit modelideal_tap_series
--load-model shunt|series|zip_splitLoad equivalent modelshunt
--include-generator-sources 0|1Include generator Norton equivalents1
--include-fallback-source 0|1Add fallback source for unexcited buses1
--include-tran 0|1Emit .tran directive1
--include-options 0|1Emit .options directive1
--dt <sec>Time step for .tran directive5e-5
--tstop <sec>Stop time for .tran directive0.4
--freq-hz|--nominal-frequency-hz <hz>Base frequency in Hz60
--global-bus-shunt-c-f <farad>Global bus shunt capacitance0
--include-inactive-bus 0|1Include inactive buses in validation0
--ac-adjust-from-sine 0|1Adjust AC phasors from sine reference1
--mag-tol-pu <x>Magnitude tolerance for validation0.03
--ang-tol-deg <x>Angle tolerance in degrees3.0
--complex-tol-pu <x>Complex tolerance for validation0.08
--report-top-k <N>Top-K worst-case buses in the report20

C API

The C API for RAW import uses the same pattern as the rest of Adapter Circuit: configure a request struct, call the function, and check the result. It uses the same tdse_adapter_circuit_err_t error type, so tdse_adapter_circuit_status_message() works for both adapter and RAW errors.

#include <tdse_adapter_circuit/raw.h>

tdse_adapter_circuit_raw_import_options_t import_opt;
memset(&import_opt, 0, sizeof(import_opt));
import_opt.struct_size = sizeof(import_opt);
import_opt.unit_mode = TDSE_ADAPTER_CIRCUIT_RAW_UNIT_PU;
import_opt.transformer_model =
    TDSE_ADAPTER_CIRCUIT_RAW_TRANSFORMER_IDEAL_TAP_SERIES;
import_opt.load_model = TDSE_ADAPTER_CIRCUIT_RAW_LOAD_SHUNT;
import_opt.include_generator_sources = 1;

tdse_adapter_circuit_raw_options_t opt;
memset(&opt, 0, sizeof(opt));
opt.struct_size = sizeof(opt);
opt.import_options = import_opt;
opt.validation_options.struct_size =
    sizeof(tdse_adapter_circuit_raw_validation_options_t);

tdse_adapter_circuit_raw_request_t req;
memset(&req, 0, sizeof(req));
req.struct_size = sizeof(req);
req.source_kind = TDSE_ADAPTER_CIRCUIT_RAW_SOURCE_FILE;
req.output_kind = TDSE_ADAPTER_CIRCUIT_RAW_OUTPUT_FILE;
req.raw_source = "case.raw";
req.out_netlist_path = "converted.cir";
req.options = &opt;

tdse_adapter_circuit_raw_result_t result;
memset(&result, 0, sizeof(result));
result.struct_size = sizeof(result);

int rc = tdse_adapter_circuit_raw_to_netlist(&req, &result);

There are two output modes. When output_kind is RAW_OUTPUT_FILE, the netlist is written to out_netlist_path. When output_kind is RAW_OUTPUT_BUFFER, the adapter allocates the output and you release it with tdse_adapter_circuit_raw_free_owned_output(). Use the buffer mode when you want to pipe the netlist directly into compilation without touching disk.

The legacy function tdse_adapter_circuit_raw_status_message() still works and delegates to the unified status message, but new code should use tdse_adapter_circuit_status_message().

For runnable examples, see raw_import_api (C API) and raw_import_cpp (C++ wrapper) in the Examples Guide.

If you want the full RAW-to-pack pipeline in a single call, use the workflow tdse_workflow_raw_to_pack() which chains RAW import, compilation, frequency sweep (or adaptive planning), and Builder handoff into one operation.

Transformer Models

Transformers in PSS/E RAW files have tap ratios and phase shifts. The model you choose determines how these are represented in the circuit netlist:

ModelEnum constantDescription
series_onlyRAW_TRANSFORMER_SERIES_ONLYOnly series impedance, ignores tap ratio. Simplest
ideal_tap_seriesRAW_TRANSFORMER_IDEAL_TAP_SERIESIdeal tap in series with leakage impedance
tap_split_shuntRAW_TRANSFORMER_TAP_SPLIT_SHUNTTap split into series + shunt. Most physical, more nodes

Load Models

Loads (constant power, constant current, constant impedance) are converted to circuit elements. The model controls the equivalent:

ModelEnum constantDescription
shuntRAW_LOAD_SHUNTConstant admittance to ground. Works well for most studies
seriesRAW_LOAD_SERIESSeries impedance. Use when you need to preserve the branch topology
zip_splitRAW_LOAD_ZIP_SPLITSeparates constant-Z, I, P components. Most accurate for load-flow

Validation Output

After conversion, the import result includes a validation summary. This tells you whether the generated netlist reproduces the original RAW case behavior:

FieldMeaning
bus_countTotal buses in the RAW case
active_bus_countBuses with defined voltage (excludes isolated/de-energized buses)
generator_countNumber of generators converted to Norton sources
branch_countNumber of branches (lines + transformers)
warning_countNumber of non-fatal warnings during conversion
netlist_required_lenSize of the generated netlist in bytes

Core C API Tasks

The C API is what you use when integrating Adapter Circuit into a host application. It is organized around a compile-then-compute pattern: compile a netlist once to get a handle, then run as many compute operations as you want against that handle.

Most integrations only need four C API tasks:

  1. compile a netlist
  2. compute a matrix, series, or probe output
  3. inspect diagnostics on failure or during tuning
  4. destroy the handle cleanly when the work is done

All functions return a tdse_adapter_circuit_err_t error code. Call tdse_adapter_circuit_status_message(code) for a human-readable description and tdse_adapter_circuit_get_last_error_text() for thread-local diagnostic detail.

If you prefer C++, the header tdse_adapter_circuit.hpp provides a thin wrapper with RAII handle management, exception-based error handling, and convenience constructors. The underlying solver is the same — the C++ wrapper is pure syntactic convenience.

Init Functions

Most structs in Adapter Circuit have a corresponding _init() function. These functions zero-initialize the struct and set struct_size correctly. Always use them — they protect you from ABI issues when structs grow new fields in future SDK versions.

Init functionReturns
tdse_adapter_circuit_options_init()tdse_adapter_circuit_options_t
tdse_adapter_circuit_region_spec_init()tdse_adapter_circuit_region_spec_t
tdse_adapter_circuit_time_options_init()tdse_adapter_circuit_time_options_t
tdse_adapter_circuit_probe_options_init()tdse_adapter_circuit_probe_options_t
tdse_adapter_circuit_ac_probe_options_init()tdse_adapter_circuit_ac_probe_options_t
tdse_adapter_circuit_ac_sweep_init()tdse_adapter_circuit_ac_sweep_t
tdse_adapter_circuit_node_voltage_probe_init(p, n)tdse_adapter_circuit_probe_def_t
tdse_adapter_circuit_branch_current_probe_init(name)tdse_adapter_circuit_probe_def_t
tdse_adapter_circuit_named_port_def_init(p, n)tdse_adapter_circuit_named_port_def_t
tdse_adapter_circuit_compile_request_init()tdse_adapter_circuit_compile_request_t
tdse_adapter_circuit_compile_result_init()tdse_adapter_circuit_compile_result_t
tdse_adapter_circuit_port_fsweep_request_init()tdse_adapter_circuit_port_fsweep_request_t
tdse_adapter_circuit_port_fsweep_result_init()tdse_adapter_circuit_port_fsweep_result_t
tdse_adapter_circuit_port_series_request_init()tdse_adapter_circuit_port_series_request_t
tdse_adapter_circuit_port_series_result_init()tdse_adapter_circuit_port_series_result_t
tdse_adapter_circuit_probe_compute_request_init()tdse_adapter_circuit_probe_compute_request_t
tdse_adapter_circuit_probe_compute_result_init()tdse_adapter_circuit_probe_compute_result_t
tdse_adapter_circuit_diagnostics_summary_init()tdse_adapter_circuit_diagnostics_summary_t
tdse_adapter_circuit_policy_trace_init()tdse_adapter_circuit_policy_trace_t
tdse_adapter_circuit_prepare_region_request_init()tdse_adapter_circuit_prepare_region_request_t
tdse_adapter_circuit_prepare_region_result_init()tdse_adapter_circuit_prepare_region_result_t
tdse_adapter_circuit_seq_options_init()tdse_adapter_circuit_seq_options_t
tdse_adapter_circuit_seq_network_compile_request_init()tdse_adapter_circuit_seq_network_compile_request_t
tdse_adapter_circuit_seq_network_compile_result_init()tdse_adapter_circuit_seq_network_compile_result_t
tdse_adapter_circuit_seq_fault_request_init()tdse_adapter_circuit_seq_fault_request_t
tdse_adapter_circuit_seq_fault_result_init()tdse_adapter_circuit_seq_fault_result_t

Related helper families are grouped by header:

  • planning.h: _adaptive_sweep_config_init, _adaptive_sweep_request_init, _adaptive_sweep_result_init, _tail_vs_nfreq_request_init, _tail_vs_nfreq_result_init
  • raw.h: _raw_request_init, _raw_result_init, _raw_options_init
  • seq.h: the five _seq_*_init functions listed above

Use this table as a lookup aid. You do not need to memorize the full list before calling the core compile and compute APIs.

Configuring Newton Solver Tolerances

tdse_adapter_circuit_time_options_init() also sets these appended fields (gated on struct_size; safe defaults for standard circuits):

FieldDefaultDescription
newton_abs_tol1e-8Absolute convergence tolerance for Newton iterations
newton_rel_tol1e-6Relative convergence tolerance
newton_residual_tol1e-6Residual norm tolerance
newton_max_iterations32Maximum Newton iterations per timestep (0 = use default)

Adjust these when circuits exhibit slow convergence (try relaxing tolerances) or require higher precision (tighten tolerances).

Compile a Netlist

Compilation is always the first step. You provide a netlist (as a file path or as an in-memory string) and receive a handle:

#include <tdse_adapter_circuit.h>

tdse_adapter_circuit_options_t opt = tdse_adapter_circuit_options_init();
opt.policy = TDSE_ADAPTER_CIRCUIT_W0_EXACT_DC_THEN_FALLBACK;
opt.inductor_gbig = 1e12;

tdse_adapter_circuit_compile_request_t req =
    tdse_adapter_circuit_compile_request_init();
req.source_kind = TDSE_ADAPTER_CIRCUIT_NETLIST_SOURCE_FILE;
req.netlist_source = "my_circuit.cir";
req.options = opt;

tdse_adapter_circuit_compile_result_t result =
    tdse_adapter_circuit_compile_result_init();
int rc = tdse_adapter_circuit_compile_from_netlist(&req, &result);
// result.handle is now ready for compute calls

The options struct controls DC policy and inductor modeling. If you are using the default EXACT_DC_THEN_FALLBACK policy, inductor_gbig sets the conductance used to regularize inductors when the exact DC solve fails. The default of 1e12 works for most circuits.

Query a Compiled Handle

Once you have a compiled handle, you can ask it about the circuit it contains. The most common queries are node count, MNA matrix size, and node names:

tdse_adapter_circuit_compiled_info_t info;
memset(&info, 0, sizeof(info));
info.struct_size = sizeof(info);
tdse_adapter_circuit_get_compiled_info(result.handle, &info);
// info.node_count           — total public nodes
// info.internal_node_count  — nodes created by MNA stamping
// info.mna_matrix_size      — dimension of the MNA system

// Query a public node name by index (0 = ground when present):
size_t required_len;
tdse_adapter_circuit_get_node_name(result.handle, 0, NULL, &required_len);
char* name = malloc(required_len);
tdse_adapter_circuit_get_node_name(result.handle, 0, name, &required_len);

Options

tdse_adapter_circuit_options_init() provides safe defaults. The fields you are most likely to adjust:

FieldDefaultMeaning
policyW0_EXACT_DC_THEN_FALLBACKHow DC (zero frequency) is handled
inductor_gbig1e12Conductance used to regularize inductors at DC
dc_extrapolate_points4Number of positive-frequency points used when extrapolating DC

Compute a Y Matrix

This is the most common operation. Given a compiled handle and a list of ports, compute the admittance matrix at every frequency in a grid:

tdse_adapter_circuit_port_def_t ports[] = {{1, 0}};
tdse_adapter_circuit_grid_t grid = {.w0 = 0.0, .dw = 100.0, .nfreq = 5};

tdse_adapter_circuit_port_fsweep_request_t freq_req =
    tdse_adapter_circuit_port_fsweep_request_init();
freq_req.handle = result.handle;
freq_req.matrix_kind = TDSE_ADAPTER_CIRCUIT_MATRIX_Y;
freq_req.ports = ports;
freq_req.port_count = 1;
freq_req.grid = grid;

// Output buffer: 2 doubles per matrix element (real + imag),
//                nfreq frequency points,
//                port_count rows × port_count columns
size_t n_vals = 2 * grid.nfreq * port_count * port_count;
double* matrix_ri = malloc(n_vals * sizeof(double));
freq_req.out_matrix_ri = matrix_ri;
freq_req.out_matrix_ri_len = n_vals;

tdse_adapter_circuit_port_fsweep_result_t freq_result =
    tdse_adapter_circuit_port_fsweep_result_init();
int rc = tdse_adapter_circuit_compute_port_fsweep(&freq_req, &freq_result);

The output is stored in interleaved real-imaginary format, row-major by port index. For a 2-port Y matrix, element Y[p][q] at frequency k lives at:

matrix_ri[2 * (k * nports * nports + p * nports + q)]      = real(Y_pq[k])
matrix_ri[2 * (k * nports * nports + p * nports + q) + 1]  = imag(Y_pq[k])

Compute Port Series (VOC / ISC)

For time-domain excitation, you need waveforms not matrices. compute_port_series produces open-circuit voltage or short-circuit current versus time:

tdse_adapter_circuit_port_series_request_t series_req =
    tdse_adapter_circuit_port_series_request_init();
series_req.handle = result.handle;
series_req.response_kind = TDSE_ADAPTER_CIRCUIT_PORT_RESPONSE_VOC;
series_req.ports = ports;
series_req.port_count = 1;
series_req.dt = 1e-4;
series_req.steps = 64;

double* series_out = malloc(series_req.steps * port_count * sizeof(double));
series_req.out_values = series_out;
series_req.out_values_len = series_req.steps * port_count;

tdse_adapter_circuit_port_series_result_t series_result =
    tdse_adapter_circuit_port_series_result_init();
int rc = tdse_adapter_circuit_compute_port_series(&series_req, &series_result);

The output is step-major: series_out[step * nports + port] gives the value at that time step for that port.

Compute Probes

Probes observe internal circuit quantities without extracting full port matrices. Define what you want to measure, then call compute_probes:

tdse_adapter_circuit_probe_def_t probe =
    tdse_adapter_circuit_node_voltage_probe_init(1, 0);
// or: tdse_adapter_circuit_branch_current_probe_init("r1");

tdse_adapter_circuit_probe_compute_request_t probe_req =
    tdse_adapter_circuit_probe_compute_request_init();
probe_req.handle = result.handle;
probe_req.domain = TDSE_ADAPTER_CIRCUIT_PROBE_DOMAIN_AC;
probe_req.probes = &probe;
probe_req.probe_count = 1;
// For AC: set ac_sweep and ac_probe_options
// For transient: set dt, steps, and time_options

tdse_adapter_circuit_probe_compute_result_t probe_result =
    tdse_adapter_circuit_probe_compute_result_init();
int rc = tdse_adapter_circuit_compute_probes(&probe_req, &probe_result);

Each probe domain has different required parameters. To prevent mistakes — like passing transient time-step fields to an AC probe — two convenience wrappers expose only the relevant fields:

// AC probes — only frequency-sweep parameters are accepted
tdse_adapter_circuit_compute_ac_probes(
    handle, probes, probe_count, ac_sweep, ac_probe_options, &result);

// Transient probes — only time-domain parameters are accepted
tdse_adapter_circuit_compute_transient_probes(
    handle, probes, probe_count, dt, steps, time_options, &result);

Use these wrappers in new code. They make domain-mismatch mistakes a compile-time error instead of a runtime surprise.

Prepare a Circuit Region

Some netlists define named regions — groups of ports treated as a unit. Region preparation resolves a region name into an explicit port list and creates a prepared handle for it:

tdse_adapter_circuit_region_spec_t region =
    tdse_adapter_circuit_region_spec_init();
region.name = "main";

tdse_adapter_circuit_prepare_region_request_t prep_req =
    tdse_adapter_circuit_prepare_region_request_init();
prep_req.source_handle = result.handle;
prep_req.region = &region;

tdse_adapter_circuit_prepare_region_result_t prep_result =
    tdse_adapter_circuit_prepare_region_result_init();
int rc = tdse_adapter_circuit_prepare_region(&prep_req, &prep_result);
// prep_result.resolved_port_count tells how many ports were resolved

This is most useful when your netlist defines regions with TDSE directives and you want the adapter to enumerate the ports automatically instead of listing them by hand.

Named Ports

When you know node names but not their numeric indices, use named ports. The adapter resolves string names to indices during the compute call:

tdse_adapter_circuit_named_port_def_t ports[] = {
    tdse_adapter_circuit_named_port_def_init("1", "0"),
    tdse_adapter_circuit_named_port_def_init("OUT", "GND"),
};
freq_req.named_ports = ports;
freq_req.port_count = 2;
// Leave freq_req.ports = NULL — the adapter resolves names to indices.

Named ports work everywhere numeric ports work: port_fsweep_request_t, port_series_request_t, and adaptive sweep / tail analysis requests. You can even mix them — pass named_ports for resolution and leave ports NULL.

Handle Reuse and Thread Safety

A compiled handle is designed to be reused. Compile once, then run as many compute calls as you need:

tdse_adapter_circuit_compile_from_netlist(&req, &result);

for (int k = 0; k < num_sweeps; ++k) {
    freq_req.handle = result.handle;
    freq_req.grid = grids[k];   // different grid each iteration
    tdse_adapter_circuit_compute_port_fsweep(&freq_req, &freq_result);
}

tdse_adapter_circuit_destroy(result.handle);

Thread safety: A handle is not safe for concurrent use from multiple threads. Each thread that needs to compute against a circuit should compile its own handle. The rule is one handle per execution thread — do not enter any compute or lifecycle API on the same handle from two threads simultaneously. This mirrors the Runtime contract and keeps the internal solver state simple and fast.

Progress and Cancellation

Long-running time-domain operations — transient probes, port series with many steps — can take seconds or minutes. You can receive progress updates and cancel in-flight computations:

tdse_adapter_circuit_time_options_t time_opt =
    tdse_adapter_circuit_time_options_init();

// Progress callback — called periodically during the solve
time_opt.progress.enable = 1;
time_opt.progress.interval_sec = 0.5;  // callback at most every 0.5 seconds
time_opt.progress.callback = my_progress_callback;
time_opt.progress.user_ptr = &my_state;

// Cancel callback — polled by the solver;
// return non-zero to request cancellation
time_opt.cancel.callback = my_cancel_callback;
time_opt.cancel.user_ptr = &my_state;

probe_req.time_options = &time_opt;

The progress event (tdse_adapter_circuit_progress_event_t) carries the current simulation time, accepted and rejected step counts, nonlinear iteration counts, and the transient integration scheme in use. The progress field is a number between 0 and 1 indicating overall completion.

Note: progress and cancellation are currently supported for time-domain operations only. Frequency-domain operations (matrix, AC probes) complete quickly enough that they do not expose progress callbacks.

Error Handling

All functions return a tdse_adapter_circuit_err_t. Check it against TDSE_ADAPTER_CIRCUIT_OK. For error messages, use tdse_adapter_circuit_status_message() — it handles adapter, RAW import, and sequence-network error codes:

if (tdse_adapter_circuit_handle_is_valid(handle)) {
    int rc = tdse_adapter_circuit_compute_port_fsweep(&req, &result);
    if (rc != TDSE_ADAPTER_CIRCUIT_OK) {
        fprintf(stderr, "fsweep failed: %s\n",
                tdse_adapter_circuit_status_message(rc));
        const char* detail = tdse_adapter_circuit_get_last_error_text();
        if (detail && detail[0]) {
            fprintf(stderr, "detail: %s\n", detail);
        }
    }
}

Always check the return code. A non-zero code means the output buffers were not filled and the result struct's diagnostics may contain partial or stale data.

Diagnostics

Every result struct includes a diagnostics field. Three query functions extract structured information from it:

// Human-readable summary of what happened
tdse_adapter_circuit_diagnostics_summary_t summary =
    tdse_adapter_circuit_diagnostics_summary_init();
tdse_adapter_circuit_diagnostics_get_summary(&result.diagnostics, &summary);
// summary.call_kind, summary.matrix_size, summary.timing_total_ns, ...

// Solver statistics — backend used, matrix properties, conditioning
tdse_adapter_circuit_solver_diagnostics_t solver =
    tdse_adapter_circuit_solver_diagnostics_init();
tdse_adapter_circuit_diagnostics_get_solver_stats(&result.diagnostics, &solver);
// solver.solver_backend, solver.matrix_density, solver.singular, ...

// Policy trace — how the solver backend was selected for this call
tdse_adapter_circuit_policy_trace_t trace =
    tdse_adapter_circuit_policy_trace_init();
tdse_adapter_circuit_diagnostics_get_policy_trace(&result.diagnostics, &trace);
// trace.solver_policy_source, trace.solver_backend,
// trace.solver_policy_override_used, ...

Use diagnostics during development and for production monitoring. The summary tells you whether things worked. The solver stats tell you why a particular backend was chosen and how the matrix behaved. The policy trace is essential when debugging "why did it use this solver instead of that one?"

Solver Policy

When you run a frequency sweep, the SDK selects a solver backend for you — CPU dense, CPU sparse (KLU), or CUDA. The default policy picks the best available backend for your circuit size and structure, and decides whether to parallelize the sweep across frequency points.

For most users, the defaults are correct and you can skip this section. You only need to touch solver policy when:

  • You want to force a specific backend (e.g., always use CUDA)
  • You want to disable parallel sweeps for deterministic ordering
  • You are benchmarking or debugging solver selection
// Query the current default policy
tdse_adapter_circuit_solver_policy_t policy =
    tdse_adapter_circuit_get_solver_policy();

// Set process-wide defaults
policy.sweep_parallel_mode = TDSE_ADAPTER_CIRCUIT_SWEEP_PARALLEL_FORCE;
policy.sweep_parallel_max_workers = 4;
tdse_adapter_circuit_set_solver_policy(&policy);

// Or override per-request (takes precedence over process-wide defaults)
req.solver_policy_override = &custom_policy;

Key policy fields:

FieldDefaultMeaning
sweep_parallel_modeAUTOAUTO (SDK decides), FORCE (always), DISABLE (never)
sweep_parallel_max_workersMaximum number of worker threads for parallel sweeps
sweep_parallel_min_pointsMinimum frequency points to trigger parallel sweep
sweep_parallel_chunk_sizePoints per chunk in dynamic scheduling
sweep_parallel_allow_cuda0Whether the CUDA backend may be selected

For a multi-threaded example using solver policy, see adapter_cpp_threaded_lanes in the Examples Guide.

Adaptive Sweep Planning

Choosing a frequency grid is one of the hardest parts of frequency-domain modeling. Too few points and you miss resonant features. Too many and you waste computation, or worse, produce an over-resolved spectrum that creates fitting artifacts in Builder.

The adaptive sweep planner automates this. You give it a circuit and a target time step, and it runs planning sweeps to determine the required frequency range and resolution. It evaluates impulse response convergence and recommends production parameters: nfreq, nh, and dt.

#include <tdse_adapter_circuit/planning.h>

tdse_adapter_circuit_adaptive_sweep_config_t plan_cfg =
    tdse_adapter_circuit_adaptive_sweep_config_init();
plan_cfg.tolerance_mode = TDSE_ADAPTER_CIRCUIT_ADAPTIVE_SWEEP_TOLERANCE_MEDIUM;
plan_cfg.dt_target = 1e-4;
plan_cfg.limit_f_max_hz = 1e4;

// Create a report handle to capture detailed planning output
tdse_adapter_circuit_adaptive_sweep_report_t* report = NULL;
tdse_adapter_circuit_adaptive_sweep_report_create(&report);

tdse_adapter_circuit_adaptive_sweep_request_t plan_req =
    tdse_adapter_circuit_adaptive_sweep_request_init();
plan_req.handle = result.handle;
plan_req.ports = ports;
plan_req.port_count = port_count;
plan_req.matrix_kind = TDSE_ADAPTER_CIRCUIT_MATRIX_Y;
plan_req.correction_method = TDSE_BUILDER_CORRECTION_RECONSTRUCT_FROM_REAL;
plan_req.adapter_options = opt;
plan_req.planning_config = &plan_cfg;
plan_req.out_report = report;

tdse_adapter_circuit_adaptive_sweep_result_t plan_result =
    tdse_adapter_circuit_adaptive_sweep_result_init();
int rc = tdse_adapter_circuit_plan_adaptive_sweep(&plan_req, &plan_result);
// plan_result.summary.production_dt_s  — recommended time step
// plan_result.summary.production_nh   — recommended history length
// plan_result.summary.production_nfreq — recommended frequency count

The report handle gives you detailed justification for each recommendation:

tdse_adapter_circuit_adaptive_sweep_report_summary_t report_summary;
memset(&report_summary, 0, sizeof(report_summary));
report_summary.struct_size = sizeof(report_summary);
tdse_adapter_circuit_adaptive_sweep_report_get_summary(report, &report_summary);

// Export the full report as JSON or text for archival
size_t required;
tdse_adapter_circuit_adaptive_sweep_report_json(
    report, buf, buf_size, &required);
tdse_adapter_circuit_adaptive_sweep_report_text(
    report, buf, buf_size, &required);

tdse_adapter_circuit_adaptive_sweep_report_destroy(report);

The tolerance mode (LOW, MEDIUM, HIGH) controls how aggressively the planner refines the grid. MEDIUM is the right starting point. Use HIGH for production runs where accuracy matters more than speed.

Tail vs. Nfreq Analysis

When preparing for Builder handoff, you need to choose nh — the number of history samples in the discrete-time impulse response. Too small and the tail is truncated, introducing error. Too large and you waste memory and computation in Runtime.

The tail-vs-nfreq analysis helps. It scans the impulse response as nfreq increases, tracking how the tail magnitude decays. When the tail settles below a threshold, you have found your nh.

tdse_adapter_circuit_tail_vs_nfreq_request_t tail_req =
    tdse_adapter_circuit_tail_vs_nfreq_request_init();
tail_req.handle = result.handle;
tail_req.ports = ports;
tail_req.port_count = port_count;
tail_req.adapter_options = opt;
// The tail-vs-nfreq scan evaluates both Y and Z internally and uses
// RECONSTRUCT_FROM_REAL, so no matrix_kind/correction_method fields exist
// on this request struct.

tdse_adapter_circuit_tail_vs_nfreq_result_t tail_result =
    tdse_adapter_circuit_tail_vs_nfreq_result_init();
int rc = tdse_adapter_circuit_scan_tail_vs_nfreq(&tail_req, &tail_result);
// tail_result.summary.recommended_nh  — suggested history length
// tail_result.summary.tail_settled   — whether the tail converged

As with adaptive sweep planning, you can create a report handle (tdse_adapter_circuit_tail_vs_nfreq_report_t) for per-point detail and JSON/text export.

NPORT

NPORT is a SPICE extension that imports frequency-dependent multiport data from Touchstone files directly into a circuit solve. If you have measured or simulated S-parameters, Y-parameters, or Z-parameters in a Touchstone file, you can reference it from a netlist without manually converting formats:

NPORT NP1 1 0 2 0 FILE=example.y2p TYPE=Y

When the adapter compiles a netlist containing NPORT elements, it reads the referenced Touchstone file, interpolates the data onto the solver's frequency grid, and stamps a frequency-dependent admittance block into the MNA matrix. All of this happens at compile time — no extra steps for you.

Supported Touchstone formats include .ynp, .znp, .y2p, .z2p, .s2p, and their multi-port variants. The file must be readable relative to the working directory at compile time.

NPORT elements work transparently with all Adapter Circuit commands: matrix, series, and probe. For a runnable example, see nport_y2p_ac in the Examples Guide.

Integration Patterns

Builder Handoff

This is the most common end-to-end pattern. Adapter Circuit produces a Y matrix, and Builder consumes it to produce a runtime pack:

// 1. Compute Y from adapter
tdse_adapter_circuit_compute_port_fsweep(&freq_req, &freq_result);

// 2. Feed into Builder
tdse_builder_cplx_mat_view_t spec;
spec.ri = y_matrix_data;
spec.nfreq = nfreq;
spec.nq = np; spec.np = np;
spec.ld = np;
spec.stride_freq = np * np;

tdse_builder_h_from_spectrum(omega, spec, dt, nh,
    TDSE_BUILDER_CORRECTION_RECONSTRUCT_FROM_REAL,
    0.0, NULL, h_data, h_data_len);

// 3. Build pack
tdse_builder_configure_ex(builder, &bopt);
tdse_builder_apply_h(builder, &h_desc);
tdse_builder_write_pack(builder, "model.pack");

A few convenience helpers make this pattern even shorter:

// Create a frequency grid in Hz instead of rad/s
tdse_adapter_circuit_grid_t g = tdse_adapter_circuit_grid_from_hz(0, 50, 101);

// 1-port shorthand
tdse_adapter_circuit_port_def_t port = TDSE_ADAPTER_CIRCUIT_PORT_1P(1, 0);

// Convert adapter output shape to Builder input view in one call
tdse_builder_cplx_mat_view_t spec =
    tdse_adapter_circuit_to_builder_view(&result.shape, matrix_ri);

Critical invariant: The port order in the adapter's ports array must match the port order in Builder's nq and np dimensions. If adapter computes Y with ports in order [A, B, C] and Builder expects [C, A, B], the resulting pack will be wrong and the error may not be obvious until runtime. Archive the frequency grid parameters and port ordering alongside every output.

Qualification Checks Before You Ship The Pack

Do not stop at "Builder wrote a file." The pack is ready for handoff only when the conversion path has also cleared the quality checks that matter for your integration.

Use this checklist:

CheckWhy it mattersBest place to read it
Port order and matrix family are intentionalwrong ordering can produce a numerically valid but physically wrong packAdapter request + Builder handoff record
Passivity result is acceptable for the exported spectrumcatches unstable or non-physical source data before Runtimeworkflow result or tdse_nport_check_passivity(...)
Round-trip frequency-response error is acceptableconfirms the written pack still reproduces the source spectrum closely enoughworkflow result or tdse_model_verify_frequency_response(...)
Adaptive planning or tail scan produced an acceptable nfreq / tail budgetavoids shipping a pack that is too short, too coarse, or too aggressively truncatedtail_scan, workflow result, planning reports
Runtime can create the pack cleanly on the target hostcatches compatibility surprises before the pack leaves engineeringtdse_pack_validate, tdse_model_create(...), create diagnostics

In practice there are two good qualification styles:

  1. Workflow-first qualification Use the workflow result as the primary artifact. It already carries passivity and round-trip fields and records whether the SDK auto-derived the grid or sweep plan.
  2. Manual handoff qualification Archive the matrix family, port list, frequency grid, Builder settings, pack validation output, and one Runtime create proof on the same host profile where the pack will be consumed.

For most teams, the quality bar is:

  • the source circuit is understood
  • the chosen representation (Y or Z) is intentional
  • passivity and round-trip metrics are within project limits
  • Runtime creates the pack without compatibility or diagnostics surprises

That is a much stronger release signal than "the CSV looked reasonable."

Embedding in a Host Solver (MNA)

When embedding TDSE inside a larger circuit simulator, the Runtime step loop integrates with the host's Modified Nodal Analysis formulation:

tdse_step_begin(model, t, dt);
tdse_step_op(model, &op);     // op.G = conductance matrix G_tdse
tdse_step_hr(model, hr);      // hr   = history current source

// Host assembles the full system:
//   (G_host + G_tdse) * v = i_src - hr
// ...solve for v...

tdse_step_commit(model, v);   // advance internal state

This pattern lets you treat the TDSE model as a black-box Norton equivalent that stamps into the host's MNA matrix. The host owns the global solve; TDSE provides the conductance stamp and the history current source each time step.

For a runnable example, see mna_block_embed_2p in the Examples Guide.

Deep Reference: Adapter Lookup

Use this section as a lookup area after the main task sections above. It is not the best place to start if you are still deciding which adapter operation you need.

Parameter Cookbook

This table is a quick lookup for every CLI flag and its purpose. Bookmark it.

ParameterWhereNotes
--netlist-kind file|text|stdinall commandsfile for scripts, stdin for pipes, text for inline
--matrix y|zmatrixY = admittance (Norton form), Z = impedance (Thevenin form)
--ports "p,n;p,n"matrix, seriesOrder matters — it must match Builder input order
--w0-radps, --dw-radps, --nfreqmatrixArchive these with your output; you will need them later
--dc-policymatrixAffects DC stability: see DC policy values
--series voc|iscseriesvoc = open-circuit voltage, isc = short-circuit current
--method transient|ifft|toneseries, transient probetransient = general; ifft = freq; tone = steady-state
--dt, --stepsseries, transient probeMust match Builder's dt for consistent handoff
--nfftseries, probeFFT size; must be ≥ 2 * (nfreq - 1) for ifft method
--observe "v(1);i(r1)"probeSemicolon-separated probe expressions
--observe-sourceprobeargument = CLI; netlist = .probe directives; argument_or_netlist = auto
--sweep-kindAC probefrom_netlist, lin, dec, oct, list
--ac-excitationAC probesmall_signal for new designs; legacy_tones for backward compatibility
--json-out -all commandsAlways use - (stdout) in automation

Enum Reference

Enum typeValues
tdse_adapter_circuit_matrix_kind_tMATRIX_Y, MATRIX_Z
tdse_adapter_circuit_port_response_kind_tPORT_RESPONSE_VOC, PORT_RESPONSE_ISC
tdse_adapter_circuit_probe_domain_tPROBE_DOMAIN_AC, PROBE_DOMAIN_TRANSIENT
tdse_adapter_circuit_netlist_source_tNETLIST_SOURCE_TEXT, NETLIST_SOURCE_FILE
tdse_adapter_circuit_raw_source_tRAW_SOURCE_TEXT, RAW_SOURCE_FILE
tdse_adapter_circuit_raw_output_kind_tRAW_OUTPUT_BUFFER, RAW_OUTPUT_FILE
tdse_adapter_circuit_raw_unit_mode_tRAW_UNIT_PU, RAW_UNIT_SI
tdse_adapter_circuit_sweep_parallel_mode_tSWEEP_PARALLEL_AUTO, _FORCE, _DISABLE
tdse_builder_correction_method_tCORRECTION_NONE, _RECONSTRUCT_FROM_REAL|_IMAG|_MAG|_PHASE

Failure Modes

When something goes wrong, start here:

SymptomLikely causeFix
Parse failureMalformed netlist, missing includeRun with --json-out - on smallest deck
Ports resolve incorrectlyWrong node names or polarityReduce to one port and verify the node pair
Singular or ill-conditioned solveFloating nodes, invalid topologyTry extrapolate_from_positive DC policy or single-freq Y
VOC/ISC differs from expectationMethod mismatch, insufficient nfftCompare transient vs ifft
NPORT import failsType mismatch, bad path, non-monotonic dataCheck TYPE, dimensions, CWD for relative paths
RAW conversion fails "invalid argument"Missing struct_size fieldsSet all struct_size; check output_kind matches method
All backends show "no" in capsBuild configuration issueRebuild with required dependencies (KLU, CUDA, etc.)

Troubleshooting

When a command fails, collect these four things before asking for help:

  1. The exact command line you ran
  2. The full --json-out - output
  3. The smallest netlist that reproduces the problem
  4. tdse adapter circuit caps output from the same machine

To narrow down the problem yourself: verify the netlist parses → verify one port → verify one frequency → add complexity. Almost all circuit bugs become obvious when you reduce to the simplest failing case. For Runtime-side issues after pack creation, continue in Troubleshooting.

Validation Checklist

Use this before handing off results to Builder or committing output to a project:

  • tdse adapter circuit caps shows expected backends
  • Minimal matrix replay passes on a known-good deck
  • Output dimensions match expectations (ports × frequency points)
  • Builder handoff produces a valid .pack file
  • Port order is consistent throughout the pipeline (adapter → Builder → Runtime)

Advanced Tuning And Performance

This section is for tuning and scale work after the basic adapter flow is already correct. It covers the key knobs that affect Adapter Circuit throughput and when it is worth touching them.

Choosing a Solver Backend

For most circuits the auto-selector picks the right backend. Here is when to override it:

BackendBest for
DENSE_LUSmall circuits (n < 200). Lowest overhead per solve point
SPARSE_KLUMedium-to-large circuits (n > 200, density < 0.18). Much faster factorisation for sparse matrices
DENSE_CUDALarge circuits (n > 256) when a CUDA GPU is available. Best for batched frequency sweeps
SPARSE_CUDAVery large sparse circuits (n > 20000). Only available with CUDA GPU
SPARSE_MKL_PARDISOLarge sparse circuits when Intel MKL is available. Alternative to KLU

Parallel Frequency Sweeps

Frequency sweeps are embarrassingly parallel — each frequency point is an independent linear solve. Enable parallel sweeps to use all CPU cores:

tdse adapter circuit matrix ... --sweep-parallel auto

Or via C API:

policy.sweep_parallel_mode = TDSE_ADAPTER_CIRCUIT_SWEEP_PARALLEL_AUTO;
policy.sweep_parallel_max_workers = 0;  // use all hardware threads
tdse_adapter_circuit_set_solver_policy(&policy);

Parallel sweeps are most effective when:

  • nfreq ≥ 8: Overhead of thread creation amortized over many points
  • n > 100: Each solve point is expensive enough to justify threading
  • CUDA backend: The CUDA solver can batch multiple frequency points into a single GPU kernel launch (see Dense CUDA Batched Fast Path below)

When NOT to use parallel sweeps:

  • Debugging: serial execution gives deterministic ordering
  • Very small circuits (n < 50): thread overhead exceeds benefit
  • Already running many independent adapter calls in parallel (nesting overhead)

Dense CUDA Batched Fast Path

When the CUDA dense backend is selected and the following conditions are met, frequency sweeps take a highly optimised batched path:

  • Affine AC build template is valid (enabled by default for nfreq ≥ 4)
  • Frequency grid starts at w0 > 0 (non-DC)
  • nfreq ≥ 2
  • The selected backend is DENSE_CUDA
  • dense_cuda_batched_fsweep_fast_path_enabled() returns true (default)

On this path, the solver batches all frequency points into one GPU kernel, dramatically reducing kernel launch overhead. For large circuits (n > 1000) with many frequency points (nfreq > 64), the speedup can be 5-20× versus per-point GPU solves.

The fast path is controlled by the environment variable:

TDSE_ADAPTER_DENSE_CUDA_BATCHED_FSWEEP_FAST_PATH=1   # enable (default)
TDSE_ADAPTER_DENSE_CUDA_BATCHED_FSWEEP_FAST_PATH=0   # disable

DC Policy Performance

The DC policy choice affects the number of factorizations at ω=0:

PolicyFactorizationsNotes
exact_dc_then_fallback1-2Tries exact first. Two factorizations only when singular
regularized_exact_dc1Always regularises with inductor_gbig. Fastest correct path
extrapolate_from_positive0Skips DC entirely. Fastest but least accurate at low frequencies

Sparse Solver Factor Caching

KLU and SPARSE_CUDA solvers cache the symbolic factorization pattern. When computing multiple sweeps against the same circuit with different frequency grids, the symbolic analysis is reused. This is automatic — no configuration needed. The diagnostics field factor_cache_enabled and factor_cache_hit report whether caching was used.

Affine AC Build

When enabled, the adapter builds an affine template of the MNA system at the first frequency point. Subsequent frequency points materialise the template with O(nnz) work instead of rebuilding the full system from scratch. This is enabled by default for nfreq ≥ 4 and provides approximately 2-3x speedup for large circuits.

Compile Once, Sweep Many

A compiled handle is reusable across multiple compute calls. Always compile once and reuse the handle for multiple sweeps, rather than recompiling:

// Good — compile once
tdse_adapter_circuit_compile_from_netlist(&req, &result);
for (int k = 0; k < num_configs; ++k) {
    freq_req.handle = result.handle;
    freq_req.grid = grids[k];
    tdse_adapter_circuit_compute_port_fsweep(&freq_req, &freq_result);
}

// Bad — recompiles every iteration
for (int k = 0; k < num_configs; ++k) {
    tdse_adapter_circuit_compile_from_netlist(&req, &result);
    freq_req.handle = result.handle;
    tdse_adapter_circuit_compute_port_fsweep(&freq_req, &freq_result);
    tdse_adapter_circuit_destroy(result.handle);
}

Compilation involves parsing, MNA construction, and topology analysis — it is typically 10-100× more expensive than a single frequency-point solve.