Physical grids for stochastic simulation
Fixed-step scalar and coupled SDE simulation uses the requested dt for full
intervals and shortens the final interval to reach t_end. For example,
dt=0.3 over [0, 1] produces times [0, 0.3, 0.6, 0.9, 1]. Drift and
stepper corrections use those interval widths, Brownian increments have the
matching variances, and supported Poisson increments use the matching rates.
Where an existing method accepts n_steps, an explicit positive integer count
instead selects equal intervals spanning the range. n_steps=2 over [0, 1]
uses two intervals of width 0.5, regardless of the configured positive dt.
The calculus configuration is not modified. Invalid or nonfinite ranges,
nonpositive dt, invalid counts and unrepresentable grids reject before noise
generation. Equal endpoints without an explicit count return the initial
state and consume no noise; an explicit count requires a positive duration.
Grid construction uses a host float64 near-integer tolerance of
8 * eps64 * max(1, abs(duration / dt)). This avoids an extra interval caused
only by representation roundoff while retaining genuine terminal remainders.
The materialized grid follows JAX's configured floating precision. Nominal
uniform widths are retained within timestamp representation roundoff so the
established seeded uniform draw behavior remains compatible.
Paths and their coordinates
Existing array-only calls remain available. Use the corresponding result method when you also need the actual time coordinates:
import jax.numpy as jnp
from gimle.asgard.runtime.stochastic.calculus import StochasticCalculus
calculus = StochasticCalculus(dt=0.3, n_paths=2, seed=42)
result = calculus.simulate_sde_result(
x0=0.0,
drift_fn=lambda x, t: jnp.ones_like(x),
diffusion_fn=lambda x, t: jnp.zeros_like(x),
t_start=0.0,
t_end=1.0,
)
# result.times: [0, 0.3, 0.6, 0.9, 1]
# result.paths: one path per row, with time on the last axis
simulate_coupled_sde_result, StochasticCircuitExecutor.execute_result and
execute_coupled_circuit_driven_result likewise return immutable
StochasticExecutionResult(paths, times) values from a single execution.
The existing simulate_sde, simulate_coupled_sde and executor methods return
only the path array. Public stochastic API results and fixed-step YAML storage
retain execution coordinates; do not reconstruct them from the path count.
Exogenous interval drivers
The direct coupled circuit API requires one driver value for every actual
interval. A shared driver has shape (interval_count,); per-path drivers have
shape (n_paths, interval_count). A value applies on [t_i, t_(i+1)), including
the shorter final interval. Obtain the count from the same public grid policy:
from gimle.asgard.runtime.stochastic.physical_grid import PhysicalTimeGrid
grid = PhysicalTimeGrid(0.0, 1.0, dt=0.3)
assert grid.interval_count == 4
YAML driver declarations remain unsupported under their existing contract.
Untiled iterated integrals and single-circuit expansions
Direct evaluate_iterated_integrals and internally generated
evaluate_expansion integrals use the same physical-grid policy: omitted
n_steps retains full configured dt intervals and a short tail; an explicit
positive count selects equal intervals. Returned coordinates are absolute
times, while integrals accumulate elapsed time from t_start. Unlike fixed-step
initial-state simulation, these integral APIs retain their existing requirement
that t_end be strictly greater than t_start.
The untiled single-circuit stream_calculus route resolves that grid before
coefficient compilation. Integral updates, Brownian variances, Poisson rates,
and jump sample counts use its actual widths, including the short interval.
Depth zero returns the root coefficient on the actual times and consumes no
randomness, including on the single jump route.
Supplied increments are already physical samples and are never rescaled.
Binary integral calls accept dW with shape (n_paths, interval_count);
multi-noise calls accept dW_all with shape
(n_children - 1, n_paths, interval_count). Supplying both inputs or the wrong
channel is rejected. Shape/channel validation also applies at depth zero.
Single-circuit strategy calls accept Brownian dW; a jump circuit prepares its
separate compound-Poisson channel. Precomputed integrals supplied together with
time_grid remain an external-data contraction and retain those coordinates.
The direct integral kernel preserves its historical full-path Gaussian draws,
including when the noise source has antithetic=True. The single jump strategy
retains its existing antithetic-aware Brownian draws and Brownian/Poisson/jump
split order. Uniform dyadic grids retain exact seeded compatibility; other
widths follow the configured floating precision. This adoption does not change
the left-point Itô or mixed midpoint-noise Stratonovich recursion, coefficient
generation, or the per-interval compound-jump approximation.
Host range/count settings remain static. Direct integral/expansion calls with
supplied arrays retain JIT and differentiation support when
nan_check_interval=0; stateful random generation is not a pure JIT API.
Untiled coupled expansions
StochasticCircuitExecutor.execute_coupled_stream_calculus returns
(paths, times) on one physical grid when tile_dt is omitted. This applies to
independent equations, acyclic upstream dependencies and the automatic
simultaneous-Picard fallback for cyclic dependencies. The direct simultaneous
entry point uses the same policy. Omitted counts retain nominal dt intervals
and a short tail; explicit positive counts select equal intervals.
Correlated Brownian sampling, every equation's iterated integrals and returned coordinates use that grid. The existing coefficient iteration, channel mapping and antithetic/correlation draw conventions are retained. The coupled drivers retain their existing supported channel layouts and equation-count limits. They still generate their one correlated array at depth zero; they do not inherit the direct integral kernel's no-noise depth-zero behavior. Nonpositive durations remain invalid. Runtime grid validation precedes coefficient evaluation and noise generation; the YAML loader still compiles equations before calling the runtime driver and retains its returned coordinates.
Tiled stream-calculus expansions
Single-circuit tiled execution, including jump circuits, now prepares one
global physical grid before sampling. Coupled tiled execution does the same
for independent equations and the simultaneous-Picard fallback. With omitted
n_steps, [2, 3] and dt=0.3 has times [2, 2.3, 2.6, 2.9, 3];
an explicit count of three produces three equal intervals. tile_dt groups
whole global intervals using the established rounded count policy. A short
final interval stays in its last group and is never split or made uniform.
Each tile uses slices of the global Brownian increments, physical widths and coordinates. Jump events use rates equal to intensity times the global widths; jump sizes retain one sample per global interval. A fixed seed on the single tiled route still starts a fresh Brownian key, while an unseeded call advances the shared key once. The existing ensemble-mean expansion point, per-path offset correction and adaptive tile-count policy remain in place. The offset correction is approximate for nonlinear systems. Acyclic coupled equations with inherited multiple noise channels still reject tiling.
First-passage statistics and remaining routes
YAML analysis.first_passage reports elapsed duration from the initial
observation: times[first_crossing_index] - times[0]. A path initially across
the threshold has duration zero. A terminal crossing uses the actual final
time, including a shortened last interval. Paths that never cross are excluded
from crossing-time statistics and reported through pct_crossed.
Adaptive YAML output retains its existing explicit interpolation onto an output grid. This change does not add methods or alter diffusion/correlation support, clamping, adaptive rejection behavior or compound-jump approximations.