Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 95 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
# pySimBlocks

Simulink python project
A deterministic block-diagram simulation framework for discrete-time modeling, co-simulation and research prototyping in Python.

pySimBlocks is a lightweight and extensible environment for building, configuring,
and executing block-diagram models—similar to Simulink—directly in Python,
featuring a PySide6-based graphical editor, a discrete-time simulation engine,
and automatic project generation from YAML configurations.
pySimBlocks allows you to build, configure, and execute discrete-time systems using either:

- A pure Python API
- A graphical editor (PySide6)
- YAML project configuration
- Optional SOFA and hardware integration

## Features

- Block-based modeling (Simulink-like)
- Deterministic discrete-time simulation engine
- PySide6 graphical editor
- YAML-based project serialization
- Exportable Python runner (`run.py`)
- Extensible block architecture

## Installation

Expand All @@ -25,17 +36,94 @@ cd pySimBlocks
pip install .
```

## Run GUI application
## First Steps

### Quick Example

The following example models a damped harmonic oscillator:

$$ \ddot{x} +0.5 \dot{x} +2 x = 0 $$

The continuous-time equation is implemented using explicit forward Euler discretization through discrete integrator blocks with a fixed time step.

The system is assembled explicitly from discrete operators.

```python
from pySimBlocks import Model, Simulator, SimulationConfig, PlotConfig
from pySimBlocks.blocks.operators import Gain, Sum, DiscreteIntegrator
from pySimBlocks.project.plot_from_config import plot_from_config

# 1. Create the blocks
I1 = DiscreteIntegrator("x", initial_state=5)
I2 = DiscreteIntegrator("v", initial_state=2.)
A1 = Gain(name="damping", gain=0.5)
A2 = Gain(name="stiffness", gain=2)
S = Sum(name="sum", signs="--")

# 2. Build the model
model = Model("Example")
for block in [I1, I2, A1, A2, S]:
model.add_block(block)

model.connect("x", "out", "v", "in")
model.connect("x", "out", "damping", "in")
model.connect("v", "out", "stiffness", "in")
model.connect("damping", "out", "sum", "in1")
model.connect("stiffness", "out", "sum", "in2")
model.connect("sum", "out", "x", "in")

# 3. Create the simulator
sim_cfg = SimulationConfig(dt=0.05, T=30.)
sim = Simulator(model, sim_cfg)

# 4. Run the simulation
logs = sim.run(logging=[
"x.outputs.out",
"v.outputs.out",
]
)

# 5. Plot the results
plot_cfg = PlotConfig([
{"title": "Position and Velocity",
"signals": ["x.outputs.out", "v.outputs.out"],},
])
plot_from_config(logs, plot_cfg)
```

The simulated position and velocity exhibit the expected damped oscillatory behavior.

![Alt Text](./docs/User_Guide/images/quick_example.png)

### Graphical Editor

To open the graphical editor, run:
```bash
pysimblocks
```

### Tutorials

See the [Getting Started Guide](./docs/User_Guide/getting_started.md) for
tutorials on building your first simulation with pySimBlocks.

### Examples

A collection of basic and advanced examples is available in the
[examples](./examples) directory, including:

- Control system demonstrations
- SOFA-based simulations
- Hardware and real-time use cases
- Comparisons with external tools

See [examples/README.md](./examples/README.md) for an overview.

## Information

### License

pySimblocks is LGPL.
pySimBlocks is LGPL.

LGPL refers to the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3.0 of the License, or (at your option) any later
Expand Down
File renamed without changes.
30 changes: 30 additions & 0 deletions docs/User_Guide/getting_started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Getting Started with pySimBlocks

pySimBlocks is a block-based simulation framework for control systems,
supporting both programmatic and graphical modeling workflows.

This guide walks you through the core concepts in a progressive manner.

## 1. First Simulation (Python)

Build and simulate a closed-loop system directly in Python.

You will learn:
- How to create blocks
- How to connect signals
- How to run a discrete-time simulation
- How to log and visualize results

→ [Start Tutorial 1 — Python API](tutorial_1_python.md)

## 2. First Simulation (GUI)

Rebuild the same system using the graphical interface.

You will learn:
- How to create a model visually
- How to configure blocks and simulation settings
- How to save and export the project

→ [Start Tutorial 2 — GUI](tutorial_2_gui.md)

Binary file added docs/User_Guide/images/quick_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/User_Guide/images/tutorial_2-drag_drop.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/User_Guide/images/tutorial_2-gui_main.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/User_Guide/images/tutorial_2-plots.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/User_Guide/images/tutorial_2-setting.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
191 changes: 191 additions & 0 deletions docs/User_Guide/tutorial_1_python.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# Tutorial 1: First Steps with pySimBlocks

## 1. Overview

### 1.1 Goals

This example introduces the core concepts of pySimBlocks:
- Creating blocks
- Connecting signals
- Running a discrete-time simulation
- Logging and plotting results

By the end of this tutorial, you will be able to build and simulate your own block-based model in Python.

### 1.2 System Description

We build a simple closed-loop control system composed of three elements:
- a step reference,
- a PI controller,
- a first-order discrete-time linear plant.

![Alt Text](./images/tutorial_1-block_diagram.png)

**Plant — Linear State-Space System**
The plant is a discrete-time first-order linear system defined by:
$$\begin{array}{rcl} x[k+1] &=& a\,x[k] + b\,u[k] \\ y[k] &=& x[k] \end{array}$$
The initial state is $x[0] = 0$.

**Controller — PI**
The PI controller computes a control command from the tracking error $e[k] = r[k] - y[k]$:
$$ u[k] = K_p\, e[k] + x_i[k] $$
where the integral state evolves as:
$$x_i[k+1] = x_i[k] + K_i\, e[k]\, dt$$
The integral action ensures zero steady-state error on a step reference.

## 2. Complete Example

Below is the full working example.
You can find it in the file
[main.py](../../examples/tutorials/tutorial_1_python/main.py) in the repository.

```python
import matplotlib.pyplot as plt
import numpy as np

from pySimBlocks import Model, SimulationConfig, Simulator
from pySimBlocks.blocks.controllers import Pid
from pySimBlocks.blocks.operators import Sum
from pySimBlocks.blocks.sources import Step
from pySimBlocks.blocks.systems import LinearStateSpace


def main():
"""This example demonstrates how to use the pySimBlocks library to create a
simple closed-loop control system with a step reference input, a PI controller,
and a linear state-space system.
"""

A = np.array([[0.9]])
B = np.array([[0.5]])
C = np.array([[1.0]])
x0 = np.zeros((A.shape[0], 1))

Kp = np.array([[0.5]])
Ki = np.array([[2.]])

# -------------------------------------------------------
# 1. Create the blocks
# -------------------------------------------------------
step = Step(
name="ref",
value_before=np.array([[0.0]]),
value_after=np.array([[1.0]]),
start_time=0.5
)
sum = Sum(name="error", signs="+-")
pid = Pid(name="pid", controller="PI", Kp=Kp, Ki=Ki)
system = LinearStateSpace(name="system", A=A, B=B, C=C, x0=x0)

# -------------------------------------------------------
# 2. Build the model
# -------------------------------------------------------
model = Model("test")
for block in [step, sum, pid, system]:
model.add_block(block)

model.connect("ref", "out", "error", "in1")
model.connect("system", "y", "error", "in2")
model.connect("error", "out", "pid", "e")
model.connect("pid", "u", "system", "u")

# -------------------------------------------------------
# 3. Create the simulator
# -------------------------------------------------------
dt = 0.01 # seconds
T = 5. # seconds
sim_cfg = SimulationConfig(dt, T)
sim = Simulator(model, sim_cfg, verbose=False)

# -------------------------------------------------------
# 4. Run the simulation with logging
# -------------------------------------------------------
logs = sim.run(logging=[
"ref.outputs.out",
"pid.outputs.u",
"system.outputs.y"
]
)

# -------------------------------------------------------
# 5. Extract logged data
# -------------------------------------------------------
t = sim.get_data("time")
u = sim.get_data("pid.outputs.u").squeeze()
r = sim.get_data("ref.outputs.out").squeeze()
y = sim.get_data("system.outputs.y").squeeze()

# -------------------------------------------------------
# 6. Plot the result
# -------------------------------------------------------
fig, axs = plt.subplots(1, 2, sharex=True)
axs[0].step(t, r, "--r", label="ref", where="post")
axs[0].step(t, y, "--b", label="output", where="post")
axs[0].set_xlabel("Time [s]")
axs[0].set_ylabel("Amplitude")
axs[0].set_title("Closed-loop response")
axs[0].grid(True)
axs[0].legend()

axs[1].step(t, u, "--b", label="u[k] (pid output)", where="post")
axs[1].set_xlabel("Time [s]")
axs[1].set_ylabel("Amplitude")
axs[1].set_title("PID controller output")
axs[1].grid(True)
axs[1].legend()

plt.show()

```
## 3. How It Works

The example follows a simple workflow:

1. **Blocks are created**
Each component of the system (reference, controller, plant) is represented as a block.
2. **Blocks are added to a Model**
The `Model` acts as a container for all blocks.
3. **Signals are connected explicitly**
Connections define how outputs are propagated to downstream inputs, forming a directed graph.
4. **The Simulator executes the model in discrete time**
At each time step:
- Blocks compute their outputs
- Signals are propagated
- States are updated
5. **Selected signals are logged and retrieved**
Logged variables can be accessed using `sim.get_data()` and visualized with standard Python tools.

This structure reflects the core philosophy of pySimBlocks: explicit block modeling with deterministic discrete-time execution.

## 4. About the data shapes

All signals in pySimBlocks follow a strict 2D convention:

- Scalars are represented as `(1,1)`
- Vectors are `(n,1)`
- Matrices are `(m,n)`

When logging a SISO signal over time, the resulting array has shape: `(N, 1, 1)` where `N` is the number of simulation steps.

For plotting convenience, we use:
```python
y = sim.get_data("system.outputs.y").squeeze()
```

## 5. Try it yourself

To better understand the framework, try experimenting with:

- Changing the controller gains (`Kp`, `Ki`)
- Modifying the system dynamics (`A`, `B`)
- Adjusting the time step `dt`
- Increasing the simulation duration `T`

Observe how the closed-loop response changes.

This simple example is the foundation for more advanced use cases,
including:
- [GUI modeling](./tutorial_2_gui.md),
- SOFA integration,
- Hardware implementation.

Loading