diff --git a/bindings/python/config_validator.cc b/bindings/python/config_validator.cc new file mode 100644 index 000000000..c9066b4eb --- /dev/null +++ b/bindings/python/config_validator.cc @@ -0,0 +1,43 @@ +#include "config_validator.h" + +namespace vt::tv::bindings::python { + + /** + * Check if the configuration file is valid + * + * @return true if the configuration is valid + */ + bool ConfigValidator::isValid() + { + bool is_valid = true; + for (std::string parameter: required_parameters) { + if (!config[parameter]) { + is_valid = false; + break; + } + } + return is_valid; + } + + + /** + * Get the list of missing parameters + * + * @return A string containing the list of the missing parameters + */ + std::string ConfigValidator::getMissingRequiredParameters() + { + std::string parameters; + for (std::string parameter: required_parameters) { + if (!config[parameter]) { + if (parameters.empty()) { + parameters = parameter; + } else { + parameters = parameters + ", " + parameter; + } + } + } + return parameters; + } + +} /* end namespace vt::tv::bindings::python */ diff --git a/bindings/python/config_validator.h b/bindings/python/config_validator.h new file mode 100644 index 000000000..8354180bb --- /dev/null +++ b/bindings/python/config_validator.h @@ -0,0 +1,64 @@ +/* +//@HEADER +// ***************************************************************************** +// +// config_validator.h +// DARMA/vt-tv => Virtual Transport -- Task Visualizer +// +// Copyright 2019-2024 National Technology & Engineering Solutions of Sandia, LLC +// (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. +// Government retains certain rights in this software. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Questions? Contact darma@sandia.gov +// +// ***************************************************************************** +//@HEADER +*/ +#ifndef vt_tv_config_validator_h +#define vt_tv_config_validator_h + +#include + +namespace vt::tv::bindings::python { + /** + * ConfigValidator Class + */ + class ConfigValidator + { + public: + std::array required_parameters = {"output_visualization_dir", "output_visualization_file_stem"}; + YAML::Node config; + bool isValid(); + std::string getMissingRequiredParameters(); + ConfigValidator(YAML::Node in_config) + :config(in_config) {} + }; +} + +#endif diff --git a/bindings/python/tv.cc b/bindings/python/tv.cc index 2d0580d56..2276e1f0a 100644 --- a/bindings/python/tv.cc +++ b/bindings/python/tv.cc @@ -1,22 +1,42 @@ #include "tv.h" +#include "config_validator.h" namespace vt::tv::bindings::python { void tvFromJson(const std::vector& input_json_per_rank_list, const std::string& input_yaml_params_str, uint64_t num_ranks) { - std::string startup_logo = std::string(" __ __\n") - + std::string(" _ __/ /_ / /__ __\n") - + std::string("| | / / __/ _____ / __/ | / /\n") - + std::string("| |/ / / /____/ / /_ | |/ /\n") - + std::string("|___/\\__/ \\__/ |___/\n"); - fmt::print("==============================\n"); - fmt::print(startup_logo); - fmt::print("==============================\n"); + std::string startup_logo = std::string(" __ __\n") + + std::string(" _ __/ /_ / /__ __\n") + + std::string("| | / / __/ _____ / __/ | / /\n") + + std::string("| |/ / / /____/ / /_ | |/ /\n") + + std::string("|___/\\__/ \\__/ |___/\n"); + fmt::print("==============================\n"); + fmt::print(startup_logo); + fmt::print("==============================\n"); + + YAML::Node viz_config; + try { + // Load the configuration from serialized YAML + viz_config = YAML::Load(input_yaml_params_str); + } catch (std::exception const& e) { + throw std::runtime_error(fmt::format( + "vt-tv: Error reading the configuration file: {}", + e.what() + )); + } + + // Config Validator + ConfigValidator config_validator(viz_config); - // parse the input yaml parameters - try { - // Load the configuration from serialized YAML - YAML::Node viz_config = YAML::Load(input_yaml_params_str); + // Check configuration + bool is_config_valid = config_validator.isValid(); + // Throw error if configuration is invalid + if (!is_config_valid) { + throw std::runtime_error(fmt::format( + "vt-tv: Error validating the configuration file: {}", + config_validator.getMissingRequiredParameters() + )); + } std::array qoi_request = { viz_config["rank_qoi"].as(), @@ -41,10 +61,16 @@ void tvFromJson(const std::vector& input_json_per_rank_list, const // Throw an error if the output directory does not exist or is not absolute if (!std::filesystem::exists(output_path)) { - throw std::runtime_error(fmt::format("Visualization output directory does not exist at {}", output_dir)); + throw std::runtime_error(fmt::format( + "vt-tv: Visualization output directory does not exist at {}", + output_dir + )); } if (!output_path.is_absolute()) { - throw std::runtime_error("Visualization output directory must be absolute."); + throw std::runtime_error(fmt::format( + "vt-tv: Visualization output directory must be absolute: {}", + output_dir + )); } // append / to avoid problems with file stems @@ -123,11 +149,8 @@ void tvFromJson(const std::vector& input_json_per_rank_list, const output_dir, output_file_stem, 1.0, save_meshes, save_pngs, std::numeric_limits::max() ); render.generate(font_size, win_size); - } catch (std::exception const& e) { - throw std::runtime_error(fmt::format("vt-tv: Error reading the configuration file: {}", e.what())); - } - fmt::print("vt-tv: Done.\n"); + fmt::print("vt-tv: Done.\n"); } namespace nb = nanobind; diff --git a/tests/test_bindings.py b/tests/test_bindings.py index 45fbc9ce4..bbbffc2a2 100644 --- a/tests/test_bindings.py +++ b/tests/test_bindings.py @@ -1,38 +1,48 @@ """This module calls vttv module to test that vttv bindings work as expected""" -import json import os - +import json +import sys import yaml import vttv - # source dir is the directory a level above this file source_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -with open(f'{source_dir}/tests/test_bindings_conf.yaml', 'r', encoding='utf-8') as stream: +# Read the YAML config file +with open(f"{source_dir}/tests/test_bindings_conf.yaml", "r", encoding="utf-8") as stream: try: params = yaml.safe_load(stream) except yaml.YAMLError as exc: print(exc) - exit(1) + +# Check main key is "visualization" +if "visualization" not in params: + print( + "The YAML configuration file is not valid: "+\ + "missing required paramater \"visualization\"" + ) + sys.exit(1) # make output_visualization_dir directory parameter absolute -if not os.path.isabs(params["visualization"]["output_visualization_dir"]): - params["visualization"]["output_visualization_dir"] = source_dir + \ - "/" + params["visualization"]["output_visualization_dir"] +if "output_visualization_dir" in params["visualization"]: + if not os.path.isabs(params["visualization"]["output_visualization_dir"]): + params["visualization"]["output_visualization_dir"] = source_dir + \ + "/" + params["visualization"]["output_visualization_dir"] +# Serialize visualization parameters params_serialized = yaml.dump(params["visualization"]) +# Calculate n_ranks n_ranks = params["visualization"]["x_ranks"] * \ params["visualization"]["y_ranks"] * params["visualization"]["z_ranks"] -rank_data = [] +rank_data = [] for rank in range(n_ranks): - with open(f'{source_dir}/data/lb_test_data/data.{rank}.json', 'r', encoding='utf-8') as f: + with open(f"{source_dir}/data/lb_test_data/data.{rank}.json", "r", encoding="utf-8") as f: data = json.load(f) - data_serialized = json.dumps(data) - - rank_data.append((data_serialized)) + # Add serialized data into the rank + rank_data.append((json.dumps(data))) +# Launch VT TV from JSON data vttv.tvFromJson(rank_data, params_serialized, n_ranks)