From b4fc18fc36d9a89c08746c95eb47780e92ef2a11 Mon Sep 17 00:00:00 2001 From: Pierre Ballif Date: Sun, 22 Feb 2026 10:31:21 +0100 Subject: [PATCH 1/3] test noise determinism --- tests/noise/test_noise.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/noise/test_noise.py diff --git a/tests/noise/test_noise.py b/tests/noise/test_noise.py new file mode 100644 index 0000000..2d2fd6f --- /dev/null +++ b/tests/noise/test_noise.py @@ -0,0 +1,35 @@ +from typing import Callable +from ampyc.noise import GaussianNoise, NoiseBase, PolytopeNoise, PolytopeVerticesNoise +from ampyc.utils import Polytope +import pytest +import numpy as np + + +def get_unit_cube_polytope(noise_dim): + A = np.zeros((2 * noise_dim, noise_dim)) + b = np.zeros((2 * noise_dim,)) + for i in range(noise_dim): + A[2 * i, i] = 1 + b[2 * i] = 1 + A[2 * i + 1, i] = -1 + b[2 * i + 1] = 1 + return Polytope(A, b) + + +@pytest.mark.parametrize( + "noise_factory", + [ + lambda noise_dim: GaussianNoise( + mean=np.zeros((noise_dim,)), covariance=np.eye(noise_dim) + ), + lambda noise_dim: PolytopeNoise(get_unit_cube_polytope(noise_dim)), + lambda noise_dim: PolytopeVerticesNoise(get_unit_cube_polytope(noise_dim)), + ], +) +def test_noise_is_deterministic_with_seed(noise_factory: Callable[[int], NoiseBase]): + noise_dim = 6 + noise1 = noise_factory(noise_dim) + noise2 = noise_factory(noise_dim) + noise1.seed(5) + noise2.seed(5) + assert np.all(noise1.generate() == noise2.generate()) From ba57ea93fd2b3ed707275a8e572fb5a439fef159 Mon Sep 17 00:00:00 2001 From: Pierre Ballif Date: Sun, 22 Feb 2026 10:55:29 +0100 Subject: [PATCH 2/3] fix polytope noise determinism --- ampyc/noise.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ampyc/noise.py b/ampyc/noise.py index 1ef5e72..b91b538 100644 --- a/ampyc/noise.py +++ b/ampyc/noise.py @@ -98,9 +98,16 @@ class PolytopeVerticesNoise(NoiseBase): def __init__(self, W: Polytope, seed: int | None = None) -> None: assert seed is None or seed >= 0 + self.W = W self.V = W.V self.rng = np.random.default_rng(seed) + def seed(self, seed: int | None = None): + super().seed(seed) + np.random.seed(seed) + self.W.vertices = None # trigger new Vrep computation + self.V = self.W.Vrep() + def _generate(self, N: int | None = None) -> np.ndarray: if N is None: idx = self.rng.choice(self.V.shape[0]) @@ -116,9 +123,16 @@ class PolytopeNoise(NoiseBase): def __init__(self, W: Polytope, seed: int | None = None) -> None: assert seed is None or seed >= 0 + self.W = W self.V = W.V self.rng = np.random.default_rng(seed) + def seed(self, seed: int | None = None): + super().seed(seed) + np.random.seed(seed) + self.W.vertices = None # trigger new Vrep computation + self.V = self.W.Vrep() + def _generate(self, N: int | None = None) -> np.ndarray: """Based on implementation for randomPoint() in MPT""" if N is None: From 6469b4add42e21f2995ed37f8effb768e2308d1c Mon Sep 17 00:00:00 2001 From: Pierre Ballif Date: Mon, 23 Feb 2026 11:10:42 +0100 Subject: [PATCH 3/3] do not modify W.V in PolytopeNoise --- ampyc/noise.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/ampyc/noise.py b/ampyc/noise.py index b91b538..9d77f26 100644 --- a/ampyc/noise.py +++ b/ampyc/noise.py @@ -9,6 +9,7 @@ from abc import ABC, abstractmethod import numpy as np +from polytope import extreme from ampyc.utils import Polytope, qhull @@ -98,16 +99,11 @@ class PolytopeVerticesNoise(NoiseBase): def __init__(self, W: Polytope, seed: int | None = None) -> None: assert seed is None or seed >= 0 - self.W = W - self.V = W.V + np.random.seed(0) # get deterministic behavior of `extreme` + # create new polytope to force re-computation + self.V = extreme(Polytope(W.A, W.b)) self.rng = np.random.default_rng(seed) - def seed(self, seed: int | None = None): - super().seed(seed) - np.random.seed(seed) - self.W.vertices = None # trigger new Vrep computation - self.V = self.W.Vrep() - def _generate(self, N: int | None = None) -> np.ndarray: if N is None: idx = self.rng.choice(self.V.shape[0]) @@ -123,16 +119,11 @@ class PolytopeNoise(NoiseBase): def __init__(self, W: Polytope, seed: int | None = None) -> None: assert seed is None or seed >= 0 - self.W = W - self.V = W.V + np.random.seed(0) # get deterministic behavior of `extreme` + # create new polytope to force re-computation + self.V = extreme(Polytope(W.A, W.b)) self.rng = np.random.default_rng(seed) - def seed(self, seed: int | None = None): - super().seed(seed) - np.random.seed(seed) - self.W.vertices = None # trigger new Vrep computation - self.V = self.W.Vrep() - def _generate(self, N: int | None = None) -> np.ndarray: """Based on implementation for randomPoint() in MPT""" if N is None: