Mechanical TestBed
Contents
Mechanical TestBed#
This example demonstrates building a Convolutional Dynamic model using the Mechanical TestBed test case, which is included in the NeurEco installation package.
Note
The case ships as the MechanicalTestBed example (MechanicalTestBed.zip);
its name in the example gallery is “Mechanical TestBed”. Its inputs are three
forces (Primary, Control and Disturbance, in N) and a bench temperature (in °C)
— i.e. different physical quantities.
On this page
Problem Description#
This test case models a mechanical test bench driven by three forces while its temperature varies. The goal is to predict the bench displacement from these inputs.
Inputs: Primary, Control and Disturbance forces (N), and the bench temperature (°C)
Outputs: Displacement (mm)
The dataset captures the nonlinear dynamic response of the bench under various operating conditions.
Test Case Data#
The test case includes:
Training: 4 trajectories
Validation: 2 trajectories
Test: 9 trajectories
Files are organized as:
Data/
|-- train/
| |-- excitations_train0.csv
| |-- outputs_train0.csv
| |-- excitations_train1.csv
| |-- outputs_train1.csv
| `-- ...
|-- valid/
| |-- excitations_valid0.csv
| |-- outputs_valid0.csv
| `-- ...
`-- test/
|-- excitations_test0.csv
|-- outputs_test0.csv
`-- ...
Setup#
Create a working directory (e.g.,
MechanicalTestBedExample)Extract the test case data into this directory
Create a Python script (e.g.,
mechanical_testbed.py)
Complete Example#
"""
NeurEco Convolutional Dynamic Example: Mechanical TestBed
This example uses the Mechanical TestBed test case included
with the NeurEco installation.
"""
from NeurEco import NeurEcoDynamic as Dynamic
import numpy as np
import matplotlib.pyplot as plt
import os
def load_dataset(data_path, set_type):
"""
Load excitation and output arrays (time in column 0) for a given dataset.
Args:
data_path: Path to the Data folder
set_type: One of 'train', 'valid', or 'test'
Returns:
Tuple of (excitation_list, output_list), each array time-first
"""
file_list = os.listdir(os.path.join(data_path, set_type))
n_experiences = len(file_list) // 2 # Each experience has 2 files
excs = []
outs = []
for exp in range(n_experiences):
# Load excitations (column 0 is time, remaining columns are features)
inputs_full = np.genfromtxt(
f"{os.path.join(data_path, set_type)}/excitations_{set_type}{exp}.csv",
skip_header=True,
delimiter=","
)
excs.append(inputs_full) # column 0 = time, remaining columns = excitations
# Load outputs (column 0 is time, remaining columns are features)
outputs_full = np.genfromtxt(
f"{os.path.join(data_path, set_type)}/outputs_{set_type}{exp}.csv",
skip_header=True,
delimiter=","
)
outs.append(outputs_full) # column 0 = time, remaining columns = outputs
return excs, outs
# ===================
# Step 1: Load Data
# ===================
data_path = "Data"
excs_train, outs_train = load_dataset(data_path, "train")
excs_valid, outs_valid = load_dataset(data_path, "valid")
excs_test, outs_test = load_dataset(data_path, "test")
print(f"Training trajectories: {len(excs_train)}")
print(f"Validation trajectories: {len(excs_valid)}")
print(f"Test trajectories: {len(excs_test)}")
# ===================
# Step 2: Build Model
# ===================
builder = Dynamic.ConvolutiveDynamic()
# Each array: column 0 = time, remaining columns = excitations / outputs.
config = Dynamic.ConvolutiveDynamicBuildConfig(
# Training data
train_exc_list=excs_train,
train_out_list=outs_train,
# Validation data
valid_exc_list=excs_valid,
valid_out_list=outs_valid,
# Test data
test_exc_list=excs_test,
test_out_list=outs_test,
# Normalization: True — the inputs are different physical quantities
# (three forces in N and a bench temperature in degC), so each feature
# is normalized on its own range.
inputs_normalize_per_feature=True,
# Convolution window size
window_size=32,
# Output paths
write_model_to="VibrationSensorModel/model",
checkpoint_address="VibrationSensorModel/model",
)
status = builder.build(config)
if status == 0:
print("Build completed successfully!")
# =====================
# Step 3: Load and Inspect Model
# =====================
evaluator = Dynamic.ConvolutiveDynamic()
evaluator.load("./VibrationSensorModel/model")
print(f"\nModel Information:")
print(f" Inputs: {evaluator.get_input_count()}")
print(f" Outputs: {evaluator.get_output_count()}")
print(f" Window size: {evaluator.get_window_size()}")
# =====================
# Step 4: Evaluate Model
# =====================
# Each excitation array has time in its first column; predictions are returned time-first.
pred_train = evaluator.evaluate(excs_train)
pred_valid = evaluator.evaluate(excs_valid)
pred_test = evaluator.evaluate(excs_test)
# Get window size for alignment
w_size = evaluator.get_window_size()
# Compute errors (align reference with predictions)
print("\n=== Training Set Errors ===")
for i in range(len(pred_train)):
ref_aligned = outs_train[i][w_size - 1:]
error = evaluator.compute_error(pred_train[i][:, 1:], ref_aligned[:, 1:])
print(f"Trajectory {i}: L2 error = {100*error:.4f}%")
print("\n=== Validation Set Errors ===")
for i in range(len(pred_valid)):
ref_aligned = outs_valid[i][w_size - 1:]
error = evaluator.compute_error(pred_valid[i][:, 1:], ref_aligned[:, 1:])
print(f"Trajectory {i}: L2 error = {100*error:.4f}%")
print("\n=== Test Set Errors ===")
for i in range(len(pred_test)):
ref_aligned = outs_test[i][w_size - 1:]
error = evaluator.compute_error(pred_test[i][:, 1:], ref_aligned[:, 1:])
print(f"Trajectory {i}: L2 error = {100*error:.4f}%")
# =====================
# Step 5: Visualize Results
# =====================
# Plot a selection of test trajectories
n_plots = min(4, len(excs_test))
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.flatten()
for i in range(n_plots):
ax = axes[i]
# Time is column 0 of the excitation array; the displacement output is column 1
time_array = excs_test[i][:, 0]
# Plot reference (full time range)
ax.plot(time_array, outs_test[i][:, 1], 'r-', label="Reference")
# Plot prediction (starts from window_size - 1)
ax.plot(time_array[w_size - 1:], pred_test[i][:, 1], 'k--', label="Prediction")
ax.set_xlabel("Time (s)")
ax.set_ylabel("Displacement (mm)")
ax.set_title(f"Test Trajectory {i}")
ax.legend()
ax.grid(True)
plt.suptitle("Mechanical TestBed: Convolutional Dynamic Model Predictions")
plt.tight_layout()
plt.show()
# =====================
# Cleanup
# =====================
builder.delete()
evaluator.delete()
Key Concepts Demonstrated#
Window Size
The Convolutional Dynamic model uses a sliding window to capture temporal dependencies. The window_size parameter controls how many past time steps influence each prediction:
window_size=32,
Larger windows can capture longer-range dependencies but increase model complexity.
Output Alignment
Because the model uses a sliding window, predictions start at time step window_size - 1. When comparing to reference data, you must align them:
w_size = evaluator.get_window_size()
ref_aligned = reference[w_size - 1:]
error = evaluator.compute_error(prediction[:, 1:], ref_aligned[:, 1:])
Per-Feature Normalization
The inputs are different physical quantities — three forces (Primary, Control and Disturbance, in N) and a bench temperature (in degC). Following the Data Preparation guidance, we enable per-feature normalization so each feature is scaled on its own range:
inputs_normalize_per_feature=True,
Multiple Trajectories
The model is trained on multiple trajectories simultaneously. This helps it generalize across different operating conditions:
train_exc_list=excs_train, # List of 4 excitation arrays (time in column 0)
train_out_list=outs_train, # List of 4 output arrays (time in column 0)
Exploring Checkpoints#
During training, NeurEco saves intermediate models. You can inspect these to find smaller models that may provide acceptable accuracy:
model = Dynamic.ConvolutiveDynamic()
n = model.get_number_of_networks_from_checkpoint("./VibrationSensorModel/model")
print(f"Found {n} checkpointed models")
for i in range(n):
model.load_model_from_checkpoint("./VibrationSensorModel/model", i)
print(f"Checkpoint {i + 1}: {model.vec.size} trainable parameters")
Next Steps#
Experiment with different
window_sizevalues to balance accuracy and complexityExport the model to FMU format (see Exporting Dynamic Models)