From 30d768f1ac5c2edfaca3e9946e8e5ab50d51911c Mon Sep 17 00:00:00 2001 From: Jeffrey Curtis Date: Sun, 25 Feb 2024 07:11:50 -0600 Subject: [PATCH] Add more functionality for AeroState (sample/add[particles], make_dry, mobility_diameters, ids) (#341) Co-authored-by: Sylwester Arabas --- src/aero_state.F90 | 105 +++++++++++++++++++++++++ src/aero_state.hpp | 134 ++++++++++++++++++++++++++++++++ src/pypartmc.cpp | 24 ++++++ tests/test_aero_state.py | 164 ++++++++++++++++++++++++++++++--------- 4 files changed, 390 insertions(+), 37 deletions(-) diff --git a/src/aero_state.F90 b/src/aero_state.F90 index a153a3a2..24e220e9 100644 --- a/src/aero_state.F90 +++ b/src/aero_state.F90 @@ -194,6 +194,24 @@ subroutine f_aero_state_dry_diameters(ptr_c, aero_data_ptr_c, diameters, n_parts end subroutine + subroutine f_aero_state_mobility_diameters(ptr_c, aero_data_ptr_c, & + env_state_ptr_c, diameters, n_parts) bind (C) + type(c_ptr), intent(in) :: ptr_c, aero_data_ptr_c, env_state_ptr_c + integer(c_int), intent(in) :: n_parts + real(c_double), intent(out) :: diameters(n_parts) + type(aero_state_t), pointer :: ptr_f => null() + type(aero_data_t), pointer :: aero_data_ptr_f => null() + type(env_state_t) , pointer :: env_state_ptr_f => null() + + call c_f_pointer(ptr_c, ptr_f) + call c_f_pointer(aero_data_ptr_c, aero_data_ptr_f) + call c_f_pointer(env_state_ptr_c, env_state_ptr_f) + + diameters = aero_state_mobility_diameters(ptr_f, aero_data_ptr_f, & + env_state_ptr_f) + + end subroutine + subroutine f_aero_state_diameters(ptr_c, aero_data_ptr_c, diameters, & n_parts, include_len, exclude_len, include, exclude) bind(C) @@ -534,4 +552,91 @@ subroutine f_aero_state_zero(ptr_c) bind(C) end subroutine + subroutine f_aero_state_ids(ptr_c, ids, n_parts) bind(C) + type(c_ptr), intent(in) :: ptr_c + integer(c_int), intent(in) :: n_parts + integer(c_int), intent(out) :: ids(n_parts) + type(aero_state_t), pointer :: ptr_f => null() + + call c_f_pointer(ptr_c, ptr_f) + + ids = aero_state_ids(ptr_f) + + end subroutine + + subroutine f_aero_state_make_dry(ptr_c, aero_data_ptr_c) bind(C) + type(c_ptr), intent(in) :: ptr_c, aero_data_ptr_c + type(aero_state_t), pointer :: ptr_f => null() + type(aero_data_t), pointer :: aero_data_ptr_f => null() + + call c_f_pointer(ptr_c, ptr_f) + call c_f_pointer(aero_data_ptr_c, aero_data_ptr_f) + + call aero_state_make_dry(ptr_f, aero_data_ptr_f) + + end subroutine + + subroutine f_aero_state_add(ptr_c, delta_ptr_c, aero_data_ptr_c) bind(C) + type(c_ptr), intent(in) :: ptr_c, delta_ptr_c, aero_data_ptr_c + type(aero_state_t), pointer :: ptr_f => null() + type(aero_state_t), pointer :: delta_ptr_f => null() + type(aero_data_t), pointer :: aero_data_ptr_f => null() + + call c_f_pointer(ptr_c, ptr_f) + call c_f_pointer(delta_ptr_c, delta_ptr_f) + call c_f_pointer(aero_data_ptr_c, aero_data_ptr_f) + + call aero_state_add(ptr_f, delta_ptr_f, aero_data_ptr_f) + + end subroutine + + subroutine f_aero_state_add_particles(ptr_c, delta_ptr_c, aero_data_ptr_c & + ) bind(C) + type(c_ptr), intent(in) :: ptr_c, delta_ptr_c, aero_data_ptr_c + type(aero_state_t), pointer :: ptr_f => null() + type(aero_state_t), pointer :: delta_ptr_f => null() + type(aero_data_t), pointer :: aero_data_ptr_f => null() + + call c_f_pointer(ptr_c, ptr_f) + call c_f_pointer(delta_ptr_c, delta_ptr_f) + call c_f_pointer(aero_data_ptr_c, aero_data_ptr_f) + + call aero_state_add_particles(ptr_f, delta_ptr_f, aero_data_ptr_f) + + end subroutine + + subroutine f_aero_state_sample(ptr_c, aero_state_to_ptr_c, aero_data_ptr_c, & + sample_prob) bind(C) + type(c_ptr), intent(in) :: ptr_c, aero_state_to_ptr_c, aero_data_ptr_c + real(c_double), intent(in) :: sample_prob + type(aero_state_t), pointer :: ptr_f => null() + type(aero_state_t), pointer :: aero_state_to_ptr_f => null() + type(aero_data_t), pointer :: aero_data_ptr_f => null() + + call c_f_pointer(ptr_c, ptr_f) + call c_f_pointer(aero_state_to_ptr_c, aero_state_to_ptr_f) + call c_f_pointer(aero_data_ptr_c, aero_data_ptr_f) + + call aero_state_sample(ptr_f, aero_state_to_ptr_f, aero_data_ptr_f, & + sample_prob, AERO_INFO_NONE) + + end subroutine + + subroutine f_aero_state_sample_particles(ptr_c, aero_state_to_ptr_c, & + aero_data_ptr_c, sample_prob) bind(C) + type(c_ptr), intent(in) :: ptr_c, aero_state_to_ptr_c, aero_data_ptr_c + real(c_double), intent(in) :: sample_prob + type(aero_state_t), pointer :: ptr_f => null() + type(aero_state_t), pointer :: aero_state_to_ptr_f => null() + type(aero_data_t), pointer :: aero_data_ptr_f => null() + + call c_f_pointer(ptr_c, ptr_f) + call c_f_pointer(aero_state_to_ptr_c, aero_state_to_ptr_f) + call c_f_pointer(aero_data_ptr_c, aero_data_ptr_f) + + call aero_state_sample_particles(ptr_f, aero_state_to_ptr_f, & + aero_data_ptr_f, sample_prob, AERO_INFO_NONE) + + end subroutine + end module diff --git a/src/aero_state.hpp b/src/aero_state.hpp index 3e15a3a0..c99f90c9 100644 --- a/src/aero_state.hpp +++ b/src/aero_state.hpp @@ -71,6 +71,14 @@ extern "C" void f_aero_state_dry_diameters( const int *n_parts ) noexcept; +extern "C" void f_aero_state_mobility_diameters( + const void *ptr, + const void *aero_dataptr, + const void *env_stateptr, + double *mobility_diameters, + const int *n_parts +) noexcept; + extern "C" void f_aero_state_diameters( const void *ptr, const void *aero_dataptr, @@ -101,6 +109,11 @@ extern "C" void f_aero_state_crit_rel_humids( const int *n_parts ) noexcept; +extern "C" void f_aero_state_make_dry( + void *ptr, + const void *aero_dataptr +) noexcept; + extern "C" void f_aero_state_mixing_state_metrics( const void *aero_state, const void *aero_data, @@ -163,6 +176,38 @@ extern "C" void f_aero_state_zero( void *ptr_c ) noexcept; +extern "C" void f_aero_state_ids( + const void *ptr_c, + int *ids, + const int *n_parts +) noexcept; + +extern "C" void f_aero_state_add( + void *ptr_c, + const void *delta_ptr_c, + const void *aero_data_ptr +) noexcept; + +extern "C" void f_aero_state_add_particles( + void *ptr_c, + const void *delta_ptr_c, + const void *aero_data_ptr +) noexcept; + +extern "C" void f_aero_state_sample( + void *ptr_c, + void *aero_state_to_ptr_c, + const void *aero_data_ptr, + const double *sample_prob +) noexcept; + +extern "C" void f_aero_state_sample_particles( + void *ptr_c, + void *aero_state_to_ptr_c, + const void *aero_data_ptr, + const double *sample_prob +) noexcept; + template auto pointer_vec_magic(arr_t &data_vec, const arg_t &arg) { std::vector pointer_vec(data_vec.size()); @@ -313,6 +358,25 @@ struct AeroState { return dry_diameters; } + static auto mobility_diameters(const AeroState &self, const EnvState &env_state) { + int len; + f_aero_state_len( + self.ptr.f_arg(), + &len + ); + std::valarray mobility_diameters(len); + + f_aero_state_mobility_diameters( + self.ptr.f_arg(), + self.aero_data->ptr.f_arg(), + env_state.ptr.f_arg(), + begin(mobility_diameters), + &len + ); + + return mobility_diameters; + } + static auto diameters( const AeroState &self, const tl::optional> &include, @@ -401,6 +465,32 @@ struct AeroState { return crit_rel_humids; } + static void make_dry( + AeroState &self + ) { + f_aero_state_make_dry( + self.ptr.f_arg_non_const(), + self.aero_data->ptr.f_arg() + ); + } + + static auto ids(const AeroState &self) { + int len; + f_aero_state_len( + self.ptr.f_arg(), + &len + ); + std::valarray ids(len); + + f_aero_state_ids( + self.ptr.f_arg(), + begin(ids), + &len + ); + + return ids; + } + static auto mixing_state( const AeroState &self, const tl::optional> &include, @@ -538,4 +628,48 @@ struct AeroState { ) { f_aero_state_zero(self.ptr.f_arg_non_const()); } + + static void add( + AeroState &self, + const AeroState &delta + ) { + f_aero_state_add(self.ptr.f_arg_non_const(), + delta.ptr.f_arg(), + self.aero_data->ptr.f_arg() + ); + } + + static void add_particles( + AeroState &self, + const AeroState &delta + ) { + f_aero_state_add_particles(self.ptr.f_arg_non_const(), + delta.ptr.f_arg(), + self.aero_data->ptr.f_arg() + ); + } + + static void sample( + AeroState &self, + AeroState &aero_state_sample, + const double sample_prob + ) { + f_aero_state_sample(self.ptr.f_arg_non_const(), + aero_state_sample.ptr.f_arg_non_const(), + self.aero_data->ptr.f_arg(), + &sample_prob + ); + } + + static void sample_particles( + AeroState &self, + AeroState &aero_state_sample, + const double sample_prob + ) { + f_aero_state_sample_particles(self.ptr.f_arg_non_const(), + aero_state_sample.ptr.f_arg_non_const(), + self.aero_data->ptr.f_arg(), + &sample_prob + ); + } }; diff --git a/src/pypartmc.cpp b/src/pypartmc.cpp index c377ab0a..37e08dcc 100644 --- a/src/pypartmc.cpp +++ b/src/pypartmc.cpp @@ -223,11 +223,17 @@ PYBIND11_MODULE(_PyPartMC, m) { py::arg("include") = py::none(), py::arg("exclude") = py::none()) .def_property_readonly("dry_diameters", AeroState::dry_diameters, "returns the dry diameter of each particle in the population") + .def("mobility_diameters", AeroState::mobility_diameters, + "returns the mobility diameter of each particle in the population") .def("diameters", AeroState::diameters, "returns the diameter of each particle in the population", py::arg("include") = py::none(), py::arg("exclude") = py::none()) .def("crit_rel_humids", AeroState::crit_rel_humids, "returns the critical relative humidity of each particle in the population") + .def("make_dry", AeroState::make_dry, + "Make all particles dry (water set to zero).") + .def_property_readonly("ids", AeroState::ids, + "returns the IDs of all particles.") .def("mixing_state", AeroState::mixing_state, "returns the mixing state parameters (d_alpha, d_gamma, chi) of the population", py::arg("include") = py::none(), py::arg("exclude") = py::none(), @@ -243,6 +249,24 @@ PYBIND11_MODULE(_PyPartMC, m) { py::arg("AeroDist"), py::arg("sample_prop") = 1.0, py::arg("create_time") = 0.0, py::arg("allow_doubling") = true, py::arg("allow_halving") = true) .def("add_particle", AeroState::add_particle, "add a particle to an AeroState") + .def("add", AeroState::add, + R"pbdoc(aero_state += aero_state_delta, including combining the + weights, so the new concentration is the weighted average of the + two concentrations.)pbdoc") + .def("add_particles", AeroState::add_particles, + R"pbdoc(aero_state += aero_state_delta, with the weight left unchanged + so the new concentration is the sum of the two concentrations.)pbdoc") + .def("sample", AeroState::sample, + R"pbdoc(Generates a random sample by removing particles from + aero_state_from and adding them to aero_state_to, transfering + weight as well. This is the equivalent of aero_state_add().)pbdoc") + .def("sample_particles", AeroState::sample_particles, + R"pbdoc( !> Generates a random sample by removing particles from + aero_state_from and adding them to aero_state_to, which must be + already allocated (and should have its weight set). + + None of the weights are altered by this sampling, making this the + equivalent of aero_state_add_particles().)pbdoc") .def("copy_weight", AeroState::copy_weight, "copy weighting from another AeroState") .def("remove_particle", AeroState::remove_particle, diff --git a/tests/test_aero_state.py b/tests/test_aero_state.py index 5a786a5c..d3374340 100644 --- a/tests/test_aero_state.py +++ b/tests/test_aero_state.py @@ -24,8 +24,8 @@ # pylint: disable=R0904 -@pytest.fixture -def sut_minimal(): +@pytest.fixture(name="sut_minimal") +def sut_minimal_fixture(): aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) aero_dist = ppmc.AeroDist(aero_data, AERO_DIST_CTOR_ARG_MINIMAL) sut = ppmc.AeroState(aero_data, *AERO_STATE_CTOR_ARG_MINIMAL) @@ -35,8 +35,8 @@ def sut_minimal(): return sut -@pytest.fixture -def sut_full(): +@pytest.fixture(name="sut_full") +def sut_full_fixture(): aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_FULL) aero_dist = ppmc.AeroDist(aero_data, AERO_DIST_CTOR_ARG_FULL) sut = ppmc.AeroState(aero_data, *AERO_STATE_CTOR_ARG_MINIMAL) @@ -46,8 +46,8 @@ def sut_full(): return sut -@pytest.fixture -def sut_average(): +@pytest.fixture(name="sut_average") +def sut_average_fixture(): aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_FULL) aero_dist = ppmc.AeroDist(aero_data, AERO_DIST_CTOR_ARG_AVERAGE) sut = ppmc.AeroState(aero_data, *AERO_STATE_CTOR_ARG_MINIMAL) @@ -103,7 +103,7 @@ def test_len(n_part): assert int(size) < n_part * 2 @staticmethod - def test_total_num_conc(sut_minimal): # pylint: disable=redefined-outer-name + def test_total_num_conc(sut_minimal): # act total_num_conc = sut_minimal.total_num_conc @@ -112,7 +112,7 @@ def test_total_num_conc(sut_minimal): # pylint: disable=redefined-outer-name assert total_num_conc > 0 @staticmethod - def test_total_mass_conc(sut_minimal): # pylint: disable=redefined-outer-name + def test_total_mass_conc(sut_minimal): # act total_mass_conc = sut_minimal.total_mass_conc @@ -121,7 +121,7 @@ def test_total_mass_conc(sut_minimal): # pylint: disable=redefined-outer-name assert total_mass_conc > 0 @staticmethod - def test_num_concs(sut_minimal): # pylint: disable=redefined-outer-name + def test_num_concs(sut_minimal): # act num_concs = sut_minimal.num_concs @@ -130,7 +130,7 @@ def test_num_concs(sut_minimal): # pylint: disable=redefined-outer-name assert len(num_concs) == len(sut_minimal) @staticmethod - def test_masses(sut_minimal): # pylint: disable=redefined-outer-name + def test_masses(sut_minimal): # act masses = sut_minimal.masses() @@ -139,7 +139,7 @@ def test_masses(sut_minimal): # pylint: disable=redefined-outer-name assert len(masses) == len(sut_minimal) @staticmethod - def test_masses_include(sut_full): # pylint: disable=redefined-outer-name + def test_masses_include(sut_full): aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_FULL) # act @@ -155,7 +155,7 @@ def test_masses_include(sut_full): # pylint: disable=redefined-outer-name np.testing.assert_allclose(masses_so4, masses) @staticmethod - def test_masses_exclude(sut_full): # pylint: disable=redefined-outer-name + def test_masses_exclude(sut_full): # act masses = sut_full.masses(exclude=["SO4"]) masses_so4 = np.zeros(len(sut_full)) @@ -168,7 +168,7 @@ def test_masses_exclude(sut_full): # pylint: disable=redefined-outer-name np.testing.assert_allclose(masses_so4, masses) @staticmethod - def test_masses_include_exclude(sut_full): # pylint: disable=redefined-outer-name + def test_masses_include_exclude(sut_full): # act masses = sut_full.masses(include=["SO4"], exclude=["SO4"]) @@ -178,7 +178,7 @@ def test_masses_include_exclude(sut_full): # pylint: disable=redefined-outer-na assert np.sum(masses) == 0.0 @staticmethod - def test_volumes(sut_minimal): # pylint: disable=redefined-outer-name + def test_volumes(sut_minimal): # act volumes = sut_minimal.volumes() @@ -187,7 +187,7 @@ def test_volumes(sut_minimal): # pylint: disable=redefined-outer-name assert len(volumes) == len(sut_minimal) @staticmethod - def test_volumes_include(sut_full): # pylint: disable=redefined-outer-name + def test_volumes_include(sut_full): aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_FULL) # act @@ -203,7 +203,7 @@ def test_volumes_include(sut_full): # pylint: disable=redefined-outer-name np.testing.assert_allclose(vol_so4, volumes) @staticmethod - def test_volumes_include_exclude(sut_full): # pylint: disable=redefined-outer-name + def test_volumes_include_exclude(sut_full): # act volumes = sut_full.volumes(include=["SO4"], exclude=["SO4"]) @@ -213,7 +213,7 @@ def test_volumes_include_exclude(sut_full): # pylint: disable=redefined-outer-n assert np.sum(volumes) == 0.0 @staticmethod - def test_volumes_exclude(sut_full): # pylint: disable=redefined-outer-name + def test_volumes_exclude(sut_full): aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_FULL) # act @@ -232,7 +232,7 @@ def test_volumes_exclude(sut_full): # pylint: disable=redefined-outer-name np.testing.assert_allclose(vol_so4, volumes) @staticmethod - def test_dry_diameters(sut_minimal): # pylint: disable=redefined-outer-name + def test_dry_diameters(sut_minimal): # act dry_diameters = sut_minimal.dry_diameters @@ -241,7 +241,20 @@ def test_dry_diameters(sut_minimal): # pylint: disable=redefined-outer-name assert len(dry_diameters) == len(sut_minimal) @staticmethod - def test_diameters(sut_minimal): # pylint: disable=redefined-outer-name + def test_mobility_diameters(sut_minimal): + # act + env_state = ppmc.EnvState(ENV_STATE_CTOR_ARG_MINIMAL) + env_state.set_temperature(300) + env_state.pressure = 1e5 + diameters = sut_minimal.mobility_diameters(env_state) + + # assert + assert isinstance(diameters, list) + assert len(diameters) == len(sut_minimal) + assert (np.asarray(diameters) > 0).all() + + @staticmethod + def test_diameters(sut_minimal): # act diameters = sut_minimal.diameters() @@ -250,7 +263,17 @@ def test_diameters(sut_minimal): # pylint: disable=redefined-outer-name assert len(diameters) == len(sut_minimal) @staticmethod - def test_crit_rel_humids(sut_full): # pylint: disable=redefined-outer-name + def test_ids(sut_minimal): + # act + ids = sut_minimal.ids + + # assert + assert isinstance(ids, list) + assert len(ids) == len(sut_minimal) + assert (np.asarray(ids) > 0).all() + + @staticmethod + def test_crit_rel_humids(sut_full): # arrange args = {"rel_humidity": 0.8, **ENV_STATE_CTOR_ARG_MINIMAL} env_state = ppmc.EnvState(args) @@ -266,7 +289,15 @@ def test_crit_rel_humids(sut_full): # pylint: disable=redefined-outer-name assert (np.asarray(crit_rel_humids) < 1.2).all() @staticmethod - def test_mixing_state(sut_minimal): # pylint: disable=redefined-outer-name + def test_make_dry(sut_minimal): + # act + sut_minimal.make_dry() + + masses = sut_minimal.masses(include=["H2O"]) + assert (np.asarray(masses) == 0).all() + + @staticmethod + def test_mixing_state(sut_minimal): # act mixing_state = sut_minimal.mixing_state() @@ -276,9 +307,7 @@ def test_mixing_state(sut_minimal): # pylint: disable=redefined-outer-name @staticmethod @pytest.mark.parametrize("n_bin", (1, 123)) - def test_bin_average_comp( - sut_average, n_bin - ): # pylint: disable=redefined-outer-name + def test_bin_average_comp(sut_average, n_bin): # arrange bin_grid = ppmc.BinGrid(n_bin, "log", 1e-9, 1e-4) @@ -296,7 +325,7 @@ def test_bin_average_comp( assert np.logical_xor(so4_masses > 0, bc_masses > 0).all() @staticmethod - def test_get_particle(sut_minimal): # pylint: disable=redefined-outer-name + def test_get_particle(sut_minimal): # act particle = sut_minimal.particle(1) @@ -304,7 +333,7 @@ def test_get_particle(sut_minimal): # pylint: disable=redefined-outer-name assert isinstance(particle, ppmc.AeroParticle) @staticmethod - def test_get_random_particle(sut_minimal): # pylint: disable=redefined-outer-name + def test_get_random_particle(sut_minimal): # act particle = sut_minimal.rand_particle() @@ -312,9 +341,7 @@ def test_get_random_particle(sut_minimal): # pylint: disable=redefined-outer-na assert isinstance(particle, ppmc.AeroParticle) @staticmethod - def test_check_correct_particle( - sut_minimal, - ): # pylint: disable=redefined-outer-name + def test_check_correct_particle(sut_minimal): # act i_part = 20 particle = sut_minimal.particle(i_part) @@ -324,7 +351,7 @@ def test_check_correct_particle( assert particle.diameter == diameters[i_part] @staticmethod - def test_different_particles(sut_minimal): # pylint: disable=redefined-outer-name + def test_different_particles(sut_minimal): # act i_part = 20 particle_1 = sut_minimal.particle(i_part) @@ -335,9 +362,7 @@ def test_different_particles(sut_minimal): # pylint: disable=redefined-outer-na @staticmethod @pytest.mark.parametrize("idx", (-1, 500)) - def test_get_particle_out_of_range( - sut_minimal, idx - ): # pylint: disable=redefined-outer-name + def test_get_particle_out_of_range(sut_minimal, idx): # act try: _ = sut_minimal.particle(idx) @@ -348,14 +373,14 @@ def test_get_particle_out_of_range( assert False @staticmethod - def test_remove_particle(sut_minimal): # pylint: disable=redefined-outer-name + def test_remove_particle(sut_minimal): diameters = sut_minimal.diameters() sut_minimal.remove_particle(len(sut_minimal) - 1) assert diameters[0:-1] == sut_minimal.diameters() @staticmethod - def test_zero(sut_minimal): # pylint: disable=redefined-outer-name + def test_zero(sut_minimal): # act sut_minimal.zero() @@ -363,7 +388,7 @@ def test_zero(sut_minimal): # pylint: disable=redefined-outer-name assert len(sut_minimal) == 0 @staticmethod - def test_add_particle(sut_minimal): # pylint: disable=redefined-outer-name + def test_add_particle(sut_minimal): particle = sut_minimal.particle(1) aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) sut = ppmc.AeroState(aero_data, *AERO_STATE_CTOR_ARG_MINIMAL) @@ -374,7 +399,72 @@ def test_add_particle(sut_minimal): # pylint: disable=redefined-outer-name assert sut.particle(0).diameter == sut_minimal.particle(1).diameter @staticmethod - def test_copy_weight(sut_minimal): # pylint: disable=redefined-outer-name + def test_add_particles(sut_minimal): + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + aero_dist = ppmc.AeroDist(aero_data, AERO_DIST_CTOR_ARG_MINIMAL) + delta = ppmc.AeroState(aero_data, *AERO_STATE_CTOR_ARG_MINIMAL) + _ = delta.dist_sample(aero_dist, 1.0, 0.0, True, True) + + num_conc = sut_minimal.total_num_conc + num_conc_delta = delta.total_num_conc + sut_minimal.add_particles(delta) + + assert np.isclose(sut_minimal.total_num_conc, (num_conc + num_conc_delta)) + + @staticmethod + def test_add(sut_minimal): + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + aero_dist = ppmc.AeroDist(aero_data, AERO_DIST_CTOR_ARG_MINIMAL) + delta = ppmc.AeroState(aero_data, *AERO_STATE_CTOR_ARG_MINIMAL) + _ = delta.dist_sample(aero_dist, 1.0, 0.0, True, True) + + num_conc = sut_minimal.total_num_conc + num_conc_delta = delta.total_num_conc + sut_minimal.add(delta) + + assert np.isclose(sut_minimal.total_num_conc, 0.5 * (num_conc + num_conc_delta)) + + @staticmethod + def test_sample_particles(sut_minimal): + # arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + sut = ppmc.AeroState(aero_data, *AERO_STATE_CTOR_ARG_MINIMAL) + + # act + num_conc = sut_minimal.total_num_conc + samp_prob = 0.25 + sut_minimal.sample_particles(sut, samp_prob) + + # assert + assert len(sut) > 0 + assert sut.total_num_conc > 0.5 * samp_prob * num_conc + assert sut.total_num_conc < sut_minimal.total_num_conc + assert np.isclose(sut.total_num_conc + sut_minimal.total_num_conc, num_conc) + + @staticmethod + def test_sample(sut_minimal): + # arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + sut = ppmc.AeroState(aero_data, *AERO_STATE_CTOR_ARG_MINIMAL) + + # act + num_conc = sut_minimal.total_num_conc + samp_prob = 0.1 + sut_minimal.sample(sut, samp_prob) + + # assert + assert len(sut) > 0 + assert sut.total_num_conc > 0.5 * samp_prob * num_conc + assert np.isclose( + ( + samp_prob * sut.total_num_conc + + (1 - samp_prob) * sut_minimal.total_num_conc + ), + num_conc, + ) + + @staticmethod + def test_copy_weight(sut_minimal): aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) sut = ppmc.AeroState(aero_data, *AERO_STATE_CTOR_ARG_MINIMAL)