.. _example-vibration-sensor: ================== 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. .. contents:: On this page :local: :depth: 2 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: .. code-block:: text 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 ===== 1. Create a working directory (e.g., ``MechanicalTestBedExample``) 2. Extract the test case data into this directory 3. Create a Python script (e.g., ``mechanical_testbed.py``) Complete Example ================ .. code-block:: python """ 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: .. code-block:: python 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: .. code-block:: python 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 :doc:`../data-preparation` guidance, we enable per-feature normalization so each feature is scaled on its own range: .. code-block:: python inputs_normalize_per_feature=True, **Multiple Trajectories** The model is trained on multiple trajectories simultaneously. This helps it generalize across different operating conditions: .. code-block:: python 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: .. code-block:: python 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_size`` values to balance accuracy and complexity - Export the model to FMU format (see :ref:`dynamic-exporting`)