diff --git a/include/bmi/AbstractCLibBmiAdapter.hpp b/include/bmi/AbstractCLibBmiAdapter.hpp index 773419879d..a4d3a67baa 100644 --- a/include/bmi/AbstractCLibBmiAdapter.hpp +++ b/include/bmi/AbstractCLibBmiAdapter.hpp @@ -1,10 +1,7 @@ #ifndef NGEN_ABSTRACTCLIBBMIADAPTER_HPP #define NGEN_ABSTRACTCLIBBMIADAPTER_HPP -#include #include "Bmi_Adapter.hpp" -#include "ExternalIntegrationException.hpp" -#include "State_Exception.hpp" namespace models { namespace bmi { @@ -27,21 +24,7 @@ namespace models { */ AbstractCLibBmiAdapter(const std::string &type_name, std::string library_file_path, std::string bmi_init_config, std::string forcing_file_path, bool allow_exceed_end, bool has_fixed_time_step, - std::string registration_func, utils::StreamHandler output) - : Bmi_Adapter(type_name, std::move(bmi_init_config), std::move(forcing_file_path), - allow_exceed_end, has_fixed_time_step, output), - bmi_lib_file(std::move(library_file_path)), - bmi_registration_function(std::move(registration_func)) { } - - AbstractCLibBmiAdapter(AbstractCLibBmiAdapter &&adapter) noexcept : - Bmi_Adapter(std::move(adapter)), - bmi_lib_file(std::move(adapter.bmi_lib_file)), - bmi_registration_function(adapter.bmi_registration_function), - dyn_lib_handle(adapter.dyn_lib_handle) - { - // Have to make sure to do this after "moving" so the original does not close the dynamically loaded library handle - adapter.dyn_lib_handle = nullptr; - } + std::string registration_func, utils::StreamHandler output); /** * Class destructor. @@ -49,9 +32,7 @@ namespace models { * Note that this performs the logic in the `Finalize()` function for cleaning up this object and its * backing BMI model. */ - virtual ~AbstractCLibBmiAdapter() { - finalizeForLibAbstraction(); - } + ~AbstractCLibBmiAdapter() override; /** * Perform tear-down task for this object and its backing model. @@ -67,77 +48,14 @@ namespace models { * * @throws models::external::State_Exception Thrown if nested model `finalize()` call is not successful. */ - void Finalize() override { - finalizeForLibAbstraction(); - } + void Finalize() override; protected: /** * Dynamically load and obtain this instance's handle to the shared library. */ - inline void dynamic_library_load() { - if (bmi_registration_function.empty()) { - this->init_exception_msg = - "Can't init " + this->model_name + "; empty name given for library's registration function."; - throw std::runtime_error(this->init_exception_msg); - } - if (dyn_lib_handle != nullptr) { - this->output.put("WARNING: ignoring attempt to reload dynamic shared library '" + bmi_lib_file + - "' for " + this->model_name); - return; - } - if (!utils::FileChecker::file_is_readable(bmi_lib_file)) { - //Try alternative extension... - size_t idx = bmi_lib_file.rfind("."); - if(idx == std::string::npos){ - idx = bmi_lib_file.length()-1; - } - std::string alt_bmi_lib_file; - if(bmi_lib_file.length() == 0){ - this->init_exception_msg = - "Can't init " + this->model_name + "; library file path is empty"; - throw std::runtime_error(this->init_exception_msg); - } - if(bmi_lib_file.substr(idx) == ".so"){ - alt_bmi_lib_file = bmi_lib_file.substr(0,idx) + ".dylib"; - } else if(bmi_lib_file.substr(idx) == ".dylib"){ - alt_bmi_lib_file = bmi_lib_file.substr(0,idx) + ".so"; - } else { - // Try appending instead of replacing... - #ifdef __APPLE__ - alt_bmi_lib_file = bmi_lib_file + ".dylib"; - #else - #ifdef __GNUC__ - alt_bmi_lib_file = bmi_lib_file + ".so"; - #endif // __GNUC__ - #endif // __APPLE__ - } - //TODO: Try looking in e.g. /usr/lib, /usr/local/lib, $LD_LIBRARY_PATH... try pre-pending "lib"... - if (utils::FileChecker::file_is_readable(alt_bmi_lib_file)) { - bmi_lib_file = alt_bmi_lib_file; - } else { - this->init_exception_msg = - "Can't init " + this->model_name + "; unreadable shared library file '" + bmi_lib_file + "'"; - throw std::runtime_error(this->init_exception_msg); - } - - } - - // Call first to ensure any previous error is cleared before trying to load the symbol - dlerror(); - // Load up the necessary library dynamically - dyn_lib_handle = dlopen(bmi_lib_file.c_str(), RTLD_NOW | RTLD_LOCAL); - // Now call again to see if there was an error (if there was, this will not be null) - char *err_message = dlerror(); - if (dyn_lib_handle == nullptr && err_message != nullptr) { - this->init_exception_msg = "Cannot load shared lib '" + bmi_lib_file + "' for model " + this->model_name; - if (err_message != nullptr) { - this->init_exception_msg += " (" + std::string(err_message) + ")"; - } - throw ::external::ExternalIntegrationException(this->init_exception_msg); - } - } + void dynamic_library_load(); /** * Load and return the address of the given symbol from the loaded dynamic model shared library. @@ -161,24 +79,7 @@ namespace models { * @throws ``std::runtime_error`` If there is not a valid handle already to the shared library. * @throws ``::external::ExternalIntegrationException`` If symbol could not be found for the shared library. */ - inline void *dynamic_load_symbol(const std::string &symbol_name, bool is_null_valid) { - if (dyn_lib_handle == nullptr) { - throw std::runtime_error("Cannot load symbol '" + symbol_name + "' without handle to shared library (bmi_lib_file = '" + bmi_lib_file + "')"); - } - // Call first to ensure any previous error is cleared before trying to load the symbol - dlerror(); - void *symbol = dlsym(dyn_lib_handle, symbol_name.c_str()); - // Now call again to see if there was an error (if there was, this will not be null) - char *err_message = dlerror(); - if (symbol == nullptr && (err_message != nullptr || !is_null_valid)) { - this->init_exception_msg = "Cannot load shared lib symbol '" + symbol_name + "' for model " + this->model_name; - if (err_message != nullptr) { - this->init_exception_msg += " (" + std::string(err_message) + ")"; - } - throw ::external::ExternalIntegrationException(this->init_exception_msg); - } - return symbol; - } + void *dynamic_load_symbol(const std::string &symbol_name, bool is_null_valid); /** * Convenience for @see dynamic_load_symbol(std::string, bool) where the symbol address must be found. @@ -192,11 +93,11 @@ namespace models { * @throws ``::external::ExternalIntegrationException`` If symbol could not be found for the shared library. * @see dynamic_load_symbol(std::string, bool) */ - inline void *dynamic_load_symbol(const std::string &symbol_name) { + inline void *dynamic_load_symbol(const std::string& symbol_name) { return dynamic_load_symbol(symbol_name, false); } - inline const std::string &get_bmi_registration_function() { + inline const std::string& get_bmi_registration_function() { return bmi_registration_function; } @@ -222,12 +123,7 @@ namespace models { * Primarily, this exists to contain the functionality appropriate for @see Finalize in a function that is * non-virtual, and can therefore be called by a destructor. */ - void finalizeForLibAbstraction() { - // close the dynamically loaded library - if (dyn_lib_handle != nullptr) { - dlclose(dyn_lib_handle); - } - } + void finalizeForLibAbstraction(); }; } diff --git a/include/bmi/Bmi_Adapter.hpp b/include/bmi/Bmi_Adapter.hpp index 29cb332d8e..34c70b5668 100644 --- a/include/bmi/Bmi_Adapter.hpp +++ b/include/bmi/Bmi_Adapter.hpp @@ -3,12 +3,12 @@ #include #include + #include "bmi.hpp" -#include "FileChecker.h" -#include "JSONProperty.hpp" -#include "State_Exception.hpp" -#include "StreamHandler.hpp" -#include + +#include "core/mediator/UnitsHelper.hpp" +#include "utilities/StreamHandler.hpp" + namespace models { namespace bmi { @@ -20,28 +20,12 @@ namespace models { public: Bmi_Adapter(std::string model_name, std::string bmi_init_config, std::string forcing_file_path, bool allow_exceed_end, - bool has_fixed_time_step, utils::StreamHandler output) - : model_name(std::move(model_name)), - bmi_init_config(std::move(bmi_init_config)), - bmi_model_uses_forcing_file(!forcing_file_path.empty()), - forcing_file_path(std::move(forcing_file_path)), - bmi_model_has_fixed_time_step(has_fixed_time_step), - allow_model_exceed_end_time(allow_exceed_end), - output(std::move(output)), - bmi_model_time_convert_factor(1.0) - { - // This replicates a lot of Initialize, but it's necessary to be able to do it separately to support - // "initializing" on construction, given using Initialize requires use of virtual functions - errno = 0; - if (!utils::FileChecker::file_is_readable(this->bmi_init_config)) { - init_exception_msg = "Cannot create and initialize " + this->model_name + " using unreadable file '" - + this->bmi_init_config + "'. Error: "+std::strerror(errno); - throw std::runtime_error(init_exception_msg); - } - } + bool has_fixed_time_step, utils::StreamHandler output); Bmi_Adapter(Bmi_Adapter const&) = delete; - Bmi_Adapter(Bmi_Adapter &&) = default; + Bmi_Adapter(Bmi_Adapter &&) = delete; + + virtual ~Bmi_Adapter() = 0; /** * Whether the backing model has been initialized yet. @@ -58,12 +42,7 @@ namespace models { * return an appropriate factor for converting its internal time values to equivalent representations * within the model, and vice versa. This function complies with the BMI get_time_units standard */ - double get_time_convert_factor() { - double value = 1.0; - std::string input_units = GetTimeUnits(); - std::string output_units = "s"; - return UnitsHelper::get_converted_value(input_units, value, output_units); - } + double get_time_convert_factor(); /** * Convert model time value to value in seconds. @@ -74,9 +53,7 @@ namespace models { * @param model_time_val Arbitrary model time value in units provided by `GetTimeUnits()` * @return Equivalent model time value to value in seconds. */ - double convert_model_time_to_seconds(const double& model_time_val) { - return model_time_val * bmi_model_time_convert_factor; - } + double convert_model_time_to_seconds(const double& model_time_val); /** * Convert a given number of seconds to equivalent in model time units. @@ -84,9 +61,7 @@ namespace models { * @param seconds_val * @return */ - double convert_seconds_to_model_time(const double& seconds_val) { - return seconds_val / bmi_model_time_convert_factor; - } + double convert_seconds_to_model_time(const double& seconds_val); /** * Get the name string for the C++ type analogous to the described type in the backing model. @@ -130,39 +105,7 @@ namespace models { * config file could not be read. * @throws models::external::State_Exception If `initialize()` in nested model does not return successful. */ - void Initialize() { - // If there was previous init attempt but w/ failure exception, throw runtime error and include previous - // message - errno = 0; - if (model_initialized && !init_exception_msg.empty()) { - throw std::runtime_error( - "Previous " + model_name + " init attempt had exception: \n\t" + init_exception_msg); - } - // If there was previous init attempt w/ (implicitly) no exception on previous attempt, just return - else if (model_initialized) { - return; - } - else if (!utils::FileChecker::file_is_readable(bmi_init_config)) { - init_exception_msg = "Cannot initialize " + model_name + " using unreadable file '" - + bmi_init_config + "'. Error: "+std::strerror(errno);; - throw std::runtime_error(init_exception_msg); - } - else { - try { - // TODO: make this same name as used with other testing (adjust name in docstring above also) - construct_and_init_backing_model(); - // Make sure this is set to 'true' after this function call finishes - model_initialized = true; - bmi_model_time_convert_factor = get_time_convert_factor(); - } - // Record the exception message before re-throwing to handle subsequent function calls properly - catch (std::exception& e) { - // Make sure this is set to 'true' after this function call finishes - model_initialized = true; - throw e; - } - } - } + void Initialize(); /** * Initialize the wrapped BMI model object using the given config file and the object's ``Initialize`` @@ -182,37 +125,14 @@ namespace models { * @throws models::external::State_Exception If `initialize()` in nested model is not successful. * @throws runtime_error If already initialized but using a different file than the passed argument. */ - void Initialize(std::string config_file) override { - if (config_file != bmi_init_config && model_initialized) { - throw std::runtime_error( - "Model init previously attempted; cannot change config from " + bmi_init_config + " to " - + config_file); - } - - if (config_file != bmi_init_config && !model_initialized) { - output.put("Warning: initialization call changes model config from " + bmi_init_config + " to " - + config_file); - bmi_init_config = config_file; - } - try { - Initialize(); - } - catch (models::external::State_Exception &e) { - throw e; - } - catch (std::exception &e) { - throw std::runtime_error(e.what()); - } - } + void Initialize(std::string config_file) override; /** * Get whether this instance has been initialized properly. * * @return Whether this instance has been initialized properly. */ - inline bool isInitialized() { - return model_initialized; - } + bool isInitialized(); /** * @brief Get the model name. @@ -220,9 +140,7 @@ namespace models { * @return The name of the model connected to the adapter. * */ - inline std::string get_model_name(){ - return model_name; - } + std::string get_model_name(); protected: /** Whether model ``Update`` calls are allowed and handled in some way by the backing model. */ diff --git a/include/bmi/Bmi_C_Adapter.hpp b/include/bmi/Bmi_C_Adapter.hpp index 5dcabbf0ad..dc7437b1e9 100755 --- a/include/bmi/Bmi_C_Adapter.hpp +++ b/include/bmi/Bmi_C_Adapter.hpp @@ -3,10 +3,13 @@ #include #include -#include "AbstractCLibBmiAdapter.hpp" + #include "bmi.h" -#include "JSONProperty.hpp" -#include "StreamHandler.hpp" +#include "AbstractCLibBmiAdapter.hpp" +#include "State_Exception.hpp" +#include "utilities/StreamHandler.hpp" +#include "utilities/ExternalIntegrationException.hpp" + // Forward declaration to provide access to protected items in testing class Bmi_C_Adapter_Test; @@ -20,7 +23,7 @@ namespace models { * An adapter class to serve as a C++ interface to the essential aspects of external models written in the C * language that implement the BMI. */ - class Bmi_C_Adapter : public AbstractCLibBmiAdapter { + class Bmi_C_Adapter final : public AbstractCLibBmiAdapter { public: @@ -101,7 +104,7 @@ namespace models { * * Note that this calls the `Finalize()` function for cleaning up this object and its backing BMI model. */ - virtual ~Bmi_C_Adapter() { + ~Bmi_C_Adapter() override { finalizeForCAdapter(); } diff --git a/include/bmi/Bmi_Cpp_Adapter.hpp b/include/bmi/Bmi_Cpp_Adapter.hpp index b3571db78d..f729b7b159 100644 --- a/include/bmi/Bmi_Cpp_Adapter.hpp +++ b/include/bmi/Bmi_Cpp_Adapter.hpp @@ -3,10 +3,12 @@ #include #include -#include "AbstractCLibBmiAdapter.hpp" + #include "bmi.hpp" -#include "JSONProperty.hpp" -#include "StreamHandler.hpp" +#include "AbstractCLibBmiAdapter.hpp" +#include "utilities/StreamHandler.hpp" +#include "utilities/ExternalIntegrationException.hpp" + // Forward declaration to provide access to protected items in testing class Bmi_Cpp_Adapter_Test; @@ -24,7 +26,7 @@ namespace models { * loaded dynamically from libraries. This is less important than e.g. @see Bmi_C_Adapter but still provides * some useful generalized functionality. */ - class Bmi_Cpp_Adapter : public AbstractCLibBmiAdapter { + class Bmi_Cpp_Adapter final : public AbstractCLibBmiAdapter { public: @@ -112,7 +114,7 @@ namespace models { * * Note that this calls the `Finalize()` function for cleaning up this object and its backing BMI model. */ - virtual ~Bmi_Cpp_Adapter() { + ~Bmi_Cpp_Adapter() override { finalizeForCppAdapter(); } diff --git a/include/bmi/Bmi_Fortran_Adapter.hpp b/include/bmi/Bmi_Fortran_Adapter.hpp index c21a77c13d..90f2a2e0a1 100644 --- a/include/bmi/Bmi_Fortran_Adapter.hpp +++ b/include/bmi/Bmi_Fortran_Adapter.hpp @@ -6,6 +6,8 @@ #include "AbstractCLibBmiAdapter.hpp" #include "Bmi_Fortran_Common.h" #include "bmi.h" +#include "State_Exception.hpp" +#include "utilities/ExternalIntegrationException.hpp" // Forward declaration to provide access to protected items in testing class Bmi_Fortran_Adapter_Test; @@ -21,7 +23,7 @@ namespace models { * An adapter class to serve as a C++ interface to the essential aspects of external models written in the * Fortran language that implement the BMI. */ - class Bmi_Fortran_Adapter : public AbstractCLibBmiAdapter { + class Bmi_Fortran_Adapter final : public AbstractCLibBmiAdapter { public: diff --git a/include/bmi/Bmi_Py_Adapter.hpp b/include/bmi/Bmi_Py_Adapter.hpp index 377e8a35d2..aafae8480e 100644 --- a/include/bmi/Bmi_Py_Adapter.hpp +++ b/include/bmi/Bmi_Py_Adapter.hpp @@ -7,14 +7,17 @@ #include #include #include + #include "pybind11/pybind11.h" #include "pybind11/pytypes.h" #include "pybind11/numpy.h" -#include "JSONProperty.hpp" -#include "StreamHandler.hpp" -#include "boost/algorithm/string.hpp" + +#include "boost/algorithm/string/join.hpp" + #include "Bmi_Adapter.hpp" -#include "python/InterpreterUtil.hpp" + +#include "utilities/StreamHandler.hpp" +#include "utilities/python/InterpreterUtil.hpp" // Forward declaration to provide access to protected items in testing class Bmi_Py_Adapter_Test; @@ -30,7 +33,7 @@ namespace models { * An adapter class to serve as a C++ interface to the aspects of external models written in the Python * language that implement the BMI. */ - class Bmi_Py_Adapter : public Bmi_Adapter { + class Bmi_Py_Adapter final : public Bmi_Adapter { public: @@ -44,6 +47,10 @@ namespace models { Bmi_Py_Adapter(Bmi_Py_Adapter const&) = delete; Bmi_Py_Adapter(Bmi_Py_Adapter&&) = delete; + ~Bmi_Py_Adapter() override { + Finalize(); + } + /** * Copy the given BMI variable's values from the backing numpy array to a C++ array. * diff --git a/include/bmi/State_Exception.hpp b/include/bmi/State_Exception.hpp index 8e33ae8b64..6154736661 100644 --- a/include/bmi/State_Exception.hpp +++ b/include/bmi/State_Exception.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace models { namespace external { diff --git a/src/bmi/AbstractCLibBmiAdapter.cpp b/src/bmi/AbstractCLibBmiAdapter.cpp new file mode 100644 index 0000000000..81d7caee1e --- /dev/null +++ b/src/bmi/AbstractCLibBmiAdapter.cpp @@ -0,0 +1,140 @@ +#include "bmi/AbstractCLibBmiAdapter.hpp" + +#include "utilities/FileChecker.h" +#include "utilities/ExternalIntegrationException.hpp" + +#include + +namespace models { +namespace bmi { + +AbstractCLibBmiAdapter::AbstractCLibBmiAdapter( + const std::string& type_name, + std::string library_file_path, + std::string bmi_init_config, + std::string forcing_file_path, + bool allow_exceed_end, + bool has_fixed_time_step, + std::string registration_func, + utils::StreamHandler output +) + : Bmi_Adapter( + type_name, + std::move(bmi_init_config), + std::move(forcing_file_path), + allow_exceed_end, + has_fixed_time_step, + output + ) + , bmi_lib_file(std::move(library_file_path)) + , bmi_registration_function(std::move(registration_func)){} + +AbstractCLibBmiAdapter::~AbstractCLibBmiAdapter() { + finalizeForLibAbstraction(); +} + +void AbstractCLibBmiAdapter::Finalize() { + finalizeForLibAbstraction(); +} + +void AbstractCLibBmiAdapter::dynamic_library_load() { + if (bmi_registration_function.empty()) { + this->init_exception_msg = "Can't init " + this->model_name + + "; empty name given for library's registration function."; + throw std::runtime_error(this->init_exception_msg); + } + if (dyn_lib_handle != nullptr) { + this->output.put( + "WARNING: ignoring attempt to reload dynamic shared library '" + bmi_lib_file + + "' for " + this->model_name + ); + return; + } + if (!utils::FileChecker::file_is_readable(bmi_lib_file)) { + // Try alternative extension... + size_t idx = bmi_lib_file.rfind("."); + if (idx == std::string::npos) { + idx = bmi_lib_file.length() - 1; + } + std::string alt_bmi_lib_file; + if (bmi_lib_file.length() == 0) { + this->init_exception_msg = + "Can't init " + this->model_name + "; library file path is empty"; + throw std::runtime_error(this->init_exception_msg); + } + if (bmi_lib_file.substr(idx) == ".so") { + alt_bmi_lib_file = bmi_lib_file.substr(0, idx) + ".dylib"; + } else if (bmi_lib_file.substr(idx) == ".dylib") { + alt_bmi_lib_file = bmi_lib_file.substr(0, idx) + ".so"; + } else { +// Try appending instead of replacing... +#ifdef __APPLE__ + alt_bmi_lib_file = bmi_lib_file + ".dylib"; +#else +#ifdef __GNUC__ + alt_bmi_lib_file = bmi_lib_file + ".so"; +#endif // __GNUC__ +#endif // __APPLE__ + } + // TODO: Try looking in e.g. /usr/lib, /usr/local/lib, $LD_LIBRARY_PATH... try pre-pending + // "lib"... + if (utils::FileChecker::file_is_readable(alt_bmi_lib_file)) { + bmi_lib_file = alt_bmi_lib_file; + } else { + this->init_exception_msg = "Can't init " + this->model_name + + "; unreadable shared library file '" + bmi_lib_file + "'"; + throw std::runtime_error(this->init_exception_msg); + } + } + + // Call first to ensure any previous error is cleared before trying to load the symbol + dlerror(); + // Load up the necessary library dynamically + dyn_lib_handle = dlopen(bmi_lib_file.c_str(), RTLD_NOW | RTLD_LOCAL); + // Now call again to see if there was an error (if there was, this will not be null) + char* err_message = dlerror(); + if (dyn_lib_handle == nullptr && err_message != nullptr) { + this->init_exception_msg = + "Cannot load shared lib '" + bmi_lib_file + "' for model " + this->model_name; + if (err_message != nullptr) { + this->init_exception_msg += " (" + std::string(err_message) + ")"; + } + throw ::external::ExternalIntegrationException(this->init_exception_msg); + } +} + +void* AbstractCLibBmiAdapter::dynamic_load_symbol( + const std::string& symbol_name, + bool is_null_valid +) { + if (dyn_lib_handle == nullptr) { + throw std::runtime_error( + "Cannot load symbol '" + symbol_name + + "' without handle to shared library (bmi_lib_file = '" + bmi_lib_file + "')" + ); + } + // Call first to ensure any previous error is cleared before trying to load the symbol + dlerror(); + void* symbol = dlsym(dyn_lib_handle, symbol_name.c_str()); + // Now call again to see if there was an error (if there was, this will not be null) + char* err_message = dlerror(); + if (symbol == nullptr && (err_message != nullptr || !is_null_valid)) { + this->init_exception_msg = + "Cannot load shared lib symbol '" + symbol_name + "' for model " + this->model_name; + if (err_message != nullptr) { + this->init_exception_msg += " (" + std::string(err_message) + ")"; + } + throw ::external::ExternalIntegrationException(this->init_exception_msg); + } + return symbol; +} + +void AbstractCLibBmiAdapter::finalizeForLibAbstraction() { + // close the dynamically loaded library + if (dyn_lib_handle != nullptr) { + dlclose(dyn_lib_handle); + } +} + +} // namespace bmi +} // namespace models diff --git a/src/bmi/Bmi_Adapter.cpp b/src/bmi/Bmi_Adapter.cpp new file mode 100644 index 0000000000..692e6aa95d --- /dev/null +++ b/src/bmi/Bmi_Adapter.cpp @@ -0,0 +1,123 @@ +#include "bmi/Bmi_Adapter.hpp" +#include "bmi/State_Exception.hpp" +#include "utilities/FileChecker.h" + +namespace models { +namespace bmi { + +Bmi_Adapter::Bmi_Adapter( + std::string model_name, + std::string bmi_init_config, + std::string forcing_file_path, + bool allow_exceed_end, + bool has_fixed_time_step, + utils::StreamHandler output +) + : model_name(std::move(model_name)) + , bmi_init_config(std::move(bmi_init_config)) + , bmi_model_uses_forcing_file(!forcing_file_path.empty()) + , forcing_file_path(std::move(forcing_file_path)) + , bmi_model_has_fixed_time_step(has_fixed_time_step) + , allow_model_exceed_end_time(allow_exceed_end) + , output(std::move(output)) + , bmi_model_time_convert_factor(1.0) { + // This replicates a lot of Initialize, but it's necessary to be able to do it separately to + // support "initializing" on construction, given using Initialize requires use of virtual + // functions + errno = 0; + if (!utils::FileChecker::file_is_readable(this->bmi_init_config)) { + init_exception_msg = "Cannot create and initialize " + this->model_name + + " using unreadable file '" + this->bmi_init_config + + "'. Error: " + std::strerror(errno); + throw std::runtime_error(init_exception_msg); + } +} + +Bmi_Adapter::~Bmi_Adapter() = default; + +double Bmi_Adapter::get_time_convert_factor() { + double value = 1.0; + std::string input_units = GetTimeUnits(); + std::string output_units = "s"; + return UnitsHelper::get_converted_value(input_units, value, output_units); +} + +double Bmi_Adapter::convert_model_time_to_seconds(const double& model_time_val) { + return model_time_val * bmi_model_time_convert_factor; +} + +double Bmi_Adapter::convert_seconds_to_model_time(const double& seconds_val) { + return seconds_val / bmi_model_time_convert_factor; +} + +void Bmi_Adapter::Initialize() { + // If there was previous init attempt but w/ failure exception, throw runtime error and include + // previous message + errno = 0; + if (model_initialized && !init_exception_msg.empty()) { + throw std::runtime_error( + "Previous " + model_name + " init attempt had exception: \n\t" + init_exception_msg + ); + } + // If there was previous init attempt w/ (implicitly) no exception on previous attempt, just + // return + else if (model_initialized) { + return; + } else if (!utils::FileChecker::file_is_readable(bmi_init_config)) { + init_exception_msg = "Cannot initialize " + model_name + " using unreadable file '" + + bmi_init_config + "'. Error: " + std::strerror(errno); + ; + throw std::runtime_error(init_exception_msg); + } else { + try { + // TODO: make this same name as used with other testing (adjust name in docstring above + // also) + construct_and_init_backing_model(); + // Make sure this is set to 'true' after this function call finishes + model_initialized = true; + bmi_model_time_convert_factor = get_time_convert_factor(); + } + // Record the exception message before re-throwing to handle subsequent function calls + // properly + catch (std::exception& e) { + // Make sure this is set to 'true' after this function call finishes + model_initialized = true; + throw e; + } + } +} + +void Bmi_Adapter::Initialize(std::string config_file) { + if (config_file != bmi_init_config && model_initialized) { + throw std::runtime_error( + "Model init previously attempted; cannot change config from " + bmi_init_config + + " to " + config_file + ); + } + + if (config_file != bmi_init_config && !model_initialized) { + output.put( + "Warning: initialization call changes model config from " + bmi_init_config + " to " + + config_file + ); + bmi_init_config = config_file; + } + try { + Initialize(); + } catch (models::external::State_Exception& e) { + throw e; + } catch (std::exception& e) { + throw std::runtime_error(e.what()); + } +} + +bool Bmi_Adapter::isInitialized() { + return model_initialized; +} + +std::string Bmi_Adapter::get_model_name() { + return model_name; +} + +} // namespace bmi +} // namespace models diff --git a/src/bmi/Bmi_C_Adapter.cpp b/src/bmi/Bmi_C_Adapter.cpp index 733fe834e1..9687918aa0 100755 --- a/src/bmi/Bmi_C_Adapter.cpp +++ b/src/bmi/Bmi_C_Adapter.cpp @@ -1,9 +1,8 @@ +#include "bmi/Bmi_C_Adapter.hpp" + #include #include -#include "FileChecker.h" -#include "Bmi_C_Adapter.hpp" - using namespace models::bmi; /** diff --git a/src/bmi/Bmi_Cpp_Adapter.cpp b/src/bmi/Bmi_Cpp_Adapter.cpp index 94a48707af..7dc776cfca 100644 --- a/src/bmi/Bmi_Cpp_Adapter.cpp +++ b/src/bmi/Bmi_Cpp_Adapter.cpp @@ -1,8 +1,8 @@ +#include "bmi/Bmi_Cpp_Adapter.hpp" + #include #include -#include "FileChecker.h" -#include "Bmi_Cpp_Adapter.hpp" using namespace models::bmi; diff --git a/src/bmi/Bmi_Fortran_Adapter.cpp b/src/bmi/Bmi_Fortran_Adapter.cpp index 8c7a8c4e57..32e0d6da32 100644 --- a/src/bmi/Bmi_Fortran_Adapter.cpp +++ b/src/bmi/Bmi_Fortran_Adapter.cpp @@ -1,5 +1,5 @@ #ifdef NGEN_BMI_FORTRAN_ACTIVE -#include "Bmi_Fortran_Adapter.hpp" +#include "bmi/Bmi_Fortran_Adapter.hpp" using namespace models::bmi; diff --git a/src/bmi/Bmi_Py_Adapter.cpp b/src/bmi/Bmi_Py_Adapter.cpp index 2ad4fb0013..e37c8d4d16 100644 --- a/src/bmi/Bmi_Py_Adapter.cpp +++ b/src/bmi/Bmi_Py_Adapter.cpp @@ -2,8 +2,8 @@ #include #include -#include "pybind11/numpy.h" -#include "Bmi_Py_Adapter.hpp" + +#include "bmi/Bmi_Py_Adapter.hpp" using namespace models::bmi; using namespace pybind11::literals; // to bring in the `_a` literal for pybind11 keyword args functionality diff --git a/src/bmi/CMakeLists.txt b/src/bmi/CMakeLists.txt index 823a54cd3f..3bb5dacfeb 100644 --- a/src/bmi/CMakeLists.txt +++ b/src/bmi/CMakeLists.txt @@ -1,25 +1,37 @@ -include(${PROJECT_SOURCE_DIR}/cmake/dynamic_sourced_library.cmake) -dynamic_sourced_cxx_library(ngen_bmi "${CMAKE_CURRENT_SOURCE_DIR}") - +add_library(ngen_bmi) add_library(NGen::ngen_bmi ALIAS ngen_bmi) -target_include_directories(ngen_bmi PUBLIC - ${PROJECT_SOURCE_DIR}/include/ - ${PROJECT_SOURCE_DIR}/include/bmi - ) - -target_link_libraries(ngen_bmi PUBLIC - ${CMAKE_DL_LIBS} - Boost::boost # Headers-only Boost - NGen::logging - NGen::geojson - NGen::core_mediator - ) +target_include_directories(ngen_bmi + PUBLIC + ${NGEN_INC_DIR} +) + +target_link_libraries(ngen_bmi + PUBLIC + ${CMAKE_DL_LIBS} + Boost::boost # Headers-only Boost + NGen::logging + NGen::geojson + NGen::core_mediator +) + +target_sources(ngen_bmi + PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/Bmi_Adapter.cpp" + "${CMAKE_CURRENT_LIST_DIR}/AbstractCLibBmiAdapter.cpp" + "${CMAKE_CURRENT_LIST_DIR}/Bmi_Cpp_Adapter.cpp" +) + +if(NGEN_WITH_BMI_C) + target_sources(ngen_bmi PRIVATE "${CMAKE_CURRENT_LIST_DIR}/Bmi_C_Adapter.cpp") +endif() if(NGEN_WITH_PYTHON) - target_link_libraries(ngen_bmi PUBLIC pybind11::embed) + target_sources(ngen_bmi PRIVATE "${CMAKE_CURRENT_LIST_DIR}/Bmi_Py_Adapter.cpp") + target_link_libraries(ngen_bmi PUBLIC pybind11::embed) endif() if(NGEN_WITH_BMI_FORTRAN) + target_sources(ngen_bmi PRIVATE "${CMAKE_CURRENT_LIST_DIR}/Bmi_Fortran_Adapter.cpp") target_link_libraries(ngen_bmi PUBLIC iso_c_bmi) endif() diff --git a/test/bmi/Bmi_Py_Adapter_Test.cpp b/test/bmi/Bmi_Py_Adapter_Test.cpp index 5523ca22fe..88b09d5c95 100644 --- a/test/bmi/Bmi_Py_Adapter_Test.cpp +++ b/test/bmi/Bmi_Py_Adapter_Test.cpp @@ -12,6 +12,8 @@ namespace py = pybind11; #include "Bmi_Py_Adapter.hpp" +#include "utilities/FileChecker.h" + using namespace models::bmi; using namespace utils::ngenPy; using namespace pybind11::literals; // to bring in the `_a` literal for pybind11 keyword args functionality diff --git a/test/realizations/catchments/Bmi_Py_Formulation_Test.cpp b/test/realizations/catchments/Bmi_Py_Formulation_Test.cpp index a55d594790..7b1661fdd2 100644 --- a/test/realizations/catchments/Bmi_Py_Formulation_Test.cpp +++ b/test/realizations/catchments/Bmi_Py_Formulation_Test.cpp @@ -13,6 +13,7 @@ #include "Bmi_Py_Formulation.hpp" #include "python/InterpreterUtil.hpp" #include +#include "utilities/FileChecker.h" namespace py = pybind11; using namespace pybind11::literals; // to bring in the `_a` literal for pybind11 keyword args functionality