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

Step Execution Model

Per-step runtime semantics for begin, evaluate, commit, and output handling.

Use this section when you are wiring TDSE Runtime into a simulator step loop. It explains what each step call means, how trial state differs from committed state, how retries behave when a trial is rejected, and what to record when the loop does not behave as expected.

Related Chapters For lifecycle and shutdown semantics, see Runtime Lifecycle. For concurrency rules, see Concurrency and Shutdown.

If the lifecycle section answers "who owns the handle," this section answers "what exact state is the handle in between begin, query calls, commit, and dr?"

For most customer integrations, this section is the contract source for one of these three host patterns:

Host patternWhat to focus on here
fixed-step solver loopcanonical step order and prime-step pattern
adaptive / trial-rejecting solver looprejected-trial semantics and state snapshots
real-time / HIL loopcommitted-state discipline, dr timing, and one-handle-per-thread rules

The Three Invariants

If you remember only three step-loop rules, remember these:

  1. tdse_step_begin(...) creates a trial context at one exact (t, dt).

  2. These trial query calls are query-only within that trial context:

    • tdse_step_op(...)
    • tdse_step_hr(...)
    • tdse_step_ir(...)
  3. tdse_step_commit(...) is the only call that advances committed history.

Everything else in this chapter follows from those three rules.

Canonical Step Order

Per ordinary step, the runtime flow is:

  1. tdse_step_begin(model, t, dt)
  2. tdse_step_op(model, op_out) as needed
  3. tdse_step_hr(model, hr_out)
  4. tdse_step_ir(model, ir_out) when IR exists and is still in range
  5. host-side trial solve or trial update
  6. tdse_step_commit(model, primary_accepted) if the trial is accepted
  7. optional tdse_step_dr(model, dr_out) after commit

tdse_step_op(...) is an instantaneous operator query. Hosts may:

  • fetch it once and cache it under LTI assumptions
  • fetch it per step when host policy wants explicit freshness

Host/TDSE ownership split in this loop:

  • the host owns t, dt, trial acceptance, solver state, and the accepted primary vector
  • TDSE owns trial-local query state, committed history, and the resulting op / hr / ir / dr surfaces

Canonical per-step timeline

Prime-Step Pattern

The standard integration pattern primes at n = -1:

tdse_step_begin(m, t0 - dt, dt);
tdse_step_op(m, &op_square);
tdse_step_commit(m, primary_minus1);

Why this matters:

  • it establishes committed history before the first ordinary simulation step
  • it makes later hr evaluation align with the expected discrete-time history model
  • it gives dr a valid first committed state immediately after prime

If you skip the prime step without redesigning initialization assumptions, the first visible issue often looks numerical even though the real bug is committed-history alignment.

Prime-step design review question:

  • what exact accepted primary vector should the runtime treat as the prehistory state?

If that answer is still implicit, the integration is not really complete.

Mathematical Contract

The trial-side relation is:

  • y_trial = op * primary_trial + hr + ir

Where:

  • op is the instantaneous operator
  • hr is the delayed-history contribution
  • ir is the independent-response contribution

The committed direct-response relation is:

  • dr[n] = op * primary_accepted[n]

Trial versus committed views

Legality Matrix

This is the fastest table to use during code review or triage:

APIRequires Live HandleRequires Active Trial StepRequires Prior CommitAdvances State
tdse_step_begin(...)yesno, unless exact re-entrynocreates or re-enters trial context
tdse_step_op(...)yesyesnono
tdse_step_hr(...)yesyesnono
tdse_step_ir(...)yesyesnono
tdse_step_commit(...)yesyesnoyes
tdse_step_dr(...)yesnoyesno

What tdse_model_state_info(...) Means During A Step Loop

The most useful runtime snapshot during integration is tdse_model_state_info(...). Its high-value fields are:

FieldOperational Meaning
step_activenonzero while a trial step is currently active
has_committed_stepnonzero after at least one successful commit
committed_stepsnumber of accepted commits
committed_t / committed_dttime coordinates of the latest committed step
active_t / active_dtactive trial-step coordinates, or zero when no trial is active
sim_timeaccumulated committed simulation time
dr_last_validwhether tdse_step_dr(...) is currently queryable

This snapshot reports dynamic execution state only. It is the quickest way to distinguish these three cases:

  • no trial has started yet
  • a trial is active but not committed
  • a committed step exists and dr is valid

State Snapshot Transition Table

The manual previously named these fields, but host teams usually need the exact transition pattern. Use this table as the expected runtime fingerprint.

Phasestep_activehas_committed_stepcommitted_stepsactive_t / active_dtcommitted_t / committed_dtdr_last_valid
newly created handle0000 / 00 / 00
after begin(t, dt)1unchangedunchangedt / dtunchangedunchanged
after trial queries only1unchangedunchangedt / dtunchangedunchanged
after accepted commit(...)01increments by 10 / 0becomes the just-committed t / dt1
after rejected trial with no commit1 until caller leaves the active trial lifecycleunchangedunchangedstill the active trial coordinatesunchangedunchanged
after dr(...)01unchanged0 / 0unchanged1

Two practical readings matter:

  • if step_active=1 and committed_steps is not moving, the runtime is still in a trial context
  • if dr_last_valid=1, at least one accepted commit already exists and no new commit is required just to read the latest committed direct response

begin Semantics And Re-Entry

tdse_step_begin(...) does three user-visible things:

  1. validates that the handle and (t, dt) are legal
  2. freezes execution-affecting runtime controls on the first successful begin
  3. records the active trial coordinates and marks step_active=1

The important re-entry rule is strict:

  • re-entering tdse_step_begin(...) while a trial is already active is valid only when t and dt match the already-active trial step within runtime tolerance

  • mismatched re-entry fails with TDSE_ERR_INVALID_STATE

This means begin is not a generic "start over" button. It is an idempotent re-entry only for the same trial coordinates.

What Re-Entry Is For

Exact re-entry is useful when:

  • a host wrapper retries a query path but is still on the same trial step
  • instrumentation or layered call sites may invoke the same "ensure step active" helper twice

It is not valid for:

  • changing t mid-trial
  • changing dt mid-trial
  • silently converting a rejected trial into a different step without resolving the active state

Rejected Trials And Retry Semantics

Rejected trials are normal in real host integrations. The runtime contract is intentionally simple:

  1. start the trial with begin
  2. query op, hr, and optional ir
  3. host decides the trial solve is not acceptable
  4. do not call commit
  5. treat committed history as unchanged

The one rule that matters most is this:

  • no commit means no committed-state movement

Operational consequences:

  • committed_steps does not increase
  • committed_t and committed_dt do not change
  • sim_time does not advance
  • dr_last_valid stays tied to the previous committed step

How To Retry Cleanly

If the host rejects the trial and wants to try again:

  • keep the retry anchored to the same step coordinates if the same trial is being reconsidered
  • do not pretend a new committed step exists
  • do not read the lack of state movement as a runtime failure; it is the intended rejection model

If the host instead wants a different (t, dt), it must follow the documented lifecycle rather than mismatched begin re-entry.

Worked Snapshot Trace

This is the most useful support trace to memorize.

Before Prime

Expected snapshot:

  • step_active=0
  • has_committed_step=0
  • committed_steps=0
  • dr_last_valid=0

Interpretation:

  • no committed history exists yet
  • dr(...) is invalid at this point

After Prime begin(t0 - dt, dt)

Expected snapshot:

  • step_active=1
  • active_t=t0 - dt
  • active_dt=dt
  • has_committed_step=0

Interpretation:

  • the runtime now has an active trial context
  • committed history still does not exist until commit

After Prime commit(primary_minus1)

Expected snapshot:

  • step_active=0
  • has_committed_step=1
  • committed_steps=1
  • committed_t=t0 - dt
  • committed_dt=dt
  • dr_last_valid=1

Interpretation:

  • history now exists
  • dr(...) is legal
  • the first ordinary step can use a properly anchored committed past

During Ordinary Step n

After begin(t_n, dt) and before commit(...):

  • step_active=1
  • active_t=t_n
  • active_dt=dt
  • committed_steps still equals the previous accepted count
  • committed_t still points to the previous accepted step

This is the point where support should ask:

  • are we still in a legitimate trial?
  • is the host expecting committed values too early?

After Accepted Commit At Step n

Expected snapshot:

  • step_active=0
  • committed_steps increments by one
  • committed_t=t_n
  • committed_dt=dt
  • sim_time increases by dt
  • dr_last_valid=1

Interpretation:

  • committed history has advanced
  • future hr calls will now see the newly accepted state in their delayed-history basis

After Rejected Trial At Step n

If the host decides not to commit:

  • committed_steps remains unchanged
  • committed_t remains the previous committed time
  • sim_time remains unchanged
  • dr_last_valid still refers to the previous committed step

This is the exact fingerprint of "trial rejected, committed history preserved."

Worked Host Pattern

The intended control split is:

  1. Runtime supplies op, hr, and optional ir
  2. host code computes or solves the trial equation
  3. host decides whether the trial primary vector is accepted
  4. Runtime advances history only when the host commits

This is why Runtime remains auditable inside larger simulator loops.

Minimal Integration Recipes

Use the smallest recipe that matches your host:

Host typeMinimal recipe
fixed-step simulatorprime once, then begin -> op/hr/ir -> host solve -> commit -> dr
adaptive simulatorsame loop, but host may reject a trial and skip commit
RT/HIL loopfixed accepted-step policy, one handle per execution thread, dr only after commit

For code review, the key question is not "did the host call the APIs" but "which side owns acceptance, timing, and history movement?"

C Example

tdse_step_begin(model, t, dt);
tdse_step_op(model, &op);
tdse_step_hr(model, hr);

tdse_status_t ir_st = tdse_step_ir(model, ir);
if (ir_st == TDSE_ERR_IR_STEP_OUT_OF_RANGE) {
  /* Host policy decides whether this is a hard stop or planned IR horizon exit. */
}

solve_trial(op, hr, ir, primary_trial, y_trial);

if (trial_is_accepted(primary_trial, y_trial)) {
  tdse_step_commit(model, primary_trial);
  tdse_step_dr(model, dr);
}

The key handbook reading is:

  • the host owns acceptance
  • the runtime owns committed-history mutation

Fixed-Step Host Skeleton

This is the simplest integration that should be proven before any adaptive or real-time embellishment:

prime_once(model, t0, dt, primary_minus1);

for (size_t n = 0; n < nsteps; ++n) {
  tdse_step_begin(model, t0 + n * dt, dt);
  tdse_step_op(model, &op);
  tdse_step_hr(model, hr);
  tdse_step_ir(model, ir);   /* when IR exists */
  host_solve_trial(op, hr, ir, primary_trial, y_trial);
  tdse_step_commit(model, primary_trial);
  tdse_step_dr(model, dr);
}

If this path is not already stable, stop here before adding retries, adaptive dt, or multi-threading.

Adaptive-Step Ownership Rule

For adaptive hosts, keep one rule explicit in design notes:

  • the host may retry or reject a trial
  • TDSE must not see history advancement unless the host actually commits

That single rule prevents most accidental state-drift bugs.

IR Horizon Behavior Inside The Loop

tdse_step_ir(...) is query-only, but it is still a contract boundary. If the current step time lies beyond configured IR support, the API returns TDSE_ERR_IR_STEP_OUT_OF_RANGE.

Read that status as:

  • the runtime is telling you the packaged IR sequence does not extend to this step
  • the runtime is not silently extrapolating IR

What to record:

  • archive committed_steps, committed_t, dt, and model ir_nsteps
  • verify whether the host advanced beyond the packaged horizon by design or by mistake

Rectangular Versus Square Operator View

tdse_step_op(...) supports:

  • square view: np x np
  • full rectangular view: nq x np

Recommended practice:

  • choose one operator-view policy per integration
  • document that policy in the host design
  • do not switch views ad hoc across call sites without a clear reason

Using the wrong view usually appears later as a shape or interpretation bug, not at the moment the host design drifted.

What to Capture for Step Incidents

When a step-loop issue shows up, collect these details in this order:

  1. failing API name and returned status
  2. tdse_model_state_info(...)
  3. tdse_model_last_error_info(...)
  4. exact t, dt, and host step index
  5. whether the host intended to accept or reject the trial
  6. whether prime was performed
  7. whether the host expected square or rectangular op

This sequence usually resolves three common ambiguities immediately:

  • lifecycle misuse versus numerical rejection
  • missing prime versus wrong-history interpretation
  • IR horizon exit versus unrelated step failure

Common Step-Loop Mistakes

MistakeWhy It Is WrongCorrect Pattern
calling dr during an active trial stepdr is post-commit onlyquery dr after commit, with no active trial step
treating hr as state-advancinghr is query-onlycall commit to advance history
skipping prime without redesigning history assumptionslater history alignment shiftsprime at n = -1 or document a different initialization contract
re-entering begin with different t or dtactive trial context must remain one exact stepfinish the active trial lifecycle before changing coordinates
assuming rejected trial implies hidden state rollback logicno commit means no advancement happenedread snapshots and keep retry logic explicit
mixing square and rectangular operator views ad hochost matrix assumptions driftchoose and document one operator-view policy

Anti-Patterns

Avoid these integration patterns even if they seem harmless in local testing:

  1. using state_info only after failures instead of also learning the normal expected snapshot
  2. inferring committed advancement from successful hr or ir queries
  3. treating dr_last_valid=1 as proof that the current trial was committed
  4. retrying a rejected trial by changing (t, dt) under an already-active trial step
  5. explaining a history bug as "numerical noise" before verifying prime and commit sequencing