From 0ddd2c47d9cc50c05319613d4461542ef2ef28c8 Mon Sep 17 00:00:00 2001 From: emmacware Date: Wed, 4 Jun 2025 10:59:27 +0200 Subject: [PATCH 1/3] removing rnd reuse logic (disabled by default) --- PySDM/dynamics/collisions/collision.py | 22 ++------ .../impl/random_generator_optimizer.py | 36 +++---------- .../impl/random_generator_optimizer_nopair.py | 23 ++------ .../collisions/test_sdm_single_cell.py | 53 ------------------- 4 files changed, 17 insertions(+), 117 deletions(-) diff --git a/PySDM/dynamics/collisions/collision.py b/PySDM/dynamics/collisions/collision.py index 18b24d8637..dc3120e73e 100644 --- a/PySDM/dynamics/collisions/collision.py +++ b/PySDM/dynamics/collisions/collision.py @@ -18,9 +18,9 @@ from PySDM.dynamics.collisions.breakup_efficiencies import ConstEb from PySDM.dynamics.collisions.breakup_fragmentations import AlwaysN from PySDM.dynamics.collisions.coalescence_efficiencies import ConstEc -from PySDM.dynamics.impl.random_generator_optimizer import RandomGeneratorOptimizer +from PySDM.dynamics.impl.random_generator_optimizer import RandomGeneratorThing from PySDM.dynamics.impl.random_generator_optimizer_nopair import ( - RandomGeneratorOptimizerNoPair, + RandomGeneratorThingNoPair, ) from PySDM.physics import si from PySDM.dynamics.impl import register_dynamic @@ -47,7 +47,6 @@ def __init__( breakup_efficiency, fragmentation_function, croupier=None, - optimized_random=False, substeps: int = DEFAULTS.substeps, adaptive: bool = DEFAULTS.adaptive, dt_coal_range=DEFAULTS.dt_coal_range, @@ -71,11 +70,9 @@ def __init__( self.rnd_opt_frag = None self.rnd_opt_coll = None self.rnd_opt_proc = None - self.optimised_random = None assert dt_coal_range[0] > 0 self.croupier = croupier - self.optimized_random = optimized_random self.__substeps = substeps self.adaptive = adaptive self.stats_n_substep = None @@ -101,14 +98,13 @@ def __init__( def register(self, builder): self.particulator = builder.particulator rnd_args = { - "optimized_random": self.optimized_random, "dt_min": self.dt_coal_range[0], "seed": builder.formulae.seed, } - self.rnd_opt_coll = RandomGeneratorOptimizer(**rnd_args) + self.rnd_opt_coll = RandomGeneratorThing(**rnd_args) if self.enable_breakup: - self.rnd_opt_proc = RandomGeneratorOptimizerNoPair(**rnd_args) - self.rnd_opt_frag = RandomGeneratorOptimizerNoPair(**rnd_args) + self.rnd_opt_proc = RandomGeneratorThingNoPair(**rnd_args) + self.rnd_opt_frag = RandomGeneratorThingNoPair(**rnd_args) if self.particulator.n_sd < 2: raise ValueError("No one to collide with!") @@ -188,10 +184,6 @@ def __call__(self): self.particulator.attributes.reset_working_length() self.particulator.attributes.reset_cell_idx() - self.rnd_opt_coll.reset() - if self.enable_breakup: - self.rnd_opt_proc.reset() - self.rnd_opt_frag.reset() def step(self): pairs_rand, rand = self.rnd_opt_coll.get_random_arrays() @@ -298,7 +290,6 @@ def __init__( collision_kernel, coalescence_efficiency=ConstEc(Ec=1), croupier=None, - optimized_random=False, substeps: int = DEFAULTS.substeps, adaptive: bool = DEFAULTS.adaptive, dt_coal_range=DEFAULTS.dt_coal_range, @@ -311,7 +302,6 @@ def __init__( breakup_efficiency=breakup_efficiency, fragmentation_function=fragmentation_function, croupier=croupier, - optimized_random=optimized_random, substeps=substeps, adaptive=adaptive, dt_coal_range=dt_coal_range, @@ -327,7 +317,6 @@ def __init__( collision_kernel, fragmentation_function, croupier=None, - optimized_random=False, substeps: int = DEFAULTS.substeps, adaptive: bool = DEFAULTS.adaptive, dt_coal_range=DEFAULTS.dt_coal_range, @@ -341,7 +330,6 @@ def __init__( breakup_efficiency=breakup_efficiency, fragmentation_function=fragmentation_function, croupier=croupier, - optimized_random=optimized_random, substeps=substeps, adaptive=adaptive, dt_coal_range=dt_coal_range, diff --git a/PySDM/dynamics/impl/random_generator_optimizer.py b/PySDM/dynamics/impl/random_generator_optimizer.py index 4e2bb54094..506e8a1ad0 100644 --- a/PySDM/dynamics/impl/random_generator_optimizer.py +++ b/PySDM/dynamics/impl/random_generator_optimizer.py @@ -1,48 +1,28 @@ """ -clever reuser of random numbers for use in adaptive coalescence +TODO: class to be removed """ -import math - -class RandomGeneratorOptimizer: # pylint: disable=too-many-instance-attributes - def __init__(self, optimized_random, dt_min, seed): +class RandomGeneratorThing: # pylint: disable=too-many-instance-attributes + def __init__(self, dt_min, seed): self.particulator = None - self.optimized_random = optimized_random self.dt_min = dt_min self.seed = seed - self.substep = 0 self.pairs_rand = None self.rand = None self.rnd = None def register(self, builder): self.particulator = builder.particulator - shift = ( - math.ceil(self.particulator.dt / self.dt_min) - if self.optimized_random - else 0 - ) self.pairs_rand = self.particulator.Storage.empty( - self.particulator.n_sd + shift, dtype=float + self.particulator.n_sd, dtype=float ) self.rand = self.particulator.Storage.empty( self.particulator.n_sd // 2, dtype=float ) - self.rnd = self.particulator.Random(self.particulator.n_sd + shift, self.seed) - - def reset(self): - self.substep = 0 + self.rnd = self.particulator.Random(self.particulator.n_sd, self.seed) def get_random_arrays(self): - if self.optimized_random: - shift = self.substep - if self.substep == 0: - self.pairs_rand.urand(self.rnd) - self.rand.urand(self.rnd) - else: - shift = 0 - self.pairs_rand.urand(self.rnd) - self.rand.urand(self.rnd) - self.substep += 1 - return self.pairs_rand[shift : self.particulator.n_sd + shift], self.rand + self.pairs_rand.urand(self.rnd) + self.rand.urand(self.rnd) + return self.pairs_rand[0 : self.particulator.n_sd], self.rand diff --git a/PySDM/dynamics/impl/random_generator_optimizer_nopair.py b/PySDM/dynamics/impl/random_generator_optimizer_nopair.py index d0fa9289a1..6b3b8bad9a 100644 --- a/PySDM/dynamics/impl/random_generator_optimizer_nopair.py +++ b/PySDM/dynamics/impl/random_generator_optimizer_nopair.py @@ -5,36 +5,21 @@ import math -class RandomGeneratorOptimizerNoPair: - def __init__(self, optimized_random, dt_min, seed): +class RandomGeneratorThingNoPair: + def __init__(self, dt_min, seed): self.particulator = None - self.optimized_random = optimized_random self.dt_min = dt_min self.seed = seed - self.substep = 0 self.rand = None self.rnd = None def register(self, builder): self.particulator = builder.particulator - shift = ( - math.ceil(self.particulator.dt / self.dt_min) - if self.optimized_random - else 0 - ) self.rand = self.particulator.Storage.empty( self.particulator.n_sd // 2, dtype=float ) - self.rnd = self.particulator.Random(self.particulator.n_sd + shift, self.seed) - - def reset(self): - self.substep = 0 + self.rnd = self.particulator.Random(self.particulator.n_sd, self.seed) def get_random_arrays(self): - if self.optimized_random: - if self.substep == 0: - self.rand.urand(self.rnd) - else: - self.rand.urand(self.rnd) - self.substep += 1 + self.rand.urand(self.rnd) return self.rand diff --git a/tests/unit_tests/dynamics/collisions/test_sdm_single_cell.py b/tests/unit_tests/dynamics/collisions/test_sdm_single_cell.py index 6b9ed7d671..81f10aab87 100644 --- a/tests/unit_tests/dynamics/collisions/test_sdm_single_cell.py +++ b/tests/unit_tests/dynamics/collisions/test_sdm_single_cell.py @@ -256,56 +256,3 @@ def expected(p, r): # Assert assert expected(p, r) == prob_arr.to_ndarray()[0] - - @staticmethod - @pytest.mark.parametrize( - "optimized_random", - (pytest.param(True, id="optimized"), pytest.param(False, id="non-optimized")), - ) - @pytest.mark.parametrize( - "adaptive", - (pytest.param(True, id="adaptive_dt"), pytest.param(False, id="const_dt")), - ) - def test_rnd_reuse(backend_class, optimized_random, adaptive): - if backend_class is ThrustRTC: - pytest.skip("# TODO #330") - - # Arrange - n_sd = 256 - n = np.random.randint(1, 64, size=n_sd) - v = np.random.uniform(size=n_sd) - n_substeps = 5 - - particles, sut = get_dummy_particulator_and_coalescence( - backend_class, - n_sd, - optimized_random=optimized_random, - substeps=n_substeps, - ) - attributes = {"multiplicity": n, "volume": v} - particles.build(attributes) - - class CountingRandom( - backend_class.Random - ): # pylint: disable=too-few-public-methods - calls = 0 - - def __call__(self, storage): - CountingRandom.calls += 1 - super().__call__(storage) - - sut.rnd_opt_coll.rnd = CountingRandom(n_sd, seed=44) - sut.stats_n_substep[:] = n_substeps - sut.adaptive = adaptive - - # Act - sut() - - # Assert - if sut.rnd_opt_coll.optimized_random: - assert CountingRandom.calls == 2 - else: - if adaptive: - assert 2 <= CountingRandom.calls <= 2 * n_substeps - else: - assert CountingRandom.calls == 2 * n_substeps From 0a779b2880731b3441dfc6e1ca8010c967245e78 Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Sun, 15 Jun 2025 00:59:06 +0200 Subject: [PATCH 2/3] code cleanup --- PySDM/dynamics/collisions/collision.py | 60 ++++++++++--------- .../impl/random_generator_optimizer.py | 28 --------- .../impl/random_generator_optimizer_nopair.py | 25 -------- .../dynamics/collisions/conftest.py | 3 +- .../collisions/test_sdm_single_cell.py | 1 - 5 files changed, 33 insertions(+), 84 deletions(-) delete mode 100644 PySDM/dynamics/impl/random_generator_optimizer.py delete mode 100644 PySDM/dynamics/impl/random_generator_optimizer_nopair.py diff --git a/PySDM/dynamics/collisions/collision.py b/PySDM/dynamics/collisions/collision.py index dc3120e73e..770167b447 100644 --- a/PySDM/dynamics/collisions/collision.py +++ b/PySDM/dynamics/collisions/collision.py @@ -18,10 +18,6 @@ from PySDM.dynamics.collisions.breakup_efficiencies import ConstEb from PySDM.dynamics.collisions.breakup_fragmentations import AlwaysN from PySDM.dynamics.collisions.coalescence_efficiencies import ConstEc -from PySDM.dynamics.impl.random_generator_optimizer import RandomGeneratorThing -from PySDM.dynamics.impl.random_generator_optimizer_nopair import ( - RandomGeneratorThingNoPair, -) from PySDM.physics import si from PySDM.dynamics.impl import register_dynamic @@ -67,9 +63,12 @@ def __init__( self.compute_breakup_efficiency = breakup_efficiency self.compute_number_of_fragments = fragmentation_function - self.rnd_opt_frag = None - self.rnd_opt_coll = None - self.rnd_opt_proc = None + self.rnd = None + self.rand = None + self.rand_frag = None + self.rand_coll = None + self.rand_proc = None + self.rand_pairs = None assert dt_coal_range[0] > 0 self.croupier = croupier @@ -97,14 +96,19 @@ def __init__( def register(self, builder): self.particulator = builder.particulator - rnd_args = { - "dt_min": self.dt_coal_range[0], - "seed": builder.formulae.seed, - } - self.rnd_opt_coll = RandomGeneratorThing(**rnd_args) + + empty_args_dropwise = {"shape": self.particulator.n_sd, "dtype": float} + empty_args_pairwise = {"shape": self.particulator.n_sd // 2, "dtype": float} + empty_args_cellwise = {"shape": self.particulator.mesh.n_cell, "dtype": float} + + self.rnd = self.particulator.Random( + self.particulator.n_sd, builder.formulae.seed + ) + self.rand = self.particulator.Storage.empty(**empty_args_pairwise) + self.rand_pairs = self.particulator.Storage.empty(**empty_args_dropwise) if self.enable_breakup: - self.rnd_opt_proc = RandomGeneratorThingNoPair(**rnd_args) - self.rnd_opt_frag = RandomGeneratorThingNoPair(**rnd_args) + self.rand_proc = self.particulator.Storage.empty(**empty_args_pairwise) + self.rand_frag = self.particulator.Storage.empty(**empty_args_pairwise) if self.particulator.n_sd < 2: raise ValueError("No one to collide with!") @@ -112,8 +116,6 @@ def register(self, builder): self.dt_coal_range = (self.dt_coal_range[0], self.particulator.dt) assert self.dt_coal_range[0] <= self.dt_coal_range[1] - empty_args_pairwise = {"shape": self.particulator.n_sd // 2, "dtype": float} - empty_args_cellwise = {"shape": self.particulator.mesh.n_cell, "dtype": float} self.kernel_temp = self.particulator.PairwiseStorage.empty( **empty_args_pairwise ) @@ -131,7 +133,6 @@ def register(self, builder): self.stats_dt_min = self.particulator.Storage.empty(**empty_args_cellwise) self.stats_dt_min[:] = np.nan - self.rnd_opt_coll.register(builder) self.collision_kernel.register(builder) if self.croupier is None: @@ -157,8 +158,6 @@ def register(self, builder): self.Eb_temp = self.particulator.PairwiseStorage.empty( **empty_args_pairwise ) - self.rnd_opt_proc.register(builder) - self.rnd_opt_frag.register(builder) self.compute_coalescence_efficiency.register(builder) self.compute_breakup_efficiency.register(builder) self.compute_number_of_fragments.register(builder) @@ -186,34 +185,39 @@ def __call__(self): self.particulator.attributes.reset_cell_idx() def step(self): - pairs_rand, rand = self.rnd_opt_coll.get_random_arrays() + self.rand_pairs.urand(self.rnd) + self.rand.urand(self.rnd) self.toss_candidate_pairs_and_sort_within_pair_by_multiplicity( - self.is_first_in_pair, pairs_rand + self.is_first_in_pair, self.rand_pairs ) prob = self.gamma self.compute_probabilities_of_collision(self.is_first_in_pair, out=prob) if self.enable_breakup: - proc_rand = self.rnd_opt_proc.get_random_arrays() - rand_frag = self.rnd_opt_frag.get_random_arrays() + self.rand_proc.urand(self.rnd) + self.rand_frag.urand(self.rnd) self.compute_coalescence_efficiency(self.Ec_temp, self.is_first_in_pair) self.compute_breakup_efficiency(self.Eb_temp, self.is_first_in_pair) self.compute_number_of_fragments( - self.n_fragment, self.fragment_mass, rand_frag, self.is_first_in_pair + self.n_fragment, + self.fragment_mass, + self.rand_frag, + self.is_first_in_pair, ) - else: - proc_rand = None self.compute_gamma( - prob=prob, rand=rand, is_first_in_pair=self.is_first_in_pair, out=self.gamma + prob=prob, + rand=self.rand, + is_first_in_pair=self.is_first_in_pair, + out=self.gamma, ) self.particulator.collision_coalescence_breakup( enable_breakup=self.enable_breakup, gamma=self.gamma, - rand=proc_rand, + rand=self.rand_proc, Ec=self.Ec_temp, Eb=self.Eb_temp, fragment_mass=self.fragment_mass, diff --git a/PySDM/dynamics/impl/random_generator_optimizer.py b/PySDM/dynamics/impl/random_generator_optimizer.py deleted file mode 100644 index 506e8a1ad0..0000000000 --- a/PySDM/dynamics/impl/random_generator_optimizer.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -TODO: class to be removed -""" - - -class RandomGeneratorThing: # pylint: disable=too-many-instance-attributes - def __init__(self, dt_min, seed): - self.particulator = None - self.dt_min = dt_min - self.seed = seed - self.pairs_rand = None - self.rand = None - self.rnd = None - - def register(self, builder): - self.particulator = builder.particulator - self.pairs_rand = self.particulator.Storage.empty( - self.particulator.n_sd, dtype=float - ) - self.rand = self.particulator.Storage.empty( - self.particulator.n_sd // 2, dtype=float - ) - self.rnd = self.particulator.Random(self.particulator.n_sd, self.seed) - - def get_random_arrays(self): - self.pairs_rand.urand(self.rnd) - self.rand.urand(self.rnd) - return self.pairs_rand[0 : self.particulator.n_sd], self.rand diff --git a/PySDM/dynamics/impl/random_generator_optimizer_nopair.py b/PySDM/dynamics/impl/random_generator_optimizer_nopair.py deleted file mode 100644 index 6b3b8bad9a..0000000000 --- a/PySDM/dynamics/impl/random_generator_optimizer_nopair.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -TODO #744 -""" - -import math - - -class RandomGeneratorThingNoPair: - def __init__(self, dt_min, seed): - self.particulator = None - self.dt_min = dt_min - self.seed = seed - self.rand = None - self.rnd = None - - def register(self, builder): - self.particulator = builder.particulator - self.rand = self.particulator.Storage.empty( - self.particulator.n_sd // 2, dtype=float - ) - self.rnd = self.particulator.Random(self.particulator.n_sd, self.seed) - - def get_random_arrays(self): - self.rand.urand(self.rnd) - return self.rand diff --git a/tests/unit_tests/dynamics/collisions/conftest.py b/tests/unit_tests/dynamics/collisions/conftest.py index 710e950d75..68bea80c19 100644 --- a/tests/unit_tests/dynamics/collisions/conftest.py +++ b/tests/unit_tests/dynamics/collisions/conftest.py @@ -43,13 +43,12 @@ def insert_zeros(array): def get_dummy_particulator_and_coalescence( - backend, n_length, optimized_random=False, environment=None, substeps=1 + backend, n_length, environment=None, substeps=1 ): particulator = DummyParticulator(backend, n_sd=n_length) particulator.environment = environment or Box(dv=1, dt=DEFAULTS.dt_coal_range[1]) coalescence = Coalescence( collision_kernel=StubKernel(particulator.backend), - optimized_random=optimized_random, substeps=substeps, adaptive=False, ) diff --git a/tests/unit_tests/dynamics/collisions/test_sdm_single_cell.py b/tests/unit_tests/dynamics/collisions/test_sdm_single_cell.py index 81f10aab87..493daa1aef 100644 --- a/tests/unit_tests/dynamics/collisions/test_sdm_single_cell.py +++ b/tests/unit_tests/dynamics/collisions/test_sdm_single_cell.py @@ -2,7 +2,6 @@ import numpy as np import pytest -from PySDM.backends import ThrustRTC from PySDM.backends.impl_common.index import make_Index from PySDM.backends.impl_common.indexed_storage import make_IndexedStorage from PySDM.backends.impl_common.pair_indicator import make_PairIndicator From 94ce24c6534ce50311313e6101e58bbb4f0c6036 Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Sun, 15 Jun 2025 01:07:34 +0200 Subject: [PATCH 3/3] removing optimized_random from examples --- examples/PySDM_examples/Morrison_and_Grabowski_2007/common.py | 1 - examples/PySDM_examples/utils/kinematic_2d/gui_settings.py | 1 - examples/PySDM_examples/utils/kinematic_2d/simulation.py | 2 -- 3 files changed, 4 deletions(-) diff --git a/examples/PySDM_examples/Morrison_and_Grabowski_2007/common.py b/examples/PySDM_examples/Morrison_and_Grabowski_2007/common.py index 81d1cbf09a..9b6b963c31 100644 --- a/examples/PySDM_examples/Morrison_and_Grabowski_2007/common.py +++ b/examples/PySDM_examples/Morrison_and_Grabowski_2007/common.py @@ -29,7 +29,6 @@ def __init__(self, formulae: Formulae): self.coalescence_adaptive = True self.coalescence_dt_coal_range = collisions.collision.DEFAULTS.dt_coal_range - self.coalescence_optimized_random = True self.coalescence_substeps = 1 self.kernel = Geometric(collection_efficiency=1) self.coalescence_efficiency = ConstEc(Ec=1.0) diff --git a/examples/PySDM_examples/utils/kinematic_2d/gui_settings.py b/examples/PySDM_examples/utils/kinematic_2d/gui_settings.py index 0951e6259c..46a5329fc5 100644 --- a/examples/PySDM_examples/utils/kinematic_2d/gui_settings.py +++ b/examples/PySDM_examples/utils/kinematic_2d/gui_settings.py @@ -201,7 +201,6 @@ def __init__(self, settings): self.kernel = settings.kernel self.spectrum_per_mass_of_dry_air = settings.spectrum_per_mass_of_dry_air self.coalescence_dt_coal_range = settings.coalescence_dt_coal_range - self.coalescence_optimized_random = settings.coalescence_optimized_random self.coalescence_substeps = settings.coalescence_substeps self.freezing_inp_frac = settings.freezing_inp_frac self.coalescence_efficiency = settings.coalescence_efficiency diff --git a/examples/PySDM_examples/utils/kinematic_2d/simulation.py b/examples/PySDM_examples/utils/kinematic_2d/simulation.py index cbe0c85e12..f5f5138638 100644 --- a/examples/PySDM_examples/utils/kinematic_2d/simulation.py +++ b/examples/PySDM_examples/utils/kinematic_2d/simulation.py @@ -117,7 +117,6 @@ def reinit(self, products=None): adaptive=self.settings.coalescence_adaptive, dt_coal_range=self.settings.coalescence_dt_coal_range, substeps=self.settings.coalescence_substeps, - optimized_random=self.settings.coalescence_optimized_random, ) ) elif ( @@ -130,7 +129,6 @@ def reinit(self, products=None): adaptive=self.settings.coalescence_adaptive, dt_coal_range=self.settings.coalescence_dt_coal_range, substeps=self.settings.coalescence_substeps, - optimized_random=self.settings.coalescence_optimized_random, ) ) assert not (