Timing Parameters
CuBIE has three groups of timing parameters: step size parameters control how the integrator advances through time, output timing parameters control when results are saved, and integration bounds define the overall time span.
Step Size Parameters
Adaptive controllers use three related parameters to control step sizes:
dt: Initial step size (starting point for adaptation)dt_min: Minimum allowable step size (floor)dt_max: Maximum allowable step size (ceiling)
Fixed-step controllers use only dt, which becomes the constant step
size throughout integration.
Default Behaviour
When you don’t specify any step size parameters, CuBIE uses these defaults:
Controller |
Parameter |
Default Value |
|---|---|---|
Adaptive |
|
|
Adaptive |
|
|
Adaptive |
|
|
Fixed |
|
|
Derivation Rules
When you provide some parameters but not others, CuBIE derives the missing values. The rules differ for adaptive and fixed controllers:
Adaptive controllers:
You Provide |
CuBIE Derives |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
All three |
Nothing derived (all values used as given) |
Fixed-step controllers:
Fixed controllers collapse dt, dt_min, and dt_max to a single
value. If you provide any of them, the first non-None value in the order
dt, dt_min, dt_max is used as the step size.
Example usage:
# Adaptive: provide dt, bounds derived automatically
solver.solve(..., dt=0.01)
# Results in: dt_min=0.0001, dt=0.01, dt_max=1.0
# Adaptive: provide bounds, initial dt computed
solver.solve(..., dt_min=1e-5, dt_max=0.1)
# Results in: dt_min=1e-5, dt=0.001, dt_max=0.1
# Fixed: any of these sets dt=0.005
solver.solve(..., step_controller="fixed", dt=0.005)
solver.solve(..., step_controller="fixed", dt_min=0.005)
Update Behaviour
When you call solver.update() to change step parameters mid-session,
CuBIE tracks which values you originally set versus which were derived:
User-set values are preserved and never automatically adjusted
Derived values may be adjusted if they become inconsistent with your changes
For example, if you originally provided only dt=0.01 (so dt_min
and dt_max were derived), then later update dt=0.001:
dtchanges to0.001(your new value)dt_minmay be adjusted if0.001 < dt_min(derived value, can change)dt_maxstays at1.0(derived value, still valid)
But if you originally provided dt_min=1e-4 explicitly, that value
won’t be changed even if it would make more sense to adjust it.
Output Timing Parameters
Output timing controls how often CuBIE records results during integration.
save_every
Time interval between saved state and observable snapshots. Each saved
point includes the current time, state values, and observable values
(depending on your output_types setting).
Default: If not specified but state/observable outputs are requested, CuBIE saves only at the start (t=0 or t=settling_time) and end of the integration (“save_last” mode).
Type:
float(seconds of simulation time)
# Save state every 0.1 seconds of simulation time
solver.solve(..., save_every=0.1, duration=10.0)
# Results in 101 saved points (including t=0)
# No save_every specified: only initial and final states saved
solver.solve(..., duration=10.0, output_types=["state"])
# Results in 2 saved points
summarise_every
Length of each summary window. CuBIE uses fixed, non-overlapping windows for summary statistics. At the end of each window, metrics (mean, max, RMS, etc.) are computed from all samples taken during that window, written to output, and the accumulator resets for the next window.
Default: If not specified but summary outputs are requested, defaults to
duration(one summary window covering the entire integration).Type:
float(seconds of simulation time)
# 10 summary windows, each 1 second long
solver.solve(..., summarise_every=1.0, duration=10.0, output_types=["mean"])
# No summarise_every: one summary over entire duration
solver.solve(..., duration=10.0, output_types=["mean"])
sample_summaries_every
Sampling interval within each summary window. This controls how many samples contribute to each summary calculation. At each sample point, the current state/observable values are fed into the running accumulator (e.g., added to a running sum for mean calculation, compared against current max, etc.).
Default:
summarise_every / 10(10 samples per window)Type:
float(seconds of simulation time)Constraint:
summarise_everymust be an integer multiple ofsample_summaries_every
# 100 samples per 1-second window
solver.solve(
...,
sample_summaries_every=0.01,
summarise_every=1.0,
output_types=["mean", "max"]
)
Higher sampling rates give more accurate summaries but increase computational overhead.
Summary Window Behaviour
The summary system uses fixed, non-overlapping windows:
At each
sample_summaries_everyinterval, current values are sampled and fed into accumulators (running sum, running max, etc.)At each
summarise_everyinterval, final metrics are computed from the accumulators, written to output, and accumulators resetThe next window starts fresh with no memory of previous windows
This differs from sliding-window approaches where windows overlap.
Timeline with summarise_every=1.0, sample_summaries_every=0.25:
t=0.0 t=0.25 t=0.5 t=0.75 t=1.0 t=1.25 ...
| | | | | |
S S S S S+W S ...
S = sample taken, W = window closes (summary written, reset)
Relationship Between Output Parameters
Parameter |
Purpose |
|---|---|
|
Interval for storing full state/observable snapshots |
|
Interval for sampling values into summary accumulators |
|
Window length; summaries computed and reset at this interval |
These are independent: you can save states at high frequency while computing summaries over longer windows, or vice versa.
Integration Bounds
Three parameters define the time span of integration:
duration
Total length of the recorded integration in simulation time units. This is the time span over which outputs are saved.
Required: Yes
Type:
float(must be positive)
solver.solve(..., duration=100.0) # Integrate for 100 time units
t0
Starting time of the integration. Affects time-dependent drivers and the timestamps in output arrays.
Default:
0.0Type:
float
# Start integration at t=5.0
solver.solve(..., t0=5.0, duration=10.0)
# Recorded output spans t=5.0 to t=15.0
settling_time
Lead-in period before recording begins. The integrator runs for this
duration starting from t0, but no output is saved. This is useful
for allowing transients to decay before collecting data.
Default:
0.0Type:
float(non-negative)
# Let the system settle for 50 time units, then record 100
solver.solve(..., settling_time=50.0, duration=100.0)
# Integration runs from t=0 to t=150
# Output spans t=50 to t=150
The total integration time is settling_time + duration. The
integration starts at t0 and runs until t0 + settling_time + duration.
Example: t0=5, settling_time=20, duration=50
t=5 t=25 t=75
| | |
|<-- settling_time ->|<------------ duration --------------->|
| (20) | (50) |
| | |
+--------------------+---------------------------------------+
^ ^ ^
| | |
Integration Recording Integration
starts starts ends
(no output) (output saved)
Complete Example
This example shows all timing parameters working together:
result = solver.solve(
initial_values={"x": x0_array},
parameters={"k": k_array},
# Integration bounds
t0=0.0,
settling_time=10.0, # Discard first 10 time units
duration=100.0, # Record 100 time units after settling
# Step size (adaptive controller)
dt=0.01, # Start at 0.01, bounds derived as 1e-4 to 1.0
# Output timing
save_every=0.1, # Save states every 0.1 time units
summarise_every=10.0, # Compute summaries every 10 time units
sample_summaries_every=0.05, # Sample for summaries every 0.05 units
# What to save
output_types=["state", "mean", "max"],
)
# Timeline:
# t=0 to t=10: settling (no output saved)
# t=10 to t=110: recording period
# - 1001 state saves (every 0.1 from t=10 to t=110)
# - 10 summary windows (every 10.0 from t=10 to t=110)
# - 200 samples per summary window (every 0.05 over 10.0)