Evaluating ODE Models
Contents
Evaluating ODE Models#
This page explains how to run inference (evaluation) on trained NeurEco ODE models. ODE models are dynamical systems that require understanding of initialization to produce accurate simulations.
On this page
Understanding ODE Initialization#
Unlike static models, ODE models maintain internal state and produce trajectories that evolve over time. To simulate a trajectory, the model needs to know where to start — this is called initialization.
NeurEco ODE supports two initialization modes:
Explicit initialization (Recommended): You provide the initial state of the system
Steady-state initialization (Default): The model starts from its own steady state for the first excitation values
Initialization Modes#
Mode 1: Explicit Initialization (Recommended)#
Provide initial conditions to start the simulation from a known state. This mode is recommended because:
It gives you control over the starting point
It allows evaluation from any operating point
It produces more accurate results when the initial state is known
Required data, over the same initialization window, normally the
model.get_number_of_init_tsteps() samples that end where the prediction starts:
init_outputs: The output values over the window, with time in the first columninit_excitations: The input values over the window, with time in the first column
Note
The initialization time is read from the first column of init_excitations
and init_outputs. The separate init_time keyword is deprecated (kept
for backward compatibility, emits a DeprecatedParameterWarning).
Important
Where the evaluation starts. The model estimates its state at the last time of
the initialization window and starts the evaluation from it. The evaluation excitations
must therefore start at that last initialization time: with the first n_init samples
of a trajectory as the window, evaluate from index n_init - 1 (exc[n_init - 1:]),
not n_init.
The first predicted sample is the model’s value at the last initialization time: the window and the prediction share that time step. To join them into one trajectory, drop the last sample of the window.
An evaluation that starts one sample later is not rejected, but the whole prediction is then one time step late.
A longer window is accepted, and only its last
get_number_of_init_tsteps()samples are used. A shorter one is padded at its start, as if the system were at rest before it, and a warning is logged.
Mode 2: Steady-State Initialization (Default)#
If no initialization is provided, NeurEco computes the steady state (equilibrium) of the learned model for the first excitation values of the sequence, and the simulation starts from it.
This equilibrium is found for the excitation values at the first time of the sequence. It is the steady state given at the build only when these values are that steady state:
First excitation values equal to
steady_state_exc: the simulation starts from the given steady state, and the first predicted outputs equalsteady_state_out.Other first excitation values: NeurEco searches the model’s equilibrium for them, starting from the given steady state (or, for a model built without one, from its equilibrium at the input normalization shift). This equilibrium was learned, not given at the build: the first predicted outputs are the model’s, and nothing guarantees they match the real system.
This mode:
Is simpler (no initialization data needed)
Assumes the system starts at rest, in the steady state that matches its first excitation values
Starts from a known point only when the first excitation values are the steady state given at the build (see Using Steady-State Constraints)
If no steady state can be computed for the first excitation values, which can happen with
or without a steady state given at the build, the evaluation starts from the initial state
learned for the training trajectory whose first excitation values are closest. The log says so, as an
information message when the model was built without a steady state and as a warning
otherwise. To set the starting point yourself, use explicit initialization
(init_outputs).
Evaluating an ODE Model#
Loading a Model
Open your project or load a saved model
Switch to the Evaluation panel
Providing Evaluation Data
Click + next to Evaluation files
Select your input (excitation) file
Optionally select an output file (for comparison/metrics)
Initialization Options
Using steady-state initialization:
Simply run evaluation without providing initialization data
The model starts from its own steady state for the first excitation values (see Mode 2 above)
Using explicit initialization:
Provide an output file that includes initial values
Use the Initialization samples slider to specify how many initial time steps to use
The model uses these points to initialize, then simulates forward. The prediction starts at the last sample of the initialization interval (the interval and the prediction share that sample) and runs to the end of the file.
Running Evaluation
Click Evaluate
View results in the plot panel
Use the Sample slider to browse through sequences
Compare predictions (blue) with reference (orange) if provided
Metrics
Switch to the Metrics panel to see:
L2 relative error for each output
Per-sequence error breakdown
Training vs. validation vs. test performance
Each file of the Metrics list offers two initializations. Default Initialization, the default, initializes every checkpoint from the first samples of the file, with the checkpoint’s own number of initialization time steps (the Time steps for initialization editor shown over the file changes this number). Steady State Initialization starts from the model’s steady state for the first excitation values (Mode 2 above).
Loading a Model
from NeurEco import NeurEcoDynamic as Dynamic
model = Dynamic.ODE()
model.load("path/to/model.ernn")
Evaluation with Steady-State Initialization
import numpy as np
# Prepare evaluation data (time in column 0)
dt = 0.01 # Must match training time step
time = np.arange(0, 5, dt)
eval_exc = np.column_stack([time, np.random.randn(len(time), 3)]) # time + 3 inputs
# Evaluate using steady-state initialization
# eval_exc has time in its first column; predictions are returned time-first.
outputs = model.evaluate(eval_exc)
print(f"Output shape: {outputs.shape}")
Evaluation with Explicit Initialization
import numpy as np
# Get number of initialization steps required
n_init_steps = model.get_number_of_init_tsteps()
print(f"Model requires {n_init_steps} initialization steps")
dt = 0.01
# Prepare initialization data (time in column 0)
init_time = np.arange(0, n_init_steps * dt, dt)
init_exc = np.column_stack([init_time, np.random.randn(n_init_steps, 3)])
init_out = np.column_stack([init_time, np.random.randn(n_init_steps, 2)]) # Known initial state
# Prepare evaluation data (time in column 0)
# Note: Start from (n_init_steps - 1) * dt because the last init point
# is also the first prediction point (they overlap)
eval_time = np.arange((n_init_steps - 1) * dt, 5, dt)
eval_exc = np.column_stack([eval_time, np.random.randn(len(eval_time), 3)])
# Evaluate with explicit initialization
# eval_exc / init_exc / init_out all carry time in their first column;
# predictions are returned time-first.
outputs = model.evaluate(
eval_exc,
init_excitations=init_exc,
init_outputs=init_out,
)
Evaluating Multiple Sequences
# Multiple sequences of different lengths (each array: time in column 0)
times = [
np.arange(0, 3, dt),
np.arange(0, 5, dt),
np.arange(0, 2, dt),
]
eval_exc_list = [
np.column_stack([times[0], np.random.randn(len(times[0]), 3)]),
np.column_stack([times[1], np.random.randn(len(times[1]), 3)]),
np.column_stack([times[2], np.random.randn(len(times[2]), 3)]),
]
# Evaluate all sequences
# Each array has time in its first column; predictions are returned time-first.
output_list = model.evaluate(eval_exc_list)
for i, out in enumerate(output_list):
print(f"Sequence {i}: output shape {out.shape}")
Computing Error
# Compare predictions to ground truth
# eval_exc has time in its first column; predictions are returned time-first.
predictions = model.evaluate(eval_exc)
# compute_error takes outputs only, so drop the leading time column from both.
error = model.compute_error(predictions[:, 1:], reference_outputs[:, 1:])
print(f"L2 relative error: {error:.6f}")
Configuration File
{
"neurecoODE_evaluate": {
"ernn_filename": "./model/ode_model.ernn",
"exc_filenames": ["data/test/test_exc_1.csv"],
"write_model_output_to_directory": "./results/"
}
}
Running Evaluation
neureco ODE evaluate config.json
Results are saved to the specified output directory, one file per excitation file,
named after it with _ernn.csv added to its stem (test_exc_1_ernn.csv here),
with the time in the first column. Without initialization files, the evaluation starts
from the steady state (Mode 2 above).
Explicit Initialization
Add the initialization files, one excitation file and one output file per evaluated file, in the same order:
{
"neurecoODE_evaluate": {
"ernn_filename": "./model/ode_model.ernn",
"exc_filenames": ["data/test/test_exc_1.csv"],
"init_exc_filenames": ["data/test/init_exc_1.csv"],
"init_output_filenames": ["data/test/init_out_1.csv"],
"write_model_output_to_directory": "./results/"
}
}
Give both lists, with as many entries as
exc_filenames.The initialization files hold the initialization window (Mode 1 above), with the time in the first column. Both files must have the same time values.
The excitation file must start at the last time of the initialization window, and the result file starts there too.
Method Reference#
predictions = model.evaluate(
excitations=None, # Input array(s), time in column 0
init_excitations=None, # Initialization inputs (required for explicit init), time in column 0
init_outputs=None, # Initialization outputs (required for explicit init), time in column 0
*,
time=None, # Deprecated: separate time array(s)
init_time=None, # Deprecated: separate initialization time
)
Parameters:
Parameter |
Required |
Description |
|---|---|---|
|
Yes |
Input array(s) with time in the first column. Shape |
|
No |
Initialization input values, time in the first column (required for explicit init) |
|
No |
Initialization output values, time in the first column (required for explicit init) |
|
No |
Deprecated (keyword-only). Separate time array(s); emits a |
|
No |
Deprecated (keyword-only). Separate initialization time values. |
Returns:
Single sequence: NumPy array of shape
(n_samples, 1 + n_outputs), with time in the first columnMultiple sequences: List of such NumPy arrays
Note
When excitations carries the time in its first column, the returned
predictions are time-first (time in column 0). The deprecated
separate-time form returns predictions without a leading time column.
Scripts written for NeurEco 4.x that call
evaluate(time, excitations, init_time, init_excitations, init_outputs) with
positional arguments still run: this form emits a DeprecatedParameterWarning
and also returns predictions without a leading time column.
Using Known Initial Conditions from Data#
When you have reference data and want to evaluate starting from a known point:
import numpy as np
from NeurEco import NeurEcoDynamic as Dynamic
# Load model
model = Dynamic.ODE()
model.load("my_ode_model.ernn")
# Reference data (time in column 0)
time_full = np.linspace(0, 10, 1000)
exc_full = np.column_stack([time_full, np.random.randn(1000, 3)])
out_full = np.column_stack([time_full, np.random.randn(1000, 2)]) # Known ground truth
# Use first N points as initialization
n_init = model.get_number_of_init_tsteps()
# Both init arrays keep their time in the first column
init_exc = exc_full[:n_init]
init_out = out_full[:n_init]
# Evaluate from the last initialization point onward
# Note: The last init point is the first prediction point (overlap)
eval_exc = exc_full[n_init - 1:]
# eval_exc / init_exc / init_out all carry time in their first column;
# predictions are returned time-first.
outputs = model.evaluate(
eval_exc,
init_excitations=init_exc,
init_outputs=init_out,
)
# Compare to ground truth (starting from the same point, also time-first).
# compute_error takes outputs only, so drop the leading time column from both.
ground_truth = out_full[n_init - 1:]
error = model.compute_error(outputs[:, 1:], ground_truth[:, 1:])
print(f"L2 relative error: {error:.6f}")
Retrieving Steady-State Values#
To inspect the steady-state values learned by the model:
steady_inputs = model.get_steady_input()
steady_outputs = model.get_steady_output()
print(f"Steady-state inputs: {steady_inputs}")
print(f"Steady-state outputs: {steady_outputs}")
Model Information#
# Number of inputs and outputs
print(f"Inputs: {model.get_input_count()}")
print(f"Outputs: {model.get_output_count()}")
# Number of hidden states
print(f"Hidden states: {model.get_state_count()}")
# Initialization steps required
print(f"Init steps: {model.get_number_of_init_tsteps()}")
Best Practices#
Match time step: The evaluation time step must match the training time step
Use explicit initialization when possible: For best accuracy, provide known initial conditions
Verify initialization length: Use
get_number_of_init_tsteps()to determine how many initialization points are neededCheck steady state: If using steady-state initialization, verify the learned steady state matches your expectations