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
2 changes: 2 additions & 0 deletions pySimBlocks/blocks/operators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
# ******************************************************************************

from pySimBlocks.blocks.operators.dead_zone import DeadZone
from pySimBlocks.blocks.operators.demux import Demux
from pySimBlocks.blocks.operators.delay import Delay
from pySimBlocks.blocks.operators.discrete_derivator import DiscreteDerivator
from pySimBlocks.blocks.operators.discrete_integrator import DiscreteIntegrator
Expand All @@ -32,6 +33,7 @@

__all__ = [
"DeadZone",
"Demux",
"Delay",
"DiscreteDerivator",
"DiscreteIntegrator",
Expand Down
123 changes: 123 additions & 0 deletions pySimBlocks/blocks/operators/demux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# ******************************************************************************
# pySimBlocks
# Copyright (c) 2026 Université de Lille & INRIA
# ******************************************************************************
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# ******************************************************************************
# Authors: see Authors.txt
# ******************************************************************************

import numpy as np
from numpy.typing import ArrayLike

from pySimBlocks.core.block import Block


class Demux(Block):
"""
Vector split block (inverse of Mux).

Summary:
Splits one input column vector into multiple output segments.

Parameters:
num_outputs : int
Number of scalar outputs to produce.
sample_time : float, optional
Block execution period.

Inputs:
in : vector (n,1)
Input must be a column vector.

Outputs:
out1, out2, ..., outP : array (k,1)
Output segment sizes follow:
- q = n // p
- m = n % p
- first m outputs have size (q+1,1)
- remaining (p-m) outputs have size (q,1)
"""

direct_feedthrough = True

def __init__(self, name: str, num_outputs: int = 2, sample_time: float | None = None):
super().__init__(name, sample_time)

if not isinstance(num_outputs, int) or num_outputs < 1:
raise ValueError(f"[{self.name}] num_outputs must be a positive integer.")
self.num_outputs = num_outputs

self.inputs["in"] = None
for i in range(num_outputs):
self.outputs[f"out{i+1}"] = None


# --------------------------------------------------------------------------
# Public methods
# --------------------------------------------------------------------------
def initialize(self, t0: float) -> None:
if self.inputs["in"] is None:
for i in range(self.num_outputs):
self.outputs[f"out{i+1}"] = np.zeros((1, 1), dtype=float)
return

self._compute_outputs()

# ---------------------------------------------------------
def output_update(self, t: float, dt: float) -> None:
self._compute_outputs()

# ---------------------------------------------------------
def state_update(self, t: float, dt: float) -> None:
return # stateless


# --------------------------------------------------------------------------
# Private methods
# --------------------------------------------------------------------------
def _to_vector(self, value: ArrayLike) -> np.ndarray:
arr = np.asarray(value, dtype=float)

if arr.ndim != 2 or arr.shape[1] != 1:
raise ValueError(
f"[{self.name}] Input 'in' must be a column vector (n,1). "
f"Got shape {arr.shape}."
)
return arr

# ---------------------------------------------------------
def _compute_outputs(self) -> None:
u = self.inputs["in"]
if u is None:
raise RuntimeError(f"[{self.name}] Input 'in' is not connected or not set.")

vec = self._to_vector(u)
n = vec.shape[0]
p = self.num_outputs

if p > n:
raise ValueError(
f"[{self.name}] num_outputs ({p}) must be <= input vector length ({n})."
)

q = n // p
m = n % p

start = 0
for i in range(p):
seg_len = q + 1 if i < m else q
end = start + seg_len
self.outputs[f"out{i+1}"] = vec[start:end].copy()
start = end
64 changes: 64 additions & 0 deletions pySimBlocks/docs/blocks/operators/demux.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Demux

## Summary

The **Demux** block splits one input column vector into multiple output
column vectors.

---

## Mathematical definition

Given an input vector $\mathrm{in} \in \mathbb{R}^{n \times 1}$ and
$p$ outputs:

$$
q = \left\lfloor \frac{n}{p} \right\rfloor, \quad
m = n \bmod p
$$

The output segments are:
- the first $m$ outputs have size $(q+1, 1)$,
- the remaining $(p-m)$ outputs have size $(q, 1)$.

This corresponds to an even split with the remainder distributed on the
first outputs.

---

## Parameters

| Name | Type | Description | Optional |
|------------|-------------|-------------|-------------|
| `num_outputs` | integer | Number of output ports to create. | True |
| `sample_time` | float | Block sample time. If omitted, the global simulation time step is used. | True |

---

## Inputs

| Port | Description |
|------|------------|
| `in` | Input column vector of shape $(n,1)$. |

---

## Outputs

| Port | Description |
|------|------------|
| `out1 … outP` | Output vector segments according to the split rule above. |

---

## Notes

- The block has no internal state.
- The block has direct feedthrough.
- Input must be a column vector of shape $(n,1)$.
- The parameter `num_outputs` must satisfy $1 \le p \le n$.
- This block is equivalent to the Simulink **Demux** concept for vector splitting.


---
© 2026 Université de Lille & INRIA – Licensed under LGPL-3.0-or-later
102 changes: 102 additions & 0 deletions pySimBlocks/docs/blocks/sources/chirp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Chirp

## Summary

The **Chirp** block generates a frequency-swept sinusoidal signal
(linear or logarithmic) with configurable amplitude, phase, offset,
start time, and duration.

---

## Mathematical definition

For each output component $i$:

$$
y_i(t) = A_i \sin(\phi_i(t)) + o_i
$$

with parameters:
- $A_i$: amplitude,
- $o_i$: offset,
- $\phi_i(t)$: chirp phase,
- $f_{0,i}$: initial frequency,
- $f_{1,i}$: final frequency,
- $T_i$: chirp duration,
- $t_{0,i}$: chirp start time,
- $\varphi_i$: initial phase.

Define:
$$
\tau_i = \max(0, t - t_{0,i}), \quad
\tau_{c,i} = \min(\tau_i, T_i)
$$

Linear mode:
$$
k_i = \frac{f_{1,i} - f_{0,i}}{T_i}
$$
$$
\phi_i(t) =
2\pi\left(f_{0,i}\tau_{c,i} + \frac{1}{2}k_i\tau_{c,i}^2\right)
+ 2\pi f_{1,i}\max(0, \tau_i - T_i)
+ \varphi_i
$$

Log mode:
$$
r_i = \frac{f_{1,i}}{f_{0,i}}
$$
$$
\phi_i(t) =
\frac{2\pi f_{0,i}T_i}{\ln(r_i)}
\left(r_i^{\tau_{c,i}/T_i} - 1\right)
+ 2\pi f_{1,i}\max(0, \tau_i - T_i)
+ \varphi_i
$$

After duration, phase continuity is preserved and oscillation continues at $f_1$.

---

## Parameters

| Name | Type | Description | Optional |
|------------|-------------|-------------|-------------|
| `amplitude` | scalar or vector or matrix | Signal amplitude. Scalars are broadcast to all dimensions. | False |
| `f0` | scalar or vector or matrix | Initial frequency in Hertz. Scalars are broadcast. | False |
| `f1` | scalar or vector or matrix | Final frequency in Hertz. Scalars are broadcast. | False |
| `duration` | scalar or vector or matrix | Sweep duration in seconds. Must be strictly positive. Scalars are broadcast. | False |
| `start_time` | scalar or vector or matrix | Start time in seconds. Before this time, sweep is not active. Default is `0.0`. | True |
| `offset` | scalar or vector or matrix | Constant offset added to output. Default is `0.0`. | True |
| `phase` | scalar or vector or matrix | Initial phase in radians. Default is `0.0`. | True |
| `mode` | string | Sweep mode: `linear` or `log`. Default is `linear`. | True |
| `sample_time` | float | Block sample time. If omitted, the global simulation time step is used. | True |

---

## Inputs

This block has **no inputs**.

---

## Outputs

| Port | Description |
|------|------------|
| `out` | Chirp output signal. |

---

## Notes

- The block has no internal state.
- All array-like parameters must have compatible shapes.
- Scalar parameters are broadcast to the common signal shape.
- In `log` mode, `f0 > 0`, `f1 > 0`, and `f0 != f1` are required.
- The output keeps oscillating after `duration` at frequency `f1`.


---
© 2026 Université de Lille & INRIA – Licensed under LGPL-3.0-or-later
Loading