From 543579384ebc7ad09ed463eaff686f783d7653ce Mon Sep 17 00:00:00 2001 From: Griger5 Date: Fri, 3 Jan 2025 20:11:16 +0100 Subject: [PATCH 01/24] Add: Basic InputGuard implementation --- src/json_resource.hpp | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/json_resource.hpp b/src/json_resource.hpp index 91e46219..f904316c 100644 --- a/src/json_resource.hpp +++ b/src/json_resource.hpp @@ -10,10 +10,56 @@ #include #include #include +#include #include "nlohmann/json.hpp" #include #include +struct InputGuard { + public: + InputGuard(const nlohmann::json &j) { + map_inputs_recursive(j); + } + + ~InputGuard() { + check_used_inputs(); + } + + void mark_used_input(const std::string &input_name) { + this->used_inputs[input_name] = true; + } + + private: + std::map used_inputs; + + void map_inputs_recursive(const nlohmann::json &j) { + if (j.is_array()) { + for (auto item : j) { + map_inputs_recursive(item); + } + return; + } + + for(auto [key, value] : j.items()) + { + if (value.is_structured()) { + map_inputs_recursive(value); + } + + if (key.find_first_not_of(" \t\n\v\f\r") != std::string::npos) + this->used_inputs[key] = false; + } + } + + void check_used_inputs() { + for (auto item : used_inputs) { + if (!item.second) { + throw std::logic_error(std::string("Failed: \"") + item.first + std::string("\" parameter remains unused.")); + } + } + } +}; + struct JSONResource { private: std::set vars; From 3dc69515a877bebf459ebbb0a1b4d80b49afad78 Mon Sep 17 00:00:00 2001 From: Griger5 Date: Fri, 3 Jan 2025 20:30:43 +0100 Subject: [PATCH 02/24] Add: Integrated InputGuard into the library's basic inner workflow --- src/json_resource.hpp | 11 +++++++++++ src/spec_file_pypartmc.cpp | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/json_resource.hpp b/src/json_resource.hpp index f904316c..98da30d1 100644 --- a/src/json_resource.hpp +++ b/src/json_resource.hpp @@ -66,6 +66,8 @@ struct JSONResource { const nlohmann::json *json; std::stack json_parent; + std::unique_ptr input_guard_ptr; + void warn(const std::exception &exception) { std::cerr << "WARN: " << exception.what() << std::endl; // assert(false); @@ -81,6 +83,8 @@ struct JSONResource { for (auto &entry : this->json->items()) { this->vars.insert(entry.key()); } + + input_guard_ptr = std::make_unique(json); }; void set_current_json_ptr(const nlohmann::json *ptr) { @@ -111,6 +115,9 @@ struct JSONResource { key = item.key(); } } + + input_guard_ptr->mark_used_input(key); + return key; } @@ -258,6 +265,10 @@ struct JSONResource { return it; } + InputGuard *get_input_guard_ptr() { + return input_guard_ptr.get(); + } + virtual std::string str() const = 0; virtual bool read_line(std::string &name, std::string &data) = 0; diff --git a/src/spec_file_pypartmc.cpp b/src/spec_file_pypartmc.cpp index 214d94f0..d9e2719f 100644 --- a/src/spec_file_pypartmc.cpp +++ b/src/spec_file_pypartmc.cpp @@ -1,4 +1,4 @@ -/*################################################################################################## + /*################################################################################################## # This file is a part of PyPartMC licensed under the GNU General Public License v3 (LICENSE file) # # Copyright (C) 2022 University of Illinois Urbana-Champaign # # Authors: https://github.com/open-atmos/PyPartMC/graphs/contributors # @@ -17,6 +17,7 @@ void c_spec_file_read_real( const char *name_data, const int *name_size, double *var ) noexcept { json_resource_ptr()->read_value(bpstd::string_view(name_data, *name_size), var); + json_resource_ptr()->get_input_guard_ptr()->mark_used_input(static_cast(bpstd::string_view(name_data, *name_size))); } /*********************************************************************************/ @@ -45,6 +46,7 @@ void spec_file_read_string( int *var_size ) noexcept { json_resource_ptr()->read_str(name, var_data, var_size); + json_resource_ptr()->get_input_guard_ptr()->mark_used_input(static_cast(name)); } extern "C" @@ -184,6 +186,9 @@ void spec_file_read_real_named_array_data( for (auto idx=0u; idx < entry.value().size(); ++idx) { vals[idx] = entry.value().at(idx).get(); } + + json_resource_ptr()->get_input_guard_ptr()->mark_used_input(entry.key()); + break; } } From 3c5f50ff341d50461e5e1fc0310202269cccafb3 Mon Sep 17 00:00:00 2001 From: Griger5 Date: Mon, 20 Jan 2025 18:33:41 +0100 Subject: [PATCH 03/24] Fix: Updated tests, so they don't have unused parameters --- tests/test_aero_dist.py | 8 ++------ tests/test_aero_mode.py | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/test_aero_dist.py b/tests/test_aero_dist.py index 94e2d6eb..a6b3a284 100644 --- a/tests/test_aero_dist.py +++ b/tests/test_aero_dist.py @@ -19,6 +19,7 @@ AERO_MODE_CTOR_LOG_NORMAL, AERO_MODE_CTOR_LOG_NORMAL_COAGULATION, AERO_MODE_CTOR_LOG_NORMAL_FULL, + AERO_MODE_CTOR_SAMPLED, ) AERO_DIST_CTOR_ARG_MINIMAL = [ @@ -222,12 +223,7 @@ def test_ctor_error_on_repeated_massfrac_keys(): 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"] = [ - {"diam": [1, 2, 3, 4]}, - {"num_conc": [1, 2, 3]}, - ] + ctor_arg = [AERO_MODE_CTOR_SAMPLED] # act sut = ppmc.AeroDist(aero_data, ctor_arg) diff --git a/tests/test_aero_mode.py b/tests/test_aero_mode.py index e8526640..0130a607 100644 --- a/tests/test_aero_mode.py +++ b/tests/test_aero_mode.py @@ -26,6 +26,7 @@ } } + AERO_MODE_CTOR_LOG_NORMAL_FULL = { "test_mode": { "mass_frac": [{"SO4": [1]}], @@ -199,7 +200,6 @@ def test_set_sample_invalid(): "mode_type": "exp", "num_conc": 100 / si.m**3, "diam_at_mean_vol": 2 * si.um, - "temp": 300 * si.K, } }, ), From 9fcf0b20dc997f511ac277e8c46deb993273e83c Mon Sep 17 00:00:00 2001 From: Griger5 Date: Mon, 20 Jan 2025 18:44:42 +0100 Subject: [PATCH 04/24] Add: InputGuard now processes reading of integers and arrays --- src/spec_file_pypartmc.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/spec_file_pypartmc.cpp b/src/spec_file_pypartmc.cpp index d9e2719f..a1c3c438 100644 --- a/src/spec_file_pypartmc.cpp +++ b/src/spec_file_pypartmc.cpp @@ -27,6 +27,7 @@ void c_spec_file_read_integer( const char *name_data, const int *name_size, int *var ) noexcept { json_resource_ptr()->read_value(bpstd::string_view(name_data, *name_size), var); + json_resource_ptr()->get_input_guard_ptr()->mark_used_input(static_cast(bpstd::string_view(name_data, *name_size))); } /*********************************************************************************/ @@ -123,6 +124,7 @@ void spec_file_read_timed_real_array_data( ) noexcept { json_resource_ptr()->read_arr("time", times); json_resource_ptr()->read_arr(name, vals); + json_resource_ptr()->get_input_guard_ptr()->mark_used_input(static_cast(name)); } extern "C" From 99d0acf33cff643607081ec338f5e48331e68f65 Mon Sep 17 00:00:00 2001 From: Griger5 Date: Tue, 21 Jan 2025 17:33:25 +0100 Subject: [PATCH 05/24] Add&fix: InputGuard now catches boolean values. Fixed typo in TestCondense::test_equilib_particle --- src/spec_file_pypartmc.cpp | 1 + tests/test_condense.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/spec_file_pypartmc.cpp b/src/spec_file_pypartmc.cpp index a1c3c438..2b61a932 100644 --- a/src/spec_file_pypartmc.cpp +++ b/src/spec_file_pypartmc.cpp @@ -37,6 +37,7 @@ void c_spec_file_read_logical( const char *name_data, const int *name_size, bool *var ) noexcept { json_resource_ptr()->read_value(bpstd::string_view(name_data, *name_size), var); + json_resource_ptr()->get_input_guard_ptr()->mark_used_input(static_cast(bpstd::string_view(name_data, *name_size))); } /*********************************************************************************/ diff --git a/tests/test_condense.py b/tests/test_condense.py index 04417946..8fba6e66 100644 --- a/tests/test_condense.py +++ b/tests/test_condense.py @@ -42,7 +42,7 @@ def test_equilib_particles(): def test_equilib_particle(aero_data_params: dict): # arrange env_state_ctor_arg = ENV_STATE_CTOR_ARG_MINIMAL - env_state_ctor_arg["rel_humid"] = 0.99 + env_state_ctor_arg["rel_humidity"] = 0.99 env_state = ppmc.EnvState(env_state_ctor_arg) env_state.set_temperature(300) aero_data = ppmc.AeroData( From 99a8525edad04127d633ab701843e6693e57c04d Mon Sep 17 00:00:00 2001 From: Griger5 Date: Tue, 21 Jan 2025 19:05:11 +0100 Subject: [PATCH 06/24] Fix: Deleted 'do_optical' from an initializing loop in RunPartOpt object. Deleted 'do_optical' from RUN_PART_OPT_CTOR_ARG_SIMULATION test parameter config. All pytest tests are passing --- src/run_part_opt.hpp | 2 +- tests/test_run_part_opt.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/run_part_opt.hpp b/src/run_part_opt.hpp index f657dbe6..cdf3426f 100644 --- a/src/run_part_opt.hpp +++ b/src/run_part_opt.hpp @@ -30,7 +30,7 @@ struct RunPartOpt { json_copy["do_parallel"] = false; for (auto key : std::set({ - "do_mosaic", "do_camp_chem", "do_condensation", "do_optical", "do_nucleation", + "do_mosaic", "do_camp_chem", "do_condensation", "do_nucleation", })) if (json_copy.find(key) == json_copy.end()) json_copy[key] = false; diff --git a/tests/test_run_part_opt.py b/tests/test_run_part_opt.py index e17f3132..0ddbd1bd 100644 --- a/tests/test_run_part_opt.py +++ b/tests/test_run_part_opt.py @@ -25,7 +25,6 @@ "do_parallel": False, "do_nucleation": False, "do_mosaic": False, - "do_optical": False, "do_condensation": False, "do_camp_chem": False, "t_max": 86400.0, From 0cb419d2a609e73972a158a5912b750e2b30e3a8 Mon Sep 17 00:00:00 2001 From: Griger5 Date: Tue, 11 Feb 2025 16:40:05 +0100 Subject: [PATCH 07/24] Updated: Updated InputGuards inner logic, so it works properly with more compliacted JSONs --- src/json_resource.hpp | 142 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 123 insertions(+), 19 deletions(-) diff --git a/src/json_resource.hpp b/src/json_resource.hpp index 98da30d1..08047066 100644 --- a/src/json_resource.hpp +++ b/src/json_resource.hpp @@ -18,7 +18,9 @@ struct InputGuard { public: InputGuard(const nlohmann::json &j) { - map_inputs_recursive(j); + process_json(j); + + dict_key_present = false; } ~InputGuard() { @@ -26,31 +28,53 @@ struct InputGuard { } void mark_used_input(const std::string &input_name) { - this->used_inputs[input_name] = true; - } + if (prefixes.find(input_name) == prefixes.end()) { + std::string prefix = combine_str_vec(curr_prefix); - private: - std::map used_inputs; - - void map_inputs_recursive(const nlohmann::json &j) { - if (j.is_array()) { - for (auto item : j) { - map_inputs_recursive(item); + if (!prefix.empty()) { + used_inputs[prefix + "/" + input_name] = true; + } + else { + used_inputs[input_name] = true; } - return; } + } - for(auto [key, value] : j.items()) - { - if (value.is_structured()) { - map_inputs_recursive(value); + void update_dict_key(std::string dict_key) { + curr_dict_key = dict_key; + } + + void check_read_line(std::string line) { + if (line == "mode_name") { + if (dict_key_present) { + curr_prefix.pop_back(); } - - if (key.find_first_not_of(" \t\n\v\f\r") != std::string::npos) - this->used_inputs[key] = false; + curr_prefix.push_back(curr_dict_key); + dict_key_present = true; + } + else if (line.empty()) { + curr_prefix.pop_back(); + dict_key_present = false; } } + void open_spec_file(std::string spec_file_name) { + curr_prefix.push_back(spec_file_name); + } + + void close_spec_file() { + curr_prefix.pop_back(); + } + + private: + std::map used_inputs; + + std::set prefixes; + std::string curr_dict_key; + std::vector curr_prefix; + + bool dict_key_present; + void check_used_inputs() { for (auto item : used_inputs) { if (!item.second) { @@ -58,6 +82,86 @@ struct InputGuard { } } } + + void process_json(const nlohmann::json &j) { + nlohmann::json flat = j.flatten(); + + // JSON Pointer, as in a string syntax for identifying a specific value in JSON + std::vector json_pointers; + + for (auto f : flat.items()) { + json_pointers.push_back(clean_string(f.key())); + } + + std::set json_pointers_set(json_pointers.begin(), json_pointers.end()); + + for (auto s : json_pointers_set) { + used_inputs[s] = false; + } + + get_prefixes(json_pointers_set); + } + + std::string clean_string(std::string str) { + bool after_slash = false; + + for (size_t i = 0; i < str.size(); i++) { + if (str.at(i) == '/' && i+1 < str.size()) { + if (isdigit(str.at(i+1))) { + after_slash = true; + str.erase(i, 1); + i -= 1; + } + } + else if (isdigit(str.at(i)) && after_slash) { + str.erase(i, 1); + i -= 1; + } + else { + after_slash = false; + } + } + + str.erase(0, 1); + + return str; + } + + std::string combine_str_vec(std::vector vec) { + if (vec.size() == 0) return ""; + + std::string temp = vec[0]; + + for (size_t i = 1; i < vec.size(); i++) { + temp += "/"; + temp += vec[i]; + } + + return temp; + } + + void get_prefixes(std::set json_pointers_set) { + std::string temp; + std::vector temp_vec; + + for (auto s : json_pointers_set) { + std::stringstream line(s); + + while(getline(line, temp, '/')) { + temp_vec.push_back(temp); + } + + if (temp_vec.size() > 1) { + temp_vec.pop_back(); + + for (auto v : temp_vec) { + prefixes.insert(v); + } + } + + temp_vec.clear(); + } + } }; struct JSONResource { @@ -116,7 +220,7 @@ struct JSONResource { } } - input_guard_ptr->mark_used_input(key); + input_guard_ptr->update_dict_key(key); return key; } From 3024ee6d7abb2f2c5833361982d9cfa7fe98b3ba Mon Sep 17 00:00:00 2001 From: Griger5 Date: Tue, 11 Feb 2025 16:41:11 +0100 Subject: [PATCH 08/24] Update: Updated function calls for the new InputGuard --- src/spec_file_pypartmc.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/spec_file_pypartmc.cpp b/src/spec_file_pypartmc.cpp index 2b61a932..5c3a9909 100644 --- a/src/spec_file_pypartmc.cpp +++ b/src/spec_file_pypartmc.cpp @@ -68,6 +68,7 @@ void c_spec_file_read_string( void spec_file_open(const bpstd::string_view &filename) noexcept { json_resource_ptr()->zoom_in(filename); + json_resource_ptr()->get_input_guard_ptr()->open_spec_file(static_cast(filename)); } extern "C" @@ -84,6 +85,7 @@ void c_spec_file_open( void spec_file_close() noexcept { json_resource_ptr()->zoom_out(); + json_resource_ptr()->get_input_guard_ptr()->close_spec_file(); } extern "C" @@ -126,6 +128,7 @@ void spec_file_read_timed_real_array_data( json_resource_ptr()->read_arr("time", times); json_resource_ptr()->read_arr(name, vals); json_resource_ptr()->get_input_guard_ptr()->mark_used_input(static_cast(name)); + json_resource_ptr()->get_input_guard_ptr()->mark_used_input("time"); } extern "C" @@ -247,4 +250,6 @@ void c_spec_file_read_line( } *data0_size = i; } + + json_resource_ptr()->get_input_guard_ptr()->check_read_line(std::string(name_data, *name_size)); } From 6876613adf687c3c4bd6714736d62ebc26d0e324 Mon Sep 17 00:00:00 2001 From: Griger5 Date: Tue, 11 Feb 2025 16:53:37 +0100 Subject: [PATCH 09/24] Updated code style of InputGuard, so it matches the rest of the codebase --- src/json_resource.hpp | 210 +++++++++++++++++++++--------------------- 1 file changed, 105 insertions(+), 105 deletions(-) diff --git a/src/json_resource.hpp b/src/json_resource.hpp index 08047066..0d5070f7 100644 --- a/src/json_resource.hpp +++ b/src/json_resource.hpp @@ -16,152 +16,152 @@ #include struct InputGuard { - public: - InputGuard(const nlohmann::json &j) { - process_json(j); + public: + InputGuard(const nlohmann::json &j) { + process_json(j); - dict_key_present = false; - } + this->dict_key_present = false; + } - ~InputGuard() { - check_used_inputs(); - } + ~InputGuard() { + check_used_inputs(); + } - void mark_used_input(const std::string &input_name) { - if (prefixes.find(input_name) == prefixes.end()) { - std::string prefix = combine_str_vec(curr_prefix); + void mark_used_input(const std::string &input_name) { + if (this->prefixes.find(input_name) == this->prefixes.end()) { + std::string prefix = combine_str_vec(this->curr_prefix); - if (!prefix.empty()) { - used_inputs[prefix + "/" + input_name] = true; - } - else { - used_inputs[input_name] = true; - } + if (!prefix.empty()) { + this->used_inputs[prefix + "/" + input_name] = true; + } + else { + this->used_inputs[input_name] = true; } } + } - void update_dict_key(std::string dict_key) { - curr_dict_key = dict_key; - } + void update_dict_key(std::string dict_key) { + this->curr_dict_key = dict_key; + } - void check_read_line(std::string line) { - if (line == "mode_name") { - if (dict_key_present) { - curr_prefix.pop_back(); - } - curr_prefix.push_back(curr_dict_key); - dict_key_present = true; - } - else if (line.empty()) { - curr_prefix.pop_back(); - dict_key_present = false; + void check_read_line(std::string line) { + if (line == "mode_name") { + if (this->dict_key_present) { + this->curr_prefix.pop_back(); } + this->curr_prefix.push_back(this->curr_dict_key); + this->dict_key_present = true; } - - void open_spec_file(std::string spec_file_name) { - curr_prefix.push_back(spec_file_name); + else if (line.empty()) { + this->curr_prefix.pop_back(); + this->dict_key_present = false; } + } - void close_spec_file() { - curr_prefix.pop_back(); - } + void open_spec_file(std::string spec_file_name) { + this->curr_prefix.push_back(spec_file_name); + } + + void close_spec_file() { + this->curr_prefix.pop_back(); + } - private: - std::map used_inputs; + private: + std::map used_inputs; - std::set prefixes; - std::string curr_dict_key; - std::vector curr_prefix; + std::set prefixes; + std::string curr_dict_key; + std::vector curr_prefix; - bool dict_key_present; + bool dict_key_present; - void check_used_inputs() { - for (auto item : used_inputs) { - if (!item.second) { - throw std::logic_error(std::string("Failed: \"") + item.first + std::string("\" parameter remains unused.")); - } + void check_used_inputs() { + for (auto item : this->used_inputs) { + if (!item.second) { + throw std::logic_error(std::string("Failed: \"") + item.first + std::string("\" parameter remains unused.")); } } - - void process_json(const nlohmann::json &j) { - nlohmann::json flat = j.flatten(); - - // JSON Pointer, as in a string syntax for identifying a specific value in JSON - std::vector json_pointers; + } + + void process_json(const nlohmann::json &j) { + nlohmann::json flat = j.flatten(); - for (auto f : flat.items()) { - json_pointers.push_back(clean_string(f.key())); - } + // JSON Pointer, as in a string syntax for identifying a specific value in JSON + std::vector json_pointers; - std::set json_pointers_set(json_pointers.begin(), json_pointers.end()); + for (auto f : flat.items()) { + json_pointers.push_back(clean_string(f.key())); + } - for (auto s : json_pointers_set) { - used_inputs[s] = false; - } + std::set json_pointers_set(json_pointers.begin(), json_pointers.end()); - get_prefixes(json_pointers_set); + for (auto s : json_pointers_set) { + this->used_inputs[s] = false; } - std::string clean_string(std::string str) { - bool after_slash = false; + get_prefixes(json_pointers_set); + } + + std::string clean_string(std::string str) { + bool after_slash = false; - for (size_t i = 0; i < str.size(); i++) { - if (str.at(i) == '/' && i+1 < str.size()) { - if (isdigit(str.at(i+1))) { - after_slash = true; - str.erase(i, 1); - i -= 1; - } - } - else if (isdigit(str.at(i)) && after_slash) { + for (size_t i = 0; i < str.size(); i++) { + if (str.at(i) == '/' && i+1 < str.size()) { + if (isdigit(str.at(i+1))) { + after_slash = true; str.erase(i, 1); i -= 1; } - else { - after_slash = false; - } } - - str.erase(0, 1); - - return str; + else if (isdigit(str.at(i)) && after_slash) { + str.erase(i, 1); + i -= 1; + } + else { + after_slash = false; + } } - std::string combine_str_vec(std::vector vec) { - if (vec.size() == 0) return ""; - - std::string temp = vec[0]; + str.erase(0, 1); - for (size_t i = 1; i < vec.size(); i++) { - temp += "/"; - temp += vec[i]; - } + return str; + } + + std::string combine_str_vec(std::vector vec) { + if (vec.size() == 0) return ""; + + std::string temp = vec[0]; - return temp; + for (size_t i = 1; i < vec.size(); i++) { + temp += "/"; + temp += vec[i]; } - void get_prefixes(std::set json_pointers_set) { - std::string temp; - std::vector temp_vec; - - for (auto s : json_pointers_set) { - std::stringstream line(s); + return temp; + } - while(getline(line, temp, '/')) { - temp_vec.push_back(temp); - } + void get_prefixes(std::set json_pointers_set) { + std::string temp; + std::vector temp_vec; - if (temp_vec.size() > 1) { - temp_vec.pop_back(); + for (auto s : json_pointers_set) { + std::stringstream line(s); - for (auto v : temp_vec) { - prefixes.insert(v); - } - } + while(getline(line, temp, '/')) { + temp_vec.push_back(temp); + } + + if (temp_vec.size() > 1) { + temp_vec.pop_back(); - temp_vec.clear(); + for (auto v : temp_vec) { + this->prefixes.insert(v); + } } + + temp_vec.clear(); } + } }; struct JSONResource { From 7b539f6784d56ac69513450dc546f1d7edade90c Mon Sep 17 00:00:00 2001 From: Griger5 Date: Thu, 27 Feb 2025 13:07:38 +0100 Subject: [PATCH 10/24] Update: Moved InputGuard definition to seperate file --- src/input_guard.hpp | 155 ++++++++++++++++++++++++++++++++++++++++++ src/json_resource.hpp | 150 +--------------------------------------- 2 files changed, 156 insertions(+), 149 deletions(-) create mode 100644 src/input_guard.hpp diff --git a/src/input_guard.hpp b/src/input_guard.hpp new file mode 100644 index 00000000..46c9b9ff --- /dev/null +++ b/src/input_guard.hpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include "pybind11_json/pybind11_json.hpp" +#include "nlohmann/json.hpp" + +struct InputGuard { + public: + InputGuard(const nlohmann::json &j) { + process_json(j); + + this->dict_key_present = false; + } + + ~InputGuard() { + check_used_inputs(); + } + + void mark_used_input(const std::string &input_name) { + if (this->prefixes.find(input_name) == this->prefixes.end()) { + std::string prefix = combine_str_vec(this->curr_prefix); + + if (!prefix.empty()) { + this->used_inputs[prefix + "/" + input_name] = true; + } + else { + this->used_inputs[input_name] = true; + } + } + } + + void update_dict_key(std::string dict_key) { + this->curr_dict_key = dict_key; + } + + void check_read_line(std::string line) { + if (line == "mode_name") { + if (this->dict_key_present) { + this->curr_prefix.pop_back(); + } + this->curr_prefix.push_back(this->curr_dict_key); + this->dict_key_present = true; + } + else if (line.empty()) { + this->curr_prefix.pop_back(); + this->dict_key_present = false; + } + } + + void open_spec_file(std::string spec_file_name) { + this->curr_prefix.push_back(spec_file_name); + } + + void close_spec_file() { + this->curr_prefix.pop_back(); + } + + private: + std::map used_inputs; + + std::set prefixes; + std::string curr_dict_key; + std::vector curr_prefix; + + bool dict_key_present; + + void check_used_inputs() { + for (auto item : this->used_inputs) { + if (!item.second) { + std::string err = std::string("Failed: \"") + item.first + std::string("\" parameter remains unused."); + throw std::runtime_error(err); + } + } + } + + void process_json(const nlohmann::json &j) { + nlohmann::json flat = j.flatten(); + + // JSON Pointer, as in a string syntax for identifying a specific value in JSON + std::vector json_pointers; + + for (auto f : flat.items()) { + json_pointers.push_back(clean_string(f.key())); + } + + std::set json_pointers_set(json_pointers.begin(), json_pointers.end()); + + for (auto s : json_pointers_set) { + this->used_inputs[s] = false; + } + + get_prefixes(json_pointers_set); + } + + std::string clean_string(std::string str) { + bool after_slash = false; + + for (size_t i = 0; i < str.size(); i++) { + if (str.at(i) == '/' && i+1 < str.size()) { + if (isdigit(str.at(i+1))) { + after_slash = true; + str.erase(i, 1); + i -= 1; + } + } + else if (isdigit(str.at(i)) && after_slash) { + str.erase(i, 1); + i -= 1; + } + else { + after_slash = false; + } + } + + str.erase(0, 1); + + return str; + } + + std::string combine_str_vec(std::vector vec) { + if (vec.size() == 0) return ""; + + std::string temp = vec[0]; + + for (size_t i = 1; i < vec.size(); i++) { + temp += "/"; + temp += vec[i]; + } + + return temp; + } + + void get_prefixes(std::set json_pointers_set) { + std::string temp; + std::vector temp_vec; + + for (auto s : json_pointers_set) { + std::stringstream line(s); + + while(getline(line, temp, '/')) { + temp_vec.push_back(temp); + } + + if (temp_vec.size() > 1) { + temp_vec.pop_back(); + + for (auto v : temp_vec) { + this->prefixes.insert(v); + } + } + + temp_vec.clear(); + } + } +}; \ No newline at end of file diff --git a/src/json_resource.hpp b/src/json_resource.hpp index 0d5070f7..3e093198 100644 --- a/src/json_resource.hpp +++ b/src/json_resource.hpp @@ -14,155 +14,7 @@ #include "nlohmann/json.hpp" #include #include - -struct InputGuard { - public: - InputGuard(const nlohmann::json &j) { - process_json(j); - - this->dict_key_present = false; - } - - ~InputGuard() { - check_used_inputs(); - } - - void mark_used_input(const std::string &input_name) { - if (this->prefixes.find(input_name) == this->prefixes.end()) { - std::string prefix = combine_str_vec(this->curr_prefix); - - if (!prefix.empty()) { - this->used_inputs[prefix + "/" + input_name] = true; - } - else { - this->used_inputs[input_name] = true; - } - } - } - - void update_dict_key(std::string dict_key) { - this->curr_dict_key = dict_key; - } - - void check_read_line(std::string line) { - if (line == "mode_name") { - if (this->dict_key_present) { - this->curr_prefix.pop_back(); - } - this->curr_prefix.push_back(this->curr_dict_key); - this->dict_key_present = true; - } - else if (line.empty()) { - this->curr_prefix.pop_back(); - this->dict_key_present = false; - } - } - - void open_spec_file(std::string spec_file_name) { - this->curr_prefix.push_back(spec_file_name); - } - - void close_spec_file() { - this->curr_prefix.pop_back(); - } - - private: - std::map used_inputs; - - std::set prefixes; - std::string curr_dict_key; - std::vector curr_prefix; - - bool dict_key_present; - - void check_used_inputs() { - for (auto item : this->used_inputs) { - if (!item.second) { - throw std::logic_error(std::string("Failed: \"") + item.first + std::string("\" parameter remains unused.")); - } - } - } - - void process_json(const nlohmann::json &j) { - nlohmann::json flat = j.flatten(); - - // JSON Pointer, as in a string syntax for identifying a specific value in JSON - std::vector json_pointers; - - for (auto f : flat.items()) { - json_pointers.push_back(clean_string(f.key())); - } - - std::set json_pointers_set(json_pointers.begin(), json_pointers.end()); - - for (auto s : json_pointers_set) { - this->used_inputs[s] = false; - } - - get_prefixes(json_pointers_set); - } - - std::string clean_string(std::string str) { - bool after_slash = false; - - for (size_t i = 0; i < str.size(); i++) { - if (str.at(i) == '/' && i+1 < str.size()) { - if (isdigit(str.at(i+1))) { - after_slash = true; - str.erase(i, 1); - i -= 1; - } - } - else if (isdigit(str.at(i)) && after_slash) { - str.erase(i, 1); - i -= 1; - } - else { - after_slash = false; - } - } - - str.erase(0, 1); - - return str; - } - - std::string combine_str_vec(std::vector vec) { - if (vec.size() == 0) return ""; - - std::string temp = vec[0]; - - for (size_t i = 1; i < vec.size(); i++) { - temp += "/"; - temp += vec[i]; - } - - return temp; - } - - void get_prefixes(std::set json_pointers_set) { - std::string temp; - std::vector temp_vec; - - for (auto s : json_pointers_set) { - std::stringstream line(s); - - while(getline(line, temp, '/')) { - temp_vec.push_back(temp); - } - - if (temp_vec.size() > 1) { - temp_vec.pop_back(); - - for (auto v : temp_vec) { - this->prefixes.insert(v); - } - } - - temp_vec.clear(); - } - } -}; +#include "input_guard.hpp" struct JSONResource { private: From 6afd3643d943d06022e0047febd8e7f530801d73 Mon Sep 17 00:00:00 2001 From: Griger5 Date: Thu, 27 Feb 2025 14:28:57 +0100 Subject: [PATCH 11/24] Update: Added try-catch statement to InputGuard's destructor, which prints the error to stderr --- src/input_guard.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/input_guard.hpp b/src/input_guard.hpp index 46c9b9ff..9bff8f4d 100644 --- a/src/input_guard.hpp +++ b/src/input_guard.hpp @@ -13,7 +13,12 @@ struct InputGuard { } ~InputGuard() { - check_used_inputs(); + try { + check_used_inputs(); + } + catch (const std::exception &e) { + std::cerr << e.what() << std::endl; + } } void mark_used_input(const std::string &input_name) { @@ -67,7 +72,7 @@ struct InputGuard { void check_used_inputs() { for (auto item : this->used_inputs) { if (!item.second) { - std::string err = std::string("Failed: \"") + item.first + std::string("\" parameter remains unused."); + std::string err = std::string("WARNING: \"") + item.first + std::string("\" parameter remains unused."); throw std::runtime_error(err); } } From be669ae9d60b4eb83c6cd8cb43dafcb9a748ba7e Mon Sep 17 00:00:00 2001 From: Griger5 Date: Thu, 27 Feb 2025 14:29:40 +0100 Subject: [PATCH 12/24] Add: Tests for InputGuard --- tests/test_input_guard.py | 51 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/test_input_guard.py diff --git a/tests/test_input_guard.py b/tests/test_input_guard.py new file mode 100644 index 00000000..a47fc05d --- /dev/null +++ b/tests/test_input_guard.py @@ -0,0 +1,51 @@ +import copy + +import PyPartMC as ppmc + +from .test_aero_data import AERO_DATA_CTOR_ARG_MINIMAL +from .test_aero_mode import AERO_MODE_CTOR_LOG_NORMAL, AERO_MODE_CTOR_SAMPLED + +class TestInputGuard: + @staticmethod + def test_unused_parameter(capfd): + # arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + mode = copy.deepcopy(AERO_MODE_CTOR_LOG_NORMAL) + mode["test_mode"]["not_used"] = 0 + + # act + ppmc.AeroDist(aero_data, [mode]) + captured = capfd.readouterr() + + # assert + assert captured.err == "WARNING: \"test_mode/not_used\" parameter remains unused.\n" + + @staticmethod + def test_nested_unsued_parameter(capfd): + # arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + mode = copy.deepcopy(AERO_MODE_CTOR_SAMPLED) + mode["test_mode"]["size_dist2"] = [{"diam" : [1]}, {"num_conc" : [100]}] + + # act + ppmc.AeroDist(aero_data, [mode]) + captured = capfd.readouterr() + + # assert + assert captured.err == "WARNING: \"test_mode/size_dist2/diam\" parameter remains unused.\n" + + @staticmethod + def test_same_name_unused_parameter(capfd): + # arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + modes = {} + modes["log_normal_mode"] = copy.deepcopy(AERO_MODE_CTOR_LOG_NORMAL["test_mode"]) + modes["sampled_mode"] = copy.deepcopy(AERO_MODE_CTOR_SAMPLED["test_mode"]) + modes["sampled_mode"]["geom_mean_diam"] = 10 + + # act + ppmc.AeroDist(aero_data, [modes]) + captured = capfd.readouterr() + + # assert + assert captured.err == "WARNING: \"sampled_mode/geom_mean_diam\" parameter remains unused.\n" \ No newline at end of file From 95b6066ca297e0e7b451bf78a160f516f50607ab Mon Sep 17 00:00:00 2001 From: Griger5 Date: Thu, 13 Mar 2025 16:28:01 +0100 Subject: [PATCH 13/24] Update: removed check_used_inputs from the InputGuard destructor, a JSONGuard function can now call this function --- src/input_guard.hpp | 23 ++++++++--------------- src/json_resource.hpp | 4 ++++ 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/input_guard.hpp b/src/input_guard.hpp index 9bff8f4d..555a3774 100644 --- a/src/input_guard.hpp +++ b/src/input_guard.hpp @@ -12,12 +12,14 @@ struct InputGuard { this->dict_key_present = false; } - ~InputGuard() { - try { - check_used_inputs(); - } - catch (const std::exception &e) { - std::cerr << e.what() << std::endl; + ~InputGuard() {} + + void check_used_inputs() { + for (auto item : this->used_inputs) { + if (!item.second) { + std::string err = std::string("WARNING: \"") + item.first + std::string("\" parameter remains unused."); + throw std::runtime_error(err); + } } } @@ -69,15 +71,6 @@ struct InputGuard { bool dict_key_present; - void check_used_inputs() { - for (auto item : this->used_inputs) { - if (!item.second) { - std::string err = std::string("WARNING: \"") + item.first + std::string("\" parameter remains unused."); - throw std::runtime_error(err); - } - } - } - void process_json(const nlohmann::json &j) { nlohmann::json flat = j.flatten(); diff --git a/src/json_resource.hpp b/src/json_resource.hpp index 3e093198..dd558e4c 100644 --- a/src/json_resource.hpp +++ b/src/json_resource.hpp @@ -355,5 +355,9 @@ struct JSONResourceGuard { ~JSONResourceGuard() { json_resource_ptr().reset(); } + + void check_parameters() { + json_resource_ptr()->get_input_guard_ptr()->check_used_inputs(); + } }; From e13cddda222b02f0b1216fdd69d163dbae2cc849 Mon Sep 17 00:00:00 2001 From: Griger5 Date: Thu, 13 Mar 2025 16:28:58 +0100 Subject: [PATCH 14/24] Add: objects accepting JSONs now check if all inputs were parsed --- src/env_state.hpp | 1 + src/gas_data.hpp | 1 + src/gas_state.hpp | 1 + src/run_part_opt.hpp | 1 + src/scenario.hpp | 1 + 5 files changed, 5 insertions(+) diff --git a/src/env_state.hpp b/src/env_state.hpp index 5a443cdc..990742d5 100644 --- a/src/env_state.hpp +++ b/src/env_state.hpp @@ -34,6 +34,7 @@ struct EnvState { { JSONResourceGuard guard(json); f_env_state_from_json(this->ptr.f_arg()); + guard.check_parameters(); } EnvState() : diff --git a/src/gas_data.hpp b/src/gas_data.hpp index ecb14998..7bcd0103 100644 --- a/src/gas_data.hpp +++ b/src/gas_data.hpp @@ -39,6 +39,7 @@ struct GasData { JSONResourceGuard guard(json_array); f_gas_data_from_json(this->ptr.f_arg()); + guard.check_parameters(); } GasData() : diff --git a/src/gas_state.hpp b/src/gas_state.hpp index 050795b1..c6769ca6 100644 --- a/src/gas_state.hpp +++ b/src/gas_state.hpp @@ -125,5 +125,6 @@ struct GasState { self.ptr.f_arg(), self.gas_data->ptr.f_arg() ); + guard.check_parameters(); } }; diff --git a/src/run_part_opt.hpp b/src/run_part_opt.hpp index cdf3426f..be7ed57c 100644 --- a/src/run_part_opt.hpp +++ b/src/run_part_opt.hpp @@ -51,6 +51,7 @@ struct RunPartOpt { JSONResourceGuard guard(json_copy); f_run_part_opt_from_json(this->ptr.f_arg()); + guard.check_parameters(); } static auto t_max(const RunPartOpt &self){ diff --git a/src/scenario.hpp b/src/scenario.hpp index 4ea56277..8ba25b69 100644 --- a/src/scenario.hpp +++ b/src/scenario.hpp @@ -129,6 +129,7 @@ struct Scenario { aero_data.ptr.f_arg(), this->ptr.f_arg() ); + guard.check_parameters(); } static auto __str__(const Scenario &self) { From 4afebb594e0bbdf61685d713eaef687d256c9f62 Mon Sep 17 00:00:00 2001 From: Griger5 Date: Thu, 13 Mar 2025 16:29:31 +0100 Subject: [PATCH 15/24] Add: added tests for all objects that check if inputs were used --- tests/test_input_guard.py | 84 +++++++++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 13 deletions(-) diff --git a/tests/test_input_guard.py b/tests/test_input_guard.py index a47fc05d..fa94f249 100644 --- a/tests/test_input_guard.py +++ b/tests/test_input_guard.py @@ -1,41 +1,99 @@ import copy +import pytest import PyPartMC as ppmc +from .common import ENV_STATE_CTOR_ARG_MINIMAL from .test_aero_data import AERO_DATA_CTOR_ARG_MINIMAL from .test_aero_mode import AERO_MODE_CTOR_LOG_NORMAL, AERO_MODE_CTOR_SAMPLED +from .test_gas_data import GAS_DATA_CTOR_ARG_MINIMAL +from .test_run_part_opt import RUN_PART_OPT_CTOR_ARG_MINIMAL +from .test_scenario import SCENARIO_CTOR_ARG_MINIMAL class TestInputGuard: @staticmethod - def test_unused_parameter(capfd): + def test_aero_dist_unused_parameter(): # arrange aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) mode = copy.deepcopy(AERO_MODE_CTOR_LOG_NORMAL) mode["test_mode"]["not_used"] = 0 # act - ppmc.AeroDist(aero_data, [mode]) - captured = capfd.readouterr() + with pytest.raises(RuntimeError) as exc_info: + ppmc.AeroDist(aero_data, [mode]) # assert - assert captured.err == "WARNING: \"test_mode/not_used\" parameter remains unused.\n" + assert str(exc_info.value) == "WARNING: \"test_mode/not_used\" parameter remains unused." @staticmethod - def test_nested_unsued_parameter(capfd): + def test_aero_mode_unused_parameter(): + # arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + mode = copy.deepcopy(AERO_MODE_CTOR_LOG_NORMAL) + mode["test_mode"]["not_used"] = 0 + + # act + with pytest.raises(RuntimeError) as exc_info: + ppmc.AeroMode(aero_data, mode) + + # assert + assert str(exc_info.value) == "WARNING: \"test_mode/not_used\" parameter remains unused." + + @staticmethod + def test_env_state_unused_parameter(): + # arrange + args = copy.deepcopy(ENV_STATE_CTOR_ARG_MINIMAL) + args["not_used"] = 0 + + # act + with pytest.raises(RuntimeError) as exc_info: + ppmc.EnvState(args) + + # assert + assert str(exc_info.value) == "WARNING: \"not_used\" parameter remains unused." + + @staticmethod + def test_run_part_opt_unused_parameter(): + #arrange + args = copy.deepcopy(RUN_PART_OPT_CTOR_ARG_MINIMAL) + args["not_used"] = 0 + + #act + with pytest.raises(RuntimeError) as exc_info: + ppmc.RunPartOpt(args) + + assert str(exc_info.value) == "WARNING: \"not_used\" parameter remains unused." + + @staticmethod + def test_scenario_unused_parameter(): + #arrange + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + gas_data = ppmc.GasData(GAS_DATA_CTOR_ARG_MINIMAL) + args = copy.deepcopy(SCENARIO_CTOR_ARG_MINIMAL) + args["not_used"] = 0 + + #act + with pytest.raises(RuntimeError) as exc_info: + ppmc.Scenario(gas_data, aero_data, args) + + assert str(exc_info.value) == "WARNING: \"not_used\" parameter remains unused." + + @staticmethod + def test_nested_unused_parameter(): # arrange aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) mode = copy.deepcopy(AERO_MODE_CTOR_SAMPLED) mode["test_mode"]["size_dist2"] = [{"diam" : [1]}, {"num_conc" : [100]}] # act - ppmc.AeroDist(aero_data, [mode]) - captured = capfd.readouterr() + with pytest.raises(RuntimeError) as exc_info: + ppmc.AeroDist(aero_data, [mode]) # assert - assert captured.err == "WARNING: \"test_mode/size_dist2/diam\" parameter remains unused.\n" + assert str(exc_info.value) == "WARNING: \"test_mode/size_dist2/diam\" parameter remains unused." @staticmethod - def test_same_name_unused_parameter(capfd): + def test_same_name_unused_parameter(): # arrange aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) modes = {} @@ -44,8 +102,8 @@ def test_same_name_unused_parameter(capfd): modes["sampled_mode"]["geom_mean_diam"] = 10 # act - ppmc.AeroDist(aero_data, [modes]) - captured = capfd.readouterr() - + with pytest.raises(RuntimeError) as exc_info: + ppmc.AeroDist(aero_data, [modes]) + # assert - assert captured.err == "WARNING: \"sampled_mode/geom_mean_diam\" parameter remains unused.\n" \ No newline at end of file + assert str(exc_info.value) == "WARNING: \"sampled_mode/geom_mean_diam\" parameter remains unused." \ No newline at end of file From 131060f1abc4047459860bacd657ba5cf32b39e3 Mon Sep 17 00:00:00 2001 From: Griger5 Date: Thu, 13 Mar 2025 16:39:17 +0100 Subject: [PATCH 16/24] Add: add missing check_parameters calls --- src/aero_data.hpp | 1 + src/aero_dist.hpp | 1 + src/aero_mode.hpp | 1 + 3 files changed, 3 insertions(+) diff --git a/src/aero_data.hpp b/src/aero_data.hpp index f830e928..4143959f 100644 --- a/src/aero_data.hpp +++ b/src/aero_data.hpp @@ -46,6 +46,7 @@ struct AeroData { JSONResourceGuard guard(json); f_aero_data_from_json(this->ptr.f_arg()); + guard.check_parameters(); } AeroData() : diff --git a/src/aero_dist.hpp b/src/aero_dist.hpp index 0287a8b7..d02665a3 100644 --- a/src/aero_dist.hpp +++ b/src/aero_dist.hpp @@ -56,6 +56,7 @@ struct AeroDist { JSONResourceGuard guard(json, "", "mode_name", 1); f_aero_dist_from_json(ptr.f_arg_non_const(), aero_data->ptr.f_arg_non_const()); + guard.check_parameters(); } AeroDist() : diff --git a/src/aero_mode.hpp b/src/aero_mode.hpp index e7576d87..4ed3d8d9 100644 --- a/src/aero_mode.hpp +++ b/src/aero_mode.hpp @@ -147,6 +147,7 @@ struct AeroMode { check_mode_json(json.begin().value()); JSONResourceGuard guard(json, "", "mode_name"); f_aero_mode_from_json(ptr.f_arg_non_const(), aero_data.ptr.f_arg_non_const()); + guard.check_parameters(); } static void check_mode_json(const nlohmann::json &mode) { From 9f387e708e4079dbc0c920f179f9642270987a3d Mon Sep 17 00:00:00 2001 From: Griger5 Date: Thu, 13 Mar 2025 16:39:43 +0100 Subject: [PATCH 17/24] Fix: pre-commit hooks --- tests/test_input_guard.py | 40 ++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/tests/test_input_guard.py b/tests/test_input_guard.py index fa94f249..e749133a 100644 --- a/tests/test_input_guard.py +++ b/tests/test_input_guard.py @@ -1,4 +1,5 @@ import copy + import pytest import PyPartMC as ppmc @@ -10,6 +11,7 @@ from .test_run_part_opt import RUN_PART_OPT_CTOR_ARG_MINIMAL from .test_scenario import SCENARIO_CTOR_ARG_MINIMAL + class TestInputGuard: @staticmethod def test_aero_dist_unused_parameter(): @@ -23,7 +25,10 @@ def test_aero_dist_unused_parameter(): ppmc.AeroDist(aero_data, [mode]) # assert - assert str(exc_info.value) == "WARNING: \"test_mode/not_used\" parameter remains unused." + assert ( + str(exc_info.value) + == 'WARNING: "test_mode/not_used" parameter remains unused.' + ) @staticmethod def test_aero_mode_unused_parameter(): @@ -37,7 +42,10 @@ def test_aero_mode_unused_parameter(): ppmc.AeroMode(aero_data, mode) # assert - assert str(exc_info.value) == "WARNING: \"test_mode/not_used\" parameter remains unused." + assert ( + str(exc_info.value) + == 'WARNING: "test_mode/not_used" parameter remains unused.' + ) @staticmethod def test_env_state_unused_parameter(): @@ -50,47 +58,50 @@ def test_env_state_unused_parameter(): ppmc.EnvState(args) # assert - assert str(exc_info.value) == "WARNING: \"not_used\" parameter remains unused." + assert str(exc_info.value) == 'WARNING: "not_used" parameter remains unused.' @staticmethod def test_run_part_opt_unused_parameter(): - #arrange + # arrange args = copy.deepcopy(RUN_PART_OPT_CTOR_ARG_MINIMAL) args["not_used"] = 0 - #act + # act with pytest.raises(RuntimeError) as exc_info: ppmc.RunPartOpt(args) - assert str(exc_info.value) == "WARNING: \"not_used\" parameter remains unused." + assert str(exc_info.value) == 'WARNING: "not_used" parameter remains unused.' @staticmethod def test_scenario_unused_parameter(): - #arrange + # arrange aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) gas_data = ppmc.GasData(GAS_DATA_CTOR_ARG_MINIMAL) args = copy.deepcopy(SCENARIO_CTOR_ARG_MINIMAL) args["not_used"] = 0 - #act + # act with pytest.raises(RuntimeError) as exc_info: ppmc.Scenario(gas_data, aero_data, args) - assert str(exc_info.value) == "WARNING: \"not_used\" parameter remains unused." + assert str(exc_info.value) == 'WARNING: "not_used" parameter remains unused.' @staticmethod def test_nested_unused_parameter(): # arrange aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) mode = copy.deepcopy(AERO_MODE_CTOR_SAMPLED) - mode["test_mode"]["size_dist2"] = [{"diam" : [1]}, {"num_conc" : [100]}] + mode["test_mode"]["size_dist2"] = [{"diam": [1]}, {"num_conc": [100]}] # act with pytest.raises(RuntimeError) as exc_info: ppmc.AeroDist(aero_data, [mode]) # assert - assert str(exc_info.value) == "WARNING: \"test_mode/size_dist2/diam\" parameter remains unused." + assert ( + str(exc_info.value) + == 'WARNING: "test_mode/size_dist2/diam" parameter remains unused.' + ) @staticmethod def test_same_name_unused_parameter(): @@ -104,6 +115,9 @@ def test_same_name_unused_parameter(): # act with pytest.raises(RuntimeError) as exc_info: ppmc.AeroDist(aero_data, [modes]) - + # assert - assert str(exc_info.value) == "WARNING: \"sampled_mode/geom_mean_diam\" parameter remains unused." \ No newline at end of file + assert ( + str(exc_info.value) + == 'WARNING: "sampled_mode/geom_mean_diam" parameter remains unused.' + ) From f4d8504d5fc0f3a674d58d513d1a18480488df65 Mon Sep 17 00:00:00 2001 From: Griger5 Date: Thu, 13 Mar 2025 17:06:21 +0100 Subject: [PATCH 18/24] Fix: added missing includes --- src/input_guard.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/input_guard.hpp b/src/input_guard.hpp index 555a3774..9ac7b18f 100644 --- a/src/input_guard.hpp +++ b/src/input_guard.hpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include "pybind11_json/pybind11_json.hpp" #include "nlohmann/json.hpp" From 413cb48984305bb025e83162b8eaff4ec0bbc5f0 Mon Sep 17 00:00:00 2001 From: Griger5 Date: Fri, 14 Mar 2025 18:22:42 +0100 Subject: [PATCH 19/24] Fix: got rid of unused parameters in the running of PyPartMC model --- examples/additive_coag_comparison.ipynb | 60 ++++++++++++++----------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/examples/additive_coag_comparison.ipynb b/examples/additive_coag_comparison.ipynb index 50af2f25..a26c13ab 100644 --- a/examples/additive_coag_comparison.ipynb +++ b/examples/additive_coag_comparison.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "159edeb4", "metadata": {}, "outputs": [], @@ -27,7 +27,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "749c1483", "metadata": {}, "outputs": [], @@ -51,7 +51,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "id": "4f8359c2", "metadata": {}, "outputs": [], @@ -73,7 +73,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 2, "id": "b494ea6e", "metadata": {}, "outputs": [], @@ -91,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "id": "03708307", "metadata": {}, "outputs": [], @@ -132,7 +132,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "id": "9272286c", "metadata": {}, "outputs": [], @@ -177,7 +177,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "id": "092168b8", "metadata": {}, "outputs": [], @@ -244,7 +244,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "id": "29491329", "metadata": {}, "outputs": [], @@ -316,7 +316,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 7, "id": "80e1dcdd", "metadata": {}, "outputs": [], @@ -342,10 +342,15 @@ " {\"H2O\": [1000 * ppmc.si.kg / ppmc.si.m**3, 0, 18.0 * ppmc.si.g / ppmc.si.mol, 0.00]},\n", " ))\n", " self.gas_state = ppmc.GasState(gas_data)\n", - " placeholder_gas = [\n", + "\n", + " common = [\n", " {\"time\": [0 * ppmc.si.s]},\n", - " {\"rate\": [0 / ppmc.si.s]},\n", - " {\"dist\": [[{\"placeholder\": {\n", + " {\"rate\": [0 / ppmc.si.s]}\n", + " ]\n", + " placeholder_gas = [\n", + " *common,\n", + " {\"dist\":\n", + " [[{\"placeholder\": {\n", " \"mass_frac\": [{\"H2O\": [1]}],\n", " \"diam_type\": \"geometric\",\n", " \"mode_type\": \"log_normal\",\n", @@ -365,8 +370,8 @@ " {\"pressure\": [100000]},\n", " ],\n", " \"height_profile\": [{\"time\": [0]}, {\"height\": [1000]}],\n", - " \"gas_emissions\": placeholder_gas,\n", - " \"gas_background\": placeholder_gas,\n", + " \"gas_emissions\": common,\n", + " \"gas_background\": common,\n", " \"aero_emissions\": placeholder_gas,\n", " \"aero_background\":placeholder_gas,\n", " \"loss_function\": \"none\",\n", @@ -462,7 +467,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 8, "id": "f7d54f29", "metadata": {}, "outputs": [], @@ -555,7 +560,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 9, "id": "b33d3ccb", "metadata": {}, "outputs": [ @@ -567,7 +572,10 @@ "Instantiating RunPartMC\n", "Instantiating RunPySDM\n", "Instantiating NewModelRun\n", - "Instantiating RunDropletsJL\n" + "Running analytical...\n", + "Running PartMC...\n", + "Running PySDM...\n", + "Running ...\n" ] } ], @@ -586,14 +594,14 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "id": "6cc84269", "metadata": {}, "outputs": [ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -605,12 +613,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "4a6734f7155f43aaae9abff70bb4b3cd", + "model_id": "435ef7f9957140b19c96fd296120c7e3", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "HTML(value=\"./tmpcuc301a8.gif
\")" + "HTML(value=\"./tmpb4zoxnjp.gif
\")" ] }, "metadata": {}, @@ -619,7 +627,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -631,12 +639,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "9a0c70e5a33a4ebba114a276d215b1f5", + "model_id": "6f90572d38b24693b7e4d4f8598fdf3e", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "HTML(value=\"./tmpzvejmu2p.gif
\")" + "HTML(value=\"./tmpqkzibhgp.gif
\")" ] }, "metadata": {}, @@ -713,7 +721,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "id": "0eaf456b", "metadata": {}, "outputs": [], @@ -752,7 +760,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.12.3" } }, "nbformat": 4, From eaedb09618540b53914e936f698774c866981e0e Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Sat, 15 Mar 2025 05:22:02 +0100 Subject: [PATCH 20/24] skip exception tests on arm64 + mark with a TODO label --- tests/test_input_guard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_input_guard.py b/tests/test_input_guard.py index e749133a..4e06cbac 100644 --- a/tests/test_input_guard.py +++ b/tests/test_input_guard.py @@ -12,6 +12,7 @@ from .test_scenario import SCENARIO_CTOR_ARG_MINIMAL +@pytest.mark.skipif(platform.machine() == "arm64", reason="TODO #348") class TestInputGuard: @staticmethod def test_aero_dist_unused_parameter(): From 6f8d09dc01cf4622bad3527bc42ee9cb5cd79754 Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Sat, 15 Mar 2025 05:30:24 +0100 Subject: [PATCH 21/24] add missing import --- tests/test_input_guard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_input_guard.py b/tests/test_input_guard.py index 4e06cbac..73e34910 100644 --- a/tests/test_input_guard.py +++ b/tests/test_input_guard.py @@ -1,4 +1,5 @@ import copy +import platform import pytest From 5f4ab071e6b180c3ecea9543532366f584ac39d8 Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Sat, 15 Mar 2025 05:51:52 +0100 Subject: [PATCH 22/24] rerun additive intercomparison notebook to bring back Droplets.jl + adding a warning if Julia not found + some notebook cleanups --- examples/additive_coag_comparison.ipynb | 122 ++++++++++++++---------- 1 file changed, 72 insertions(+), 50 deletions(-) diff --git a/examples/additive_coag_comparison.ipynb b/examples/additive_coag_comparison.ipynb index a26c13ab..77b477cb 100644 --- a/examples/additive_coag_comparison.ipynb +++ b/examples/additive_coag_comparison.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "159edeb4", "metadata": {}, "outputs": [], @@ -27,7 +27,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "749c1483", "metadata": {}, "outputs": [], @@ -51,7 +51,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 3, "id": "4f8359c2", "metadata": {}, "outputs": [], @@ -68,12 +68,12 @@ " if shutil.which('julia') is None:\n", " !wget -q https://julialang-s3.julialang.org/bin/linux/x64/1.11/julia-1.11.3-linux-x86_64.tar.gz -O /tmp/julia.tar.gz; \\\n", " tar -x -f /tmp/julia.tar.gz -C /usr/local --strip-components 1; \\\n", - " rm /tmp/julia.tar.gz; \\\n" + " rm /tmp/julia.tar.gz;" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 4, "id": "b494ea6e", "metadata": {}, "outputs": [], @@ -83,15 +83,16 @@ "import urllib\n", "import json\n", "import subprocess\n", + "import warnings\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from open_atmos_jupyter_utils.show_anim import show_anim \n", - "# model-related imports have been moved to the specific model class cells" + "# note: model-related imports have been moved to the specific model class cells" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "id": "03708307", "metadata": {}, "outputs": [], @@ -116,7 +117,7 @@ "SETTINGS[\"N_PLOT_STEPS\"] = SETTINGS[\"T_MAX_SEC\"] // SETTINGS[\"PLOT_TIME_STEP_SEC\"]\n", "assert SETTINGS[\"N_PLOT_STEPS\"] * SETTINGS[\"PLOT_TIME_STEP_SEC\"] == SETTINGS[\"T_MAX_SEC\"]\n", "\n", - "#Load settings into json file for julia process\n", + "# Save settings into json file for julia process\n", "with open('setup.json', 'w', encoding='UTF-8') as f:\n", " json.dump(SETTINGS, f)\n", "\n", @@ -130,14 +131,21 @@ " self.settings = settings" ] }, + { + "cell_type": "markdown", + "id": "26e72d27-d1ea-4840-b7f9-7470e378ce6f", + "metadata": {}, + "source": [ + "### Format for a new model run, search NEW_MODEL for lines to change" + ] + }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "id": "9272286c", "metadata": {}, "outputs": [], "source": [ - "# Format for a new model run, search NEW_MODEL for lines to change\n", "# pylint: disable=unnecessary-pass\n", "class NewModelRun(Run):\n", " def __init__(self,settings):\n", @@ -177,7 +185,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "id": "092168b8", "metadata": {}, "outputs": [], @@ -244,7 +252,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "id": "29491329", "metadata": {}, "outputs": [], @@ -316,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "id": "80e1dcdd", "metadata": {}, "outputs": [], @@ -467,7 +475,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "id": "f7d54f29", "metadata": {}, "outputs": [], @@ -478,7 +486,7 @@ " 'https://raw.githubusercontent.com/emmacware/droplets.jl/REVISION/'\n", " ).replace('REVISION', 'c3b5ae4edf12c7b10dc8759639f18de1267bc52b')\n", " for path in (\n", - " 'src/SDfunc/constants.jl', 'src/SDfunc/binning.jl','src/SDfunc/coalescence.jl',\n", + " 'src/SDfunc/constants.jl', 'src/SDfunc/binning.jl', 'src/SDfunc/coalescence.jl',\n", " ):\n", " with open('Droplets.jl-' + path.replace('/','-'), 'w', encoding='utf-8') as fout:\n", " with urllib.request.urlopen(BASE_URL + path) as fin:\n", @@ -486,7 +494,7 @@ " \n", " code_to_write = \"\"\"\n", " using Pkg\n", - " Pkg.add([\"Combinatorics\", \"Distributions\", \"Random\",\"JSON\",\"Interpolations\"])\n", + " Pkg.add([\"Combinatorics\", \"Distributions\", \"Random\", \"JSON\", \"Interpolations\"])\n", " using Random,Combinatorics,Distributions\n", " include(\"Droplets.jl-src-SDfunc-constants.jl\")\n", " include(\"Droplets.jl-src-SDfunc-coalescence.jl\")\n", @@ -558,50 +566,52 @@ " return Droplets" ] }, + { + "cell_type": "markdown", + "id": "7436416c-7525-44f8-82d8-544a9e16039a", + "metadata": {}, + "source": [ + "### Create instances of the models with common setup" + ] + }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "b33d3ccb", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Instantiating AnalyticalSoln\n", - "Instantiating RunPartMC\n", - "Instantiating RunPySDM\n", - "Instantiating NewModelRun\n", - "Running analytical...\n", - "Running PartMC...\n", - "Running PySDM...\n", - "Running ...\n" - ] - } - ], + "outputs": [], "source": [ "models = {\n", - " # Create instances of the models with common setup\n", " 'analytical': AnalyticalSoln(SETTINGS),\n", " 'PartMC': RunPartMC(SETTINGS),\n", " 'PySDM': RunPySDM(SETTINGS),\n", " '': NewModelRun(SETTINGS) #NEW_MODEL\n", "}\n", - "if shutil.which('julia') is not None: # pylint: disable=undefined-variable\n", + "if shutil.which('julia') is not None:\n", " models['Droplets.jl'] = RunDropletsJL(SETTINGS)\n", + "else:\n", + " warnings.warn('Julia not found, skipping Droplets.jl run')\n", "output = {k: print(f\"Running {k}...\") or model() for k, model in models.items()}" ] }, + { + "cell_type": "markdown", + "id": "c1d3a943-4523-4364-aa37-603dd2a45d6e", + "metadata": {}, + "source": [ + "### use open_atmos_jupyter_utils to create comparison animation" + ] + }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 15, "id": "6cc84269", "metadata": {}, "outputs": [ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -613,12 +623,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "435ef7f9957140b19c96fd296120c7e3", + "model_id": "afd169224d2540a0a170acb9ef676fac", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "HTML(value=\"./tmpb4zoxnjp.gif
\")" + "HTML(value=\"./tmpl0kmjlg7.gif
\")" ] }, "metadata": {}, @@ -627,7 +637,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -639,12 +649,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6f90572d38b24693b7e4d4f8598fdf3e", + "model_id": "04d5423b58cf434abd60acb4cb72a17d", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "HTML(value=\"./tmpqkzibhgp.gif
\")" + "HTML(value=\"./tmpo_g26hgw.gif
\")" ] }, "metadata": {}, @@ -659,7 +669,6 @@ "radius_bins = np.array(models['PartMC'].rad_grid.centers)\n", "kilograms_to_grams = 1e3\n", "\n", - "\n", "markers = {\n", " 'PartMC':'o', \n", " 'PySDM':'x',\n", @@ -667,7 +676,6 @@ " '':'s' #NEW_MODEL\n", "} \n", "\n", - "# use open_atmos_jupyter_utils to create comparison animation\n", "class AnimFunc:\n", " def __init__(self, binning_method):\n", " self.binning_method = binning_method\n", @@ -707,7 +715,7 @@ " if self.binning_method != 'Number Concentration (#/m^3/unit ln R)':\n", " plt.ylim([0,2])\n", " plt.xlim([SETTINGS.RADIUS_BIN_EDGES_M[0],SETTINGS.RADIUS_BIN_EDGES_M[-1]])\n", - " plt.legend(loc='upper right')#, bbox_to_anchor=(1, 0.5))\n", + " plt.legend(loc='upper right')\n", " plt.title(\"Time Evolution of Particle Size Distribution, t = \"\n", " +f\"{frame*SETTINGS.PLOT_TIME_STEP_SEC//60:02d} min\")\n", " return plt.gcf()\n", @@ -719,15 +727,21 @@ " )" ] }, + { + "cell_type": "markdown", + "id": "a5fe6e01-c736-45a7-87d1-436a72d53100", + "metadata": {}, + "source": [ + "### sanity check for conservation of mass" + ] + }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 16, "id": "0eaf456b", "metadata": {}, "outputs": [], "source": [ - "# sanity check for conservation of mass\n", - "\n", "for model in output:\n", " if model == '':\n", " continue\n", @@ -740,8 +754,16 @@ " np.log(SETTINGS.RADIUS_BIN_EDGES_M[1:]) - np.log(SETTINGS.RADIUS_BIN_EDGES_M[:-1])\n", " ),\n", " significant=1.75\n", - " )\n" + " )" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fead341e-6092-428f-8dce-3b4660d04526", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -760,7 +782,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.9.2" } }, "nbformat": 4, From ed7006d73b02c9fb4c36392fbcd3b6800bf32db7 Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Sat, 15 Mar 2025 06:01:16 +0100 Subject: [PATCH 23/24] bring back a fishy pylint disable --- examples/additive_coag_comparison.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/additive_coag_comparison.ipynb b/examples/additive_coag_comparison.ipynb index 77b477cb..f17d161a 100644 --- a/examples/additive_coag_comparison.ipynb +++ b/examples/additive_coag_comparison.ipynb @@ -587,7 +587,7 @@ " 'PySDM': RunPySDM(SETTINGS),\n", " '': NewModelRun(SETTINGS) #NEW_MODEL\n", "}\n", - "if shutil.which('julia') is not None:\n", + "if shutil.which('julia') is not None: # pylint: disable=undefined-variable\n", " models['Droplets.jl'] = RunDropletsJL(SETTINGS)\n", "else:\n", " warnings.warn('Julia not found, skipping Droplets.jl run')\n", From 6239aa54a0992ed80f1e48ca41757d0569253adb Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Sat, 15 Mar 2025 07:02:14 +0100 Subject: [PATCH 24/24] add Gracjan to list of contributors in .zenodo.json --- .zenodo.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.zenodo.json b/.zenodo.json index 6c3be8b1..6482ced6 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -25,6 +25,10 @@ "affiliation": "University of California, Davis, CA, USA", "name": "Ware, Emma" }, + { + "affiliation": "AGH University of Krakow, Kraków, Poland", + "name": "Adamus, Gracjan" + }, { "affiliation": "University of Illinois Urbana-Champaign", "name": "Riemer, Nicole",