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
66 changes: 66 additions & 0 deletions examples/benchmarks/bench_01_minimal_loop/bdsim_case.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import os
import time
from contextlib import redirect_stdout

import bdsim
import numpy as np
import params as prm

# params

def test_bdsim_case():
sim = bdsim.BDSim(animation=False, progress=False, verbose=False, toolboxes=False)
bd = sim.blockdiagram() # create an empty block diagram

tuples = [(k * prm.dt, float(prm.noise_sequence[k])) for k in range(prm.N + 2)]
clock = bd.clock(prm.dt)

# define the blocks
src = bd.PIECEWISE(seq=tuples, name='noise')
gain_alpha = bd.GAIN(prm.alpha)
gain_1malpha = bd.GAIN(1 - prm.alpha)
sum_block = bd.SUM('++')
zoh = bd.ZOH(clock, x0=prm.x0)

# connect the blocks
bd.connect(src, gain_alpha)
bd.connect(gain_alpha, sum_block[0])
bd.connect(gain_1malpha, sum_block[1])
bd.connect(sum_block, zoh)
bd.connect(zoh, gain_1malpha)

bd.compile(report=False, verbose=False)


t0 = time.perf_counter()
with redirect_stdout(open(os.devnull, 'w')):
out = sim.run(bd, T=prm.T, dt=prm.dt)
t1 = time.perf_counter()

dt_sim = t1 - t0
t = out.clock0.t.flatten() # flatten to 1D array
x = out.clock0.x.flatten() # flatten to 1D array

t = np.hstack(([0], t))
x = np.hstack(([prm.x0], x))

return t, prm.noise_sequence[:len(t)], x, dt_sim

if __name__ == "__main__":
import matplotlib.pyplot as plt

t, noise_data, output_data, dt_sim = test_bdsim_case()

print(f"bdsim simulation completed in {dt_sim:.2f} seconds")

# Plot results
plt.figure(figsize=(10, 6))
plt.plot(t, noise_data, "--r", label='Noise', alpha=0.7)
plt.plot(t, output_data, "--b", label='Output', alpha=0.7)
plt.title('pySimBlocks Simulation Results')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid()
plt.tight_layout()
plt.show()
118 changes: 118 additions & 0 deletions examples/benchmarks/bench_01_minimal_loop/compare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import logging

import numpy as np
import matplotlib.pyplot as plt

from bdsim_case import test_bdsim_case
from pathsim_case import test_pathsim_case
from pysimblocks_case import test_pysimblocks_case


logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger(__name__)


if __name__ == "__main__":
N_RUNS = 1

logger.info(f"Running benchmark ({N_RUNS} runs each)...")

results_bd, results_ps, results_pb = [], [], []
for i in range(N_RUNS):
print(f"Run {i+1}/{N_RUNS}...", end="\r")
results_bd.append(test_bdsim_case())
results_ps.append(test_pathsim_case())
results_pb.append(test_pysimblocks_case())

times_bd = np.array([r[3] for r in results_bd])
times_ps = np.array([r[3] for r in results_ps])
times_pb = np.array([r[3] for r in results_pb])

t_bd, noise_bd, output_bd, _ = results_bd[-1]
t_ps, noise_ps, output_ps, _ = results_ps[-1]
t_pb, noise_pb, output_pb, _ = results_pb[-1]

n = min(len(t_ps), len(t_pb), len(t_bd))
t_bd, noise_bd, output_bd = t_bd[:n], noise_bd[:n], output_bd[:n]
t_ps, noise_ps, output_ps = t_ps[:n], noise_ps[:n], output_ps[:n]
t_pb, noise_pb, output_pb = t_pb[:n], noise_pb[:n], output_pb[:n]

t_error_bd_ps = np.linalg.norm(t_bd - t_ps)
noise_error_bd_ps = np.linalg.norm(noise_bd - noise_ps)
output_error_bd_ps = np.linalg.norm(output_bd - output_ps)
t_error_bd_pb = np.linalg.norm(t_bd - t_pb)
noise_error_bd_pb = np.linalg.norm(noise_bd - noise_pb)
output_error_bd_pb = np.linalg.norm(output_bd - output_pb)
t_error_ps_pb = np.linalg.norm(t_ps - t_pb)
noise_error_ps_pb = np.linalg.norm(noise_ps - noise_pb)
output_error_ps_pb = np.linalg.norm(output_ps - output_pb)

logger.info("")
logger.info("=== Timing ===")
logger.info(f" bdsim: median={np.median(times_bd)*1e3:.1f} ms, std={np.std(times_bd)*1e3:.1f} ms")
logger.info(f" PathSim: median={np.median(times_ps)*1e3:.1f} ms, std={np.std(times_ps)*1e3:.1f} ms")
logger.info(f" pySimBlocks: median={np.median(times_pb)*1e3:.1f} ms, std={np.std(times_pb)*1e3:.1f} ms")
logger.info(f" Speedup pySimBlocks vs PathSim: {np.median(times_ps) / np.median(times_pb):.2f}x")
logger.info(f" Speedup pySimBlocks vs bdsim: {np.median(times_bd) / np.median(times_pb):.2f}x")

logger.info("")
logger.info("=== Numerical errors (last run) ===")
logger.info("Librairies | Time error (a/b%) | Noise error (a/b%) | Output error (a/b%)")
logger.info(f"bdsim vs PathSim | {t_error_bd_ps:.2e} ({t_error_bd_ps / np.linalg.norm(t_ps) * 100:.2f}%) | {noise_error_bd_ps:.2e} ({noise_error_bd_ps / np.linalg.norm(noise_ps) * 100:.2f}%) | {output_error_bd_ps:.2e} ({output_error_bd_ps / np.linalg.norm(output_ps) * 100:.2f}%)")
logger.info(f"bdsim vs pySimBlocks | {t_error_bd_pb:.2e} ({t_error_bd_pb / np.linalg.norm(t_pb) * 100:.2f}%) | {noise_error_bd_pb:.2e} ({noise_error_bd_pb / np.linalg.norm(noise_pb) * 100:.2f}%) | {output_error_bd_pb:.2e} ({output_error_bd_pb / np.linalg.norm(output_pb) * 100:.2f}%)")
logger.info(f"PathSim vs pySimBlocks | {t_error_ps_pb:.2e} ({t_error_ps_pb / np.linalg.norm(t_pb) * 100:.2f}%) | {noise_error_ps_pb:.2e} ({noise_error_ps_pb / np.linalg.norm(noise_pb) * 100:.2f}%) | {output_error_ps_pb:.2e} ({output_error_ps_pb / np.linalg.norm(output_pb) * 100:.2f}%)")

plt.figure(figsize=(10, 6))
plt.plot(t_bd, noise_bd, "-.r", label="Noise (bdsim)", alpha=0.7)
plt.plot(t_bd, output_bd, "-.b", label="Output (bdsim)", alpha=0.7)
plt.plot(t_ps, noise_ps, "--r", label="Noise (PathSim)", alpha=0.7)
plt.plot(t_ps, output_ps, "--b", label="Output (PathSim)", alpha=0.7)
plt.plot(t_pb, noise_pb, ":r", label="Noise (pySimBlocks)", alpha=0.7)
plt.plot(t_pb, output_pb, ":b", label="Output (pySimBlocks)", alpha=0.7)
plt.title("Simulation Results Comparison")
plt.xlabel("Time (s)")
plt.ylabel("Amplitude")
plt.legend()
plt.grid()
plt.tight_layout()

# --- Histogramme des temps ---
benchmarks = {
'bench_01': {
'bdsim': np.median(times_bd) * 1e3,
'PathSim': np.median(times_ps) * 1e3,
'pySimBlocks': np.median(times_pb) * 1e3,
},
# 'bench_02': { 'bdsim': ..., 'PathSim': ..., 'pySimBlocks': ... },
# 'bench_03': { 'bdsim': ..., 'PathSim': ..., 'pySimBlocks': ... },
}

libs = ['bdsim', 'PathSim', 'pySimBlocks']
colors = {'bdsim': '#7F77DD', 'PathSim': '#1D9E75', 'pySimBlocks': '#D85A30'}

n_bench = len(benchmarks)
width = 0.25
group_gap = 1.0 # espace entre groupes

fig, ax = plt.subplots(figsize=(4 + 2 * n_bench, 5))

for g, (bench_name, values) in enumerate(benchmarks.items()):
group_center = g * group_gap
offsets = [-width, 0, width]
for lib, offset in zip(libs, offsets):
val = values[lib]
bar = ax.bar(group_center + offset, val, width=width,
color=colors[lib], label=lib if g == 0 else None)
ax.bar_label(bar, fmt='%.1f', padding=3, fontsize=8)

ax.set_xticks([g * group_gap for g in range(n_bench)])
ax.set_xticklabels(list(benchmarks.keys()))
ax.set_ylabel('Median time (ms)')
ax.set_title('Time benchmark comparison')
ax.set_yscale('log')
ax.legend(loc='upper right')
ax.grid(axis='y', linestyle='--', alpha=0.4)
ax.spines[['top', 'right']].set_visible(False)

plt.tight_layout()
plt.show()
Binary file not shown.
14 changes: 14 additions & 0 deletions examples/benchmarks/bench_01_minimal_loop/params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import numpy as np

dt = 0.1
N = 100
T = N * dt
alpha = 0.1

# noise
std = 1.
seed = 0
rng = np.random.default_rng(seed)
noise_sequence = rng.standard_normal(N + 2)
np.save("noise_seq.npy", noise_sequence)
x0 = 0.
151 changes: 151 additions & 0 deletions examples/benchmarks/bench_01_minimal_loop/pathsim_case.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PathSim Simulation
==================

Generated by PathView on 2026-04-18 09:41:48
https://view.pathsim.org

PathSim documentation: https://docs.pathsim.org
"""

# ────────────────────────────────────────────────────────────────────────────
# IMPORTS
# ────────────────────────────────────────────────────────────────────────────

import time
import numpy as np

from pathsim import Simulation, Connection
from pathsim.blocks import (
Adder,
Amplifier,
Delay,
Scope,
Source,
)
from pathsim.solvers import RK4

import params as prm

def make_noise_source(seq, dt):
def f(t):
k = int(round(t / dt))
return float(seq[min(k, len(seq) - 1)])
return Source(f)


def test_pathsim_case():

# ────────────────────────────────────────────────────────────────────────────
# BLOCKS
# ────────────────────────────────────────────────────────────────────────────

# Sources
whitenoise = make_noise_source(prm.noise_sequence, prm.dt)

# Dynamic
delay = Delay(
tau=prm.dt
)

# Algebraic
adder = Adder(
operations="++"
)
amplifier = Amplifier(
gain=prm.alpha
)
block_4 = Amplifier(
gain=1 - prm.alpha
)

# Recording
result = Scope()

blocks = [
whitenoise,
delay,
adder,
amplifier,
block_4,
result,
]

# ────────────────────────────────────────────────────────────────────────────
# CONNECTIONS
# ────────────────────────────────────────────────────────────────────────────

conn_0 = Connection(adder[0], delay[0])
conn_1 = Connection(amplifier[0], adder[0])
conn_2 = Connection(block_4[0], adder[1])
conn_3 = Connection(whitenoise[0], amplifier[0])
conn_4 = Connection(delay[0], block_4[0])
conn_5 = Connection(whitenoise[0], result[0])
conn_6 = Connection(delay[0], result[1])

connections = [
conn_0,
conn_1,
conn_2,
conn_3,
conn_4,
conn_5,
conn_6,
]

# ────────────────────────────────────────────────────────────────────────────
# SIMULATION
# ────────────────────────────────────────────────────────────────────────────

sim = Simulation(
blocks,
connections,
Solver=RK4,
dt=prm.dt,
dt_min=prm.dt,
dt_max=prm.dt,
tolerance_lte_rel=0.0001,
tolerance_lte_abs=1e-08,
tolerance_fpi=1e-10,
log=False,
)

# ────────────────────────────────────────────────────────────────────────────
# MAIN
# ────────────────────────────────────────────────────────────────────────────

# Run simulation
t0 = time.perf_counter()
sim.run(duration=prm.T, reset=True, adaptive=False)
t1 = time.perf_counter()
dt_sim = t1 - t0

t = np.array(result.recording_time)
data = np.array(result.recording_data)
noise_data = data[:, 0]
output_data = data[:, 1]

return t, noise_data, output_data, dt_sim

if __name__ == "__main__":

import matplotlib.pyplot as plt

t, noise_data, output_data, dt_sim = test_pathsim_case()

print(f"PathSim simulation completed in {dt_sim:.2f} seconds")

# Plot results
plt.figure(figsize=(10, 6))
plt.plot(t, noise_data, "--r", label='Noise', alpha=0.7)
plt.plot(t, output_data, "--b", label='Output', alpha=0.7)
plt.title('PathSim Simulation Results')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid()
plt.tight_layout()
plt.show()

Loading
Loading