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", diff --git a/examples/additive_coag_comparison.ipynb b/examples/additive_coag_comparison.ipynb index 50af2f25..f17d161a 100644 --- a/examples/additive_coag_comparison.ipynb +++ b/examples/additive_coag_comparison.ipynb @@ -68,7 +68,7 @@ " 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;" ] }, { @@ -83,10 +83,11 @@ "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" ] }, { @@ -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,6 +131,14 @@ " 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": 6, @@ -137,7 +146,6 @@ "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", @@ -342,10 +350,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 +378,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 +475,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 10, "id": "f7d54f29", "metadata": {}, "outputs": [], @@ -473,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", @@ -481,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", @@ -553,27 +566,22 @@ " 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": 18, + "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", - "Instantiating RunDropletsJL\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", @@ -581,19 +589,29 @@ "}\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", "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": 12, + "execution_count": 15, "id": "6cc84269", "metadata": {}, "outputs": [ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -605,12 +623,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "4a6734f7155f43aaae9abff70bb4b3cd", + "model_id": "afd169224d2540a0a170acb9ef676fac", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "HTML(value=\"./tmpcuc301a8.gif
\")" + "HTML(value=\"./tmpl0kmjlg7.gif
\")" ] }, "metadata": {}, @@ -619,7 +637,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -631,12 +649,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "9a0c70e5a33a4ebba114a276d215b1f5", + "model_id": "04d5423b58cf434abd60acb4cb72a17d", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "HTML(value=\"./tmpzvejmu2p.gif
\")" + "HTML(value=\"./tmpo_g26hgw.gif
\")" ] }, "metadata": {}, @@ -651,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", @@ -659,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", @@ -699,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", @@ -711,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": 13, + "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", @@ -732,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": { @@ -752,7 +782,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.9.2" } }, "nbformat": 4, 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 b7e3b0c0..fe867561 100644 --- a/src/aero_dist.hpp +++ b/src/aero_dist.hpp @@ -54,6 +54,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) { 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/input_guard.hpp b/src/input_guard.hpp new file mode 100644 index 00000000..9ac7b18f --- /dev/null +++ b/src/input_guard.hpp @@ -0,0 +1,155 @@ +#include +#include +#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() {} + + 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 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 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 91e46219..dd558e4c 100644 --- a/src/json_resource.hpp +++ b/src/json_resource.hpp @@ -10,9 +10,11 @@ #include #include #include +#include #include "nlohmann/json.hpp" #include #include +#include "input_guard.hpp" struct JSONResource { private: @@ -20,6 +22,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); @@ -35,6 +39,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) { @@ -65,6 +71,9 @@ struct JSONResource { key = item.key(); } } + + input_guard_ptr->update_dict_key(key); + return key; } @@ -212,6 +221,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; @@ -342,5 +355,9 @@ struct JSONResourceGuard { ~JSONResourceGuard() { json_resource_ptr().reset(); } + + void check_parameters() { + json_resource_ptr()->get_input_guard_ptr()->check_used_inputs(); + } }; diff --git a/src/run_part_opt.hpp b/src/run_part_opt.hpp index f657dbe6..be7ed57c 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; @@ -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) { diff --git a/src/spec_file_pypartmc.cpp b/src/spec_file_pypartmc.cpp index 214d94f0..5c3a9909 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))); } /*********************************************************************************/ @@ -26,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))); } /*********************************************************************************/ @@ -35,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))); } /*********************************************************************************/ @@ -45,6 +48,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" @@ -64,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" @@ -80,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" @@ -121,6 +127,8 @@ 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)); + json_resource_ptr()->get_input_guard_ptr()->mark_used_input("time"); } extern "C" @@ -184,6 +192,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; } } @@ -239,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)); } diff --git a/tests/test_aero_dist.py b/tests/test_aero_dist.py index 6a11b8a8..732d3543 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 = [ @@ -209,12 +210,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, } }, ), 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( diff --git a/tests/test_input_guard.py b/tests/test_input_guard.py new file mode 100644 index 00000000..73e34910 --- /dev/null +++ b/tests/test_input_guard.py @@ -0,0 +1,125 @@ +import copy +import platform + +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 + + +@pytest.mark.skipif(platform.machine() == "arm64", reason="TODO #348") +class TestInputGuard: + @staticmethod + 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 + with pytest.raises(RuntimeError) as exc_info: + ppmc.AeroDist(aero_data, [mode]) + + # assert + assert ( + str(exc_info.value) + == 'WARNING: "test_mode/not_used" parameter remains unused.' + ) + + @staticmethod + 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 + 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.' + ) + + @staticmethod + def test_same_name_unused_parameter(): + # 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 + 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.' + ) 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,