diff --git a/examples/generate/state_space/polytope/layout.yaml b/examples/generate/state_space/polytope/layout.yaml
new file mode 100644
index 0000000..2a4be81
--- /dev/null
+++ b/examples/generate/state_space/polytope/layout.yaml
@@ -0,0 +1,18 @@
+version: 1
+blocks:
+ Polytope:
+ x: -164.0
+ y: -107.0
+ orientation: normal
+ weights:
+ x: -400.0
+ y: -150.0
+ orientation: normal
+ Step:
+ x: -400.0
+ y: -60.0
+ orientation: normal
+ Constant:
+ x: -575.0
+ y: -150.0
+ orientation: normal
diff --git a/examples/generate/state_space/polytope/model.yaml b/examples/generate/state_space/polytope/model.yaml
new file mode 100644
index 0000000..8763594
--- /dev/null
+++ b/examples/generate/state_space/polytope/model.yaml
@@ -0,0 +1,17 @@
+blocks:
+- name: Polytope
+ category: systems
+ type: polytopic_state_space
+- name: weights
+ category: operators
+ type: algebraic_function
+- name: Step
+ category: sources
+ type: step
+- name: Constant
+ category: sources
+ type: constant
+connections:
+- [Step.out, Polytope.u]
+- [weights.w, Polytope.w]
+- [Constant.out, weights.c]
diff --git a/examples/generate/state_space/polytope/parameters.yaml b/examples/generate/state_space/polytope/parameters.yaml
new file mode 100644
index 0000000..d933810
--- /dev/null
+++ b/examples/generate/state_space/polytope/parameters.yaml
@@ -0,0 +1,28 @@
+simulation:
+ dt: 0.1
+ T: 10.0
+ solver: fixed
+blocks:
+ Polytope:
+ A: '#A'
+ B: '#B'
+ C: '#C'
+ weights:
+ file_path: weights.py
+ function_name: get_weights
+ input_keys:
+ - c
+ output_keys:
+ - w
+ Step:
+ value_before: [[0.0]]
+ value_after: [[1.0]]
+ start_time: 1.0
+ Constant:
+ value: [[0.3]]
+logging:
+- Polytope.outputs.x
+- Polytope.outputs.y
+- weights.outputs.w
+plots: []
+external: params.py
diff --git a/examples/generate/state_space/polytope/params.py b/examples/generate/state_space/polytope/params.py
new file mode 100644
index 0000000..be960e8
--- /dev/null
+++ b/examples/generate/state_space/polytope/params.py
@@ -0,0 +1,39 @@
+import numpy as np
+import matplotlib.pyplot as plt
+
+# Dimensions
+r = 3 # number of vertices
+nx = 4 # state dimension
+nu = 1 # input dimension
+ny = 2 # output dimension
+
+
+# Create A_i matrices
+A1 = np.array([
+ [0.8, 0.1, 0.0, 0.0],
+ [0.0, 0.7, 0.1, 0.0],
+ [0.0, 0.0, 0.6, 0.1],
+ [0.0, 0.0, 0.0, 0.5],
+])
+
+A2 = A1 + 0.05 * np.eye(nx)
+A3 = A1 - 0.05 * np.eye(nx)
+
+A = np.hstack([A1, A2, A3])
+
+
+# Create B_i matrices
+B1 = np.array([[1.0], [0.0], [0.0], [0.0]])
+B2 = np.array([[0.5], [0.5], [0.0], [0.0]])
+B3 = np.array([[0.2], [0.3], [0.3], [0.2]])
+
+B = np.hstack([B1, B2, B3])
+
+
+# Output matrix
+C = np.array([
+ [1.0, 0.0, 0.0, 0.0],
+ [0.0, 0.0, 1.0, 0.0],
+])
+
+
diff --git a/examples/generate/state_space/polytope/weights.py b/examples/generate/state_space/polytope/weights.py
new file mode 100644
index 0000000..b99730f
--- /dev/null
+++ b/examples/generate/state_space/polytope/weights.py
@@ -0,0 +1,12 @@
+import numpy as np
+
+
+def get_weights(t, dt, c):
+ w1 = np.clip(0.4 + 0.2 * np.sin(0.5 * t) + c, 0.0, 1.0)
+ w2 = np.clip(0.3 + 0.1 * np.cos(0.3 * t), 0.0, 1-w1)
+ w3 = 1.0 - w1 - w2
+ w3 = np.clip(w3, 0.0, 1.0)
+
+ val = np.array([w1, w2, w3]).reshape(-1, 1)
+ return {"w": val}
+
diff --git a/pySimBlocks/blocks/__init__.py b/pySimBlocks/blocks/__init__.py
index a2f9bb5..997e188 100644
--- a/pySimBlocks/blocks/__init__.py
+++ b/pySimBlocks/blocks/__init__.py
@@ -26,7 +26,7 @@
Gain, Mux, Product, RateLimiter, Saturation, Sum, ZeroOrderHold
)
from pySimBlocks.blocks.sources import Constant, Ramp, Step, Sinusoidal, WhiteNoise
-from pySimBlocks.blocks.systems import LinearStateSpace
+from pySimBlocks.blocks.systems import LinearStateSpace, PolytopicStateSpace
__all__ = [
"Pid",
@@ -56,4 +56,5 @@
"WhiteNoise",
"LinearStateSpace",
+ "PolytopicStateSpace",
]
diff --git a/pySimBlocks/blocks/systems/__init__.py b/pySimBlocks/blocks/systems/__init__.py
index 5f269cb..e26a04a 100644
--- a/pySimBlocks/blocks/systems/__init__.py
+++ b/pySimBlocks/blocks/systems/__init__.py
@@ -19,7 +19,9 @@
# ******************************************************************************
from pySimBlocks.blocks.systems.linear_state_space import LinearStateSpace
+from pySimBlocks.blocks.systems.polytopic_state_space import PolytopicStateSpace
__all__ = [
"LinearStateSpace",
+ "PolytopicStateSpace",
]
diff --git a/pySimBlocks/blocks/systems/polytopic_state_space.py b/pySimBlocks/blocks/systems/polytopic_state_space.py
new file mode 100644
index 0000000..8439e84
--- /dev/null
+++ b/pySimBlocks/blocks/systems/polytopic_state_space.py
@@ -0,0 +1,193 @@
+# ******************************************************************************
+# 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 .
+# ******************************************************************************
+# Authors: see Authors.txt
+# ******************************************************************************
+
+import numpy as np
+from numpy.typing import ArrayLike
+from pySimBlocks.core.block import Block
+
+
+class PolytopicStateSpace(Block):
+ """
+ Discrete-time polytopic state-space block.
+
+ Summary:
+ Implements:
+ x[k+1] = sum_{i=1}^r w_i[k] (A_i x[k] + B_i u[k])
+ y[k] = C x[k]
+
+ Parameters (overview):
+ A : array-like
+ concatenation of A_i matrices [A_1, A_2, ..., A_r], shape (nx, r*nx).
+ B : array-like
+ concatenation of B_i matrices [B_1, B_2, ..., B_r], shape (nx, r*nu).
+ C : array-like
+ Output matrix.
+ x0 : array-like, optional
+ Initial state vector.
+ sample_time : float, optional
+ Block execution period.
+
+ I/O:
+ Inputs:
+ u : input vector.
+ w : vertex weight vector (must sum to 1).
+ Outputs:
+ y : output vector.
+ x : state vector.
+
+ Notes:
+ - The system is strictly proper (no direct feedthrough).
+ - The block has internal state.
+ """
+
+ direct_feedthrough = False
+
+ def __init__(
+ self,
+ name: str,
+ A: ArrayLike,
+ B: ArrayLike,
+ C: ArrayLike,
+ x0: ArrayLike | None = None,
+ sample_time: float | None = None,
+ ):
+ super().__init__(name, sample_time)
+
+ self.A = np.asarray(A, dtype=float)
+ self.B = np.asarray(B, dtype=float)
+ self.C = np.asarray(C, dtype=float)
+
+ if self.A.ndim != 2:
+ raise ValueError(f"[{self.name}] A must be 2D. Got shape {self.A.shape}.")
+ if self.B.ndim != 2:
+ raise ValueError(f"[{self.name}] B must be 2D. Got shape {self.B.shape}.")
+ if self.C.ndim != 2:
+ raise ValueError(f"[{self.name}] C must be 2D. Got shape {self.C.shape}.")
+
+ nx = self.A.shape[0]
+ if nx <= 0:
+ raise ValueError(f"[{self.name}] A must have at least one row.")
+ if self.A.shape[1] % nx != 0:
+ raise ValueError(
+ f"[{self.name}] A must have shape (nx, r*nx). Got {self.A.shape}."
+ )
+
+ r = self.A.shape[1] // nx
+ if r <= 0:
+ raise ValueError(f"[{self.name}] Number of vertices r must be >= 1.")
+
+ if self.B.shape[0] != nx:
+ raise ValueError(
+ f"[{self.name}] B must have nx rows. A is {self.A.shape}, B is {self.B.shape}."
+ )
+ if self.B.shape[1] % r != 0:
+ raise ValueError(
+ f"[{self.name}] B must have shape (nx, r*nu). A gives r={r}, B is {self.B.shape}."
+ )
+
+ nu = self.B.shape[1] // r
+ if nu <= 0:
+ raise ValueError(f"[{self.name}] Input size nu must be >= 1.")
+
+ if self.C.shape[1] != nx:
+ raise ValueError(
+ f"[{self.name}] C must have nx columns. A is {self.A.shape}, C is {self.C.shape}."
+ )
+
+ ny = self.C.shape[0]
+
+ self._nx = nx
+ self._nu = nu
+ self._ny = ny
+ self._r = r
+
+ if x0 is None:
+ x0_arr = np.zeros((nx, 1), dtype=float)
+ else:
+ x0_arr = self._to_col_vec("x0", x0, nx)
+
+ self.state["x"] = x0_arr.copy()
+ self.next_state["x"] = x0_arr.copy()
+
+ self.inputs["w"] = None
+ self.inputs["u"] = None
+ self.outputs["x"] = None
+ self.outputs["y"] = None
+
+ # --------------------------------------------------------------------------
+ # Public methods
+ # --------------------------------------------------------------------------
+ def initialize(self, t0: float) -> None:
+ x = self.state["x"]
+ self.outputs["x"] = x.copy()
+ self.outputs["y"] = self.C @ x
+ self.next_state["x"] = x.copy()
+
+ # ------------------------------------------------------------------
+ def output_update(self, t: float, dt: float) -> None:
+ x = self.state["x"]
+ self.outputs["x"] = x.copy()
+ self.outputs["y"] = self.C @ x
+
+ # ------------------------------------------------------------------
+ def state_update(self, t: float, dt: float) -> None:
+ w = self.inputs["w"]
+ u = self.inputs["u"]
+ if w is None:
+ raise RuntimeError(f"[{self.name}] Input 'w' is not connected or not set.")
+ if u is None:
+ raise RuntimeError(f"[{self.name}] Input 'u' is not connected or not set.")
+
+ w_vec = self._to_col_vec("w", w, self._r)
+ if not np.isclose(np.sum(w_vec), 1.0):
+ raise ValueError(f"[{self.name}] Vertex weights w must sum to 1. Got sum {np.sum(w_vec)}.")
+ if np.any(w_vec < 0):
+ raise ValueError(f"[{self.name}] Vertex weights w must be non-negative. Got {w_vec.flatten()}.")
+
+ u_vec = self._to_col_vec("u", u, self._nu)
+ x = self.state["x"]
+
+ x_next = self.A @ np.kron(w_vec, x) + self.B @ np.kron(w_vec, u_vec)
+ self.next_state["x"] = x_next
+
+ # --------------------------------------------------------------------------
+ # Private methods
+ # --------------------------------------------------------------------------
+ def _to_col_vec(self, name: str, value: ArrayLike, expected_rows: int) -> np.ndarray:
+ arr = np.asarray(value, dtype=float)
+
+ if arr.ndim == 0:
+ arr = arr.reshape(1, 1)
+ elif arr.ndim == 1:
+ arr = arr.reshape(-1, 1)
+ elif arr.ndim == 2:
+ pass
+ else:
+ raise ValueError(f"[{self.name}] {name} must be 1D or 2D. Got shape {arr.shape}.")
+
+ if arr.shape[1] != 1:
+ raise ValueError(f"[{self.name}] {name} must be a column vector (k,1). Got {arr.shape}.")
+
+ if arr.shape[0] != expected_rows:
+ raise ValueError(
+ f"[{self.name}] {name} must have shape ({expected_rows},1). Got {arr.shape}."
+ )
+
+ return arr
diff --git a/pySimBlocks/docs/blocks/systems/polytopic_state_space.md b/pySimBlocks/docs/blocks/systems/polytopic_state_space.md
new file mode 100644
index 0000000..55f8a22
--- /dev/null
+++ b/pySimBlocks/docs/blocks/systems/polytopic_state_space.md
@@ -0,0 +1,72 @@
+# PolytopicStateSpace
+
+## Summary
+
+The **PolytopicStateSpace** block implements a discrete-time polytopic state-space model without direct feedthrough.
+
+---
+
+## Mathematical definition
+
+The system is defined by:
+
+$$
+x[k+1] = \sum_{i=1}^{r} w_i[k] (A_i x[k] + B_i u[k])
+$$
+
+$$
+y[k] = C x[k]
+$$
+
+where:
+- $x[k] \in \mathbb{R}^{nx}$ is the state vector,
+- $u[k] \in \mathbb{R}^{nu}$ is the input vector,
+- $w[k] \in \mathbb{R}^{r}$ is the vertex weight vector,
+- $y[k] \in \mathbb{R}^{ny}$ is the output vector.
+
+The block expects:
+- $A \in \mathbb{R}^{nx \times (r\,nx)}$ as $[A_1 \ \cdots \ A_r]$,
+- $B \in \mathbb{R}^{nx \times (r\,nu)}$ as $[B_1 \ \cdots \ B_r]$,
+- $C \in \mathbb{R}^{ny \times nx}$.
+
+---
+
+## Parameters
+
+| Name | Type | Description | Optional |
+|------------|-------------|-------------|-------------|
+| `A` | 2D array | Stacked state matrix of size (nx, r*nx): `[A1, ..., Ar]`. | False |
+| `B` | 2D array | Stacked input matrix of size (nx, r*nu): `[B1, ..., Br]`. | False |
+| `C` | 2D array | Output matrix of size (ny, nx). | False |
+| `x0` | 1D array | Initial state vector of size (nx,). If omitted, initialized to zero. | True |
+| `sample_time` | float | Block sample time. If omitted, the global simulation time step is used. | True |
+
+---
+
+## Inputs
+
+| Port | Description |
+|------|------------|
+| `w` | Weight vector of shape (r,1) (or `(r,)` as parameterized array). |
+| `u` | Input vector of shape (nu,1). |
+
+---
+
+## Outputs
+
+| Port | Description |
+|------|------------|
+| `x` | State vector of shape (nx,1). |
+| `y` | Output vector of shape (ny,1). |
+
+---
+
+## Notes
+
+- The block has internal state.
+- The system is strictly proper (no direct feedthrough).
+- Input dimensions are validated at runtime.
+
+
+---
+© 2026 Université de Lille & INRIA – Licensed under LGPL-3.0-or-later
diff --git a/pySimBlocks/gui/blocks/systems/polytopic_state_space.py b/pySimBlocks/gui/blocks/systems/polytopic_state_space.py
new file mode 100644
index 0000000..ee142d0
--- /dev/null
+++ b/pySimBlocks/gui/blocks/systems/polytopic_state_space.py
@@ -0,0 +1,109 @@
+# ******************************************************************************
+# 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 .
+# ******************************************************************************
+# Authors: see Authors.txt
+# ******************************************************************************
+
+from pySimBlocks.gui.blocks.block_meta import BlockMeta
+from pySimBlocks.gui.blocks.parameter_meta import ParameterMeta
+from pySimBlocks.gui.blocks.port_meta import PortMeta
+
+
+class PolytopicStateSpaceMeta(BlockMeta):
+
+ def __init__(self):
+ self.name = "PolytopicStateSpace"
+ self.category = "systems"
+ self.type = "polytopic_state_space"
+ self.summary = "Discrete-time polytopic state-space system."
+ self.description = (
+ "Implements the discrete-time polytopic state-space equations:\n"
+ "$$\n"
+ "x[k+1] = A\\,\\mathrm{kron}(w[k], x[k]) + B\\,\\mathrm{kron}(w[k], u[k])\n"
+ "$$\n"
+ "$$\n"
+ "y[k] = C x[k]\n"
+ "$$\n"
+ "with $w[k]$ the vertex weight vector.\n"
+ )
+
+ self.parameters = [
+ ParameterMeta(
+ name="A",
+ type="matrix",
+ required=True,
+ autofill=True,
+ default=[[1.0, 0.0]],
+ description="Stacked polytopic state matrix of shape (nx, r*nx): [A1 ... Ar]."
+ ),
+ ParameterMeta(
+ name="B",
+ type="matrix",
+ required=True,
+ autofill=True,
+ default=[[1.0, 1.0]],
+ description="Stacked polytopic input matrix of shape (nx, r*nu): [B1 ... Br]."
+ ),
+ ParameterMeta(
+ name="C",
+ type="matrix",
+ required=True,
+ autofill=True,
+ default=[[1.0]],
+ description="Output matrix of shape (ny, nx)."
+ ),
+ ParameterMeta(
+ name="x0",
+ type="vector",
+ description="Initial state vector."
+ ),
+ ParameterMeta(
+ name="sample_time",
+ type="float",
+ description="Block execution period."
+ )
+ ]
+
+ self.inputs = [
+ PortMeta(
+ name="w",
+ display_as="w",
+ shape=["r", 1],
+ description="Vertex weights vector."
+ ),
+ PortMeta(
+ name="u",
+ display_as="u",
+ shape=["nu", 1],
+ description="Input vector."
+ )
+ ]
+
+ self.outputs = [
+ PortMeta(
+ name="x",
+ display_as="x",
+ shape=["nx", 1],
+ description="State vector."
+ ),
+ PortMeta(
+ name="y",
+ display_as="y",
+ shape=["ny", 1],
+ description="Output vector."
+ )
+ ]
diff --git a/pySimBlocks/project/pySimBlocks_blocks_index.yaml b/pySimBlocks/project/pySimBlocks_blocks_index.yaml
index 8fa60e5..225a559 100644
--- a/pySimBlocks/project/pySimBlocks_blocks_index.yaml
+++ b/pySimBlocks/project/pySimBlocks_blocks_index.yaml
@@ -83,6 +83,9 @@ systems:
linear_state_space:
class: LinearStateSpace
module: pySimBlocks.blocks.systems.linear_state_space
+ polytopic_state_space:
+ class: PolytopicStateSpace
+ module: pySimBlocks.blocks.systems.polytopic_state_space
non_linear_state_space:
class: NonLinearStateSpace
module: pySimBlocks.blocks.systems.non_linear_state_space
diff --git a/test/blocks/systems/test_polytopic_state_space.py b/test/blocks/systems/test_polytopic_state_space.py
new file mode 100644
index 0000000..46c32a0
--- /dev/null
+++ b/test/blocks/systems/test_polytopic_state_space.py
@@ -0,0 +1,133 @@
+import numpy as np
+import pytest
+
+from pySimBlocks.core import Model, Simulator, SimulationConfig
+from pySimBlocks.blocks.sources.constant import Constant
+from pySimBlocks.blocks.systems.polytopic_state_space import PolytopicStateSpace
+
+
+def run_sim(src_w, src_u, sys, dt=0.1, T=0.3):
+ m = Model()
+ m.add_block(src_w)
+ m.add_block(src_u)
+ m.add_block(sys)
+ m.connect(src_w.name, "out", sys.name, "w")
+ m.connect(src_u.name, "out", sys.name, "u")
+
+ sim_cfg = SimulationConfig(dt, T, logging=[f"{sys.name}.outputs.y", f"{sys.name}.outputs.x"])
+ sim = Simulator(m, sim_cfg)
+ logs = sim.run()
+ return logs
+
+
+def test_poly_scalar_basic():
+ # r=2, nx=1, nu=1, ny=1
+ A = [[0.8, 0.5]] # [A1 A2]
+ B = [[1.0, 2.0]] # [B1 B2]
+ C = [[1.0]]
+
+ src_w = Constant("w", [[0.25], [0.75]])
+ src_u = Constant("u", [[2.0]])
+ sys = PolytopicStateSpace("sys", A=A, B=B, C=C, x0=[[0.0]])
+
+ logs = run_sim(src_w, src_u, sys)
+ y = logs["sys.outputs.y"]
+ x = logs["sys.outputs.x"]
+
+ # x[k+1] = 0.575*x[k] + 3.5
+ assert np.allclose(y[0], [[0.0]])
+ assert np.allclose(x[1], [[3.5]])
+ assert np.allclose(y[1], [[3.5]])
+ assert np.allclose(x[2], [[5.5125]])
+ assert np.allclose(y[2], [[5.5125]])
+
+
+def test_poly_vector_dimensions():
+ # r=2, nx=2, nu=1, ny=1
+ A1 = np.array([[1.0, 0.1], [0.0, 1.0]])
+ A2 = np.array([[0.9, 0.0], [0.0, 0.95]])
+ A = np.hstack([A1, A2])
+
+ B1 = np.array([[0.0], [1.0]])
+ B2 = np.array([[1.0], [0.0]])
+ B = np.hstack([B1, B2])
+ C = np.array([[1.0, 0.0]])
+
+ src_w = Constant("w", [[0.2], [0.8]])
+ src_u = Constant("u", [[2.0]])
+ sys = PolytopicStateSpace("sys", A=A, B=B, C=C, x0=[[0.0], [0.0]])
+
+ logs = run_sim(src_w, src_u, sys)
+ x = logs["sys.outputs.x"]
+ y = logs["sys.outputs.y"]
+
+ assert np.allclose(x[1], [[1.6], [0.4]])
+ assert np.allclose(y[1], [[1.6]])
+ assert np.allclose(x[2], [[3.08], [0.784]])
+ assert np.allclose(y[2], [[3.08]])
+
+
+def test_poly_missing_w_input_raises():
+ A = [[1.0, 0.0]]
+ B = [[1.0, 1.0]]
+ C = [[1.0]]
+
+ src_u = Constant("u", [[1.0]])
+ sys = PolytopicStateSpace("sys", A=A, B=B, C=C)
+
+ m = Model()
+ m.add_block(src_u)
+ m.add_block(sys)
+ m.connect("u", "out", "sys", "u")
+
+ sim_cfg = SimulationConfig(0.1, 0.1, logging=["sys.outputs.y"])
+ sim = Simulator(m, sim_cfg)
+ sim.initialize()
+
+ with pytest.raises(RuntimeError):
+ sim.run()
+
+
+def test_poly_rejects_non_column_w():
+ A = [[1.0, 0.0]]
+ B = [[1.0, 1.0]]
+ C = [[1.0]]
+
+ src_w = Constant("w", [[0.2, 0.8], [0.1, 0.9]]) # 2x2, not column vector
+ src_u = Constant("u", [[1.0]])
+ sys = PolytopicStateSpace("sys", A=A, B=B, C=C)
+
+ m = Model()
+ m.add_block(src_w)
+ m.add_block(src_u)
+ m.add_block(sys)
+ m.connect("w", "out", "sys", "w")
+ m.connect("u", "out", "sys", "u")
+
+ sim_cfg = SimulationConfig(0.1, 0.1, logging=["sys.outputs.y"])
+ sim = Simulator(m, sim_cfg)
+
+ with pytest.raises(ValueError) as err:
+ sim.run()
+
+ assert "must be a column vector" in str(err.value)
+
+
+def test_poly_invalid_matrix_dimensions():
+ C = [[1.0]]
+
+ # A has nx=1 but 3 columns -> not (nx, r*nx)
+ with pytest.raises(ValueError):
+ PolytopicStateSpace("sys1", A=[[1.0, 2.0, 3.0]], B=[[1.0, 1.0]], C=C)
+
+ # A => nx=1, r=2, so B must have 2*nu columns
+ with pytest.raises(ValueError):
+ PolytopicStateSpace("sys2", A=[[1.0, 2.0]], B=[[1.0, 2.0, 3.0]], C=C)
+
+ # C has wrong number of columns
+ with pytest.raises(ValueError):
+ PolytopicStateSpace("sys3", A=[[1.0, 2.0]], B=[[1.0, 1.0]], C=[[1.0, 2.0]])
+
+
+if __name__ == "__main__":
+ pytest.main([__file__, "-v"])