From 8d2460dce6b7eb35f91412b602700b677ebb0185 Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Tue, 23 Apr 2024 19:11:58 +0200 Subject: [PATCH 01/18] first try at instantiating an AeroMode with sampled mode type --- tests/test_aero_mode.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_aero_mode.py b/tests/test_aero_mode.py index fb148698..d8906ae7 100644 --- a/tests/test_aero_mode.py +++ b/tests/test_aero_mode.py @@ -289,3 +289,26 @@ def test_segfault_case(): # TODO #319 ) print(fishy_ctor_arg) ppmc.AeroMode(aero_data, fishy_ctor_arg) + + @staticmethod + def test_sampled(): + # arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + + # act + sut = ppmc.AeroMode( + aero_data, + { + "test_mode": { + "mass_frac": [{"H2O": [1]}], + "diam_type": "geometric", + "mode_type": "sampled", + "size_dist": [ + {"diam": [1, 2, 3]}, + {"num_conc": [1, 2, 3]}, + ], + } + }, + ) + + # assert From e8b0eec9767f304c7a00505aeb2928869e4de9df Mon Sep 17 00:00:00 2001 From: Jeffrey Curtis Date: Tue, 23 Apr 2024 14:39:13 -0500 Subject: [PATCH 02/18] add deallocate if allocated arrays in spec_file_read_real_named_array --- src/spec_file_pypartmc.F90 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/spec_file_pypartmc.F90 b/src/spec_file_pypartmc.F90 index 31116516..a92e7433 100644 --- a/src/spec_file_pypartmc.F90 +++ b/src/spec_file_pypartmc.F90 @@ -129,6 +129,8 @@ subroutine spec_file_read_real_named_array(file, max_lines, names, vals) n_rows = max_lines end if + if (allocated(names)) deallocate(names) + if (allocated(vals)) deallocate(vals) allocate(names(n_rows)) allocate(vals(n_rows, n_cols)) allocate(vals_row(max(1, n_cols))) From 1243d23774601a7845d36618fe103e65e15a15ec Mon Sep 17 00:00:00 2001 From: Jeffrey Curtis Date: Tue, 23 Apr 2024 14:39:59 -0500 Subject: [PATCH 03/18] add something temporary for testing sampled mode --- tests/test_aero_mode.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_aero_mode.py b/tests/test_aero_mode.py index d8906ae7..c5d9f117 100644 --- a/tests/test_aero_mode.py +++ b/tests/test_aero_mode.py @@ -312,3 +312,4 @@ def test_sampled(): ) # assert + assert sut is not None From 4ce0a4a7fd40057b723daf175c37b14df42a6be8 Mon Sep 17 00:00:00 2001 From: Jeffrey Curtis Date: Tue, 23 Apr 2024 14:50:06 -0500 Subject: [PATCH 04/18] add nontrivial test for sampled mode --- tests/test_aero_mode.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_aero_mode.py b/tests/test_aero_mode.py index c5d9f117..688d18a7 100644 --- a/tests/test_aero_mode.py +++ b/tests/test_aero_mode.py @@ -296,6 +296,7 @@ def test_sampled(): aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) # act + num_concs = [1,2,3] sut = ppmc.AeroMode( aero_data, { @@ -304,12 +305,12 @@ def test_sampled(): "diam_type": "geometric", "mode_type": "sampled", "size_dist": [ - {"diam": [1, 2, 3]}, - {"num_conc": [1, 2, 3]}, + {"diam": [1, 2, 3, 4]}, + {"num_conc": num_concs}, ], } }, ) # assert - assert sut is not None + assert sut.num_conc == np.sum(num_concs) From 86c49bcb7489a9ec197450e94ad5f93be4e52390 Mon Sep 17 00:00:00 2001 From: Jeffrey Curtis Date: Tue, 23 Apr 2024 14:54:10 -0500 Subject: [PATCH 05/18] fix formatting of test for sampled mode --- tests/test_aero_mode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_aero_mode.py b/tests/test_aero_mode.py index 688d18a7..bc59df25 100644 --- a/tests/test_aero_mode.py +++ b/tests/test_aero_mode.py @@ -296,7 +296,7 @@ def test_sampled(): aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) # act - num_concs = [1,2,3] + num_concs = [1, 2, 3] sut = ppmc.AeroMode( aero_data, { @@ -313,4 +313,4 @@ def test_sampled(): ) # assert - assert sut.num_conc == np.sum(num_concs) + assert sut.num_conc == np.sum(num_concs) From a9760923ed9ca987d0566d7734e9d126ef93d0be Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Tue, 23 Apr 2024 22:35:55 +0200 Subject: [PATCH 06/18] remove surplus element? (... probably worth alerting user about it) --- tests/test_aero_mode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_aero_mode.py b/tests/test_aero_mode.py index bc59df25..59cffb61 100644 --- a/tests/test_aero_mode.py +++ b/tests/test_aero_mode.py @@ -305,7 +305,7 @@ def test_sampled(): "diam_type": "geometric", "mode_type": "sampled", "size_dist": [ - {"diam": [1, 2, 3, 4]}, + {"diam": [1, 2, 3]}, {"num_conc": num_concs}, ], } From 3c1b34d631f6896ad919b629011e8cec51319914 Mon Sep 17 00:00:00 2001 From: Jeffrey Curtis Date: Tue, 23 Apr 2024 21:25:52 -0500 Subject: [PATCH 07/18] fix error checking off by 1 error for mode types --- src/aero_mode.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aero_mode.hpp b/src/aero_mode.hpp index d2e4685a..9943b21e 100644 --- a/src/aero_mode.hpp +++ b/src/aero_mode.hpp @@ -291,7 +291,7 @@ struct AeroMode { int type; f_aero_mode_get_type(self.ptr.f_arg(), &type); - if (type < 0 || (unsigned int)type >= AeroMode::types().size()) + if (type <= 0 || (unsigned int)type > AeroMode::types().size()) throw std::logic_error("Unknown mode type."); return AeroMode::types()[type - 1]; From 49c06bcf8f5d84e6e1ffca9dfa6059580f0b3d58 Mon Sep 17 00:00:00 2001 From: Jeffrey Curtis Date: Tue, 23 Apr 2024 21:38:06 -0500 Subject: [PATCH 08/18] test for sample mode type. modify values so test does not accidentally pass --- tests/test_aero_mode.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_aero_mode.py b/tests/test_aero_mode.py index 59cffb61..1b0b0084 100644 --- a/tests/test_aero_mode.py +++ b/tests/test_aero_mode.py @@ -296,7 +296,7 @@ def test_sampled(): aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) # act - num_concs = [1, 2, 3] + num_concs = [100, 200, 300] sut = ppmc.AeroMode( aero_data, { @@ -305,7 +305,7 @@ def test_sampled(): "diam_type": "geometric", "mode_type": "sampled", "size_dist": [ - {"diam": [1, 2, 3]}, + {"diam": [1, 2, 3, 4]}, {"num_conc": num_concs}, ], } @@ -313,4 +313,5 @@ def test_sampled(): ) # assert + assert sut.type == "sampled" assert sut.num_conc == np.sum(num_concs) From 0e5988cc7a52f893dec89e9f353abf82cba5f229 Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Fri, 26 Apr 2024 21:55:24 +0200 Subject: [PATCH 09/18] add a check for presence of size_dist --- src/aero_mode.hpp | 4 ++++ tests/test_aero_mode.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/aero_mode.hpp b/src/aero_mode.hpp index 9943b21e..2f1a1e1d 100644 --- a/src/aero_mode.hpp +++ b/src/aero_mode.hpp @@ -145,6 +145,10 @@ struct AeroMode { throw std::runtime_error("mass_frac value must be a list of single-element dicts"); if (!InputJSONResource::unique_keys(mass_frac)) throw std::runtime_error("mass_frac keys must be unique"); + if (mode["mode_type"] == "sampled") { + if (mode.find("size_dist") == mode.end()) + throw std::runtime_error("size_dist key must be set for mode_type=sampled"); + } } static auto get_num_conc(const AeroMode &self){ diff --git a/tests/test_aero_mode.py b/tests/test_aero_mode.py index 1b0b0084..fbcc026d 100644 --- a/tests/test_aero_mode.py +++ b/tests/test_aero_mode.py @@ -290,6 +290,20 @@ def test_segfault_case(): # TODO #319 print(fishy_ctor_arg) ppmc.AeroMode(aero_data, fishy_ctor_arg) + @staticmethod + def test_sampled_without_size_dist(): + # arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + fishy_ctor_arg = copy.deepcopy(AERO_MODE_CTOR_LOG_NORMAL) + fishy_ctor_arg["test_mode"]["mode_type"] = "sampled" + + # act + with pytest.raises(Exception) as exc_info: + ppmc.AeroMode(aero_data, fishy_ctor_arg) + + # assert + assert str(exc_info.value) == "size_dist key must be set for mode_type=sampled" + @staticmethod def test_sampled(): # arrange From 327ecd450c94f9ad92e1e3ddc78c60f95d314aca Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Fri, 17 May 2024 10:54:19 +0200 Subject: [PATCH 10/18] more ctor checks in AeroMode, sampled test for AeroDist --- src/aero_mode.hpp | 14 +++++++++++ tests/test_aero_dist.py | 19 +++++++++++++++ tests/test_aero_mode.py | 53 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/aero_mode.hpp b/src/aero_mode.hpp index 2f1a1e1d..ed69cb89 100644 --- a/src/aero_mode.hpp +++ b/src/aero_mode.hpp @@ -148,6 +148,20 @@ struct AeroMode { if (mode["mode_type"] == "sampled") { if (mode.find("size_dist") == mode.end()) throw std::runtime_error("size_dist key must be set for mode_type=sampled"); + auto sd = mode["size_dist"]; + if ( + sd.size() != 2 || + !sd[0].is_object() || + sd[0].size() != 1 || + sd[1].size() != 1 || + sd[0].find("num_conc") == sd[0].end() || + sd[1].find("diam") == sd[1].end() + ) + throw std::runtime_error("size_dist value must be an iterable of two single-element dicts (first with 'num_conc', second with 'diam' as keys)"); + auto num_conc = *sd[0].find("num_conc"); + auto diam = *sd[1].find("diam"); + if (diam.size() != num_conc.size() + 1) + throw std::runtime_error("size_dist['num_conc'] must have len(size_dist['diam'])-1 elements"); } } diff --git a/tests/test_aero_dist.py b/tests/test_aero_dist.py index adb64cac..790c9f2e 100644 --- a/tests/test_aero_dist.py +++ b/tests/test_aero_dist.py @@ -217,3 +217,22 @@ def test_ctor_error_on_repeated_massfrac_keys(): # assert assert str(exc_info.value) == "mass_frac keys must be unique" + + @staticmethod + def test_ctor_sampled_mode(): + # arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + ctor_arg = copy.deepcopy(AERO_DIST_CTOR_ARG_MINIMAL) + ctor_arg[0]["test_mode"]["mode_type"] = "sampled" + ctor_arg[0]["test_mode"]["size_dist"] = [ + {"num_conc": [1, 2, 3]}, + {"diam": [1, 2, 3, 4]}, + ] + + # act + sut = ppmc.AeroDist(aero_data, ctor_arg) + + # assert + assert sut.mode(0).num_conc == sum( + ctor_arg[0]["test_mode"]["size_dist"][0]["num_conc"] + ) diff --git a/tests/test_aero_mode.py b/tests/test_aero_mode.py index fbcc026d..250af41c 100644 --- a/tests/test_aero_mode.py +++ b/tests/test_aero_mode.py @@ -304,6 +304,57 @@ def test_sampled_without_size_dist(): # assert assert str(exc_info.value) == "size_dist key must be set for mode_type=sampled" + @staticmethod + @pytest.mark.parametrize( + "fishy", + ( + None, + [], + [{}, {}, {}], + [{}, []], + [{"diam": None}, {}], + [{"num_conc": None}, {}], + [{"diam": None, "": None}, {}], + [{"num_conc": None, "": None}, {}], + ), + ) + def test_sampled_with_fishy_size_dist(fishy): + # arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + fishy_ctor_arg = copy.deepcopy(AERO_MODE_CTOR_LOG_NORMAL) + fishy_ctor_arg["test_mode"]["mode_type"] = "sampled" + fishy_ctor_arg["test_mode"]["size_dist"] = fishy + + # act + with pytest.raises(Exception) as exc_info: + ppmc.AeroMode(aero_data, fishy_ctor_arg) + + # assert + assert ( + str(exc_info.value) + == "size_dist value must be an iterable of two single-element dicts (first with 'num_conc', second with 'diam' as keys)" + ) + + def test_sampled_with_diam_of_different_len_than_num_conc(fishy): + # arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + fishy_ctor_arg = copy.deepcopy(AERO_MODE_CTOR_LOG_NORMAL) + fishy_ctor_arg["test_mode"]["mode_type"] = "sampled" + fishy_ctor_arg["test_mode"]["size_dist"] = [ + {"num_conc": [1, 2, 3]}, + {"diam": [1, 2, 3]}, + ] + + # act + with pytest.raises(Exception) as exc_info: + ppmc.AeroMode(aero_data, fishy_ctor_arg) + + # assert + assert ( + str(exc_info.value) + == "size_dist['num_conc'] must have len(size_dist['diam'])-1 elements" + ) + @staticmethod def test_sampled(): # arrange @@ -319,8 +370,8 @@ def test_sampled(): "diam_type": "geometric", "mode_type": "sampled", "size_dist": [ - {"diam": [1, 2, 3, 4]}, {"num_conc": num_concs}, + {"diam": [1, 2, 3, 4]}, ], } }, From 18b3c2fef43517d5f76233b615f9f721d6f28eb6 Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Fri, 17 May 2024 11:04:32 +0200 Subject: [PATCH 11/18] addressing pylint hints --- tests/test_aero_mode.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_aero_mode.py b/tests/test_aero_mode.py index 250af41c..8c112bdb 100644 --- a/tests/test_aero_mode.py +++ b/tests/test_aero_mode.py @@ -332,10 +332,12 @@ def test_sampled_with_fishy_size_dist(fishy): # assert assert ( str(exc_info.value) - == "size_dist value must be an iterable of two single-element dicts (first with 'num_conc', second with 'diam' as keys)" + == "size_dist value must be an iterable of two single-element dicts" + + " (first with 'num_conc', second with 'diam' as keys)" ) - def test_sampled_with_diam_of_different_len_than_num_conc(fishy): + @staticmethod + def test_sampled_with_diam_of_different_len_than_num_conc(): # arrange aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) fishy_ctor_arg = copy.deepcopy(AERO_MODE_CTOR_LOG_NORMAL) From 7069e0c86fef296c724687ffc5e070fdc1066e26 Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Fri, 17 May 2024 11:42:24 +0200 Subject: [PATCH 12/18] skip exception tests on Apple Silicon --- tests/test_aero_mode.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_aero_mode.py b/tests/test_aero_mode.py index 8c112bdb..7b0d60f4 100644 --- a/tests/test_aero_mode.py +++ b/tests/test_aero_mode.py @@ -291,6 +291,7 @@ def test_segfault_case(): # TODO #319 ppmc.AeroMode(aero_data, fishy_ctor_arg) @staticmethod + @pytest.mark.skipif(platform.machine() == "arm64", reason="TODO #348") def test_sampled_without_size_dist(): # arrange aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) @@ -318,6 +319,7 @@ def test_sampled_without_size_dist(): [{"num_conc": None, "": None}, {}], ), ) + @pytest.mark.skipif(platform.machine() == "arm64", reason="TODO #348") def test_sampled_with_fishy_size_dist(fishy): # arrange aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) @@ -337,6 +339,7 @@ def test_sampled_with_fishy_size_dist(fishy): ) @staticmethod + @pytest.mark.skipif(platform.machine() == "arm64", reason="TODO #348") def test_sampled_with_diam_of_different_len_than_num_conc(): # arrange aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) From 201d2e67f65d5452b5d7063a180b3e5b0c223322 Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Tue, 21 May 2024 15:54:11 +0200 Subject: [PATCH 13/18] handle multiple calls to read_named_array to fix sampled mode reading --- src/aero_mode.hpp | 10 +++++----- src/json_resource.hpp | 12 ++++++++---- src/spec_file_pypartmc.cpp | 10 +++++----- tests/test_aero_dist.py | 4 ++-- tests/test_aero_mode.py | 6 +++--- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/aero_mode.hpp b/src/aero_mode.hpp index ed69cb89..8c76b80f 100644 --- a/src/aero_mode.hpp +++ b/src/aero_mode.hpp @@ -154,12 +154,12 @@ struct AeroMode { !sd[0].is_object() || sd[0].size() != 1 || sd[1].size() != 1 || - sd[0].find("num_conc") == sd[0].end() || - sd[1].find("diam") == sd[1].end() + sd[0].find("diam") == sd[0].end() || + sd[1].find("num_conc") == sd[1].end() ) - throw std::runtime_error("size_dist value must be an iterable of two single-element dicts (first with 'num_conc', second with 'diam' as keys)"); - auto num_conc = *sd[0].find("num_conc"); - auto diam = *sd[1].find("diam"); + throw std::runtime_error("size_dist value must be an iterable of two single-element dicts (first with 'diam', second with 'num_conc' as keys)"); + auto diam = *sd[0].find("diam"); + auto num_conc = *sd[1].find("num_conc"); if (diam.size() != num_conc.size() + 1) throw std::runtime_error("size_dist['num_conc'] must have len(size_dist['diam'])-1 elements"); } diff --git a/src/json_resource.hpp b/src/json_resource.hpp index e3736b7e..91e46219 100644 --- a/src/json_resource.hpp +++ b/src/json_resource.hpp @@ -26,7 +26,7 @@ struct JSONResource { } protected: - size_t index = 0; + size_t index = 0, named_array_read_count = 0; JSONResource() {} @@ -71,6 +71,10 @@ struct JSONResource { public: virtual ~JSONResource() {} + auto n_named_array_read_count() noexcept { + return this->named_array_read_count; + } + void zoom_in(const bpstd::string_view &sub) noexcept { auto it = this->json->is_array() ? this->json->at(this->json->size()-1).find(sub) @@ -84,6 +88,7 @@ struct JSONResource { else this->set_current_json_ptr(&(*it)); + this->named_array_read_count = 0; } void zoom_out() noexcept { @@ -101,13 +106,12 @@ struct JSONResource { return this->json->begin(); } - // TODO #112: to be removed after initialising GasData with a list, and not JSON? - auto first_field_name() const noexcept { + auto first_field_name() noexcept { // TODO #112: handle errors std::string name = ""; assert(this->json->size() > 0); assert(this->json->begin()->size() > 0); - for (auto &entry : this->json->at(0).items()) + for (auto &entry : this->json->at(this->named_array_read_count++).items()) { name = entry.key(); } diff --git a/src/spec_file_pypartmc.cpp b/src/spec_file_pypartmc.cpp index fdda521f..305c5bbb 100644 --- a/src/spec_file_pypartmc.cpp +++ b/src/spec_file_pypartmc.cpp @@ -145,10 +145,10 @@ void spec_file_read_real_named_array_size( int *n_rows, int *n_cols ) noexcept { - auto first_field = json_resource_ptr()->first_field_name(); *n_rows = json_resource_ptr()->n_numeric_array_entries(); + + auto first_field = json_resource_ptr()->first_field_name(); *n_cols = json_resource_ptr()->n_elements(first_field); - // TODO #112: check each line has the same number of elements as time } extern "C" @@ -174,17 +174,17 @@ void spec_file_read_real_named_array_data( ++i, ++it ) { assert(it->is_object()); - if (i == row-1) { + if (i == (row - 1) + (json_resource_ptr()->n_named_array_read_count() - 1)) { assert(it->size() == 1); for (auto &entry : it->items()) { - // TODO #112: use input name_size as limit param + assert(*name_size > entry.key().size()); for (auto c=0u; c < entry.key().size(); ++c) name_data[c] = entry.key()[c]; *name_size = entry.key().size(); for (auto idx=0u; idx < entry.value().size(); ++idx) { vals[idx] = entry.value().at(idx).get(); } - break; // TODO #112 + break; } } } diff --git a/tests/test_aero_dist.py b/tests/test_aero_dist.py index 790c9f2e..94e2d6eb 100644 --- a/tests/test_aero_dist.py +++ b/tests/test_aero_dist.py @@ -225,8 +225,8 @@ def test_ctor_sampled_mode(): ctor_arg = copy.deepcopy(AERO_DIST_CTOR_ARG_MINIMAL) ctor_arg[0]["test_mode"]["mode_type"] = "sampled" ctor_arg[0]["test_mode"]["size_dist"] = [ - {"num_conc": [1, 2, 3]}, {"diam": [1, 2, 3, 4]}, + {"num_conc": [1, 2, 3]}, ] # act @@ -234,5 +234,5 @@ def test_ctor_sampled_mode(): # assert assert sut.mode(0).num_conc == sum( - ctor_arg[0]["test_mode"]["size_dist"][0]["num_conc"] + ctor_arg[0]["test_mode"]["size_dist"][1]["num_conc"] ) diff --git a/tests/test_aero_mode.py b/tests/test_aero_mode.py index 7b0d60f4..fb283eeb 100644 --- a/tests/test_aero_mode.py +++ b/tests/test_aero_mode.py @@ -335,7 +335,7 @@ def test_sampled_with_fishy_size_dist(fishy): assert ( str(exc_info.value) == "size_dist value must be an iterable of two single-element dicts" - + " (first with 'num_conc', second with 'diam' as keys)" + + " (first with 'diam', second with 'num_conc' as keys)" ) @staticmethod @@ -346,8 +346,8 @@ def test_sampled_with_diam_of_different_len_than_num_conc(): fishy_ctor_arg = copy.deepcopy(AERO_MODE_CTOR_LOG_NORMAL) fishy_ctor_arg["test_mode"]["mode_type"] = "sampled" fishy_ctor_arg["test_mode"]["size_dist"] = [ - {"num_conc": [1, 2, 3]}, {"diam": [1, 2, 3]}, + {"num_conc": [1, 2, 3]}, ] # act @@ -375,8 +375,8 @@ def test_sampled(): "diam_type": "geometric", "mode_type": "sampled", "size_dist": [ - {"num_conc": num_concs}, {"diam": [1, 2, 3, 4]}, + {"num_conc": num_concs}, ], } }, From 70b36220a36d6a7fc4bb57ec365607a082a433cf Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Tue, 21 May 2024 18:02:28 +0200 Subject: [PATCH 14/18] fix cast --- src/spec_file_pypartmc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spec_file_pypartmc.cpp b/src/spec_file_pypartmc.cpp index 305c5bbb..3131e6e1 100644 --- a/src/spec_file_pypartmc.cpp +++ b/src/spec_file_pypartmc.cpp @@ -177,7 +177,7 @@ void spec_file_read_real_named_array_data( if (i == (row - 1) + (json_resource_ptr()->n_named_array_read_count() - 1)) { assert(it->size() == 1); for (auto &entry : it->items()) { - assert(*name_size > entry.key().size()); + assert(*name_size > (decltype(*name_size))entry.key().size()); for (auto c=0u; c < entry.key().size(); ++c) name_data[c] = entry.key()[c]; *name_size = entry.key().size(); From 5a45044d44035c22c4225060fd37403ca941be33 Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Tue, 21 May 2024 19:19:17 +0200 Subject: [PATCH 15/18] simpler cast --- src/spec_file_pypartmc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spec_file_pypartmc.cpp b/src/spec_file_pypartmc.cpp index 3131e6e1..214d94f0 100644 --- a/src/spec_file_pypartmc.cpp +++ b/src/spec_file_pypartmc.cpp @@ -177,7 +177,7 @@ void spec_file_read_real_named_array_data( if (i == (row - 1) + (json_resource_ptr()->n_named_array_read_count() - 1)) { assert(it->size() == 1); for (auto &entry : it->items()) { - assert(*name_size > (decltype(*name_size))entry.key().size()); + assert(*name_size > (long)entry.key().size()); for (auto c=0u; c < entry.key().size(); ++c) name_data[c] = entry.key()[c]; *name_size = entry.key().size(); From 4aecd3008f64454d160287e0cfd7b9e761d091df Mon Sep 17 00:00:00 2001 From: Jeffrey Curtis Date: Tue, 21 May 2024 22:10:56 -0500 Subject: [PATCH 16/18] add getters for sampled aero_mode properties. add to tests --- src/aero_mode.F90 | 36 ++++++++++++++++++++++++++++++++++++ src/aero_mode.hpp | 39 +++++++++++++++++++++++++++++++++++++++ src/pypartmc.cpp | 4 ++++ tests/test_aero_mode.py | 37 ++++++++++++++++++++++++++++++++++++- 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/aero_mode.F90 b/src/aero_mode.F90 index 1b514205..91952c10 100644 --- a/src/aero_mode.F90 +++ b/src/aero_mode.F90 @@ -244,4 +244,40 @@ subroutine f_aero_mode_get_name(ptr_c, name_data, name_size) bind(C) name_data = c_loc(aero_mode%name) name_size = len_trim(aero_mode%name) end subroutine + + subroutine f_aero_mode_get_sample_num_conc(ptr_c, arr_data, data_size) bind(C) + type(c_ptr), intent(in) :: ptr_c + type(aero_mode_t), pointer :: aero_mode => null() + integer(c_int), intent(in) :: data_size + real(c_double), dimension(data_size), intent(inout) :: arr_data + + call c_f_pointer(ptr_c, aero_mode) + + arr_data = aero_mode%sample_num_conc + + end subroutine + + subroutine f_aero_mode_get_sample_radius(ptr_c, arr_data, data_size) bind(C) + type(c_ptr), intent(in) :: ptr_c + type(aero_mode_t), pointer :: aero_mode => null() + integer(c_int), intent(in) :: data_size + real(c_double), dimension(data_size), intent(inout) :: arr_data + + call c_f_pointer(ptr_c, aero_mode) + + arr_data = aero_mode%sample_radius + + end subroutine + + subroutine f_aero_mode_get_sample_bins(ptr_c, n_bins) bind(c) + type(c_ptr), intent(in) :: ptr_c + type(aero_mode_t), pointer :: aero_mode => null() + integer(c_int), intent(out) :: n_bins + + call c_f_pointer(ptr_c, aero_mode) + + n_bins = size(aero_mode%sample_num_conc) + + end subroutine + end module diff --git a/src/aero_mode.hpp b/src/aero_mode.hpp index 8c76b80f..e7576d87 100644 --- a/src/aero_mode.hpp +++ b/src/aero_mode.hpp @@ -118,6 +118,20 @@ extern "C" void f_aero_mode_from_json( void *aero_data_ptr ) noexcept; +extern "C" void f_aero_mode_get_sample_num_conc( + const void *ptr, + void *sample_num_conc_data, + const int *sample_num_conc_data_size +) noexcept; + +extern "C" void f_aero_mode_get_sample_radius( + const void *ptr, + void *sample_radius_data, + const int *sample_radius_data_size +) noexcept; + +extern "C" void f_aero_mode_get_sample_bins(const void *ptr, int *n_bins) noexcept; + struct AeroMode { PMCResource ptr; @@ -330,4 +344,29 @@ struct AeroMode { name[i] = f_ptr[i]; return name; } + + static auto get_sample_radius(const AeroMode &self) { + int len; + f_aero_mode_get_sample_bins(self.ptr.f_arg(), &len); + len++; + std::valarray sample_radius(len); + f_aero_mode_get_sample_radius( + self.ptr.f_arg(), + begin(sample_radius), + &len + ); + return sample_radius; + } + + static auto get_sample_num_conc(const AeroMode &self) { + int len; + f_aero_mode_get_sample_bins(self.ptr.f_arg(), &len); + std::valarray sample_num_conc(len); + f_aero_mode_get_sample_num_conc( + self.ptr.f_arg(), + begin(sample_num_conc), + &len + ); + return sample_num_conc; + } }; diff --git a/src/pypartmc.cpp b/src/pypartmc.cpp index 900b4726..94b200b6 100644 --- a/src/pypartmc.cpp +++ b/src/pypartmc.cpp @@ -453,6 +453,10 @@ PYBIND11_MODULE(_PyPartMC, m) { .def_property("gsd", &AeroMode::get_gsd, &AeroMode::set_gsd, "Geometric standard deviation") .def("set_sample", &AeroMode::set_sampled) + .def_property_readonly("sample_num_conc", &AeroMode::get_sample_num_conc, + "Sample bin number concentrations (m^{-3})") + .def_property_readonly("sample_radius", &AeroMode::get_sample_radius, + "Sample bin radii (m).") .def_property("type", &AeroMode::get_type, &AeroMode::set_type, "Mode type (given by module constants)") .def_property("name", &AeroMode::get_name, &AeroMode::set_name, diff --git a/tests/test_aero_mode.py b/tests/test_aero_mode.py index fb283eeb..0fecece9 100644 --- a/tests/test_aero_mode.py +++ b/tests/test_aero_mode.py @@ -366,6 +366,7 @@ def test_sampled(): aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) # act + diams = [1, 2, 3, 4] num_concs = [100, 200, 300] sut = ppmc.AeroMode( aero_data, @@ -375,7 +376,7 @@ def test_sampled(): "diam_type": "geometric", "mode_type": "sampled", "size_dist": [ - {"diam": [1, 2, 3, 4]}, + {"diam": diams}, {"num_conc": num_concs}, ], } @@ -385,3 +386,37 @@ def test_sampled(): # assert assert sut.type == "sampled" assert sut.num_conc == np.sum(num_concs) + assert sut.sample_num_conc == num_concs + assert (np.array(sut.sample_radius) * 2 == diams).all() + + @staticmethod + def test_set_sample(): + # arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + + diams = [1, 2, 3, 4] + num_concs = [100, 200, 300] + sut = ppmc.AeroMode( + aero_data, + { + "test_mode": { + "mass_frac": [{"H2O": [1]}], + "diam_type": "geometric", + "mode_type": "sampled", + "size_dist": [ + {"diam": diams}, + {"num_conc": num_concs}, + ], + } + }, + ) + num_conc_orig = sut.num_conc + # act + diams = [0.5 * x for x in diams] + num_concs = [2 * x for x in num_concs] + sut.set_sample(diams, num_concs) + # assert + assert sut.num_conc == np.sum(num_concs) + assert sut.sample_num_conc == num_concs + assert (np.array(sut.sample_radius) * 2 == diams).all() + assert sut.num_conc == num_conc_orig * 2 From ab6b60b093c163633f9ee55e8d6bcd0a5f3415f9 Mon Sep 17 00:00:00 2001 From: Jeffrey Curtis Date: Tue, 21 May 2024 22:35:38 -0500 Subject: [PATCH 17/18] add dist sample tests for sampled and monodisperse modes --- tests/test_aero_state.py | 58 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/test_aero_state.py b/tests/test_aero_state.py index 85217bd6..bfbfb1f7 100644 --- a/tests/test_aero_state.py +++ b/tests/test_aero_state.py @@ -508,3 +508,61 @@ def test_dist_sample(args, weighting): # assert assert n_added > n_part * 0.5 assert n_added < n_part * 2 + + @staticmethod + def test_dist_sample_sampled(): + # arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + diams = [1, 2, 3, 4] + num_concs = [100, 200, 300] + aero_dist = ppmc.AeroDist( + aero_data, + [ + { + "test_mode": { + "mass_frac": [{"H2O": [1]}], + "diam_type": "geometric", + "mode_type": "sampled", + "size_dist": [ + {"diam": diams}, + {"num_conc": num_concs}, + ], + } + } + ], + ) + sut = ppmc.AeroState(aero_data, *AERO_STATE_CTOR_ARG_MINIMAL) + + # act + _ = sut.dist_sample(aero_dist, 1.0, 0.0, True, True) + + # assert + assert (np.array(sut.diameters()) >= diams[0]).all() + assert (np.array(sut.diameters()) <= diams[-1]).all() + + @staticmethod + def test_dist_sample_mono(): + # arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + diam = 2e-6 + aero_dist = ppmc.AeroDist( + aero_data, + [ + { + "test_mode": { + "mass_frac": [{"H2O": [1]}], + "diam_type": "geometric", + "mode_type": "mono", + "num_conc": 1e12, + "diam": diam, + } + } + ], + ) + sut = ppmc.AeroState(aero_data, *AERO_STATE_CTOR_ARG_MINIMAL) + + # act + _ = sut.dist_sample(aero_dist, 1.0, 0.0, True, True) + + # assert + assert np.isclose(np.array(sut.diameters()), diam).all() From 2f155e9eff6fa73ba39aba3afedacb73e5ae9235 Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Wed, 22 May 2024 21:59:07 +0200 Subject: [PATCH 18/18] remove code duplication in tests (thank you pylint) --- tests/test_aero_mode.py | 43 ++++++++++++++++++++++------------------ tests/test_aero_state.py | 30 ++++++++++------------------ 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/tests/test_aero_mode.py b/tests/test_aero_mode.py index 0fecece9..1e0b1c18 100644 --- a/tests/test_aero_mode.py +++ b/tests/test_aero_mode.py @@ -48,6 +48,18 @@ } } +AERO_MODE_CTOR_SAMPLED = { + "test_mode": { + "mass_frac": [{"H2O": [1]}], + "diam_type": "geometric", + "mode_type": "sampled", + "size_dist": [ + {"diam": [1, 2, 3, 4]}, + {"num_conc": [100, 200, 300]}, + ], + } +} + class TestAeroMode: @staticmethod @@ -366,28 +378,21 @@ def test_sampled(): aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) # act - diams = [1, 2, 3, 4] - num_concs = [100, 200, 300] - sut = ppmc.AeroMode( - aero_data, - { - "test_mode": { - "mass_frac": [{"H2O": [1]}], - "diam_type": "geometric", - "mode_type": "sampled", - "size_dist": [ - {"diam": diams}, - {"num_conc": num_concs}, - ], - } - }, - ) + sut = ppmc.AeroMode(aero_data, AERO_MODE_CTOR_SAMPLED) # assert assert sut.type == "sampled" - assert sut.num_conc == np.sum(num_concs) - assert sut.sample_num_conc == num_concs - assert (np.array(sut.sample_radius) * 2 == diams).all() + assert sut.num_conc == np.sum( + AERO_MODE_CTOR_SAMPLED["test_mode"]["size_dist"][1]["num_conc"] + ) + assert ( + sut.sample_num_conc + == AERO_MODE_CTOR_SAMPLED["test_mode"]["size_dist"][1]["num_conc"] + ) + assert ( + np.array(sut.sample_radius) * 2 + == AERO_MODE_CTOR_SAMPLED["test_mode"]["size_dist"][0]["diam"] + ).all() @staticmethod def test_set_sample(): diff --git a/tests/test_aero_state.py b/tests/test_aero_state.py index bfbfb1f7..74813d36 100644 --- a/tests/test_aero_state.py +++ b/tests/test_aero_state.py @@ -18,6 +18,7 @@ AERO_DIST_CTOR_ARG_FULL, AERO_DIST_CTOR_ARG_MINIMAL, ) +from .test_aero_mode import AERO_MODE_CTOR_SAMPLED from .test_env_state import ENV_STATE_CTOR_ARG_MINIMAL AERO_STATE_CTOR_ARG_MINIMAL = 44, "nummass_source" @@ -513,32 +514,21 @@ def test_dist_sample(args, weighting): def test_dist_sample_sampled(): # arrange aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) - diams = [1, 2, 3, 4] - num_concs = [100, 200, 300] - aero_dist = ppmc.AeroDist( - aero_data, - [ - { - "test_mode": { - "mass_frac": [{"H2O": [1]}], - "diam_type": "geometric", - "mode_type": "sampled", - "size_dist": [ - {"diam": diams}, - {"num_conc": num_concs}, - ], - } - } - ], - ) + aero_dist = ppmc.AeroDist(aero_data, [AERO_MODE_CTOR_SAMPLED]) sut = ppmc.AeroState(aero_data, *AERO_STATE_CTOR_ARG_MINIMAL) # act _ = sut.dist_sample(aero_dist, 1.0, 0.0, True, True) # assert - assert (np.array(sut.diameters()) >= diams[0]).all() - assert (np.array(sut.diameters()) <= diams[-1]).all() + assert ( + np.array(sut.diameters()) + >= AERO_MODE_CTOR_SAMPLED["test_mode"]["size_dist"][0]["diam"][0] + ).all() + assert ( + np.array(sut.diameters()) + <= AERO_MODE_CTOR_SAMPLED["test_mode"]["size_dist"][0]["diam"][-1] + ).all() @staticmethod def test_dist_sample_mono():