Skip to main content

SIL, HIL & AI Integration

One of the main advantages of Synapsys's agent-based architecture is the seamless transition through different stages of the control system development lifecycle: MIL, SIL, and HIL.

Because the transport layer is heavily detached from the control algorithms, you can move your controller from a local simulation script to a separate network node, and finally to physical hardware, without altering the control logic itself. This paradigm is especially powerful when validating Artificial Intelligence (AI) and Machine Learning (ML) models in realistic scenarios.


1. MIL: Model-in-the-Loop

In Model-in-the-Loop (MIL) simulation, both the plant and the controller exist within the same process and simulation thread. Time is advanced artificially by a synchronous for loop.

This is the standard approach for early validation, mathematical tuning, and stability proofs.

from synapsys.api import c2d, tf
from synapsys.algorithms import PID
import numpy as np

# Both exist in the same Python script memory
plant = c2d(tf([1], [1, 1]), dt=0.01)
pid = PID(Kp=2.0, Ki=0.5, dt=0.01)

x = np.zeros(plant.n_states)
y = 0.0

# MIL execution
for k in range(100):
u = pid.compute(setpoint=1.0, measurement=y)
x, y = plant.evolve(x, u) # direct method call
info

Focus: Mathematical correctness, algorithm tuning, and continuous-time analysis.

Limitation: It assumes zero execution time for the controller and perfect synchrony. It cannot capture operating system latency, network delays, or unpredictable computation jitter.


2. SIL: Software-in-the-Loop

In Software-in-the-Loop (SIL), the control algorithm runs exactly as it would in deployment, but it executes on a desktop/server (often in a separate process or Docker container) rather than embedded hardware. The plant remains simulated.

To transition from MIL to SIL in Synapsys, you wrap your components into Agents and connect them using a real transport mechanism (e.g., SharedMemoryTransport or ZMQTransport).

SIL Implementation

Terminal 1 (Plant)

transport_plant = SharedMemoryTransport("bus", {"y": 1, "u": 1}, create=True)
plant_agent = PlantAgent("plant", plant_d, transport_plant, SyncEngine())
plant_agent.start(blocking=True)

Terminal 2 (Controller)

transport_ctrl = SharedMemoryTransport("bus", {"y": 1, "u": 1}, create=False)
law = lambda y: pid.compute(setpoint=1.0, measurement=y[0])

ctrl_agent = ControllerAgent("ctrl", law, transport_ctrl, SyncEngine())
ctrl_agent.start(blocking=True)
tip

Focus: Concurrency, transport latency, zero-copy architecture limits, and software integration tests.

Advantage: Exposes real-world race conditions and sampling jitter before physical hardware is ever integrated.


3. HIL: Hardware-in-the-Loop

In Hardware-in-the-Loop (HIL), the control software runs on the target embedded hardware (e.g., an ESP32, STM32, or PLC), while the plant is still simulated by Synapsys.

The physical hardware receives simulated sensor signals and generates physical control actions (like actual PWM or voltage signals) that are fed back into the simulation through a hardware interface bridge.

HIL Bridge Concept

Instead of instantiating a ControllerAgent, you bind the plant's transport to a hardware bridge layer (synapsys.hw). Note: Concrete hardware interfaces are planned for v0.5.

from synapsys.hw import SerialHardwareInterface

# Connect to the physical controller via USB/UART
hw_bridge = SerialHardwareInterface(port="/dev/ttyACM0", baudrate=115200)

while True:
y = transport_plant.read("y")
hw_bridge.write(y) # Send sensor data over Serial

u = hw_bridge.read() # Wait for MCU control action
transport_plant.write("u", u)
note

Advantage: You can rigorously test boundary conditions, system failures, and corner cases on the final compiled C/C++ firmware without risking physical damage to an expensive, actual plant (e.g., drones, industrial motors).


4. Integrating AI and Machine Learning

The classical controls world (PID, LQR) heavily relies on MIL. However, when working with Reinforcement Learning (RL), Neural Networks, or complex stochastic models, MIL is often insufficient. AI inference is highly non-deterministic regarding computation time.

Synapsys's decoupled architecture makes it an incredibly robust environment for AI integration.

The Problem with AI in Control Loops

  1. Inference Time Jitter: A neural network forward pass (model(state)) might take 2ms on one tick, and 15ms on the next due to Python garbage collection, OS scheduling, or GPU transfer limits.
  2. Language Barriers: AI is predominantly built in Python (PyTorch, TensorFlow, JAX), but traditional HIL environments are heavily C++/Simulink based, requiring complex compilation steps, ONNX exports, or slow middleware.

The Synapsys Solution

Since Synapsys is natively Python, you can run massive, uncompiled PyTorch models directly inside a ControllerAgent in a SIL setup.

Key Advantages for AI Testing

  1. Fault Isolation: If your PyTorch inference spikes to 100ms, your PlantAgent doesn't freeze or crash. It continues running the physical simulation in real-time independently, holding the last known control signal (Zero-Order Hold). This perfectly replicates how a physical system behaves when its smart controller lags.
  2. Gymnasium Wrappers: You can train your RL agent using standard OpenAI Gym / Gymnasium APIs (which are purely sequential), and then validate the pre-trained weights in a Synapsys SIL environment that introduces real-time constraints, latency, and network drops. This proves if your AI is robust enough for the real world.
  3. Zero-Effort Transition to Reality: Once your RL agent successfully controls the simulated PlantAgent in the SIL environment, you can drop the simulated plant and connect the exact same PyTorch script to physical hardware via the HIL bridge. The AI model doesn't know it's now controlling a real motor instead of a matrix array!

Example: PyTorch RL Agent in a SIL loop

import torch
import numpy as np
from synapsys.agents import ControllerAgent, SyncEngine, SyncMode
from synapsys.transport import SharedMemoryTransport

# Load your pre-trained PyTorch model
model = torch.load("rl_controller.pth")
model.eval()

def ai_control_law(y: np.ndarray) -> np.ndarray:
# Convert latest sensor data to tensor
state_tensor = torch.tensor(y, dtype=torch.float32)

# Run inference to predict control action
with torch.no_grad():
action = model(state_tensor).numpy()

return action

# Create transport and run the AI controller as an agent
ai_transport = SharedMemoryTransport("ctrl_bus", {"y": 2, "u": 1}, create=False)
sync = SyncEngine(mode=SyncMode.WALL_CLOCK, dt=0.01)

ai_agent = ControllerAgent("ai_ctrl", ai_control_law, ai_transport, sync)
ai_agent.start(blocking=True)