Advanced Runtime Integration
Variable time-step behavior and integration constraints.
Audience: Integration engineers embedding TDSE into simulators with adaptive or multi-rate time-stepping (SPICE-family, PSCAD/EMTP, Keysight ADS, etc.).
Use this chapter when your simulator does not step at exactly the same interval
used to build the pack. It explains how runtime dt relates to build-time
model_dt, what accuracy changes when they differ, and which integration
patterns are usually safe.
This chapter starts with variable time-step integration, then continues into runtime concurrency, scaling, and multi-model deployment patterns in the sections that follow.
Use it only after the fixed-step step loop is already understood. For a first
minimal integration, keep dt_runtime = model_dt and return here only when the
host truly needs adaptive or multi-rate stepping.
The three most common host cases are:
| Host case | Recommended first policy |
|---|---|
| fixed-step simulator | keep dt_runtime = model_dt |
| adaptive simulator | start with clamped adaptive dt, then measure interpolation error |
| multi-rate orchestration | use multiple model handles, one time base per handle |
Core Semantics
The impulse-response tensor h[k] (shape [nh, nq, np]) is sampled at
build time with a fixed step size model_dt:
tau_k = k * model_dt, k = 0, 1, ..., nh - 1
model_dt is an intrinsic model property, queryable via tdse_model_info_t.dt.
The dt parameter passed to tdse_step_begin(model, t, dt) is the
simulator's current physical time span for this step. It does not
need to equal model_dt. The runtime convolution engine bridges the two
automatically.
Two Internal Paths
The runtime selects one of two paths based on the time-line history.
Uniform Fast Path
Triggered when all of these conditions are satisfied simultaneously:
- The committed history timeline is monotonically increasing and uniform,
with step size equal to
model_dt(relative tolerance 1e-9, absolute 1e-12). - The current step time
tminus the latest committed timet_newestequalsmodel_dt(same tolerance).
Under this path:
- History terms are read directly from the ring buffer by tap index, with no interpolation.
- This is the most accurate and fastest path, equivalent to fixed-step convolution.
Time-Driven Interpolation Fallback
Entered whenever any trigger condition fails (including but not limited to:
dt != model_dt, dt varying between steps, a large time-step jump, or
initial conditions where the accepted queue has not yet been uniformized).
For each delayed tap k >= 1:
sample_t = t - tau_k = t - k * model_dt
The runtime performs linear interpolation on the committed primary input
timeline to obtain v(sample_t), then weights and accumulates with h[k].
- Boundary semantics: when
sample_tprecedes the earliest committed time, that tap's contribution is treated as zero (equivalent to assuming the model was at rest before time zero). - Interpolation order: first-order (linear). There is no zero-order hold or higher-order interpolation path.
Accuracy Impact
The dominant error in the history term hr[n] under the time-driven path is
governed by linear interpolation:
err(hr) = O(dt_runtime^2 * sup_t |d^2 v / dt^2|)
where v is the committed primary input sequence. Practical guidance:
| Scenario | Recommended dt_runtime / model_dt | Notes |
|---|---|---|
| Smooth input (AC steady-state, low-freq transient) | 0.5 -- 10 | Large steps only lose linear interpolation accuracy in history |
| General transient (step, pulse) | 0.5 -- 2 | Excessive ratio causes visible "smearing" near step edges |
| Steep transient (switching, surge, impulse) | 0.5 -- 1 | Recommend dt_runtime ~ model_dt |
Key numerical conclusions:
- The instantaneous path (
tdse_step_op, direct response intdse_step_commit) is driven byh[0]and is exact for any runtime step size. - The IR term (
tdse_step_ir) queries the pack's internal sampling table by absolute timet;dt_runtimeaffects only the time quantization. - Only the history term
hraccumulates linear interpolation error asdt_runtime / model_dtdeviates from 1.
Stability
Variable time-stepping does not change the model's stability properties:
- Model stability is determined by the spectral radius of
h[k]in the pack, which is fixed at build time. - Variable stepping only affects the computational approximation of the history term; it cannot make a stable model unstable.
- Extreme dt jumps (e.g., from
model_dtto100 * model_dtand back) may amplify interpolation error in transients but will not inject spurious energy.
Monitoring
When exploring aggressive dt strategies, poll runtime guard metrics via
tdse_ext_get_runtime_guard_metrics():
| Metric | Meaning | Watch For |
|---|---|---|
max_abs_g0 | max amplitude of h[0] entries this step | model-dependent; use as trend |
pivot_min | minimum pivot | significant drop means op near-singular |
pivot_ratio | current pivot_min vs baseline | sustained < 0.1 warrants investigation |
growth_factor | inter-step ` | hr |
Multi-Rate Models
TDSE multi-rate at the runtime level is achieved by running multiple model handles concurrently, not through internal multi-clock support:
tdse_model_t* fast_model; /* model_dt = 1 ns */
tdse_model_t* slow_model; /* model_dt = 10 ns */
tdse_model_create(fast_pack, fast_len, &diag, &fast_model);
tdse_model_create(slow_pack, slow_len, &diag, &slow_model);
for (step = 0; step < N; ++step) {
/* fast model: step every 1 ns */
tdse_step_begin(fast_model, t_fast, 1e-9);
/* ... */ tdse_step_commit(fast_model, primary_fast);
/* slow model: step every 10 ns */
if (step % 10 == 0) {
tdse_step_begin(slow_model, t_slow, 10e-9);
/* ... */ tdse_step_commit(slow_model, primary_slow);
}
}
Each handle maintains its own history ring buffer independently. Concurrency rules still apply: no concurrent API calls on the same handle; multiple independent handles may run in parallel on different threads.
Recommended Integration Patterns
Use these in order. Do not jump to adaptive or multi-rate operation before the fixed-step path is already validated.
Fixed dt (simple SPICE integration)
Always use dt_runtime = model_dt in the simulator main loop:
const double model_dt = info.dt;
for (...) {
tdse_step_begin(model, t, model_dt);
/* op / hr / ir */
tdse_step_commit(model, primary);
t += model_dt;
}
This always hits the uniform fast path, maximizing accuracy and performance.
Adaptive dt (LTE-controlled SPICE)
Let dt_runtime follow the solver's local truncation error control:
const double model_dt = info.dt;
for (...) {
double dt = solver_proposed_dt();
/* Clamp to [0.1 * model_dt, 10 * model_dt] for typical EDA scenarios */
if (dt < 0.1 * model_dt) dt = 0.1 * model_dt;
if (dt > 10.0 * model_dt) dt = 10.0 * model_dt;
tdse_step_begin(model, t, dt);
/* ... */
tdse_step_commit(model, primary);
t += dt;
}
Host/TDSE split here:
- the host proposes and clamps
dt - TDSE interprets the accepted
(t, dt)againstmodel_dt - interpolation error belongs to the integration policy, not to Runtime
Trace Replay
When primary comes from an external trace file with trace_dt != model_dt:
- Option A: drive the runtime with
trace_dtdirectly (time-driven interpolation). - Option B: resample the trace to
model_dtexternally, then use the fast path.
Both are mathematically equivalent. Option B enables more optimized convolution backends (CPU_BLAS / GPU_PACK_BLAS paths perform best on the uniform fast path).
Decision Table
| If your main goal is... | Start with... | Escalate to... |
|---|---|---|
| fastest bring-up | fixed dt_runtime = model_dt | nothing until basic loop is stable |
| adaptive solver fidelity | clamped adaptive dt | error measurement and tuning |
| multi-rate orchestration | one handle per rate | explicit scheduling and cross-model review |
Related API
tdse_step_begin(model, t, dt)intdse/tdse.h: core entry point discussed heretdse_model_info_t.dtintdse/tdse.h: querymodel_dttdse_ext_get_runtime_guard_metrics()intdse_ext.h: runtime stability monitoringtdse_builder_compute_consistent_grid()intdse_builder.h: derive consistentmodel_dt,nh,nfft, anddwat build time
