From 633eca19b8bc3e61c79c85e03dce812fffb0afaa Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Tue, 30 Aug 2022 10:11:32 +0200 Subject: [PATCH 01/18] [random] implement new approach to manage random state --- src/pymor/algorithms/eigs.py | 9 +- src/pymor/algorithms/hapod.py | 7 +- src/pymor/algorithms/lradi.py | 12 +-- src/pymor/algorithms/lrradi.py | 12 +-- src/pymor/algorithms/samdp.py | 10 +- src/pymor/basic.py | 2 +- src/pymor/bindings/dunegdt.py | 4 +- src/pymor/bindings/fenics.py | 4 +- src/pymor/parallel/ipython.py | 15 ++- src/pymor/parallel/mpi.py | 16 +-- src/pymor/parameters/base.py | 15 +-- src/pymor/reductors/h2.py | 4 +- src/pymor/reductors/neural_network.py | 3 +- src/pymor/tools/random.py | 126 +++++++++++++++------- src/pymor/tools/timing.py | 3 +- src/pymor/vectorarrays/interface.py | 36 ++----- src/pymor/vectorarrays/list.py | 19 ++-- src/pymor/vectorarrays/numpy.py | 7 +- src/pymordemos/dmd_identification.py | 4 +- src/pymordemos/output_error_estimation.py | 8 +- src/pymordemos/parabolic_mor.py | 2 +- src/pymordemos/thermalblock.py | 2 +- 22 files changed, 166 insertions(+), 154 deletions(-) diff --git a/src/pymor/algorithms/eigs.py b/src/pymor/algorithms/eigs.py index 25609d88db..a09dd1185a 100644 --- a/src/pymor/algorithms/eigs.py +++ b/src/pymor/algorithms/eigs.py @@ -9,10 +9,11 @@ from pymor.core.logger import getLogger from pymor.operators.constructions import IdentityOperator, InverseOperator from pymor.operators.interface import Operator +from pymor.tools.random import set_rng def eigs(A, E=None, k=3, sigma=None, which='LM', b=None, l=None, maxiter=1000, tol=1e-13, - imag_tol=1e-12, complex_pair_tol=1e-12, complex_evp=False, left_evp=False, seed=0): + imag_tol=1e-12, complex_pair_tol=1e-12, complex_evp=False, left_evp=False): """Approximate a few eigenvalues of a linear |Operator|. Computes `k` eigenvalues `w` with corresponding eigenvectors `v` which solve @@ -67,9 +68,6 @@ def eigs(A, E=None, k=3, sigma=None, which='LM', b=None, l=None, maxiter=1000, t are real setting this argument to `False` will increase stability and performance. left_evp If set to `True` compute left eigenvectors else compute right eigenvectors. - seed - Random seed which is used for computing the initial vector for the Arnoldi - iteration. Returns ------- @@ -93,7 +91,8 @@ def eigs(A, E=None, k=3, sigma=None, which='LM', b=None, l=None, maxiter=1000, t assert E.source == A.source if b is None: - b = A.source.random(seed=seed) + with set_rng(0): + b = A.source.random() else: assert b in A.source diff --git a/src/pymor/algorithms/hapod.py b/src/pymor/algorithms/hapod.py index a6500c610d..3eb7e0a752 100644 --- a/src/pymor/algorithms/hapod.py +++ b/src/pymor/algorithms/hapod.py @@ -10,6 +10,7 @@ from pymor.algorithms.pod import pod from pymor.core.logger import getLogger +from pymor.tools.random import spawn_rng class Node: @@ -180,7 +181,7 @@ async def hapod_step(node): if node.children: modes, svals, snap_counts = zip( - *await asyncio.gather(*(hapod_step(c) for c in node.children)) + *await asyncio.gather(*(spawn_rng(hapod_step(c)) for c in node.children)) ) for m, sv in zip(modes, svals): m.scal(sv) @@ -221,7 +222,7 @@ def main(): nonlocal result result = asyncio.run(hapod_step(tree)) result = None - hapod_thread = Thread(target=main) + hapod_thread = Thread(target=spawn_rng(main)) hapod_thread.start() hapod_thread.join() return result @@ -433,7 +434,7 @@ def __init__(self, executor, max_workers=None): def submit(self, f, *args): future = asyncio.get_event_loop().create_future() - self.queue.append((future, f, args)) + self.queue.append((future, spawn_rng(f), args)) asyncio.get_event_loop().create_task(self.run_task()) return future diff --git a/src/pymor/algorithms/lradi.py b/src/pymor/algorithms/lradi.py index 878f55b0d3..e24c8f375d 100644 --- a/src/pymor/algorithms/lradi.py +++ b/src/pymor/algorithms/lradi.py @@ -12,18 +12,17 @@ from pymor.core.defaults import defaults from pymor.core.logger import getLogger from pymor.operators.constructions import IdentityOperator, InverseOperator -from pymor.tools.random import get_random_state +from pymor.tools.random import set_rng from pymor.vectorarrays.constructions import cat_arrays @defaults('lradi_tol', 'lradi_maxiter', 'lradi_shifts', 'projection_shifts_init_maxiter', - 'projection_shifts_init_seed', 'projection_shifts_subspace_columns', + 'projection_shifts_subspace_columns', 'wachspress_large_ritz_num', 'wachspress_small_ritz_num', 'wachspress_tol') def lyap_lrcf_solver_options(lradi_tol=1e-10, lradi_maxiter=500, lradi_shifts='projection_shifts', projection_shifts_init_maxiter=20, - projection_shifts_init_seed=None, projection_shifts_subspace_columns=6, wachspress_large_ritz_num=50, wachspress_small_ritz_num=25, @@ -40,8 +39,6 @@ def lyap_lrcf_solver_options(lradi_tol=1e-10, See :func:`solve_lyap_lrcf`. projection_shifts_init_maxiter See :func:`projection_shifts_init`. - projection_shifts_init_seed - See :func:`projection_shifts_init`. projection_shifts_subspace_columns See :func:`projection_shifts`. wachspress_large_ritz_num @@ -62,7 +59,6 @@ def lyap_lrcf_solver_options(lradi_tol=1e-10, 'shift_options': {'projection_shifts': {'type': 'projection_shifts', 'init_maxiter': projection_shifts_init_maxiter, - 'init_seed': projection_shifts_init_seed, 'subspace_columns': projection_shifts_subspace_columns}, 'wachspress_shifts': {'type': 'wachspress_shifts', 'large_ritz_num': wachspress_large_ritz_num, @@ -194,14 +190,14 @@ def projection_shifts_init(A, E, B, shift_options): shifts A |NumPy array| containing a set of stable shift parameters. """ - random_state = get_random_state(seed=shift_options['init_seed']) for i in range(shift_options['init_maxiter']): Q = gram_schmidt(B, atol=0, rtol=0) shifts = spla.eigvals(A.apply2(Q, Q), E.apply2(Q, Q)) shifts = shifts[shifts.real < 0] if shifts.size == 0: # use random subspace instead of span{B} (with same dimensions) - B = B.random(len(B), distribution='normal', random_state=random_state) + with set_rng(0): + B = B.random(len(B), distribution='normal') else: return shifts raise RuntimeError('Could not generate initial shifts for low-rank ADI iteration.') diff --git a/src/pymor/algorithms/lrradi.py b/src/pymor/algorithms/lrradi.py index 26d62c65d1..b05eb46a8f 100644 --- a/src/pymor/algorithms/lrradi.py +++ b/src/pymor/algorithms/lrradi.py @@ -12,16 +12,15 @@ from pymor.core.logger import getLogger from pymor.operators.constructions import IdentityOperator from pymor.algorithms.gram_schmidt import gram_schmidt -from pymor.tools.random import get_random_state +from pymor.tools.random import set_rng @defaults('lrradi_tol', 'lrradi_maxiter', 'lrradi_shifts', 'hamiltonian_shifts_init_maxiter', - 'hamiltonian_shifts_init_seed', 'hamiltonian_shifts_subspace_columns') + 'hamiltonian_shifts_subspace_columns') def ricc_lrcf_solver_options(lrradi_tol=1e-10, lrradi_maxiter=500, lrradi_shifts='hamiltonian_shifts', hamiltonian_shifts_init_maxiter=20, - hamiltonian_shifts_init_seed=None, hamiltonian_shifts_subspace_columns=6): """Returns available Riccati equation solvers with default solver options. @@ -35,8 +34,6 @@ def ricc_lrcf_solver_options(lrradi_tol=1e-10, See :func:`solve_ricc_lrcf`. hamiltonian_shifts_init_maxiter See :func:`hamiltonian_shifts_init`. - hamiltonian_shifts_init_seed - See :func:`hamiltonian_shifts_init`. hamiltonian_shifts_subspace_columns See :func:`hamiltonian_shifts`. @@ -51,7 +48,6 @@ def ricc_lrcf_solver_options(lrradi_tol=1e-10, 'shift_options': {'hamiltonian_shifts': {'type': 'hamiltonian_shifts', 'init_maxiter': hamiltonian_shifts_init_maxiter, - 'init_seed': hamiltonian_shifts_init_seed, 'subspace_columns': hamiltonian_shifts_subspace_columns}}}} @@ -229,7 +225,6 @@ def hamiltonian_shifts_init(A, E, B, C, shift_options): shifts A |NumPy array| containing a set of stable shift parameters. """ - random_state = get_random_state(seed=shift_options['init_seed']) for _ in range(shift_options['init_maxiter']): Q = gram_schmidt(C, atol=0, rtol=0) Ap = A.apply2(Q, Q) @@ -249,7 +244,8 @@ def hamiltonian_shifts_init(A, E, B, C, shift_options): eigpairs = list(filter(lambda e: e[0].real < 0, eigpairs)) if len(eigpairs) == 0: # use random subspace instead of span{C} (with same dimensions) - C = C.random(len(C), distribution='normal', random_state=random_state) + with set_rng(0): + C = C.random(len(C), distribution='normal') continue # find shift with most impact on convergence maxval = -1 diff --git a/src/pymor/algorithms/samdp.py b/src/pymor/algorithms/samdp.py index d07d256bf2..90465f2840 100644 --- a/src/pymor/algorithms/samdp.py +++ b/src/pymor/algorithms/samdp.py @@ -70,8 +70,6 @@ def samdp(A, E, B, C, nwanted, init_shifts=None, which='NR', tol=1e-10, imagtol= Maximum dimension of search space before performing a restart. rqi_maxiter Maximum number of iterations for the two-sided Rayleigh quotient iteration. - seed - Random seed which is used for computing the initial shift and random restarts. Returns ------- @@ -106,7 +104,7 @@ def samdp(A, E, B, C, nwanted, init_shifts=None, which='NR', tol=1e-10, imagtol= k = 0 nrestart = 0 nr_converged = 0 - np.random.seed(seed) + rng = np.random.default_rng(0) X = A.source.empty() Q = A.source.empty() @@ -121,7 +119,7 @@ def samdp(A, E, B, C, nwanted, init_shifts=None, which='NR', tol=1e-10, imagtol= poles = np.empty(0) if init_shifts is None: - st = np.random.uniform() * 10.j + st = rng().uniform() * 10.j shift_nr = 0 nr_shifts = 0 else: @@ -164,7 +162,7 @@ def samdp(A, E, B, C, nwanted, init_shifts=None, which='NR', tol=1e-10, imagtol= SH, UR, URt, res = _select_max_eig(H, G, X, V, B_defl, C_defl, which) if np.all(res < np.finfo(float).eps): - st = np.random.uniform() * 10.j + st = rng().uniform() * 10.j found = False else: found = True @@ -287,7 +285,7 @@ def samdp(A, E, B, C, nwanted, init_shifts=None, which='NR', tol=1e-10, imagtol= if found: st = SH[0, 0] else: - st = np.random.uniform() * 10.j + st = rng().uniform() * 10.j if shift_nr < nr_shifts: st = shifts[shift_nr] diff --git a/src/pymor/basic.py b/src/pymor/basic.py index ce05dd7625..f73356116e 100644 --- a/src/pymor/basic.py +++ b/src/pymor/basic.py @@ -76,7 +76,7 @@ SOBTReductor) from pymor.reductors.sor_irka import SORIRKAReductor -from pymor.tools.random import default_random_state +from pymor.tools.random import get_rng, set_rng from pymor.vectorarrays.constructions import cat_arrays from pymor.vectorarrays.list import ListVectorSpace diff --git a/src/pymor/bindings/dunegdt.py b/src/pymor/bindings/dunegdt.py index 2a53a5370f..e502dc5232 100644 --- a/src/pymor/bindings/dunegdt.py +++ b/src/pymor/bindings/dunegdt.py @@ -125,8 +125,8 @@ def real_zero_vector(self): def real_full_vector(self, value): return DuneXTVector(self.dune_vector_type(self.dim, value)) - def real_random_vector(self, distribution, random_state, **kwargs): - values = _create_random_values(self.dim, distribution, random_state, **kwargs) + def real_random_vector(self, distribution, **kwargs): + values = _create_random_values(self.dim, distribution, **kwargs) return self.real_vector_from_numpy(values) def real_vector_from_numpy(self, data, ensure_copy=False): diff --git a/src/pymor/bindings/fenics.py b/src/pymor/bindings/fenics.py index 05e7f3b41f..7135a24502 100644 --- a/src/pymor/bindings/fenics.py +++ b/src/pymor/bindings/fenics.py @@ -157,9 +157,9 @@ def real_full_vector(self, value): impl += value return FenicsVector(impl) - def real_random_vector(self, distribution, random_state, **kwargs): + def real_random_vector(self, distribution, **kwargs): impl = df.Function(self.V).vector() - values = _create_random_values(impl.local_size(), distribution, random_state, **kwargs) + values = _create_random_values(impl.local_size(), distribution, **kwargs) impl[:] = np.ascontiguousarray(values) return FenicsVector(impl) diff --git a/src/pymor/parallel/ipython.py b/src/pymor/parallel/ipython.py index 765615f2a8..8531f22d7a 100644 --- a/src/pymor/parallel/ipython.py +++ b/src/pymor/parallel/ipython.py @@ -147,7 +147,8 @@ def __init__(self, num_engines=None, **kwargs): else: self.view = self.client[:] self.logger.info(f'Connected to {len(self.view)} engines') - self.view.map_sync(_setup_worker, range(len(self.view))) + from pymor.tools.random import get_seed_seq + self.view.map_sync(_setup_worker, get_seed_seq().spawn(len(self.view))) self._remote_objects_created = Counter() if defaults.defaults_changes() > 0: @@ -202,15 +203,13 @@ def _worker_call_function(function, loop, args, kwargs): return function(*args, **kwargs) -def _setup_worker(worker_id): +def _setup_worker(seed_seq): global _remote_objects _remote_objects = {} - # ensure that each worker starts with a different RandomState - from pymor.tools import random - import numpy as np - state = random.default_random_state() - new_state = np.random.RandomState(state.randint(0, 2**16) + worker_id) - random._default_random_state = new_state + # ensure that each worker starts with a different yet deterministically + # initialized rng + from pymor.tools.random import init_rng + init_rng(seed_seq) def _push_object(remote_id, obj): diff --git a/src/pymor/parallel/mpi.py b/src/pymor/parallel/mpi.py index fb6ff7cded..40861eb5d0 100644 --- a/src/pymor/parallel/mpi.py +++ b/src/pymor/parallel/mpi.py @@ -8,6 +8,7 @@ from pymor.parallel.basic import WorkerPoolBase from pymor.tools import mpi +from pymor.tools.random import get_seed_seq class MPIPool(WorkerPoolBase): @@ -18,6 +19,7 @@ def __init__(self): self.logger.info(f'Connected to {mpi.size} ranks') self._payload = mpi.call(mpi.function_call_manage, _setup_worker) self._apply(os.chdir, os.getcwd()) + self._map(_setup_rng, [[s] for s in get_seed_seq().spawn(mpi.size)]) def __del__(self): mpi.call(mpi.remove_object, self._payload) @@ -90,15 +92,15 @@ def _worker_map_function(payload, function, **kwargs): def _setup_worker(): - # ensure that each worker starts with a different RandomState - if not mpi.rank0: - from pymor.tools import random - import numpy as np - state = random.default_random_state() - new_state = np.random.RandomState(state.randint(0, 2**16) + mpi.rank) - random._default_random_state = new_state return [None] +def _setup_rng(seed_seq): + # ensure that each worker starts with a different yet deterministically + # initialized rng + from pymor.tools.random import init_rng + init_rng(seed_seq) + + def _push_object(obj): return obj diff --git a/src/pymor/parameters/base.py b/src/pymor/parameters/base.py index 4dab1dd256..776629eb0a 100644 --- a/src/pymor/parameters/base.py +++ b/src/pymor/parameters/base.py @@ -21,7 +21,7 @@ from pymor.tools.floatcmp import float_cmp_all from pymor.tools.frozendict import FrozenDict, SortedFrozenDict from pymor.tools.pprint import format_array -from pymor.tools.random import get_random_state +from pymor.tools.random import get_rng class Parameters(SortedFrozenDict): @@ -554,7 +554,7 @@ def sample_uniformly(self, counts): return [Mu((k, np.array(v)) for k, v in zip(self.parameters, i)) for i in product(*iters)] - def sample_randomly(self, count=None, random_state=None, seed=None): + def sample_randomly(self, count=None): """Randomly sample |parameter values| from the space. Parameters @@ -563,21 +563,12 @@ def sample_randomly(self, count=None, random_state=None, seed=None): If `None`, a single dict `mu` of |parameter values| is returned. Otherwise, the number of random samples to generate and return as a list of |parameter values| dicts. - random_state - :class:`~numpy.random.RandomState` to use for sampling. - If `None`, a new random state is generated using `seed` - as random seed, or the :func:`default ` - random state is used. - seed - If not `None`, a new random state with this seed is used. Returns ------- The sampled |parameter values|. """ - assert not random_state or seed is None - random_state = get_random_state(random_state, seed) - get_param = lambda: Mu(((k, random_state.uniform(self.ranges[k][0], self.ranges[k][1], size)) + get_param = lambda: Mu(((k, get_rng().uniform(self.ranges[k][0], self.ranges[k][1], size)) for k, size in self.parameters.items())) if count is None: return get_param() diff --git a/src/pymor/reductors/h2.py b/src/pymor/reductors/h2.py index 3f7acf5538..1fd1e2a644 100644 --- a/src/pymor/reductors/h2.py +++ b/src/pymor/reductors/h2.py @@ -94,10 +94,10 @@ def _order_to_sigma_b_c(self, r): sigma = np.logspace(-1, 1, r) b = (np.ones((r, 1)) if self.fom.dim_input == 1 - else np.random.RandomState(0).normal(size=(r, self.fom.dim_input))) + else np.random.default_rng(0).normal(size=(r, self.fom.dim_input))) c = (np.ones((r, 1)) if self.fom.dim_output == 1 - else np.random.RandomState(0).normal(size=(r, self.fom.dim_output))) + else np.random.default_rng(0).normal(size=(r, self.fom.dim_output))) return sigma, b, c @staticmethod diff --git a/src/pymor/reductors/neural_network.py b/src/pymor/reductors/neural_network.py index 4fc3abb6a3..a7619240c1 100644 --- a/src/pymor/reductors/neural_network.py +++ b/src/pymor/reductors/neural_network.py @@ -25,6 +25,7 @@ NeuralNetworkStatefreeOutputModel, NeuralNetworkInstationaryModel, NeuralNetworkInstationaryStatefreeOutputModel) +from pymor.tools.random import get_rng class NeuralNetworkReductor(BasicObject): @@ -196,7 +197,7 @@ def reduce(self, hidden_layers='[(N+P)*3, (N+P)*3]', activation_function=torch.t else: number_validation_snapshots = int(len(self.training_data)*self.validation_ratio) # randomly shuffle training data before splitting into two sets - np.random.shuffle(self.training_data) + get_rng().shuffle(self.training_data) # split training data into validation and training set self.validation_data = self.training_data[0:number_validation_snapshots] self.training_data = self.training_data[number_validation_snapshots+1:] diff --git a/src/pymor/tools/random.py b/src/pymor/tools/random.py index a07c02dc0e..272202187e 100644 --- a/src/pymor/tools/random.py +++ b/src/pymor/tools/random.py @@ -2,54 +2,104 @@ # Copyright pyMOR developers and contributors. All rights reserved. # License: BSD 2-Clause License (https://opensource.org/licenses/BSD-2-Clause) +from contextvars import ContextVar +import inspect + from pymor.core.defaults import defaults import numpy as np -def get_random_state(random_state=None, seed=None): - """Returns a |NumPy| :class:`~numpy.random.RandomState`. - - Parameters - ---------- - random_state - If specified, this state is returned. - seed - If specified, the seed to initialize a new random state. - - Returns - ------- - Either the provided, a newly created or the default `RandomState` - object. - """ - assert random_state is None or seed is None - if random_state is not None: - return random_state - elif seed is not None: - return np.random.RandomState(seed) - else: - return default_random_state() +@defaults('seed_seq') +def init_rng(seed_seq=42): + if not isinstance(seed_seq, np.random.SeedSequence): + seed_seq = np.random.SeedSequence(seed_seq) + # Store a new rng together with seed_seq, as the latter cannot be recovered from the + # rng via a public interface (see https://github.com/numpy/numpy/issues/15322). + # The first field is a flag to indicate whether the current _rng_state has been consumed + # via get_rng. This is a safeguard to detect calls to get_rng in concurrent code + # paths. + _rng_state.set([False, np.random.default_rng(seed_seq), seed_seq]) + + +def get_rng(): + rng_state = _get_rng_state() + rng_state[0] = True + _rng_state.set([False] + rng_state[1:]) + return rng_state[1] + + +class set_rng: + + def __init__(self, seed_seq): + self.old_state = _rng_state.get(None) + self.seed_seq = seed_seq + self.set() + + def set(self): + self._is_set = True + init_rng(self.seed_seq) + + def reset(self): + self._is_set = False + _rng_state.set(self.old_state) + + def __enter__(self): + if not self._is_set: + self.set() + + def __exit__(self, exc_type, exc_value, exc_tb): + self.reset() -@defaults('seed') -def default_random_state(seed=42): - """Returns the default |NumPy| :class:`~numpy.random.RandomState`. +def get_seed_seq(): + return _get_rng_state()[2] + + +def spawn_rng(f): + seed_seq = get_seed_seq().spawn(1)[0] + + if inspect.iscoroutine(f): + + async def spawn_rng_wrapper(): + with set_rng(seed_seq): + return await f + + return spawn_rng_wrapper() + + elif inspect.isfunction(f): + + return _SpawnRngWrapper(f, seed_seq) # use a class to obtain something picklable + + else: + raise TypeError + - Parameters - ---------- - seed - Seed to use for initializing the random state. +class _SpawnRngWrapper: + def __init__(self, f, seed_seq): + self.f, self.seed_seq = f, seed_seq - Returns - ------- - The default `RandomState` object. - """ - global _default_random_state + def __call__(self, *args, **kwargs): + with set_rng(self.seed_seq): + return self.f(*args, **kwargs) - if _default_random_state is None: - _default_random_state = np.random.RandomState(seed) - return _default_random_state +def _get_rng_state(): + try: + rng_state = _rng_state.get() + except LookupError: + import warnings + warnings.warn( + 'get_rng called but _rng_state not initialized. (Call spawn_rng when creating new thread.) ' + 'Initializing a new RNG from the default seed. This may lead to correlated data.') + init_rng() + rng_state = _rng_state.get() + if rng_state[0]: + import warnings + warnings.warn('You are using the same RNG in concurrent code paths.\n' + 'This may lead to truly random, irreproducible behavior') + return rng_state -_default_random_state = None +_rng_state = ContextVar('_rng_state') +init_rng() diff --git a/src/pymor/tools/timing.py b/src/pymor/tools/timing.py index 7a615a2e14..7592a39472 100644 --- a/src/pymor/tools/timing.py +++ b/src/pymor/tools/timing.py @@ -68,6 +68,7 @@ def new_func(*args, **kwargs): def busywait(amount): + from pymor.tools.random import get_rng arr = np.arange(1000) for _ in range(amount): - np.random.shuffle(arr) + get_rng().shuffle(arr) diff --git a/src/pymor/vectorarrays/interface.py b/src/pymor/vectorarrays/interface.py index 3bcaac83e3..10276c03ab 100644 --- a/src/pymor/vectorarrays/interface.py +++ b/src/pymor/vectorarrays/interface.py @@ -8,7 +8,7 @@ from pymor.core.base import BasicObject, ImmutableObject, abstractmethod from pymor.core.defaults import defaults -from pymor.tools.random import get_random_state +from pymor.tools.random import get_rng class VectorArray(BasicObject): @@ -146,11 +146,11 @@ def full(self, value, count=1, reserve=0): """ return self.space.full(value, count, reserve=reserve) - def random(self, count=1, distribution='uniform', random_state=None, seed=None, reserve=0, **kwargs): + def random(self, count=1, distribution='uniform', reserve=0, **kwargs): """Create a |VectorArray| of vectors with random entries. This is a shorthand for - `self.space.random(count, distribution, radom_state, seed, **kwargs)`. + `self.space.random(count, distribution, **kwargs)`. Supported random distributions:: @@ -176,17 +176,10 @@ def random(self, count=1, distribution='uniform', random_state=None, seed=None, Mean for `'normal'` distribution (defaults to `0`). scale Standard deviation for `'normal'` distribution (defaults to `1`). - random_state - :class:`~numpy.random.RandomState` to use for sampling. - If `None`, a new random state is generated using `seed` - as random seed, or the :func:`default ` - random state is used. - seed - If not `None`, a new radom state with this seed is used. reserve Hint for the backend to which length the array will grow. """ - return self.space.random(count, distribution, random_state, seed, **kwargs) + return self.space.random(count, distribution, **kwargs) def empty(self, reserve=0): """Create an empty |VectorArray| of the same |VectorSpace|. @@ -903,7 +896,7 @@ def full(self, value, count=1, reserve=0): """ return self.from_numpy(np.full((count, self.dim), value)) - def random(self, count=1, distribution='uniform', random_state=None, seed=None, reserve=0, **kwargs): + def random(self, count=1, distribution='uniform', reserve=0, **kwargs): """Create a |VectorArray| of vectors with random entries. Supported random distributions:: @@ -930,19 +923,10 @@ def random(self, count=1, distribution='uniform', random_state=None, seed=None, Mean for `'normal'` distribution (defaults to `0`). scale Standard deviation for `'normal'` distribution (defaults to `1`). - random_state - :class:`~numpy.random.RandomState` to use for sampling. - If `None`, a new random state is generated using `seed` - as random seed, or the :func:`default ` - random state is used. - seed - If not `None`, a new random state with this seed is used. reserve Hint for the backend to which length the array will grow. """ - assert random_state is None or seed is None - random_state = get_random_state(random_state, seed) - values = _create_random_values((count, self.dim), distribution, random_state, **kwargs) + values = _create_random_values((count, self.dim), distribution, **kwargs) return self.from_numpy(values) def empty(self, reserve=0): @@ -995,10 +979,12 @@ def __hash__(self): return hash(self.id) -def _create_random_values(shape, distribution, random_state, **kwargs): +def _create_random_values(shape, distribution, **kwargs): if distribution not in ('uniform', 'normal'): raise NotImplementedError + rng = get_rng() + if distribution == 'uniform': if not kwargs.keys() <= {'low', 'high'}: raise ValueError @@ -1006,13 +992,13 @@ def _create_random_values(shape, distribution, random_state, **kwargs): high = kwargs.get('high', 1.) if high <= low: raise ValueError - return random_state.uniform(low, high, shape) + return rng.uniform(low, high, shape) elif distribution == 'normal': if not kwargs.keys() <= {'loc', 'scale'}: raise ValueError loc = kwargs.get('loc', 0.) scale = kwargs.get('scale', 1.) - return random_state.normal(loc, scale, shape) + return rng.normal(loc, scale, shape) else: assert False diff --git a/src/pymor/vectorarrays/list.py b/src/pymor/vectorarrays/list.py index a64d301aaa..6856fd1c69 100644 --- a/src/pymor/vectorarrays/list.py +++ b/src/pymor/vectorarrays/list.py @@ -5,7 +5,6 @@ import numpy as np from pymor.core.base import BasicObject, abstractmethod, abstractclassmethod, classinstancemethod -from pymor.tools.random import get_random_state from pymor.vectorarrays.interface import VectorArray, VectorArrayImpl, VectorSpace, _create_random_values @@ -536,8 +535,8 @@ def ones_vector(self): def full_vector(self, value): return self.vector_from_numpy(np.full(self.dim, value)) - def random_vector(self, distribution, random_state, **kwargs): - values = _create_random_values(self.dim, distribution, random_state, **kwargs) + def random_vector(self, distribution, **kwargs): + values = _create_random_values(self.dim, distribution, **kwargs) return self.vector_from_numpy(values) @abstractmethod @@ -567,13 +566,11 @@ def full(self, value, count=1, reserve=0): assert count >= 0 and reserve >= 0 return ListVectorArray(self, ListVectorArrayImpl([self.full_vector(value) for _ in range(count)], self)) - def random(self, count=1, distribution='uniform', random_state=None, seed=None, reserve=0, **kwargs): + def random(self, count=1, distribution='uniform', reserve=0, **kwargs): assert count >= 0 and reserve >= 0 - assert random_state is None or seed is None - random_state = get_random_state(random_state, seed) return ListVectorArray( self, - ListVectorArrayImpl([self.random_vector(distribution=distribution, random_state=random_state, **kwargs) + ListVectorArrayImpl([self.random_vector(distribution=distribution, **kwargs) for _ in range(count)], self) ) @@ -621,12 +618,12 @@ def real_full_vector(self, value): def full_vector(self, value): return self.vector_type(self.real_full_vector(value), None) - def real_random_vector(self, distribution, random_state, **kwargs): - values = _create_random_values(self.dim, distribution, random_state, **kwargs) + def real_random_vector(self, distribution, **kwargs): + values = _create_random_values(self.dim, distribution, **kwargs) return self.real_vector_from_numpy(values) - def random_vector(self, distribution, random_state, **kwargs): - return self.vector_type(self.real_random_vector(distribution, random_state, **kwargs), None) + def random_vector(self, distribution, **kwargs): + return self.vector_type(self.real_random_vector(distribution, **kwargs), None) @abstractmethod def real_make_vector(self, obj): diff --git a/src/pymor/vectorarrays/numpy.py b/src/pymor/vectorarrays/numpy.py index 322181156f..e48d9f1893 100644 --- a/src/pymor/vectorarrays/numpy.py +++ b/src/pymor/vectorarrays/numpy.py @@ -8,7 +8,6 @@ from scipy.sparse import issparse from pymor.core.base import classinstancemethod -from pymor.tools.random import get_random_state from pymor.vectorarrays.interface import VectorArray, VectorArrayImpl, VectorSpace, _create_random_values @@ -237,13 +236,11 @@ def full(self, value, count=1, reserve=0): assert reserve >= 0 return NumpyVectorArray(self, NumpyVectorArrayImpl(np.full((max(count, reserve), self.dim), value), count)) - def random(self, count=1, distribution='uniform', random_state=None, seed=None, reserve=0, **kwargs): + def random(self, count=1, distribution='uniform', seed=None, reserve=0, **kwargs): assert count >= 0 assert reserve >= 0 - assert random_state is None or seed is None - random_state = get_random_state(random_state, seed) va = self.zeros(count, reserve) - va.impl._array[:count] = _create_random_values((count, self.dim), distribution, random_state, **kwargs) + va.impl._array[:count] = _create_random_values((count, self.dim), distribution, **kwargs) return va @classinstancemethod diff --git a/src/pymordemos/dmd_identification.py b/src/pymordemos/dmd_identification.py index 88ca3177ce..adf20e7ef0 100755 --- a/src/pymordemos/dmd_identification.py +++ b/src/pymordemos/dmd_identification.py @@ -16,9 +16,7 @@ def main( M: int = Option(10, help='Number of data pairs.'), seed: int = Option(42, help='Random seed.') ): - np.random.seed(seed) - - A = np.random.rand(n, n) + A = get_rng().random((n, n)) A = A / np.linalg.norm(A) print(f'A: {A}') X = np.zeros((M + 1, n)) diff --git a/src/pymordemos/output_error_estimation.py b/src/pymordemos/output_error_estimation.py index fb5bbf652c..9b53cb6625 100755 --- a/src/pymordemos/output_error_estimation.py +++ b/src/pymordemos/output_error_estimation.py @@ -37,9 +37,9 @@ def main( # an output which is actually a lincomb operator fom = create_fom(grid_intervals, vector_valued_output=True) dim_source = fom.output_functional.source.dim - np.random.seed(1) - random_matrix_1 = np.random.rand(2, dim_source) - random_matrix_2 = np.random.rand(2, dim_source) + rng = get_rng() + random_matrix_1 = rng.random((2, dim_source)) + random_matrix_2 = rng.random((2, dim_source)) op1 = NumpyMatrixOperator(random_matrix_1, source_id='STATE') op2 = NumpyMatrixOperator(random_matrix_2, source_id='STATE') ops = [op1, op2] @@ -52,7 +52,7 @@ def main( if fom_number == 3: fom = fom.with_(output_functional=fom.rhs.operators[0].H) else: - random_matrix_1 = np.random.rand(2, fom.solution_space.dim) + random_matrix_1 = get_rng().random((2, fom.solution_space.dim)) op = NumpyMatrixOperator(random_matrix_1, source_id='STATE') fom = fom.with_(output_functional=op) diff --git a/src/pymordemos/parabolic_mor.py b/src/pymordemos/parabolic_mor.py index e179d5ba0f..997318f44c 100755 --- a/src/pymordemos/parabolic_mor.py +++ b/src/pymordemos/parabolic_mor.py @@ -66,7 +66,7 @@ def main( rom, fom=fom, reductor=reductor, error_estimator=True, error_norms=[lambda U: DT * np.sqrt(np.sum(fom.h1_0_semi_norm(U)[1:]**2))], error_norm_names=['l^2-h^1'], - condition=False, test_mus=parameter_space.sample_randomly(test, seed=999) + condition=False, test_mus=parameter_space.sample_randomly(test) ) # show results diff --git a/src/pymordemos/thermalblock.py b/src/pymordemos/thermalblock.py index a09c17f455..721d703a0b 100755 --- a/src/pymordemos/thermalblock.py +++ b/src/pymordemos/thermalblock.py @@ -169,7 +169,7 @@ def main( error_estimator=True, error_norms=(fom.h1_0_semi_norm, fom.l2_norm), condition=True, - test_mus=parameter_space.sample_randomly(test, seed=999), + test_mus=parameter_space.sample_randomly(test), basis_sizes=0 if plot_error_sequence else 1, pool=None if fenics else pool # cannot pickle FEniCS model ) From 0148782c75855ee86c4468a089215f0c5edfe495 Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Thu, 1 Sep 2022 08:46:43 +0200 Subject: [PATCH 02/18] Update src/pymor/algorithms/samdp.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Petar Mlinarić --- src/pymor/algorithms/samdp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pymor/algorithms/samdp.py b/src/pymor/algorithms/samdp.py index 90465f2840..039ca59873 100644 --- a/src/pymor/algorithms/samdp.py +++ b/src/pymor/algorithms/samdp.py @@ -119,7 +119,7 @@ def samdp(A, E, B, C, nwanted, init_shifts=None, which='NR', tol=1e-10, imagtol= poles = np.empty(0) if init_shifts is None: - st = rng().uniform() * 10.j + st = rng.uniform() * 10.j shift_nr = 0 nr_shifts = 0 else: From 4386b4b9a65c15cdb63f926b7c0328d8c0057997 Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Thu, 1 Sep 2022 08:47:01 +0200 Subject: [PATCH 03/18] Update src/pymor/algorithms/samdp.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Petar Mlinarić --- src/pymor/algorithms/samdp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pymor/algorithms/samdp.py b/src/pymor/algorithms/samdp.py index 039ca59873..3671561459 100644 --- a/src/pymor/algorithms/samdp.py +++ b/src/pymor/algorithms/samdp.py @@ -285,7 +285,7 @@ def samdp(A, E, B, C, nwanted, init_shifts=None, which='NR', tol=1e-10, imagtol= if found: st = SH[0, 0] else: - st = rng().uniform() * 10.j + st = rng.uniform() * 10.j if shift_nr < nr_shifts: st = shifts[shift_nr] From df4b6f8fd4300653397ee8608280f2711468b56c Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Thu, 1 Sep 2022 08:47:14 +0200 Subject: [PATCH 04/18] Update src/pymor/algorithms/samdp.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Petar Mlinarić --- src/pymor/algorithms/samdp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pymor/algorithms/samdp.py b/src/pymor/algorithms/samdp.py index 3671561459..b8c8d1ec40 100644 --- a/src/pymor/algorithms/samdp.py +++ b/src/pymor/algorithms/samdp.py @@ -162,7 +162,7 @@ def samdp(A, E, B, C, nwanted, init_shifts=None, which='NR', tol=1e-10, imagtol= SH, UR, URt, res = _select_max_eig(H, G, X, V, B_defl, C_defl, which) if np.all(res < np.finfo(float).eps): - st = rng().uniform() * 10.j + st = rng.uniform() * 10.j found = False else: found = True From e4e8b3e2656d836fbc574d869a2a41ed76dc7c23 Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Thu, 1 Sep 2022 12:28:45 +0200 Subject: [PATCH 05/18] [random] remove remaining seed arguments --- src/pymor/algorithms/samdp.py | 4 ++-- src/pymor/reductors/neural_network.py | 23 ++++++----------------- src/pymor/vectorarrays/numpy.py | 2 +- src/pymordemos/dmd_identification.py | 3 +-- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/pymor/algorithms/samdp.py b/src/pymor/algorithms/samdp.py index b8c8d1ec40..e18af9afd8 100644 --- a/src/pymor/algorithms/samdp.py +++ b/src/pymor/algorithms/samdp.py @@ -13,9 +13,9 @@ @defaults('which', 'tol', 'imagtol', 'conjtol', 'dorqitol', 'rqitol', 'maxrestart', 'krestart', 'init_shifts', - 'rqi_maxiter', 'seed') + 'rqi_maxiter') def samdp(A, E, B, C, nwanted, init_shifts=None, which='NR', tol=1e-10, imagtol=1e-6, conjtol=1e-8, - dorqitol=1e-4, rqitol=1e-10, maxrestart=100, krestart=20, rqi_maxiter=10, seed=0): + dorqitol=1e-4, rqitol=1e-10, maxrestart=100, krestart=20, rqi_maxiter=10): """Compute the dominant pole triplets and residues of the transfer function of an LTI system. This function uses the subspace accelerated dominant pole (SAMDP) algorithm as described in diff --git a/src/pymor/reductors/neural_network.py b/src/pymor/reductors/neural_network.py index a7619240c1..4af810b985 100644 --- a/src/pymor/reductors/neural_network.py +++ b/src/pymor/reductors/neural_network.py @@ -25,7 +25,7 @@ NeuralNetworkStatefreeOutputModel, NeuralNetworkInstationaryModel, NeuralNetworkInstationaryStatefreeOutputModel) -from pymor.tools.random import get_rng +from pymor.tools.random import get_rng, get_seed_seq class NeuralNetworkReductor(BasicObject): @@ -105,7 +105,7 @@ def reduce(self, hidden_layers='[(N+P)*3, (N+P)*3]', activation_function=torch.t loss_function=None, restarts=10, lr_scheduler=optim.lr_scheduler.StepLR, lr_scheduler_params={'step_size': 10, 'gamma': 0.7}, es_scheduler_params={'patience': 10, 'delta': 0.}, weight_decay=0., - log_loss_frequency=0, seed=0): + log_loss_frequency=0): """Reduce by training artificial neural networks. Parameters @@ -155,9 +155,6 @@ def reduce(self, hidden_layers='[(N+P)*3, (N+P)*3]', activation_function=torch.t Frequency of epochs in which to log the current validation and training loss during training of the neural networks. If `0`, no intermediate logging of losses is done. - seed - Seed to use for various functions in PyTorch. Using a fixed seed, - it is possible to reproduce former results. Returns ------- @@ -170,9 +167,7 @@ def reduce(self, hidden_layers='[(N+P)*3, (N+P)*3]', activation_function=torch.t assert learning_rate > 0. assert weight_decay >= 0. - # set a seed for the PyTorch initialization of weights and biases - # and further PyTorch methods - torch.manual_seed(seed) + torch.manual_seed(get_seed_seq().spawn(1)[0].generate_state(1).item()) # build a reduced basis using POD and compute training data if not hasattr(self, 'training_data'): @@ -232,7 +227,7 @@ def weighted_mse_loss_function(inputs, targets): self.neural_network, self.losses = multiple_restarts_training(self.training_data, self.validation_data, neural_network, target_loss, restarts, log_loss_frequency, training_parameters, - self.scaling_parameters, seed) + self.scaling_parameters) self._check_tolerances() @@ -1083,7 +1078,7 @@ def closure(): def multiple_restarts_training(training_data, validation_data, neural_network, target_loss=None, max_restarts=10, log_loss_frequency=0, - training_parameters={}, scaling_parameters={}, seed=None): + training_parameters={}, scaling_parameters={}): """Algorithm that performs multiple restarts of neural network training. This method either performs a predefined number of restarts and returns @@ -1115,9 +1110,6 @@ def multiple_restarts_training(training_data, validation_data, neural_network, scaling_parameters Additional parameters for scaling inputs respectively outputs, see :func:`train_neural_network` for more information. - seed - Seed to use for various functions in PyTorch. Using a fixed seed, - it is possible to reproduce former results. Returns ------- @@ -1137,10 +1129,7 @@ def multiple_restarts_training(training_data, validation_data, neural_network, logger = getLogger('pymor.algorithms.neural_network.multiple_restarts_training') - # if applicable, set a common seed for the PyTorch initialization - # of weights and biases and further PyTorch methods for all training runs - if seed: - torch.manual_seed(seed) + torch.manual_seed(get_seed_seq().spawn(1)[0].generate_state(1).item()) # in case no training data is provided, return a neural network # that always returns zeros independent of the input diff --git a/src/pymor/vectorarrays/numpy.py b/src/pymor/vectorarrays/numpy.py index e48d9f1893..a98c0fa351 100644 --- a/src/pymor/vectorarrays/numpy.py +++ b/src/pymor/vectorarrays/numpy.py @@ -236,7 +236,7 @@ def full(self, value, count=1, reserve=0): assert reserve >= 0 return NumpyVectorArray(self, NumpyVectorArrayImpl(np.full((max(count, reserve), self.dim), value), count)) - def random(self, count=1, distribution='uniform', seed=None, reserve=0, **kwargs): + def random(self, count=1, distribution='uniform', reserve=0, **kwargs): assert count >= 0 assert reserve >= 0 va = self.zeros(count, reserve) diff --git a/src/pymordemos/dmd_identification.py b/src/pymordemos/dmd_identification.py index adf20e7ef0..2b5ff1a24a 100755 --- a/src/pymordemos/dmd_identification.py +++ b/src/pymordemos/dmd_identification.py @@ -13,8 +13,7 @@ def main( n: int = Option(4, help='Dimension of the state.'), - M: int = Option(10, help='Number of data pairs.'), - seed: int = Option(42, help='Random seed.') + M: int = Option(10, help='Number of data pairs.') ): A = get_rng().random((n, n)) A = A / np.linalg.norm(A) From 95e4e79245ce7f4859c69ffe02361d54951116bf Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Thu, 1 Sep 2022 12:28:56 +0200 Subject: [PATCH 06/18] [random] fix tests --- src/pymortests/algorithms/ei.py | 9 +++++++-- src/pymortests/algorithms/symplectic.py | 13 +++++++++---- src/pymortests/fixtures/operator.py | 5 ++++- src/pymortests/model.py | 5 ++++- src/pymortests/rand_la.py | 17 +++++++++++++---- src/pymortests/vectorarray.py | 13 +++++++++---- 6 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/pymortests/algorithms/ei.py b/src/pymortests/algorithms/ei.py index 47507948dc..82accf21a3 100644 --- a/src/pymortests/algorithms/ei.py +++ b/src/pymortests/algorithms/ei.py @@ -7,6 +7,7 @@ from pymor.algorithms.pod import pod from pymor.operators.ei import EmpiricalInterpolatedOperator from pymor.reductors.basic import StationaryRBReductor +from pymor.tools.random import set_rng from pymortests.base import runmodule, assert_all_almost_equal @@ -18,7 +19,9 @@ def test_ei_restricted_to_full(stationary_models): ei_op = EmpiricalInterpolatedOperator(op, collateral_basis=cb, interpolation_dofs=dofs, triangular=True) ei_model = model.with_(operator=ei_op) - for mu in model.parameters.space(1, 2).sample_randomly(3, seed=234): + with set_rng(234): + mus = model.parameters.space(1, 2).sample_randomly(3, seed=234) + for mu in mus: a = model.solve(mu) b = ei_model.solve(mu) assert_all_almost_equal(a, b, rtol=1e-4) @@ -41,7 +44,9 @@ def test_ei_rom(stationary_models): U = fom.solution_space.empty() base_mus = [] - for mu in fom.parameters.space(1, 2).sample_randomly(3, seed=234): + with set_rng(234): + mus = fom.parameters.space(1, 2).sample_randomly(3, seed=234) + for mu in mus: a = fom.solve(mu) U.append(a) base_mus.append(mu) diff --git a/src/pymortests/algorithms/symplectic.py b/src/pymortests/algorithms/symplectic.py index ba0c804370..a834d847cf 100644 --- a/src/pymortests/algorithms/symplectic.py +++ b/src/pymortests/algorithms/symplectic.py @@ -5,6 +5,7 @@ psd_svd_like_decomp, symplectic_gram_schmidt) from pymor.operators.symplectic import CanonicalSymplecticFormOperator +from pymor.tools.random import set_rng from pymor.vectorarrays.block import BlockVectorSpace from pymor.vectorarrays.numpy import NumpyVectorSpace @@ -23,7 +24,8 @@ def test_symplecticity(key_method): half_space = NumpyVectorSpace(half_dim) phase_space = BlockVectorSpace([half_space] * 2) n_data = 100 - U = phase_space.random(n_data, seed=42) + with set_rng(42): + U = phase_space.random(n_data) modes = 10 basis = METHODS_DICT[key_method](U, modes) tis_basis = basis.transposed_symplectic_inverse() @@ -37,7 +39,8 @@ def test_orthonormality(key_orthosympl_method): half_space = NumpyVectorSpace(half_dim) phase_space = BlockVectorSpace([half_space] * 2) n_data = 100 - U = phase_space.random(n_data, seed=42) + with set_rng(42): + U = phase_space.random(n_data) modes = 10 basis = METHODS_DICT[key_orthosympl_method](U, modes).to_array() assert np.allclose(basis.inner(basis), np.eye(modes)) @@ -53,13 +56,15 @@ def test_symplectic_gram_schmidt(test_orthonormality, reiterate): J = CanonicalSymplecticFormOperator(phase_space) half_red_dim = 10 - E = phase_space.random(half_red_dim, seed=42) + with set_rng(42): + E = phase_space.random(half_red_dim) if test_orthonormality: # special choice, such that result is orthosymplectic F = J.apply(E) else: # less structure in snapshots, no orthogonality - F = phase_space.random(half_red_dim, seed=43) + with set_rng(43): + F = phase_space.random(half_red_dim) S, Lambda = symplectic_gram_schmidt(E, F, return_Lambda=True, reiterate=reiterate) tsi_S = S.transposed_symplectic_inverse() diff --git a/src/pymortests/fixtures/operator.py b/src/pymortests/fixtures/operator.py index e26f43e8a2..5417828522 100644 --- a/src/pymortests/fixtures/operator.py +++ b/src/pymortests/fixtures/operator.py @@ -11,6 +11,7 @@ from pymor.operators.interface import Operator from pymor.operators.list import NumpyListVectorArrayMatrixOperator from pymor.operators.numpy import NumpyMatrixOperator +from pymor.tools.random import set_rng from pymor.vectorarrays.numpy import NumpyVectorSpace @@ -137,7 +138,9 @@ def thermalblock_factory(xblocks, yblocks, diameter, seed): U.append(iop.as_vector(f.parameters.parse(exp))) for exp in np.random.random(6): V.append(iop.as_vector(f.parameters.parse(exp))) - return m.operator, p.parameter_space.sample_randomly(1, seed=seed)[0], U, V, m.h1_product, m.l2_product + with set_rng(seed): + mu = p.parameter_space.sample_randomly(1)[0] + return m.operator, mu, U, V, m.h1_product, m.l2_product def thermalblock_assemble_factory(xblocks, yblocks, diameter, seed): diff --git a/src/pymortests/model.py b/src/pymortests/model.py index 3243d9cba8..6569387548 100644 --- a/src/pymortests/model.py +++ b/src/pymortests/model.py @@ -14,6 +14,7 @@ from pymor.models.symplectic import QuadraticHamiltonianModel from pymor.operators.block import BlockDiagonalOperator from pymor.operators.constructions import IdentityOperator +from pymor.tools.random import set_rng from pymor.vectorarrays.numpy import NumpyVectorSpace from pymortests.base import runmodule from pymortests.pickling import assert_picklable, assert_picklable_without_dumps_function @@ -32,7 +33,9 @@ def test_pickle_by_solving(model): m2 = loads(dumps(m)) m.disable_caching() m2.disable_caching() - for mu in m.parameters.space(1, 2).sample_randomly(3, seed=234): + with set_rng(234): + mus = m.parameters.space(1, 2).sample_randomly(3) + for mu in mus: assert np.all(almost_equal(m.solve(mu), m2.solve(mu))) diff --git a/src/pymortests/rand_la.py b/src/pymortests/rand_la.py index 881362a62b..e7b1cfb9f1 100644 --- a/src/pymortests/rand_la.py +++ b/src/pymortests/rand_la.py @@ -9,6 +9,7 @@ from pymor.algorithms.rand_la import rrf, adaptive_rrf, random_ghep, random_generalized_svd from pymor.operators.numpy import NumpyMatrixOperator from pymor.operators.constructions import VectorArrayOperator +from pymor.tools.random import set_rng def test_adaptive_rrf(): @@ -22,10 +23,14 @@ def test_adaptive_rrf(): B = B.dot(B.T) source_product = NumpyMatrixOperator(B) - C = range_product.range.random(10, seed=10) + with set_rng(10): + C = range_product.range.random(10) op = VectorArrayOperator(C) - D = range_product.range.random(10, seed=11)+1j*range_product.range.random(10, seed=12) + with set_rng(11): + D = range_product.range.random(10) + with set_rng(12): + D += 1j*range_product.range.random(10) op_complex = VectorArrayOperator(D) Q1 = adaptive_rrf(op, source_product, range_product) @@ -47,10 +52,14 @@ def test_rrf(): B = B @ B.T source_product = NumpyMatrixOperator(B) - C = range_product.range.random(10, seed=10) + with set_rng(10): + C = range_product.range.random(10) op = VectorArrayOperator(C) - D = range_product.range.random(10, seed=11)+1j*range_product.range.random(10, seed=12) + with set_rng(11): + D = range_product.range.random(10) + with set_rng(12): + D += 1j*range_product.range.random(10) op_complex = VectorArrayOperator(D) Q1 = rrf(op, source_product, range_product) diff --git a/src/pymortests/vectorarray.py b/src/pymortests/vectorarray.py index fcfb7119ee..72dd96dcbb 100644 --- a/src/pymortests/vectorarray.py +++ b/src/pymortests/vectorarray.py @@ -14,6 +14,7 @@ from pymor.vectorarrays.interface import VectorSpace from pymor.vectorarrays.numpy import NumpyVectorSpace from pymor.tools.floatcmp import float_cmp, bounded +from pymor.tools.random import set_rng from pymortests.base import might_exceed_deadline from pymortests.pickling import assert_picklable_without_dumps_function import pymortests.strategies as pyst @@ -166,7 +167,8 @@ def _test_random_uniform(vector_array, realizations, low, high): return seed = 123 try: - v = vector_array.random(c, low=low, high=high, seed=seed) + with set_rng(seed): + v = vector_array.random(c, low=low, high=high) except ValueError as e: if high <= low: return @@ -182,7 +184,8 @@ def _test_random_uniform(vector_array, realizations, low, high): assert np.all(x >= low) except NotImplementedError: pass - vv = vector_array.random(c, distribution='uniform', low=low, high=high, seed=seed) + with set_rng(seed): + vv = vector_array.random(c, distribution='uniform', low=low, high=high) assert np.allclose((v - vv).sup_norm(), 0.) @@ -199,7 +202,8 @@ def test_random_normal(vector_array, realizations, loc, scale): return seed = 123 try: - v = vector_array.random(c, 'normal', loc=loc, scale=scale, seed=seed) + with set_rng(seed): + v = vector_array.random(c, 'normal', loc=loc, scale=scale) except ValueError as e: if scale <= 0: return @@ -222,7 +226,8 @@ def test_random_normal(vector_array, realizations, loc, scale): bounded(lower, upper, loc) except NotImplementedError: pass - vv = vector_array.random(c, 'normal', loc=loc, scale=scale, seed=seed) + with set_rng(seed): + vv = vector_array.random(c, 'normal', loc=loc, scale=scale) data = vv.to_numpy() # due to scaling data might actually now include nan or inf assume(not np.isnan(data).any()) From be56c5b2c513800ebf8d7b48034325d5d53ffdbd Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Thu, 1 Sep 2022 12:58:04 +0200 Subject: [PATCH 07/18] [random] rename set_rng by new_rng and make the result an actual RNG --- src/pymor/algorithms/eigs.py | 4 +- src/pymor/algorithms/lradi.py | 4 +- src/pymor/algorithms/lrradi.py | 4 +- src/pymor/basic.py | 2 +- src/pymor/parallel/ipython.py | 4 +- src/pymor/parallel/mpi.py | 4 +- src/pymor/tools/random.py | 55 ++++++++++++------------- src/pymortests/algorithms/ei.py | 6 +-- src/pymortests/algorithms/symplectic.py | 10 ++--- src/pymortests/fixtures/operator.py | 4 +- src/pymortests/model.py | 4 +- src/pymortests/rand_la.py | 14 +++---- src/pymortests/vectorarray.py | 9 ++-- 13 files changed, 60 insertions(+), 64 deletions(-) diff --git a/src/pymor/algorithms/eigs.py b/src/pymor/algorithms/eigs.py index a09dd1185a..ae24ebde61 100644 --- a/src/pymor/algorithms/eigs.py +++ b/src/pymor/algorithms/eigs.py @@ -9,7 +9,7 @@ from pymor.core.logger import getLogger from pymor.operators.constructions import IdentityOperator, InverseOperator from pymor.operators.interface import Operator -from pymor.tools.random import set_rng +from pymor.tools.random import new_rng def eigs(A, E=None, k=3, sigma=None, which='LM', b=None, l=None, maxiter=1000, tol=1e-13, @@ -91,7 +91,7 @@ def eigs(A, E=None, k=3, sigma=None, which='LM', b=None, l=None, maxiter=1000, t assert E.source == A.source if b is None: - with set_rng(0): + with new_rng(0): b = A.source.random() else: assert b in A.source diff --git a/src/pymor/algorithms/lradi.py b/src/pymor/algorithms/lradi.py index e24c8f375d..dd27e1db63 100644 --- a/src/pymor/algorithms/lradi.py +++ b/src/pymor/algorithms/lradi.py @@ -12,7 +12,7 @@ from pymor.core.defaults import defaults from pymor.core.logger import getLogger from pymor.operators.constructions import IdentityOperator, InverseOperator -from pymor.tools.random import set_rng +from pymor.tools.random import new_rng from pymor.vectorarrays.constructions import cat_arrays @@ -196,7 +196,7 @@ def projection_shifts_init(A, E, B, shift_options): shifts = shifts[shifts.real < 0] if shifts.size == 0: # use random subspace instead of span{B} (with same dimensions) - with set_rng(0): + with new_rng(0): B = B.random(len(B), distribution='normal') else: return shifts diff --git a/src/pymor/algorithms/lrradi.py b/src/pymor/algorithms/lrradi.py index b05eb46a8f..9ba57ede05 100644 --- a/src/pymor/algorithms/lrradi.py +++ b/src/pymor/algorithms/lrradi.py @@ -12,7 +12,7 @@ from pymor.core.logger import getLogger from pymor.operators.constructions import IdentityOperator from pymor.algorithms.gram_schmidt import gram_schmidt -from pymor.tools.random import set_rng +from pymor.tools.random import new_rng @defaults('lrradi_tol', 'lrradi_maxiter', 'lrradi_shifts', 'hamiltonian_shifts_init_maxiter', @@ -244,7 +244,7 @@ def hamiltonian_shifts_init(A, E, B, C, shift_options): eigpairs = list(filter(lambda e: e[0].real < 0, eigpairs)) if len(eigpairs) == 0: # use random subspace instead of span{C} (with same dimensions) - with set_rng(0): + with new_rng(0): C = C.random(len(C), distribution='normal') continue # find shift with most impact on convergence diff --git a/src/pymor/basic.py b/src/pymor/basic.py index f73356116e..f09f171db5 100644 --- a/src/pymor/basic.py +++ b/src/pymor/basic.py @@ -76,7 +76,7 @@ SOBTReductor) from pymor.reductors.sor_irka import SORIRKAReductor -from pymor.tools.random import get_rng, set_rng +from pymor.tools.random import get_rng, new_rng from pymor.vectorarrays.constructions import cat_arrays from pymor.vectorarrays.list import ListVectorSpace diff --git a/src/pymor/parallel/ipython.py b/src/pymor/parallel/ipython.py index 8531f22d7a..1564cf026c 100644 --- a/src/pymor/parallel/ipython.py +++ b/src/pymor/parallel/ipython.py @@ -208,8 +208,8 @@ def _setup_worker(seed_seq): _remote_objects = {} # ensure that each worker starts with a different yet deterministically # initialized rng - from pymor.tools.random import init_rng - init_rng(seed_seq) + from pymor.tools.random import new_rng + new_rng(seed_seq).install() def _push_object(remote_id, obj): diff --git a/src/pymor/parallel/mpi.py b/src/pymor/parallel/mpi.py index 40861eb5d0..160ea12c21 100644 --- a/src/pymor/parallel/mpi.py +++ b/src/pymor/parallel/mpi.py @@ -98,8 +98,8 @@ def _setup_worker(): def _setup_rng(seed_seq): # ensure that each worker starts with a different yet deterministically # initialized rng - from pymor.tools.random import init_rng - init_rng(seed_seq) + from pymor.tools.random import new_rng + new_rng(seed_seq).install() def _push_object(obj): diff --git a/src/pymor/tools/random.py b/src/pymor/tools/random.py index 272202187e..b6d8b586b8 100644 --- a/src/pymor/tools/random.py +++ b/src/pymor/tools/random.py @@ -10,18 +10,6 @@ import numpy as np -@defaults('seed_seq') -def init_rng(seed_seq=42): - if not isinstance(seed_seq, np.random.SeedSequence): - seed_seq = np.random.SeedSequence(seed_seq) - # Store a new rng together with seed_seq, as the latter cannot be recovered from the - # rng via a public interface (see https://github.com/numpy/numpy/issues/15322). - # The first field is a flag to indicate whether the current _rng_state has been consumed - # via get_rng. This is a safeguard to detect calls to get_rng in concurrent code - # paths. - _rng_state.set([False, np.random.default_rng(seed_seq), seed_seq]) - - def get_rng(): rng_state = _get_rng_state() rng_state[0] = True @@ -29,27 +17,36 @@ def get_rng(): return rng_state[1] -class set_rng: +@defaults('seed_seq') +def new_rng(seed_seq=42): + if not isinstance(seed_seq, np.random.SeedSequence): + seed_seq = np.random.SeedSequence(seed_seq) + return RNG(seed_seq) + + +class RNG(np.random.Generator): def __init__(self, seed_seq): self.old_state = _rng_state.get(None) - self.seed_seq = seed_seq - self.set() - - def set(self): - self._is_set = True - init_rng(self.seed_seq) - - def reset(self): - self._is_set = False + super().__init__(np.random.default_rng(seed_seq).bit_generator) + self._seed_seq = seed_seq + + def install(self): + # Store a new rng together with seed_seq, as the latter cannot be recovered from the + # rng via a public interface (see https://github.com/numpy/numpy/issues/15322). + # The first field is a flag to indicate whether the current _rng_state has been consumed + # via get_rng. This is a safeguard to detect calls to get_rng in concurrent code + # paths. + _rng_state.set([False, self, self._seed_seq]) + + def uninstall(self): _rng_state.set(self.old_state) def __enter__(self): - if not self._is_set: - self.set() + self.install() def __exit__(self, exc_type, exc_value, exc_tb): - self.reset() + self.uninstall() def get_seed_seq(): @@ -62,7 +59,7 @@ def spawn_rng(f): if inspect.iscoroutine(f): async def spawn_rng_wrapper(): - with set_rng(seed_seq): + with new_rng(seed_seq): return await f return spawn_rng_wrapper() @@ -80,7 +77,7 @@ def __init__(self, f, seed_seq): self.f, self.seed_seq = f, seed_seq def __call__(self, *args, **kwargs): - with set_rng(self.seed_seq): + with new_rng(self.seed_seq): return self.f(*args, **kwargs) @@ -92,7 +89,7 @@ def _get_rng_state(): warnings.warn( 'get_rng called but _rng_state not initialized. (Call spawn_rng when creating new thread.) ' 'Initializing a new RNG from the default seed. This may lead to correlated data.') - init_rng() + new_rng().install() rng_state = _rng_state.get() if rng_state[0]: import warnings @@ -102,4 +99,4 @@ def _get_rng_state(): _rng_state = ContextVar('_rng_state') -init_rng() +new_rng().install() diff --git a/src/pymortests/algorithms/ei.py b/src/pymortests/algorithms/ei.py index 82accf21a3..b1adf74496 100644 --- a/src/pymortests/algorithms/ei.py +++ b/src/pymortests/algorithms/ei.py @@ -7,7 +7,7 @@ from pymor.algorithms.pod import pod from pymor.operators.ei import EmpiricalInterpolatedOperator from pymor.reductors.basic import StationaryRBReductor -from pymor.tools.random import set_rng +from pymor.tools.random import new_rng from pymortests.base import runmodule, assert_all_almost_equal @@ -19,7 +19,7 @@ def test_ei_restricted_to_full(stationary_models): ei_op = EmpiricalInterpolatedOperator(op, collateral_basis=cb, interpolation_dofs=dofs, triangular=True) ei_model = model.with_(operator=ei_op) - with set_rng(234): + with new_rng(234): mus = model.parameters.space(1, 2).sample_randomly(3, seed=234) for mu in mus: a = model.solve(mu) @@ -44,7 +44,7 @@ def test_ei_rom(stationary_models): U = fom.solution_space.empty() base_mus = [] - with set_rng(234): + with new_rng(234): mus = fom.parameters.space(1, 2).sample_randomly(3, seed=234) for mu in mus: a = fom.solve(mu) diff --git a/src/pymortests/algorithms/symplectic.py b/src/pymortests/algorithms/symplectic.py index a834d847cf..674a7e4288 100644 --- a/src/pymortests/algorithms/symplectic.py +++ b/src/pymortests/algorithms/symplectic.py @@ -5,7 +5,7 @@ psd_svd_like_decomp, symplectic_gram_schmidt) from pymor.operators.symplectic import CanonicalSymplecticFormOperator -from pymor.tools.random import set_rng +from pymor.tools.random import new_rng from pymor.vectorarrays.block import BlockVectorSpace from pymor.vectorarrays.numpy import NumpyVectorSpace @@ -24,7 +24,7 @@ def test_symplecticity(key_method): half_space = NumpyVectorSpace(half_dim) phase_space = BlockVectorSpace([half_space] * 2) n_data = 100 - with set_rng(42): + with new_rng(42): U = phase_space.random(n_data) modes = 10 basis = METHODS_DICT[key_method](U, modes) @@ -39,7 +39,7 @@ def test_orthonormality(key_orthosympl_method): half_space = NumpyVectorSpace(half_dim) phase_space = BlockVectorSpace([half_space] * 2) n_data = 100 - with set_rng(42): + with new_rng(42): U = phase_space.random(n_data) modes = 10 basis = METHODS_DICT[key_orthosympl_method](U, modes).to_array() @@ -56,14 +56,14 @@ def test_symplectic_gram_schmidt(test_orthonormality, reiterate): J = CanonicalSymplecticFormOperator(phase_space) half_red_dim = 10 - with set_rng(42): + with new_rng(42): E = phase_space.random(half_red_dim) if test_orthonormality: # special choice, such that result is orthosymplectic F = J.apply(E) else: # less structure in snapshots, no orthogonality - with set_rng(43): + with new_rng(43): F = phase_space.random(half_red_dim) S, Lambda = symplectic_gram_schmidt(E, F, return_Lambda=True, reiterate=reiterate) diff --git a/src/pymortests/fixtures/operator.py b/src/pymortests/fixtures/operator.py index 5417828522..c8da149088 100644 --- a/src/pymortests/fixtures/operator.py +++ b/src/pymortests/fixtures/operator.py @@ -11,7 +11,7 @@ from pymor.operators.interface import Operator from pymor.operators.list import NumpyListVectorArrayMatrixOperator from pymor.operators.numpy import NumpyMatrixOperator -from pymor.tools.random import set_rng +from pymor.tools.random import new_rng from pymor.vectorarrays.numpy import NumpyVectorSpace @@ -138,7 +138,7 @@ def thermalblock_factory(xblocks, yblocks, diameter, seed): U.append(iop.as_vector(f.parameters.parse(exp))) for exp in np.random.random(6): V.append(iop.as_vector(f.parameters.parse(exp))) - with set_rng(seed): + with new_rng(seed): mu = p.parameter_space.sample_randomly(1)[0] return m.operator, mu, U, V, m.h1_product, m.l2_product diff --git a/src/pymortests/model.py b/src/pymortests/model.py index 6569387548..1d88729594 100644 --- a/src/pymortests/model.py +++ b/src/pymortests/model.py @@ -14,7 +14,7 @@ from pymor.models.symplectic import QuadraticHamiltonianModel from pymor.operators.block import BlockDiagonalOperator from pymor.operators.constructions import IdentityOperator -from pymor.tools.random import set_rng +from pymor.tools.random import new_rng from pymor.vectorarrays.numpy import NumpyVectorSpace from pymortests.base import runmodule from pymortests.pickling import assert_picklable, assert_picklable_without_dumps_function @@ -33,7 +33,7 @@ def test_pickle_by_solving(model): m2 = loads(dumps(m)) m.disable_caching() m2.disable_caching() - with set_rng(234): + with new_rng(234): mus = m.parameters.space(1, 2).sample_randomly(3) for mu in mus: assert np.all(almost_equal(m.solve(mu), m2.solve(mu))) diff --git a/src/pymortests/rand_la.py b/src/pymortests/rand_la.py index e7b1cfb9f1..283c6bd72a 100644 --- a/src/pymortests/rand_la.py +++ b/src/pymortests/rand_la.py @@ -9,7 +9,7 @@ from pymor.algorithms.rand_la import rrf, adaptive_rrf, random_ghep, random_generalized_svd from pymor.operators.numpy import NumpyMatrixOperator from pymor.operators.constructions import VectorArrayOperator -from pymor.tools.random import set_rng +from pymor.tools.random import new_rng def test_adaptive_rrf(): @@ -23,13 +23,13 @@ def test_adaptive_rrf(): B = B.dot(B.T) source_product = NumpyMatrixOperator(B) - with set_rng(10): + with new_rng(10): C = range_product.range.random(10) op = VectorArrayOperator(C) - with set_rng(11): + with new_rng(11): D = range_product.range.random(10) - with set_rng(12): + with new_rng(12): D += 1j*range_product.range.random(10) op_complex = VectorArrayOperator(D) @@ -52,13 +52,13 @@ def test_rrf(): B = B @ B.T source_product = NumpyMatrixOperator(B) - with set_rng(10): + with new_rng(10): C = range_product.range.random(10) op = VectorArrayOperator(C) - with set_rng(11): + with new_rng(11): D = range_product.range.random(10) - with set_rng(12): + with new_rng(12): D += 1j*range_product.range.random(10) op_complex = VectorArrayOperator(D) diff --git a/src/pymortests/vectorarray.py b/src/pymortests/vectorarray.py index 72dd96dcbb..390f528b4b 100644 --- a/src/pymortests/vectorarray.py +++ b/src/pymortests/vectorarray.py @@ -14,7 +14,6 @@ from pymor.vectorarrays.interface import VectorSpace from pymor.vectorarrays.numpy import NumpyVectorSpace from pymor.tools.floatcmp import float_cmp, bounded -from pymor.tools.random import set_rng from pymortests.base import might_exceed_deadline from pymortests.pickling import assert_picklable_without_dumps_function import pymortests.strategies as pyst @@ -167,7 +166,7 @@ def _test_random_uniform(vector_array, realizations, low, high): return seed = 123 try: - with set_rng(seed): + with new_rng(seed): v = vector_array.random(c, low=low, high=high) except ValueError as e: if high <= low: @@ -184,7 +183,7 @@ def _test_random_uniform(vector_array, realizations, low, high): assert np.all(x >= low) except NotImplementedError: pass - with set_rng(seed): + with new_rng(seed): vv = vector_array.random(c, distribution='uniform', low=low, high=high) assert np.allclose((v - vv).sup_norm(), 0.) @@ -202,7 +201,7 @@ def test_random_normal(vector_array, realizations, loc, scale): return seed = 123 try: - with set_rng(seed): + with new_rng(seed): v = vector_array.random(c, 'normal', loc=loc, scale=scale) except ValueError as e: if scale <= 0: @@ -226,7 +225,7 @@ def test_random_normal(vector_array, realizations, loc, scale): bounded(lower, upper, loc) except NotImplementedError: pass - with set_rng(seed): + with new_rng(seed): vv = vector_array.random(c, 'normal', loc=loc, scale=scale) data = vv.to_numpy() # due to scaling data might actually now include nan or inf From 8edcdaf8ca72ca27b12cf93cdb25ed8821309832 Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Thu, 1 Sep 2022 13:05:24 +0200 Subject: [PATCH 08/18] [random] fix usage of new_rng in loops --- src/pymor/algorithms/lradi.py | 3 ++- src/pymor/algorithms/lrradi.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pymor/algorithms/lradi.py b/src/pymor/algorithms/lradi.py index dd27e1db63..1fecf52702 100644 --- a/src/pymor/algorithms/lradi.py +++ b/src/pymor/algorithms/lradi.py @@ -190,13 +190,14 @@ def projection_shifts_init(A, E, B, shift_options): shifts A |NumPy array| containing a set of stable shift parameters. """ + rng = new_rng(0) for i in range(shift_options['init_maxiter']): Q = gram_schmidt(B, atol=0, rtol=0) shifts = spla.eigvals(A.apply2(Q, Q), E.apply2(Q, Q)) shifts = shifts[shifts.real < 0] if shifts.size == 0: # use random subspace instead of span{B} (with same dimensions) - with new_rng(0): + with rng: B = B.random(len(B), distribution='normal') else: return shifts diff --git a/src/pymor/algorithms/lrradi.py b/src/pymor/algorithms/lrradi.py index 9ba57ede05..30917bce45 100644 --- a/src/pymor/algorithms/lrradi.py +++ b/src/pymor/algorithms/lrradi.py @@ -225,6 +225,7 @@ def hamiltonian_shifts_init(A, E, B, C, shift_options): shifts A |NumPy array| containing a set of stable shift parameters. """ + rng = new_rng(0) for _ in range(shift_options['init_maxiter']): Q = gram_schmidt(C, atol=0, rtol=0) Ap = A.apply2(Q, Q) @@ -244,7 +245,7 @@ def hamiltonian_shifts_init(A, E, B, C, shift_options): eigpairs = list(filter(lambda e: e[0].real < 0, eigpairs)) if len(eigpairs) == 0: # use random subspace instead of span{C} (with same dimensions) - with new_rng(0): + with rng: C = C.random(len(C), distribution='normal') continue # find shift with most impact on convergence From 33290496984dd3469383feeda908b620a29727ad Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Thu, 1 Sep 2022 13:05:44 +0200 Subject: [PATCH 09/18] [random] use new_rng instead of np.random.default_rng --- src/pymor/algorithms/samdp.py | 3 ++- src/pymor/reductors/h2.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pymor/algorithms/samdp.py b/src/pymor/algorithms/samdp.py index e18af9afd8..7e3edff3f5 100644 --- a/src/pymor/algorithms/samdp.py +++ b/src/pymor/algorithms/samdp.py @@ -10,6 +10,7 @@ from pymor.core.logger import getLogger from pymor.operators.constructions import IdentityOperator from pymor.operators.interface import Operator +from pymor.tools.random import new_rng @defaults('which', 'tol', 'imagtol', 'conjtol', 'dorqitol', 'rqitol', 'maxrestart', 'krestart', 'init_shifts', @@ -104,7 +105,7 @@ def samdp(A, E, B, C, nwanted, init_shifts=None, which='NR', tol=1e-10, imagtol= k = 0 nrestart = 0 nr_converged = 0 - rng = np.random.default_rng(0) + rng = new_rng(0) X = A.source.empty() Q = A.source.empty() diff --git a/src/pymor/reductors/h2.py b/src/pymor/reductors/h2.py index 1fd1e2a644..fbcc0636da 100644 --- a/src/pymor/reductors/h2.py +++ b/src/pymor/reductors/h2.py @@ -21,6 +21,7 @@ from pymor.parameters.base import Mu from pymor.reductors.basic import LTIPGReductor from pymor.reductors.interpolation import LTIBHIReductor, TFBHIReductor +from pymor.tools.random import new_rng class GenericIRKAReductor(BasicObject): @@ -94,10 +95,10 @@ def _order_to_sigma_b_c(self, r): sigma = np.logspace(-1, 1, r) b = (np.ones((r, 1)) if self.fom.dim_input == 1 - else np.random.default_rng(0).normal(size=(r, self.fom.dim_input))) + else new_rng(0).normal(size=(r, self.fom.dim_input))) c = (np.ones((r, 1)) if self.fom.dim_output == 1 - else np.random.default_rng(0).normal(size=(r, self.fom.dim_output))) + else new_rng(0).normal(size=(r, self.fom.dim_output))) return sigma, b, c @staticmethod From 4c44bb8b3087f9929ac94c5ad1e9b8947779ff8c Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Thu, 1 Sep 2022 14:57:13 +0200 Subject: [PATCH 10/18] [random] return self in RNG.__enter__ --- src/pymor/tools/random.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pymor/tools/random.py b/src/pymor/tools/random.py index b6d8b586b8..3b79ecb0f2 100644 --- a/src/pymor/tools/random.py +++ b/src/pymor/tools/random.py @@ -44,6 +44,7 @@ def uninstall(self): def __enter__(self): self.install() + return self def __exit__(self, exc_type, exc_value, exc_tb): self.uninstall() From a76655998712a8f65af56e40a6f2e2d5fd8ac7c6 Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Thu, 1 Sep 2022 14:57:50 +0200 Subject: [PATCH 11/18] [random] fix tests --- src/pymortests/algorithms/ei.py | 4 ++-- src/pymortests/low_rank_op.py | 15 ++++++++------- .../ca3b1109e5bd10d21b0b9bc432d6ac101cf1fa4d | Bin 3722 -> 3722 bytes .../105686ef68a9b4b3655c790d395526c623efd162 | Bin 1558 -> 1558 bytes .../29e5f263a02a275f4d29b7e5bdf32ad20c1e6346 | Bin 1528 -> 1528 bytes .../38a8bfe939551df7383a6058a2862fb9d2eea5a4 | Bin 1552 -> 1552 bytes .../a92ec4631a6f0edc2eae1cc3b0fa17af6c467cef | Bin 4668 -> 4668 bytes .../c8a460e412d9170f12508ab3608fd7e1e1750603 | Bin 1529 -> 1529 bytes .../ce0770de067386276c9e89afa2d63daeb512a9f5 | Bin 1539 -> 1539 bytes .../ea8f66e263715fd0185d0dd23ab6d57feb6ee898 | Bin 1543 -> 1543 bytes 10 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/pymortests/algorithms/ei.py b/src/pymortests/algorithms/ei.py index b1adf74496..7f34aa61cd 100644 --- a/src/pymortests/algorithms/ei.py +++ b/src/pymortests/algorithms/ei.py @@ -20,7 +20,7 @@ def test_ei_restricted_to_full(stationary_models): ei_model = model.with_(operator=ei_op) with new_rng(234): - mus = model.parameters.space(1, 2).sample_randomly(3, seed=234) + mus = model.parameters.space(1, 2).sample_randomly(3) for mu in mus: a = model.solve(mu) b = ei_model.solve(mu) @@ -45,7 +45,7 @@ def test_ei_rom(stationary_models): U = fom.solution_space.empty() base_mus = [] with new_rng(234): - mus = fom.parameters.space(1, 2).sample_randomly(3, seed=234) + mus = fom.parameters.space(1, 2).sample_randomly(3) for mu in mus: a = fom.solve(mu) U.append(a) diff --git a/src/pymortests/low_rank_op.py b/src/pymortests/low_rank_op.py index b53c050479..d61d798335 100644 --- a/src/pymortests/low_rank_op.py +++ b/src/pymortests/low_rank_op.py @@ -8,19 +8,20 @@ from pymor.algorithms.lincomb import assemble_lincomb from pymor.operators.constructions import LowRankOperator, LowRankUpdatedOperator from pymor.operators.numpy import NumpyMatrixOperator +from pymor.tools.random import new_rng from pymor.vectorarrays.numpy import NumpyVectorSpace def construct_operators_and_vectorarrays(m, n, r, k, seed=0): space_m = NumpyVectorSpace(m) space_n = NumpyVectorSpace(n) - rng = np.random.RandomState(seed) - A = NumpyMatrixOperator(rng.randn(m, n)) - L = space_m.random(r, distribution='normal', random_state=rng) - C = rng.randn(r, r) - R = space_n.random(r, distribution='normal', random_state=rng) - U = space_n.random(k, distribution='normal', random_state=rng) - V = space_m.random(k, distribution='normal', random_state=rng) + with new_rng(seed) as rng: + A = NumpyMatrixOperator(rng.normal(size=(m, n))) + L = space_m.random(r, distribution='normal') + C = rng.normal(size=(r, r)) + R = space_n.random(r, distribution='normal') + U = space_n.random(k, distribution='normal') + V = space_m.random(k, distribution='normal') return A, L, C, R, U, V diff --git a/src/pymortests/testdata/check_results/test_parabolic_mor_results/ca3b1109e5bd10d21b0b9bc432d6ac101cf1fa4d b/src/pymortests/testdata/check_results/test_parabolic_mor_results/ca3b1109e5bd10d21b0b9bc432d6ac101cf1fa4d index 4583a8dbd5bc59a02eee63e2c120b7ba1a63abe1..4953f24a95dd9c5e91bf01e1843e8d57efc28068 100644 GIT binary patch literal 3722 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 diff --git a/src/pymortests/testdata/check_results/test_thermalblock_results/105686ef68a9b4b3655c790d395526c623efd162 b/src/pymortests/testdata/check_results/test_thermalblock_results/105686ef68a9b4b3655c790d395526c623efd162 index 2e2c6a2583e57007e885a200c14d7869a1a57524..1e6a313fe13903a126744ab10ff945ba99a46886 100644 GIT binary patch literal 1558 zcmZvcdr(wm7{-?c1YzZ(qGDtuSLLGMr2ye2L0)magoth~$MpyYSY24p?t;n)h^7oV zCR)N$d5N511{0LT%Q%^4nqz~Sl|yP8*pQfr*dOXx)^n7`b*7nf{&=3>`+eX0zTZV3 z78NC$@)L_hV~G?N83<{Sa*K0pW=o>YBIX)xMsty90*QV0#Y~J0oFaIvQ(R+=)*f z$-c?O4tp@{%Lz%_wmz7~)Y3U3OIP3n+e3+N=xs1PY({iw{hj>L`(b;+z7ie)-{VsBs3j*GCpVBgHU z(o?)^5Yw^8{r?0%*&<20!6>^>Ty1z>y0;+v{NG}_K?sv97PG}F`wH?Dm$73ZI? zsRDk5X>D1qZEE#G1qT4X%Czp!+R~0)THrBEY5rXaH&4w~@N2+S_u*KDiA%gQ?27 z;(hnzZ3<2UW~Og%Ea08(H-Tp==l=6hry3UprvuMoI^Hw1)0A7H5@g%O%@5-~@dkys7p`TfTybyyHIVP-gNLc6n-?=wPR-3U#W~wf6Sv;ZV7KFfbqP8M?f8=q9B}`kQHZCuVZGtf1M(mC#==M&B zSjzNe<`es=j2;LPH#=k8%JTUmA(mnDer#Cmc1MQD!6hp_rrEGaX?8BgxcS$oCP<>B z8|HYJ91=t;@?K1*8=};ZmnY@ej3sy*Q9-^S7oY+o(@%dDW{or_VdmvbJ#D)CYg0Bu ztYGT8UL7K;Y9UMzbqhSMR_%o-Wcq!5sW7v@4x)(ZH_bJ>HZvc>%+%@kO;C0vL#)J$ zwc%vShtuXj$e4Qjb)QKsGa)Pp%Nf&ixBDzA@Kq`>78zbn&nn2a7pXlD!V~p!P_ZDF Jyhyn?`d>5OVfFw3 literal 1558 zcmZXU3s6*L6vq(|VG)prsDwx+&rks$CauwEfy8K_GnAIv zFb$YV8fr`qgb*r%_iD|!};@gNyYTJz^^kB zEAGs?=sc6-vA}OIs&{H!=WICvJdROe@rPIMbR}{e0368Z$df44S(iH5s?=xk28~WR5I;qbrXoHH z*E9@HS%}Vec^e)wol%w4_GL_3DL+FrFov_aYWR}_2NHp2Fse~^yJVlT<2V*Lj!|b? zX!Gf>bAX9aTVRP_ly)=6GlAn7ojrKFYFcSEZ~~(f2UFV8y;?bb3wRcz?~ZHrbGDBI zPGnS_`LKw>Iys&V%orVdfD zwC2cVj#GeBG0Yj0wpsSX0?%c%<%_Z$_u4#;(}3S$v~RlO+E&Fb;B*u=QN8AZ@)5`H z0^>6~d#Eho0Xf_DeV2#AYs$KIHh^QfBkxH6951J diff --git a/src/pymortests/testdata/check_results/test_thermalblock_results/29e5f263a02a275f4d29b7e5bdf32ad20c1e6346 b/src/pymortests/testdata/check_results/test_thermalblock_results/29e5f263a02a275f4d29b7e5bdf32ad20c1e6346 index 4c4544799790fb1d010890bb5ee3a9d2d0d5dbf3..e36146a941aa30738c25e6e4bf8768549a738f80 100644 GIT binary patch literal 1528 zcmZXUc}&z-5XVvRKv}%7^#ZYqB7!JZpaKp`VWfgp3%DphmStVmrS8(-?s5v&2-Q?j zqqY{cQ3FNHY7R3XkMhg_FQcb^4X;{Yn+E+f!B|Vu={o{1_PV;W9B=>;)>&BZwju}Upeylopsc?fc@>^5Ys#1^B6DiyOxpmt7vxJzho88xelOEcK3x=*3Ex1rgM7`#F($eEGp1rM2m2WR z0LZ-|KWK>_Wz?==I^qAK{(O}Rav#X|+5_WGG~Hn;pIVvdv~N3Pd&pN-4t=oT;RvP! zC$n}JcpiqVfP5oi@cmac{Qj4=vIpF>CR4J>Ecch>0VvNv{Ekc~-P=66w(nF+@*t+KUHtaw4WraJ z?mgi5nKI&47w4W|3_O^L{ZbZ%{H_sr2=Gv*Oq<6@f?BOfX6WveP^t{;1>q$&SYF? z8+vEEoxtOPJ(%{U-RpMx=nP;_CfWUA`O~5(fhPca;Z9$b%uKU?2|ST$nX}F%qrsp? zxqEk%yQ7wqB-tAaCR+#lp;fSN{7u>=st*4tlI#P2U;KVdXI0~j@mbzD`&V5RI2Tp008H3jjU;juIH9hA%$0AXwXefPN}bJp2Ap!H`A-C?so2WRKEj7*RBIv#Pz)4@o_kXmJ%zZ&n(H7&duIBVMM*_lp@Kgogt@8_!j`@bS3}* literal 1528 zcmZXUc}!GS6vi1UP&SdJ1!bGIBB+3@A{4!f;#FJ_EjTdq7zY@1EQ@buSVSZWLK~D; zYl^lk#wbRxv<|JHC`(HP)Myn!P(&zD5GY+@LxP6b?a(!9*M-SC1_|bkMQ_wgK!; zabHd8fRK{!J%-DRHa&vWELfj)_hw2*%z=4VYg{aLd@b`QgYG1H_Pjo z61EZS-d|pfI*orwNo~P}=7gw1*v8Bb|7_6K)0dJfWfKvtjp5nVU*V{3xj&TOh&?S{&C*TFxyR%Vq#Igw(eaddH%sCgT%diXZLQ0NLmGT7Xbi6OeFGai`zGal$vNgBq8-I*V{85h1ozp6bJ3B)F zFb1eud}`-$z01IXxTlMTQIE358D0*&f|7e?^C~NBZU7UEWt9fm+Z zDE;JLYKGSXhfs1Ox}U#90)aQ+p0WtWE5jZ^u{)m!piP@g(8wITQv!{En z)z5H_k3iCP?Ie*_lF018xtGdSYDJ<0@4*vav`P=NwLlbH|0|CVFr-IR>9zLdlrNrdJmlD>I7700V*R(>6XC+f|h6pl}w*)hfaUvUC!nE(I) diff --git a/src/pymortests/testdata/check_results/test_thermalblock_results/38a8bfe939551df7383a6058a2862fb9d2eea5a4 b/src/pymortests/testdata/check_results/test_thermalblock_results/38a8bfe939551df7383a6058a2862fb9d2eea5a4 index ee85571d15ac6f961709c2eca66a19d66f9e3ef5..f178a2364aa7b0d0e87df3c20671a3903e41a097 100644 GIT binary patch literal 1552 zcmZXUdr(wm7{)ho5l~Q26bW((4Mnh87c303+2S*b} zDFH8|&XDAd@HRQ5BbOPoKgx9+f>b6;gG9W9=$Oe&W@FDmN}T>Ue>~6c{m%Qn&-u=m z7ob*`nAHZ0C>E|&mlwx~s`&1llw%$9&)RamepBv=(1teP#jl#f_1DFU^l z6$(YZ-fFb!ti}&TtMrB-jYv`IwMzIUW{b%xISA6oKtWOlGTnNyb-$vki|OmwKYKfB zmxzMo2)k2Vaq^;dBU5A16n~TJeAuI4UvKQH?Af)C=`6SZ=(TD9wiE2$mEMg$o$oMR zi2k&&FX9GlXV_P3l{XW6wlJNxO=*ctnho0p_T`Ih6T+Dj+H#@tEJP^-#JQJ9< zcI-J#3=yPIEC?H(BR>6qIS$ra#A4l0{cJ&s zKz$^h_n3CwxtnD-DpUJ5;18I>e%<@AUw@j6lYuo%Rlgn2Jw)!nDNI>= zI{b@b#>sd-a4J*v)y+MzUj4vn=v{fH>So<&8K(nhFzvkTq>oHF0K9-{S^bEz#m@&n zY$k9PlW54=5pgIEIGZV8vs?JRwGDFiZbR7*%QHui7GhEE@GP~%veb_K_uT7-t`*!E ziv(#g7Uf}4W$fe9Ew1RagvnIjn$(o7fe?`W#-P}NwhD-)DEq6^S*f-|=x}i2Gb8Md z#~_wr^jS@H%0KEL@Ml*gE?c0#A-^pj=fUl1UX5oEL;*<475fjK;ShyPiR~`wBP%ZH zMeX?EhiTXbkyO#Eu3A@lhN2>|z-BBp+KiaMAV|wGff3hAR$lFcF$eMH6-@U+59esE zoP$`&bZ1w&B6Yg~qL}HLNz*a8^>+vp*0e<*nPKpQC}HY5SlwM(^$fzybh|V*JZiTJ zVioSr1BW|(?S~*F2<_*&Le4`73se6Z-+I3V37EiLp1@e5dwqLWL9)H-`LYOC)aym7 M1*!Dat5(nW2bB_EA^-pY literal 1552 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 Hm&U&U;sDQY literal 4668 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 GHU0%D8QBB? diff --git a/src/pymortests/testdata/check_results/test_thermalblock_results/c8a460e412d9170f12508ab3608fd7e1e1750603 b/src/pymortests/testdata/check_results/test_thermalblock_results/c8a460e412d9170f12508ab3608fd7e1e1750603 index a54e1b621eb104964c16e026ea34cd76c2a7594b..b90eba01326dd7f40f3713d97a5ebc9e76cc4adf 100644 GIT binary patch literal 1529 zcmZXUdr(w$6vsD&m4~nbDhMjC5CoTpL=Y|hP=sG3gaX#`xY7#Cx;z%{?gFA9f;I^a zK^8MOB_mlp#6U_QFa?wVV~hd~jpOwNpnT)`71` zRi>!eWeltB$gnz&lnm6~%kDNhK}m!EK;pPH~q|MGIUD%WdTNn07P{0XwnK3076kW?b&KbbggZ)c!sl%hPYD&H=)o)xl zeg?Ka?4M-4m4Ehaqoi4IS*BNi2(|(29|{^KA7uYS$>+J#?|l_w273kUTTb&Z!|LPO-5hhhCQH8WDao7n4_H$nlOWw1 zU^5g^W4L{9n+~u!CHcR0ZWx@j=kQu!3rboR&Og4BRSaxN$y*N=-Zmfdw^TEjZwz*@+i4I`Jf4DNT-DjQ3>N7ZLA;zc(d)XLW=ZEjJ*^OO&*q{XK7M zoG`(9Nla>?CVLxDu zUl+eoThZYRydBv5u2#PLD2M%lcTmzDJr(y|#R}j6N*bG^eip<}a5xZ{Qqoy9I~_TZ z4g3n`{-bMr|Eb#?4g!9al5hCD#O}&!;GLATj?BAN_117W7&wHIua$GtTygMh|8C&jz|N*A_w`&k90rV;zk0cNW!9-I;0R!!)*-v(e6IIK zi@n$A=N^WA9f#h~?4?+vmtw(xa~~^JspTmWH8w^h!$#pyGzRd{$=Q$tPRJC4n!KDv zwW4qc1{Ik0Ub6^Ig4m14%~%!&_WlhKi%-+**!`H?!w_#$a-FX=(Jr|MA;Bkn^%D!b zGDC=c_}=>+`2ib4A>!~Z%I$^KGeHpX=)5Gtb(b3NLTsuzOd1zND-xCxi4D0L5)!0w zYI%lSEk^}1hTV?}BbBxZuf&SUqD!TD6fUA(F6JTE`-zt~5X-Q!@I&{Kb}1 zZqiaH`Ck5x;6_0_go2Vmuc$iZ@#_$&Sb*NT!GSj;Akrx5A2x0J)}jxB#UfSej8>U= tLMX8ir~dM(%oLykW3B?ZBKFzqsTfwhl;&v=R@AdW=?t5(bpLdhe*p-RY0Urt literal 1529 zcmZXU2~bs49LCvY@j)Pp_Shg}`C$6d;y?z8+kk7kI#9zwfaP#F9}=I0K^M zGy^izM(HpVBS=sJDX|P0B(ey~rV)iuL~fHy-&Gu+)6Baw@B4oL|Goe7oqKMyvx^Ix zn&_LE5$`OrbASBXA+qyi^h-1fIW?i!Kut|8mQ~7?Ql&g!rsUKaP9sd46>Gy!&rqZ( zIZcMs5;B~&kdpDf^pB$in<;6IY4Dl7C6O_l4(yp2-)q&0yD0gts-oU2YYg@}*kdu) zy)#`ol$_b_o+kNZ0Jbjd>ko?mDKUDRlCF&n_fnnOVC%uY)U<7!Gk8SFH{KTU8gXv0 z^j!52vKP!f<|7^EPY)-j14V8lA_Vw)%a`l>@o3*Fz1TQHwjJ^)Dq&`pc3! za~s$K*mLUwK1x=)P}1paUF)~70^1Pw%mb-;rb#{}XBRIz-(9JR5MCV))pmz}<>4mf_4ODUe_7%F)zg@i=qfjg+)* zt=ZC_TL!#|lG3A%`@NU!cx(Y|NlD|MEq;!cA;4CY3e9>oO# z+fZ^yAPI6mk9}ci|Bgj9Fj78zusJTb#*D|Vz%NtM+v-~w>az^&MoCqz*AJUIyjjGpzZUmemfIQ5 z9Su8F8$47R9@i^msnWH2PlnrxdM{kBQ1YX2*RlCyT6oB-l(dxRTV!;e?6B zxvA4gj1904qMQv#H7X3|u`jS6y4^}dUg_$<1l_}Pf{xA+9{U3aP;$ZW^k!}AYrugB z9Psxi#1XIE0dObAc<2)Pu*b0aJ8$ho>|=ECqfYher}Ral7&Y zkKX_erKH<5X_76n1P;SS_!o_oi+ibh>_*iu!61D+IvL>gVqk_;0e7;NXV-`LL+ z>md$MGIYfu_^a#@2q`w=rlYQzi4Md;+#q7~_x?}2Ay^29y9L?qmmp$s`eP$UwKC2@ z#9=2-*Dd%JjzPrZ5cky`(W|OvW#SF0FO$lEh$7)xomjxHAt6B)mnF}UXUVaGM21Vk z3gncGWIA0`Sl+^&lPS4!`S{6FmOl|GlwAJWDssW25+W7p)D|CDnTUW$qh!AD-iPYP z^B~gEGu7s}+RmTB3`!=94=bFDRv|JexiKiX{!L_kWd@gR_Z)^?{B4As??rWOb>KdKL^mVeI zU9_7Q?D5hjlm7VX#&k=j;|@D@nTm6;Z6w>Vw5Pu?0edgLyG3tN3wr|WtAl@jsA=nG zI#J>uk#iy(_C(knLsJWPYj| zanRQqcskRTeWgSBQ(`4N1K6GEa;cE_+xx}9C~5!cl=zYZ2PEtX{4zSV@4-onhfcs= zOeN&uo>+cP!mj|oinGgoQ}^Dy1MJOI5qdZ zoDUqr)a;ZxFx#pSIFzZe;`;qFE}0Sz0}f~E%=(Nk+^Yd5rq-u9T@khg5?%ltfeHd2 z?_G2>6nG(eb-u4SZB~zj-vo}t^sd;Ze00|eI10nLqF-^~qm=>}fH)MA&e{V4Hg3BLs#i`0A@?&UxX>N$9IvQPssS)1}_q zj`ZH7pUVU>8H<*i)}t_~M`8Ex+$+_3gCSe2h%2yYB~yKsD(A6g3{H9n*p}f{tntv5OXJ4+F zqW)uuRY*bB@42}(A0mzEVpB}Pk+!oCtC?;#ZxD)XXR6c+N7G?4Nf50_AB|H?lxj#% z$IYlQY781wutpHqq5=(Wgh1=EUo$3S=M1JhHg(!$|2BwqAZst|sKzmZn8|d1|G}~G zM+YFXm~J&WhlOo5htT4b&MEmV3m77sX&|vGcJ&V{Aaa;`tCyCY^6G>Tv8MO;s6BNv rAao%18_kny4XD6gsz9SvzL=g~5DlYI&x3G9y%@A!5RIdWt@r;2gi>a$ literal 1539 zcmZXU3s6*57{`~FDDnujMdfKkfQkqxiUCJZIjDf8i!8X8`MxFL+|NAN{$AH#3z&- zUN|+=GGh*GJJ`P%!=ke-;wfpZa_k$jdI)W zeoRTT-M!R$sV8g)*uUCFS8fmkDLLimz9;I=AZ$n2y`xHLzDo%u$L^l@8%fPpspJ#T zb}nD6eA>DbC%lBX|5Yp>hcJ~+m!}g2XHIazcqZYQOi5eV;X~43p67&@DS1D0b8nF( zo5yleU{|z!A($&`e*ip%lIm}ayCmL;3{M4~hVm5aO?khc19qdNvMf3Nc`6)eUrixfd^a=f1?)}9fkW2&SGNWLPp71~qH%b`^i>SM0_;Oc`|e}= zrs@2EU!`QX{kv*)`F4hVfnTG<;2Aye_~Hn#A0;2ubs94|o-phW96-rFho0iN;|GCf zP@;FZ_ULAW55qHoXHjx;$Kd{1p=rRF@_YODzT@R`nc>;MLD)FTu8NvaSKv96G@dHC zpQ2Us7`NTnxW{H0%n2b#2sKZbYnJeDyiS#)9E+dF31Ntzk7ogLx}2@f-|ELA;gp=a zeLxIX)Up~D0%LO;^|GFX`sKh8l$^~TiQRrh%5Wrb6eSl+OzP&yoxp^WZx<&G{Vm15Sxh%`#Bsg0vQ@TCw+N}6g^>B-xq5a~F+KS%g185SXU zh{U!BR|a-LtV7t1=$#}$29bebTW9g(i>e?pDYy(fykz0AbjqklrT4l99#$q z^M=ww+aPi&x!dqLwK~bdw76+Pwfa%(E|V-O4zO?P=~7$s zFT_{Jr_J@saf9s$yS={U-lmO3#Gf`)m&O0{3G7#3->EMx&l!G$_*41ON4~r>3APjL z#=DJ=IIT1B2R`%oI;><5Y-iY4J5MZbZZH#n^oNeUXA;lB9tyj8#UIlhFE1g!vZHud z-uY74!(iWDIJ-AyY$qD5scP_c>4NP7`-Z2mT8OPDzUtivF}LC_!ghuI)04Hv!G4E` zKVEmb^_!FgSr(iSJ6n3&_#(QSs0>Hl|AGh(D3i_RT(dBV zu8Xg!Utis%VozW%V*17Fr71~Sz~02{e{CN-C;Oy|eSmeu^*%GElseS_k0Y+m`)G1R z!aFMV1s+dKx4E)w)FlOY0&%TnJGwp{S*c<_V1HunTNXC7etrgcB5`f>)y_NhdsG|% z97xQ+isH3DE&_g?xb|s>wtrFJqvA=xdThR5sL*u`0S+SWaS31X!L=$C2Ln$=uoK-= zL;iFI4k4~jN}r@XA0c7f&i&)=pQS-mLa`vsc8*hQqCT%T%UQ<$`fyR1iuwqA(}>NV zV}81?t`~<)C+6!Dc_Fu;SnVMacm}axhpPH>J9B_JV%`r{cnBqC6*J(O#3r}vOiivu zz&x>lY`em^3#U~a1w4z`G~KO=505zkM?>sssC*hYLB+Fy=is=kx(%z!z6XvWHf*Q= zfgbG;70(4GViB6n_X-cz0?#An)zQ4ecy_sp=L5e%EW|7H?B(kM@B(Z#6TJuuR}sVjkhE#d6Jk6~76*m{{nOhpA!X)jxI#F~9dcWry!vRJ;^;8L`Os zHo0EuQ9F;rR&Lua6ld;LXYblSdz*R2i%J3(CECtXu!$?U{dez;vc+o3maKR&mWxUf z79|sl+jTYJvULqkdW+alqv6=otv^GE`2Rf%sy%9VL#!Y+?r`Vm!_(EfVkEA^@ApA& zD`!HiM3Sb0?%(u-Atd5@^iA4p?(GmMAj`^&j>MdVNG0alUE6zi=|hM#Vs_?j8-0)C zHVN*wm&v9QS`s00Dau~HTQf^G zK&&CYt)|gm13Pst4d|Z-XxD8>ZsF8g=Z3jB; Pmy6bl%DREA)&~CzqK9Tz literal 1543 zcmZXU3s6*L6vtN(j3q!3dBoEPEV?f72m!sH7cx8Mf)5Z~k?XR;O3O>`?utR9pff(0 z9E>u9Nho8WR7h3~2b`30)Eve%EMO`HqL3_!Lb5cX|5X~-X=d-t{{O%8ecw6fe)nup zs8qU~Os31w<(mx!dTpjrug@q{%nt~E_zMf29}p2L$V+Uti?U#iqf92-q%#>zT9ctj zZxSa7qK(>5r?G>dx5b!i5^V)>ad0s`blP~e7QtP?C(Gu)9)yF7>t*5Z@FQo||%{9rjbO`^Sp!mbfGl-{w*OS59CHYzNpK zmbu-c<2dn+3%xRI(nDd(VfS2H5SKkEp7_=>=YbL1KVUmbcJjBD&H?;ssardApnV(c zr=hMl$`C0cFB=0~DwPuU0)B>=&+a{Th3BRLdlT0; zbYsGVr$Vy+V5e!QJsM+1&EKr6HP3( zZ|3FBOYh^57-CVU_q(n4i<44Z2)u|G_fc@wSWzD^M@(7UWz*OcDPab@m{>&VjKnjk zL%=*S-+=>$*K+l(dI<>83H%>|=7M*>1tobuFgn)bRRvNQ+$$f~8#5V4UT$cXzVF)d8V>jl` z9!QLZNFx?D=;hv^l!Oi`H06))|E>yR6EV-!+T`f8N{DncFwl9_A#xW)2KwPCugE@r zN2k|Jx4ukP38EF7AD^mml4{tzS)Y#SXfPX4L8c(Sj0z0I_l>sJT)ndcJ7?juUvR#o zb^9rZYz&-D@AativmkOX2NT*nwTJ2;a)}=qSKKt0e*%$*HOuU4&PF|e*h2g_UiRYI zq+*DC;_rXB+T#B4+Ylnf+D^Wh&?U@+FkS7s^UmaR2}S From 18738fe1d4e04670b5627284ff8b8c6ad86e7307 Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Thu, 1 Sep 2022 16:13:04 +0200 Subject: [PATCH 12/18] [random] fix resetting of rng state in demo tests --- src/pymortests/demos.py | 2 +- .../ca3b1109e5bd10d21b0b9bc432d6ac101cf1fa4d | Bin 3722 -> 3722 bytes .../105686ef68a9b4b3655c790d395526c623efd162 | Bin 1558 -> 1558 bytes .../29e5f263a02a275f4d29b7e5bdf32ad20c1e6346 | Bin 1528 -> 1528 bytes .../38a8bfe939551df7383a6058a2862fb9d2eea5a4 | Bin 1552 -> 1552 bytes .../c8a460e412d9170f12508ab3608fd7e1e1750603 | Bin 1529 -> 1529 bytes .../ce0770de067386276c9e89afa2d63daeb512a9f5 | Bin 1539 -> 1539 bytes .../ea8f66e263715fd0185d0dd23ab6d57feb6ee898 | Bin 1543 -> 1543 bytes 8 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pymortests/demos.py b/src/pymortests/demos.py index 342d293718..01a3647e22 100644 --- a/src/pymortests/demos.py +++ b/src/pymortests/demos.py @@ -230,7 +230,7 @@ def nop(*args, **kwargs): # reset default RandomState import pymor.tools.random - pymor.tools.random._default_random_state = None + pymor.tools.random.new_rng().install() result = None try: diff --git a/src/pymortests/testdata/check_results/test_parabolic_mor_results/ca3b1109e5bd10d21b0b9bc432d6ac101cf1fa4d b/src/pymortests/testdata/check_results/test_parabolic_mor_results/ca3b1109e5bd10d21b0b9bc432d6ac101cf1fa4d index 4953f24a95dd9c5e91bf01e1843e8d57efc28068..01a375f087e4b12aa41074245c98273ed1a03960 100644 GIT binary patch literal 3722 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 RVMM)Iv{sNaU-w-*^`+eX0zTZV3 z78NC$@)L_hV~G?N83<{Sa*K0pW=o>YBIX)xMsty90*QV0#Y~J0oFaIvQ(R+=)*f z$-c?O4tp@{%Lz%_wmz7~)Y3U3OIP3n+e3+N=xs1PY({iw{hj>L`(b;+z7ie)-{VsBs3j*GCpVBgHU z(o?)^5Yw^8{r?0%*&<20!6>^>Ty1z>y0;+v{NG}_K?sv97PG}F`wH?Dm$73ZI? zsRDk5X>D1qZEE#G1qT4X%Czp!+R~0)THrBEY5rXaH&4w~@N2+S_u*KDiA%gQ?27 z;(hnzZ3<2UW~Og%Ea08(H-Tp==l=6hry3UprvuMoI^Hw1)0A7H5@g%O%@5-~@dkys7p`TfTybyyHIVP-gNLc6n-?=wPR-3U#W~wf6Sv;ZV7KFfbqP8M?f8=q9B}`kQHZCuVZGtf1M(mC#==M&B zSjzNe<`es=j2;LPH#=k8%JTUmA(mnDer#Cmc1MQD!6hp_rrEGaX?8BgxcS$oCP<>B z8|HYJ91=t;@?K1*8=};ZmnY@ej3sy*Q9-^S7oY+o(@%dDW{or_VdmvbJ#D)CYg0Bu ztYGT8UL7K;Y9UMzbqhSMR_%o-Wcq!5sW7v@4x)(ZH_bJ>HZvc>%+%@kO;C0vL#)J$ zwc%vShtuXj$e4Qjb)QKsGa)Pp%Nf&ixBDzA@Kq`>78zbn&nn2a7pXlD!V~p!P_ZDF Jyhyn?`d>5OVfFw3 diff --git a/src/pymortests/testdata/check_results/test_thermalblock_results/29e5f263a02a275f4d29b7e5bdf32ad20c1e6346 b/src/pymortests/testdata/check_results/test_thermalblock_results/29e5f263a02a275f4d29b7e5bdf32ad20c1e6346 index e36146a941aa30738c25e6e4bf8768549a738f80..1a8cdb3a54b518db712e923097de6f5209e45876 100644 GIT binary patch literal 1528 zcmZvceNa?Y7{+0hPvz58L=#wyFBdS#Qerz6$ss_+Ot=V_ZNW=iKE1mOu0<|Hq3A#Y zMyZ2{spLlpsAy1z0s}E4ff=YMZIUxB(4bD_A5ivOq+y+DX7`Wh`Mu{o_kGWMPMoK& zZ!(w3C#gMUt^rfOK$+`G);dq}pk&Vp?X zyXVMg;E~u`O3oaun8;{wggpoLy`zP9{Ib0%`EDTpO!+Z;*mGh3tPQhi`>mRi`a|b` zEN+Z~Z3BByv%@Y-_=l3Fy`AeOlWbwz!u}<#q+RPiA9iu$BmH(6$1zqYJ42~eJT2Xh z73}|SGsYZYoJy6YQVR~O;E49j!~Z!-4z`|s+;O)`$qGoD()#(8d8w94Y_|Y-Atmi* zV_NTtRA47cN;A$R`R5 z{`eLVzX0q($+z;;TIU*u<8?RGks)IAJ9|;yn>Sa`wt{#iJnTd+j_d))4hC= z6#}pz(72YDjB1&RS8?fz>G+kb@G|0G!G9Gc-z@p{Nzo-oY_gh?Q&m3w+@O{C4MD)K zV!(Q$Gj})ZfP*RNZckVL)WL{Y4*VJ=os&g16{G3E1l4Z#U0!9mNyKY_Uk7G9nuCTP z0>1%#@^Wc|-C7Zc0KZAe%^Q)Dzn0nquchSL$u))LR-qye1*Vj=R!jNb6XIBe;goe> zEGTaE5%F8V;Yd#MyrU$xz!A6>`u%?d@b)5(1YU=0r}B5ZK6CIt`Sqni`7@ zz)`q$y2!#@4>#bAlvIt6ojY8i6j#3uyor*IgcM0!@Hp^0*t_(yZ2isSV()FIdvDaw z&8!fOMKQ+BVvL%_*!?&63Qn!zGn5*<7_qFd1&iV^1g-qgq(=>Qil?Nj#{ZsS@HPZX zNpnI@%5X#sL;{9Eh#6~;ABIp+a(!%Dmvm1g#JiN#hUpj8Z3>4_B7vc0sQeRmh(uiT zs2Z=HGZ7F;lze$(^~VpL4?%22>WL72cfVH2G4qUv$>;;oisYG%7#p#MM!{_iC z9u>UD3Mr_7r{wp=yP3ELrpTO1i9ud@;k;Z5kp|)XL3UkHCPX^Igqu#A`Ca5Sf(RFG-N6JOPqLEu?46b~1a99Rk=P%@fw+h)Y0A3{aRu>7-) lY5Q=ZjDuJKpQ(8E^VF=MnMw9E2v5|rLD{U3Gm|&l?_W%XY8?Oo literal 1528 zcmZXUc}&z-5XVvRKv}%7^#ZYqB7!JZpaKp`VWfgp3%DphmStVmrS8(-?s5v&2-Q?j zqqY{cQ3FNHY7R3XkMhg_FQcb^4X;{Yn+E+f!B|Vu={o{1_PV;W9B=>;)>&BZwju}Upeylopsc?fc@>^5Ys#1^B6DiyOxpmt7vxJzho88xelOEcK3x=*3Ex1rgM7`#F($eEGp1rM2m2WR z0LZ-|KWK>_Wz?==I^qAK{(O}Rav#X|+5_WGG~Hn;pIVvdv~N3Pd&pN-4t=oT;RvP! zC$n}JcpiqVfP5oi@cmac{Qj4=vIpF>CR4J>Ecch>0VvNv{Ekc~-P=66w(nF+@*t+KUHtaw4WraJ z?mgi5nKI&47w4W|3_O^L{ZbZ%{H_sr2=Gv*Oq<6@f?BOfX6WveP^t{;1>q$&SYF? z8+vEEoxtOPJ(%{U-RpMx=nP;_CfWUA`O~5(fhPca;Z9$b%uKU?2|ST$nX}F%qrsp? zxqEk%yQ7wqB-tAaCR+#lp;fSN{7u>=st*4tlI#P2U;KVdXI0~j@mbzD`&V5RI2Tp008H3jjU;juIH9hA%$0AXwXefPN}bJp2Ap!H`A-C?so2WRKEj7*RBIv#Pz)4@o_kXmJ%zZ&n(H7&duIBVMM*_lp@Kgogt@8_!j`@bS3}* diff --git a/src/pymortests/testdata/check_results/test_thermalblock_results/38a8bfe939551df7383a6058a2862fb9d2eea5a4 b/src/pymortests/testdata/check_results/test_thermalblock_results/38a8bfe939551df7383a6058a2862fb9d2eea5a4 index f178a2364aa7b0d0e87df3c20671a3903e41a097..652e715aff86cae0f3f0189f620b526edd4e73b8 100644 GIT binary patch literal 1552 zcmZvcdrXsO6vpv_8_2ETP^Xid3L*um+*HQdknM<~fL17%uU&-}szCkPiXvXnO`U+t zCJH(iqDXZ_jiN%lWP?OAC1Z%_1SPwuFo?2@IJPXh>7GwntjYf9AJ6kU@Asbb^v%ie zm&*%`@*I;+mt7=ZYp~?XbtaS1B-fcOhJ3BXXz~y8o*m>J8sx1A@(vdqOGa)K?E+L| z?dP!Fm}Fno#w^pu7c?He|4%cD0?2SS(FK)JL2)cZyb}j z{G3yBNr0SQkAehVKzmg8cbzYBN@lPq)AhPJ*A33~y1Gv%ji_U0_@0`>v+ z{cC96>R1W;0{b!Tn)NWxlGF)2m1$0D&eJ0YevU?ccd^>>A+VfDvD0nydZ#B64h9Zk zDwi$pnAXw{Jc}u&>!Z~Xl~c5c+i^JV;VeT1QGo-qZG#H43F>*hNtdS?t`8T)2-HX7 z7sd4Hp^)VA<`uXEuShkr>iD9-`BD#af#)$DX|HvhvTZAHG*ell^LP8=+a>%qa17Jl zu;})t*pKD*o|?gx>*v7Y8oPNY7|%0gh!#On79xGf*Vq`M_~ZJM|w&hfa+L zevc{HWk=L@Cm#vF56nze`kr&8y$!$%m~u-l*Ttlbl5jlmLZ+gL+syktU4a)d%}qGo z7L`>l;RN8t7|bp|`R@nUfR`{S6VB%LUb`pZMBpT*eZ47lrJwi!Co||{mQRGE@lHOnG6-O{XiOo$mew^tL*(Pk)>Wxa9c2&&*m>5f-ugBV2qV+L0r%KbtyvJO zvB;wQly{BOAw;Ho3%eg&A?YtOVLRO(y4$iufeB>N1cn05tL>Qu(ekpJ7e!c6uNJKl L#KM<1t_l7ZpQmR% literal 1552 zcmZXUdr(wm7{)ho5l~Q26bW((4Mnh87c303+2S*b} zDFH8|&XDAd@HRQ5BbOPoKgx9+f>b6;gG9W9=$Oe&W@FDmN}T>Ue>~6c{m%Qn&-u=m z7ob*`nAHZ0C>E|&mlwx~s`&1llw%$9&)RamepBv=(1teP#jl#f_1DFU^l z6$(YZ-fFb!ti}&TtMrB-jYv`IwMzIUW{b%xISA6oKtWOlGTnNyb-$vki|OmwKYKfB zmxzMo2)k2Vaq^;dBU5A16n~TJeAuI4UvKQH?Af)C=`6SZ=(TD9wiE2$mEMg$o$oMR zi2k&&FX9GlXV_P3l{XW6wlJNxO=*ctnho0p_T`Ih6T+Dj+H#@tEJP^-#JQJ9< zcI-J#3=yPIEC?H(BR>6qIS$ra#A4l0{cJ&s zKz$^h_n3CwxtnD-DpUJ5;18I>e%<@AUw@j6lYuo%Rlgn2Jw)!nDNI>= zI{b@b#>sd-a4J*v)y+MzUj4vn=v{fH>So<&8K(nhFzvkTq>oHF0K9-{S^bEz#m@&n zY$k9PlW54=5pgIEIGZV8vs?JRwGDFiZbR7*%QHui7GhEE@GP~%veb_K_uT7-t`*!E ziv(#g7Uf}4W$fe9Ew1RagvnIjn$(o7fe?`W#-P}NwhD-)DEq6^S*f-|=x}i2Gb8Md z#~_wr^jS@H%0KEL@Ml*gE?c0#A-^pj=fUl1UX5oEL;*<475fjK;ShyPiR~`wBP%ZH zMeX?EhiTXbkyO#Eu3A@lhN2>|z-BBp+KiaMAV|wGff3hAR$lFcF$eMH6-@U+59esE zoP$`&bZ1w&B6Yg~qL}HLNz*a8^>+vp*0e<*nPKpQC}HY5SlwM(^$fzybh|V*JZiTJ zVioSr1BW|(?S~*F2<_*&Le4`73se6Z-+I3V37EiLp1@e5dwqLWL9)H-`LYOC)aym7 M1*!Dat5(nW2bB_EA^-pY diff --git a/src/pymortests/testdata/check_results/test_thermalblock_results/c8a460e412d9170f12508ab3608fd7e1e1750603 b/src/pymortests/testdata/check_results/test_thermalblock_results/c8a460e412d9170f12508ab3608fd7e1e1750603 index b90eba01326dd7f40f3713d97a5ebc9e76cc4adf..cd210e98d4bf839beb324eea48c58e947785b133 100644 GIT binary patch literal 1529 zcmZXUdr(wW9LE=MSz&qj;HiKlffOtPVuXeVaq+YgDVn-Mx8(|Vad|A<-39T@Q)33A zEOihHA34LIfP|o9kRuY4kRo7OLP7V*G@V|a7eH#fyvKPr1;$Qgvs5oe zw`KSlsQce$N^_KPI$fGh&(CD|S%_yg{&tj{cB*v}s{>eux2I%Rc3Jd{X#*^_a{zV( zE(;jBWW5B~iIS|+XfF%Lb^$vB&jCI(_?y|hMBurU?2H@o-`YJUU>9Il#4D-Ezb>r< zcB5p^U&B)OzRd!j2kefST|#EclCA(F^X*>yOiBmO~8J@3-L|G4ii83-vIWfBzLsZDtzu^7I9ln#yy#3 z0K*4j!6M^+K}P+a*Xy_x)nxr*hJOR~!T7&P$0a^?Jqgb>?>z|ojn@>$XS>`~L;l#dO5XqGM^rdrP*uxBn6iWKr<+Tkt_aIX7 zLfN*|2R9W#XhELIhx3o!he)HO`=X88r{{7Z(kbatNZVrS{2_Sk(;QcvJFbM#VU6N$ mf~s1K31$itXtb)AkEdsN!_+n}itt3eT$I7^nN#P_@cS2b>t`we literal 1529 zcmZXUdr(w$6vsD&m4~nbDhMjC5CoTpL=Y|hP=sG3gaX#`xY7#Cx;z%{?gFA9f;I^a zK^8MOB_mlp#6U_QFa?wVV~hd~jpOwNpnT)`71` zRi>!eWeltB$gnz&lnm6~%kDNhK}m!EK;pPH~q|MGIUD%WdTNn07P{0XwnK3076kW?b&KbbggZ)c!sl%hPYD&H=)o)xl zeg?Ka?4M-4m4Ehaqoi4IS*BNi2(|(29|{^KA7uYS$>+J#?|l_w273kUTTb&Z!|LPO-5hhhCQH8WDao7n4_H$nlOWw1 zU^5g^W4L{9n+~u!CHcR0ZWx@j=kQu!3rboR&Og4BRSaxN$y*N=-Zmfdw^TEjZwz*@+i4I`Jf4DNT-DjQ3>N7ZLA;zc(d)XLW=ZEjJ*^OO&*q{XK7M zoG`(9Nla>?CVLxDu zUl+eoThZYRydBv5u2#PLD2M%lcTmzDJr(y|#R}j6N*bG^eip<}a5xZ{Qqoy9I~_TZ z4g3n`{-bMr|Eb#?4g!9al5hCD#O}&!;GLATj?BAN_117W7&wHIua$GtTygMh|8C&jz|N*A_w`&k90rV;zk0cNW!9-I;0R!!)*-v(e6IIK zi@n$A=N^WA9f#h~?4?+vmtw(xa~~^JspTmWH8w^h!$#pyGzRd{$=Q$tPRJC4n!KDv zwW4qc1{Ik0Ub6^Ig4m14%~%!&_WlhKi%-+**!`H?!w_#$a-FX=(Jr|MA;Bkn^%D!b zGDC=c_}=>+`2ib4A>!~Z%I$^KGeHpX=)5Gtb(b3NLTsuzOd1zND-xCxi4D0L5)!0w zYI%lSEk^}1hTV?}BbBxZuf&SUqD!TD6fUA(F6JTE`-zt~5X-Q!@I&{Kb}1 zZqiaH`Ck5x;6_0_go2Vmuc$iZ@#_$&Sb*NT!GSj;Akrx5A2x0J)}jxB#UfSej8>U= tLMX8ir~dM(%oLykW3B?ZBKFzqsTfwhl;&v=R@AdW=?t5(bpLdhe*p-RY0Urt diff --git a/src/pymortests/testdata/check_results/test_thermalblock_results/ce0770de067386276c9e89afa2d63daeb512a9f5 b/src/pymortests/testdata/check_results/test_thermalblock_results/ce0770de067386276c9e89afa2d63daeb512a9f5 index 8349d79502e7bfaef26eca3fb8e4bc1d6054cd98..c6ac51d892ab213817caf6ab421a66e662be9429 100644 GIT binary patch literal 1539 zcmZXUdr(zX6vhET;*IeLl&8o`yz)j2WD5y9BTy*dA|5XXxfieUI_F*p5(fzLQ4T}_ zmY4*L1`Uj*5SURyqy`BELJ^3B6c9GdRF3hO>^n+v9cRw@X_KktkKJ zP>QmpdzBfyh~;@U*Lj1T*Z&WrTcB}>Q*#mPsi~x-guU3nkwhom*(1nj>s& z*u(i*O65*_NX&as$Ij?64TOam7&cTSu+%ZZ% zsl2~@+q< z@~CtphBHRo6NiacB23Ej8N7m9#c)rfJtlZfDS2=5-dQdE!z{y@Q6gQd=X`a)AB*G6 zfuEt|TFj-&+^Q~M3rZ@g2a&%M~&KTLD|6y|Y6r z+=FL}8+{A5YUApvg$cBiBxjx*QvGy(QNYG>XU^_cEq(Qe(P zb}#kPli|Fuz*{vNAC+t##`Drt$x^&8!}%e86JCExS|dvu#BY6pLpGz@mQqjBbX3UU zS>OPC?S++ox`o!jft2*m@9C`VO&0JL;2>P$Y}?_k*XDo;CB3fqrWIe_67W{wU`o2~ zw3#HH@&XQ_x_S8t1eH90p7&`8s92IOl^{ z;O8l63em0cD!eS!s;EsJJH-%M5FVi_P|ltynMH3Wl)xYqk&&SD;wt$^B2v4U&bBz@y@XediZiu zvEfqhRr(pjaIshvr`jyBN=9O%|L(m+s!+<)SS2P#Jj3n6q6A9bFZNzAmqg*D-N^Gu zul`R%mJkdEM{7x9lBO@j9!fsDNRxpHwk{%yZx5bmS5Q&u3|9;M4*V&IDSPX=> zK|OvtAFES)W*!=@A0KVU1CJ wck>^@kIrN3ww4kLn>bXkN~l1dE_pmX1;Z(q6L=Ja5%qXb7Q`Tzg` literal 1539 zcmZvcdr(w$6vtOY9-<&1Di8`pLBgUaM#$rcfk!Y15OL8i+lq@W!ouBM7cg*m45Q*B z%B&p42o2P97!hYkOQWX9RL}+)l!u6l541tXl16)cuTrct&D{IP_xm}&-|w7r&;70R zmdi6WdV}1kPBUcd{!L_kWd@gR_Z)^?{B4As??rWOb>KdKL^mVeI zU9_7Q?D5hjlm7VX#&k=j;|@D@nTm6;Z6w>Vw5Pu?0edgLyG3tN3wr|WtAl@jsA=nG zI#J>uk#iy(_C(knLsJWPYj| zanRQqcskRTeWgSBQ(`4N1K6GEa;cE_+xx}9C~5!cl=zYZ2PEtX{4zSV@4-onhfcs= zOeN&uo>+cP!mj|oinGgoQ}^Dy1MJOI5qdZ zoDUqr)a;ZxFx#pSIFzZe;`;qFE}0Sz0}f~E%=(Nk+^Yd5rq-u9T@khg5?%ltfeHd2 z?_G2>6nG(eb-u4SZB~zj-vo}t^sd;Ze00|eI10nLqF-^~qm=>}fH)MA&e{V4Hg3BLs#i`0A@?&UxX>N$9IvQPssS)1}_q zj`ZH7pUVU>8H<*i)}t_~M`8Ex+$+_3gCSe2h%2yYB~yKsD(A6g3{H9n*p}f{tntv5OXJ4+F zqW)uuRY*bB@42}(A0mzEVpB}Pk+!oCtC?;#ZxD)XXR6c+N7G?4Nf50_AB|H?lxj#% z$IYlQY781wutpHqq5=(Wgh1=EUo$3S=M1JhHg(!$|2BwqAZst|sKzmZn8|d1|G}~G zM+YFXm~J&WhlOo5htT4b&MEmV3m77sX&|vGcJ&V{Aaa;`tCyCY^6G>Tv8MO;s6BNv rAao%18_kny4XD6gsz9SvzL=g~5DlYI&x3G9y%@A!5RIdWt@r;2gi>a$ diff --git a/src/pymortests/testdata/check_results/test_thermalblock_results/ea8f66e263715fd0185d0dd23ab6d57feb6ee898 b/src/pymortests/testdata/check_results/test_thermalblock_results/ea8f66e263715fd0185d0dd23ab6d57feb6ee898 index 8ec9d8ff9e8454f21ff1a76775c6b94f278358f1..f4725c3c4a440af2d5a75e89264b04fa90fca3e3 100644 GIT binary patch literal 1543 zcmZXUeNa?o6vlU9!9WmMP(n9AMA!vkSwIoFFLxE_5UiA-wF%ed0vBAD1@G>H0y+7C z6KYD0i7}(2W-8ifU^pVEGft+cV-}$qM7Cmnk6Ai^OjPGZ8rErM?;p?eJMTU3bMDT` z35$&6ONt3!$gi*&D+EKaMGy)r!)6A>jQljwGlOEIIrlYAt3`=YKVBk{o~r%CNq;lQ@6|LfqH?g4=lUD3X??ds;v zJSnhe5B&OV?Sovj$Dw_5_@PC9>oTxy_Vx3(+`ESMEA%C;1N|L=aljtmDQV=lo<`dZ zZF_dtgI@n>z@C!8xto>MXuG5R{iRQ4oXzY5_Nc1;Y|RJT(H@WXwbiByT|g7C$JRZr z_2-|X?Sb|$hvgfaJw#wnPV1i9bWV-7C)z*RRdu(WuVBk-3!URN8_}MCcITXihcN~J z0Q==_>E?k+gMy%U!`NnimElEnFHW3@b^pt!cf~TnVkxs&MQ=`&<9K}V@dcv%tlYO~ zP|tJXBw+46QF{(X?&q=HWW;_zlJlCyk`zHYM{ce} ztO8Q7XZfSjQ=1Wo1M${qH!-ob^mGK`nLt98J0#F`!0Bmu~D%inGLd_JOgn2R_ONXAOp)7nFgh#CA&0sT2KGAD`&;v^vIdC3YS6HLmMe!Sm(}B!NzgNa(M2s&07F*=GVt?dzYU>TIU?O~h{jNgz*U6yW+Yn(@8+V0r61uv>oW8WGN8m0d+#xPzVYGkebGL5Bd8G zP~-t|OOoxbELnwu2jX(k)S2<|a}@bNR*r8>KCyi@iUJ^-z=x-Im}%fbV1`wj=AJ*; z$_siQ$3K(91#uKbV~zBlG(%C5P=MRfXftAhVoqF!35>XeRtf5O&7b(@<+#6kwhwo^ zoj_p%t+eA%T5I(#6eXZ_t2!Td^~3j2n1StWzZ)8UI0r>3umeSTrmR*!6lI`wk?dXO zRPqCg6~Ojrk6JI}m{8z)>XUb*SG&+Y7SPHvx86@J@W2G#G=Z_y@bdMnoM;<6|Dp(2 Q)XPQXoLDh-vGS;Y0cFQgMgRZ+ literal 1543 zcmZXUeNa?Y7{(VB2ozA!LYI#f1tAw?K}7K$FQWKrD*3_36}qk$xWFnO-rZFLWkekv zhH=tpDjEz&($uC&G)+LMG(kkn64A!|7zEA6G%=$jK6wfa%(E|V-O4zO?P=~7$s zFT_{Jr_J@saf9s$yS={U-lmO3#Gf`)m&O0{3G7#3->EMx&l!G$_*41ON4~r>3APjL z#=DJ=IIT1B2R`%oI;><5Y-iY4J5MZbZZH#n^oNeUXA;lB9tyj8#UIlhFE1g!vZHud z-uY74!(iWDIJ-AyY$qD5scP_c>4NP7`-Z2mT8OPDzUtivF}LC_!ghuI)04Hv!G4E` zKVEmb^_!FgSr(iSJ6n3&_#(QSs0>Hl|AGh(D3i_RT(dBV zu8Xg!Utis%VozW%V*17Fr71~Sz~02{e{CN-C;Oy|eSmeu^*%GElseS_k0Y+m`)G1R z!aFMV1s+dKx4E)w)FlOY0&%TnJGwp{S*c<_V1HunTNXC7etrgcB5`f>)y_NhdsG|% z97xQ+isH3DE&_g?xb|s>wtrFJqvA=xdThR5sL*u`0S+SWaS31X!L=$C2Ln$=uoK-= zL;iFI4k4~jN}r@XA0c7f&i&)=pQS-mLa`vsc8*hQqCT%T%UQ<$`fyR1iuwqA(}>NV zV}81?t`~<)C+6!Dc_Fu;SnVMacm}axhpPH>J9B_JV%`r{cnBqC6*J(O#3r}vOiivu zz&x>lY`em^3#U~a1w4z`G~KO=505zkM?>sssC*hYLB+Fy=is=kx(%z!z6XvWHf*Q= zfgbG;70(4GViB6n_X-cz0?#An)zQ4ecy_sp=L5e%EW|7H?B(kM@B(Z#6TJuuR}sVjkhE#d6Jk6~76*m{{nOhpA!X)jxI#F~9dcWry!vRJ;^;8L`Os zHo0EuQ9F;rR&Lua6ld;LXYblSdz*R2i%J3(CECtXu!$?U{dez;vc+o3maKR&mWxUf z79|sl+jTYJvULqkdW+alqv6=otv^GE`2Rf%sy%9VL#!Y+?r`Vm!_(EfVkEA^@ApA& zD`!HiM3Sb0?%(u-Atd5@^iA4p?(GmMAj`^&j>MdVNG0alUE6zi=|hM#Vs_?j8-0)C zHVN*wm&v9QS`s00Dau~HTQf^G zK&&CYt)|gm13Pst4d|Z-XxD8>ZsF8g=Z3jB; Pmy6bl%DREA)&~CzqK9Tz From 1f75e3e80241238a58e949cdd4920ebdf3b9442a Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Thu, 1 Sep 2022 22:51:52 +0200 Subject: [PATCH 13/18] [random] fix MPIPool initialization --- src/pymor/parallel/mpi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pymor/parallel/mpi.py b/src/pymor/parallel/mpi.py index 160ea12c21..56150ba29f 100644 --- a/src/pymor/parallel/mpi.py +++ b/src/pymor/parallel/mpi.py @@ -19,7 +19,7 @@ def __init__(self): self.logger.info(f'Connected to {mpi.size} ranks') self._payload = mpi.call(mpi.function_call_manage, _setup_worker) self._apply(os.chdir, os.getcwd()) - self._map(_setup_rng, [[s] for s in get_seed_seq().spawn(mpi.size)]) + self._map(_setup_rng, [[[s] for s in get_seed_seq().spawn(mpi.size)]]) def __del__(self): mpi.call(mpi.remove_object, self._payload) From b2e1d3c648eed2b5584246cc978743e3974dce5d Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Fri, 2 Sep 2022 11:17:08 +0200 Subject: [PATCH 14/18] [docs] document new impl in tools.random --- docs/source/substitutions.py | 2 + src/pymor/tools/random.py | 96 ++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/docs/source/substitutions.py b/docs/source/substitutions.py index f7f9c2c436..52cfbfaa9f 100644 --- a/docs/source/substitutions.py +++ b/docs/source/substitutions.py @@ -164,6 +164,8 @@ .. |SymplecticBasis| replace:: :class:`~pymor.algorithms.symplectic.SymplecticBasis` .. |CanonicalSymplecticFormOperator| replace:: :class:`~pymor.operators.symplectic.CanonicalSymplecticFormOperator` + +.. |RNG| replace:: :class:`~random number generator ` ''' substitutions = interfaces + common diff --git a/src/pymor/tools/random.py b/src/pymor/tools/random.py index 3b79ecb0f2..501a3f8a1a 100644 --- a/src/pymor/tools/random.py +++ b/src/pymor/tools/random.py @@ -2,6 +2,35 @@ # Copyright pyMOR developers and contributors. All rights reserved. # License: BSD 2-Clause License (https://opensource.org/licenses/BSD-2-Clause) +"""Methods for managing random state in pyMOR. + +Many algorithms potentially depend directly or indirectly on randomness. +To ensure reproducible execution of pyMOR code without having to pass around +a random number generator object everywhere, pyMOR manages a global |RNG| +object. This object is initialized automatically from a configurable |default| +random seed during startup, and can be obtained by calling :func:`get_rng`. +The returned object is a subclass of :class:`numpy.random.Generator` and +inherits all its sampling methods. + +To locally reset the global |RNG| in order to deterministically sample random +numbers independently of previously executed code, a new |RNG| can be created +via :func:`new_rng` and installed by using it as a context manager. For +instance, to sample a deterministic initialization vector for an iterative +algorithm we can write: + +.. code:: python + + with new_rng(12345): + U0 = some_operator.source.random() + +Using a single global random state can lead to either non-deterministic or +correlated behavior in parallel or asynchronous code. :func:`get_rng` takes +provisions to detect such situations and issue a warning. In such cases +:func:`spawn_rng` needs to be called on the entry points of concurrent code +paths to ensure the desired behavior. For an advanced example, see +:mod:`pymor.algorithms.hapod`. +""" + from contextvars import ContextVar import inspect @@ -11,6 +40,7 @@ def get_rng(): + """Returns the current globally installed :class:`random number generator `.""" rng_state = _get_rng_state() rng_state[0] = True _rng_state.set([False] + rng_state[1:]) @@ -19,12 +49,43 @@ def get_rng(): @defaults('seed_seq') def new_rng(seed_seq=42): + """Creates a new |RNG| and returns it. + + Parameters + ---------- + seed_seq + Entropy to seed the generator with. Either a :class:`~numpy.random.SeedSequence` + or an `int` or list of `ints` from which the :class:`~numpy.random.SeedSequence` + will be created. If `None` entropy is sampled from the operating system. + + Returns + ------- + The newly created |RNG|. + """ if not isinstance(seed_seq, np.random.SeedSequence): seed_seq = np.random.SeedSequence(seed_seq) return RNG(seed_seq) class RNG(np.random.Generator): + """Random number generator. + + This class inherits from :class:`np.random.Generator` and inherits all its sampling + methods. Further, the class can be used as a context manager, which upon entry + installs the RNG as pyMOR's global RNG that is returned from :func:`get_rng`. + When the context is left, the previous global RNG is installed again. + + When using a context manager is not feasible, i.e. in an interactive workflow, this + functionality can be accessed via the :meth:`~RNG.install` and :meth:`~RNG:uninstall` + methods. + + A new instance of this class should be obtained using :func:`new_rng`. + + Parameters + ---------- + seed_seq + A :class:`~numpy.random.SeedSequence` to initialized the RNG with. + """ def __init__(self, seed_seq): self.old_state = _rng_state.get(None) @@ -32,6 +93,7 @@ def __init__(self, seed_seq): self._seed_seq = seed_seq def install(self): + """Installs the generator as pyMOR's global random generator.""" # Store a new rng together with seed_seq, as the latter cannot be recovered from the # rng via a public interface (see https://github.com/numpy/numpy/issues/15322). # The first field is a flag to indicate whether the current _rng_state has been consumed @@ -40,6 +102,7 @@ def install(self): _rng_state.set([False, self, self._seed_seq]) def uninstall(self): + """Restores the previously set global random generator.""" _rng_state.set(self.old_state) def __enter__(self): @@ -51,10 +114,43 @@ def __exit__(self, exc_type, exc_value, exc_tb): def get_seed_seq(): + """Returns :class:`~np.random.SeedSequence` of the current global |RNG|. + + This function returns the :class:`~np.random.SeedSequence` with which pyMOR's + currently installed global |RNG| has been initialized. The returned instance can + be used to deterministically create a new :class:`~np.random.SeedSequence` via + the :meth:`~np.random.SeedSequence.spawn` method, which then can be used to + initialize a new random generator in external library code or concurrent code + paths. + """ return _get_rng_state()[2] def spawn_rng(f): + """Wraps a function or coroutine to create a new |RNG| in concurrent code paths. + + Calling this function on a function or coroutine object creates a wrapper which + will execute the wrapped function with a new globally installed |RNG|. This + ensures that random numbers in concurrent code paths (:mod:`threads `, + :mod:`multiprocessing`, :mod:`asyncio`) are deterministically generated yet + uncorrelated. + + .. warning:: + If the control flow within a single code path depends on communication events + with concurrent code, e.g., the order in which some parallel jobs finish, + deterministic behavior can no longer be guaranteed by just using :func:`spawn_rng`. + In such cases, the code additionally has to ensure that random numbers are sampled + independently of the communication order. + + Parameters + ---------- + f + The function or coroutine to wrap. + + Returns + ------- + The wrapped function or coroutine. + """ seed_seq = get_seed_seq().spawn(1)[0] if inspect.iscoroutine(f): From 5c5e224b4c70e0fec19dfe40e84a83a23d84d447 Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Fri, 16 Sep 2022 08:31:59 +0200 Subject: [PATCH 15/18] Apply suggestions from code review Co-authored-by: Art Pelling --- src/pymor/tools/random.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pymor/tools/random.py b/src/pymor/tools/random.py index 501a3f8a1a..88dd6f5921 100644 --- a/src/pymor/tools/random.py +++ b/src/pymor/tools/random.py @@ -8,7 +8,7 @@ To ensure reproducible execution of pyMOR code without having to pass around a random number generator object everywhere, pyMOR manages a global |RNG| object. This object is initialized automatically from a configurable |default| -random seed during startup, and can be obtained by calling :func:`get_rng`. +random seed during startup and can be obtained by calling :func:`get_rng`. The returned object is a subclass of :class:`numpy.random.Generator` and inherits all its sampling methods. @@ -56,7 +56,7 @@ def new_rng(seed_seq=42): seed_seq Entropy to seed the generator with. Either a :class:`~numpy.random.SeedSequence` or an `int` or list of `ints` from which the :class:`~numpy.random.SeedSequence` - will be created. If `None` entropy is sampled from the operating system. + will be created. If `None`, entropy is sampled from the operating system. Returns ------- From 089d466f4c3849b32b43a5f362ac992cf7596494 Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Wed, 28 Sep 2022 16:35:01 +0200 Subject: [PATCH 16/18] Update docs/source/substitutions.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Petar Mlinarić --- docs/source/substitutions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/substitutions.py b/docs/source/substitutions.py index 52cfbfaa9f..c482fde83d 100644 --- a/docs/source/substitutions.py +++ b/docs/source/substitutions.py @@ -165,7 +165,7 @@ .. |SymplecticBasis| replace:: :class:`~pymor.algorithms.symplectic.SymplecticBasis` .. |CanonicalSymplecticFormOperator| replace:: :class:`~pymor.operators.symplectic.CanonicalSymplecticFormOperator` -.. |RNG| replace:: :class:`~random number generator ` +.. |RNG| replace:: :class:`random number generator ` ''' substitutions = interfaces + common From b22aa56ae9cfe83fd71844303e6f52ccda78431a Mon Sep 17 00:00:00 2001 From: Stephan Rave Date: Tue, 18 Oct 2022 16:38:50 +0200 Subject: [PATCH 17/18] [test] fix missing import --- src/pymortests/vectorarray.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pymortests/vectorarray.py b/src/pymortests/vectorarray.py index 390f528b4b..4fbd467516 100644 --- a/src/pymortests/vectorarray.py +++ b/src/pymortests/vectorarray.py @@ -14,6 +14,7 @@ from pymor.vectorarrays.interface import VectorSpace from pymor.vectorarrays.numpy import NumpyVectorSpace from pymor.tools.floatcmp import float_cmp, bounded +from pymor.tools.random import new_rng from pymortests.base import might_exceed_deadline from pymortests.pickling import assert_picklable_without_dumps_function import pymortests.strategies as pyst From 8a34c7f55c33a1dc9a05a6efae87f75ca9748eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fritze?= Date: Mon, 24 Oct 2022 09:03:57 +0200 Subject: [PATCH 18/18] [ci] completely turn off coverage targets --- .codecov.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.codecov.yml b/.codecov.yml index 0e9aa6870b..17e0b61338 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -16,8 +16,11 @@ coverage: changes: false project: pymor: + # since we have no policy on this might as well turn this off entirely + target: 0% flags: - gitlab_ci - github_actions patch: pymor: + target: 0%