Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Input guard for flagging used input (JSON entries), raising exception when some remain unused #395

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions src/json_resource.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,168 @@
#include <sstream>
#include <set>
#include <stack>
#include <map>
#include "nlohmann/json.hpp"
#include <tcb/span.hpp>
#include <bpstd/string_view.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<std::string, bool> used_inputs;

std::set<std::string> prefixes;
std::string curr_dict_key;
std::vector<std::string> 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<std::string> json_pointers;

for (auto f : flat.items()) {
json_pointers.push_back(clean_string(f.key()));
}

std::set<std::string> 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<std::string> 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<std::string> json_pointers_set) {
std::string temp;
std::vector<std::string> 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();
}
}
};

struct JSONResource {
private:
std::set<std::string> vars;
const nlohmann::json *json;
std::stack<const nlohmann::json*> json_parent;

std::unique_ptr<InputGuard> input_guard_ptr;

void warn(const std::exception &exception) {
std::cerr << "WARN: " << exception.what() << std::endl;
// assert(false);
Expand All @@ -35,6 +187,8 @@ struct JSONResource {
for (auto &entry : this->json->items()) {
this->vars.insert(entry.key());
}

input_guard_ptr = std::make_unique<InputGuard>(json);
};

void set_current_json_ptr(const nlohmann::json *ptr) {
Expand Down Expand Up @@ -65,6 +219,9 @@ struct JSONResource {
key = item.key();
}
}

input_guard_ptr->update_dict_key(key);

return key;
}

Expand Down Expand Up @@ -212,6 +369,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;
Expand Down
2 changes: 1 addition & 1 deletion src/run_part_opt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct RunPartOpt {
json_copy["do_parallel"] = false;

for (auto key : std::set<std::string>({
"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;
Expand Down
15 changes: 14 additions & 1 deletion src/spec_file_pypartmc.cpp
Original file line number Diff line number Diff line change
@@ -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 #
Expand All @@ -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<std::string>(bpstd::string_view(name_data, *name_size)));
}

/*********************************************************************************/
Expand All @@ -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<std::string>(bpstd::string_view(name_data, *name_size)));
}

/*********************************************************************************/
Expand All @@ -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<std::string>(bpstd::string_view(name_data, *name_size)));
}

/*********************************************************************************/
Expand All @@ -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<std::string>(name));
}

extern "C"
Expand All @@ -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<std::string>(filename));
}

extern "C"
Expand All @@ -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"
Expand Down Expand Up @@ -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<std::string>(name));
json_resource_ptr()->get_input_guard_ptr()->mark_used_input("time");
}

extern "C"
Expand Down Expand Up @@ -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<double>();
}

json_resource_ptr()->get_input_guard_ptr()->mark_used_input(entry.key());

break;
}
}
Expand Down Expand Up @@ -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));
}
8 changes: 2 additions & 6 deletions tests/test_aero_dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_aero_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
}
}


AERO_MODE_CTOR_LOG_NORMAL_FULL = {
"test_mode": {
"mass_frac": [{"SO4": [1]}],
Expand Down Expand Up @@ -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,
}
},
),
Expand Down
2 changes: 1 addition & 1 deletion tests/test_condense.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 0 additions & 1 deletion tests/test_run_part_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading