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

dt_min

1e-6

Adaptive

dt_max

1.0

Adaptive

dt

sqrt(dt_min * dt_max) = 1e-3

Fixed

dt

1e-3

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

dt only

dt_min = dt / 100, dt_max = dt * 100

dt_min only

dt_max = 1.0 (default), dt = sqrt(dt_min * dt_max)

dt_max only

dt_min = 1e-6 (default), dt = sqrt(dt_min * dt_max)

dt_min and dt_max

dt = sqrt(dt_min * dt_max)

dt and dt_min

dt_max = dt * 100

dt and dt_max

dt_min = dt / 100

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:

  • dt changes to 0.001 (your new value)

  • dt_min may be adjusted if 0.001 < dt_min (derived value, can change)

  • dt_max stays at 1.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_every must be an integer multiple of sample_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:

  1. At each sample_summaries_every interval, current values are sampled and fed into accumulators (running sum, running max, etc.)

  2. At each summarise_every interval, final metrics are computed from the accumulators, written to output, and accumulators reset

  3. The 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

save_every

Interval for storing full state/observable snapshots

sample_summaries_every

Interval for sampling values into summary accumulators

summarise_every

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.0

  • Type: 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.0

  • Type: 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)