Skip to content
Open
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
447 changes: 447 additions & 0 deletions docs/submodule_instructions.rst

Large diffs are not rendered by default.

18 changes: 10 additions & 8 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ set (HEADER_FILES
modules/interp_met
modules/snobal
modules/testing
modules/submodules
math
libmaw
interpolation
Expand Down Expand Up @@ -412,17 +413,18 @@ if (BUILD_TESTS)
message(STATUS "Tests enabled. Run with make check")

set(TEST_SRCS
tests/test_station.cpp
tests/test_interpolation.cpp
tests/test_timeseries.cpp
tests/test_core.cpp
tests/test_variablestorage.cpp
tests/test_metdata.cpp
tests/test_netcdf.cpp
#tests/test_station.cpp
# tests/test_interpolation.cpp
# tests/test_timeseries.cpp
# tests/test_core.cpp
# tests/test_variablestorage.cpp
# tests/test_metdata.cpp
# tests/test_netcdf.cpp
# test_mesh.cpp
tests/test_regexptokenizer.cpp
# test_daily.cpp
tests/test_triangulation.cpp
# tests/test_triangulation.cpp
tests/submoduletests/test_data_base.cpp
tests/main.cpp
)

Expand Down
15 changes: 8 additions & 7 deletions src/mesh/triangulation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -436,9 +436,10 @@ class face : public Fb

// void set_module_data(const std::string &module, face_info *fi);

template<typename T>
T& make_module_data(const std::string &module);

// Overload for module data constructor
template<typename T, typename... Args>
T& make_module_data(const std::string &module, Args... args);

std::string _debug_name; //for debugging to find the elem that we want
int _debug_ID; //also for debugging. ID == the position in the output order, starting at 0
size_t cell_global_id;
Expand Down Expand Up @@ -1788,22 +1789,22 @@ timeseries::iterator face<Gt, Fb>::now()
}


// Overloaded for construtor that requires arguments
template < class Gt, class Vb>
template<typename T>
T& face<Gt, Vb>::make_module_data(const std::string &module)
template<typename T, typename... Args>
T& face<Gt, Vb>::make_module_data(const std::string &module, Args... args)
{

//we don't already have this, make a new one.
if(!_module_face_data[module])
{
// T* data = new T;
_module_face_data[module] = std::make_unique<T>();
_module_face_data[module] = std::make_unique<T>(args...);
}

return get_module_data<T&>(module);
}


template < class Gt, class Fb>
template < typename T>
T& face<Gt, Fb>::get_module_data(const std::string &module)
Expand Down
68 changes: 68 additions & 0 deletions src/modules/submodules/base_step.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// Canadian Hydrological Model - The Canadian Hydrological Model (CHM) is a novel
// modular unstructured mesh based approach for hydrological modelling
// Copyright (C) 2018 Christopher Marsh
//
// This file is part of Canadian Hydrological Model.
//
// Canadian Hydrological Model is free software: you can redistribute it and/or
// modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Canadian Hydrological Model is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Canadian Hydrological Model. If not, see
// <http://www.gnu.org/licenses/>.
//

#pragma once
#include <concepts>

/**
* @brief Base class for submodules using the Curiously Recurring Template Pattern (CRTP).
*
* This class enforces that derived classes implement an `execute_impl(Data&) const` method,
* and provides a public `execute(Data&) const` method that forwards to the derived implementation.
*
* The CRTP pattern allows compile-time polymorphism, enabling static dispatch and
* avoiding the overhead of virtual functions. This is useful for performance-critical
* code or when templates are preferred over inheritance.
*
* Usage example:
* @code
* struct MyStep : public base_step<MyStep, MyData> {
* void execute_impl(MyData& d) {
* // Step-specific logic here
* }
* };
* MyStep step;
* MyData data;
* step.execute(data); // Calls MyStep::execute_impl(data)
* @endcode
*/

template<typename T, typename Data>
concept GuaranteeImplementExecute = requires(const T& t, Data& d) {
{ t.execute_impl(d) } -> std::same_as<void>;
};

template<class Derived, class Data>
class base_step {
protected:
base_step() {};
public:
void execute(Data& d) const {

static_assert(GuaranteeImplementExecute<Derived,Data>,
"Derived class must implement: void execute_impl(Data&) const");

static_cast<const Derived*>(this)->execute_impl(d);
}
~base_step() {};
};
224 changes: 224 additions & 0 deletions src/modules/submodules/data_base.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
//
// Canadian Hydrological Model - The Canadian Hydrological Model (CHM) is a novel
// modular unstructured mesh based approach for hydrological modelling
// Copyright (C) 2018 Christopher Marsh
//
// This file is part of Canadian Hydrological Model.
//
// Canadian Hydrological Model is free software: you can redistribute it and/or
// modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Canadian Hydrological Model is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Canadian Hydrological Model. If not, see
// <http://www.gnu.org/licenses/>.
//

//
// Created by Donovan Allum 2025
//

#pragma once

#include "global.hpp"
#include "triangulation.hpp"
#include <limits>
#include <optional>
#include <boost/shared_ptr.hpp>
#include <boost/property_tree/ptree.hpp>
#include <cstddef>
#include <stdexcept>
#include <cassert>
#include <cstdint>
#include <concepts>
#include <type_traits>

/**
* @brief Base class for data management with lazy caching and output handling.
*
* This class provides a framework for managing data with optional caching,
* lazy evaluation, and output assignment. It uses CRTP-like patterns with
* concepts to enforce interface contracts at compile-time.
*
* Key features:
* - Automatic cache initialization and staleness checking
* - Lazy evaluation of values with cache-aware updates
* - Type-safe output assignment with runtime checks
* - Compile-time concept enforcement for derived cache types and value/output providers
*
* The class is designed for computational scenarios where:
* - Data may be expensive to compute and should be cached
* - Cache validity depends on external timestep counters
* - Values need to be fetched lazily when accessed
* - Outputs must be accumulated safely
*
* Usage example:
* @code
* struct MyCache : public cache_base {
* double temperature = default_value<double>();
* int iteration_count = default_value<int>();
* };
*
* class MyData : public data_base<MyCache> {
* public:
* MyData(const mesh_elem& face_in, const boost::shared_ptr<global> param,
* const pt::ptree& cfg) : data_base(face_in, param, cfg) {}
*
* void compute_temperature() {
* update_value(
* [this]() -> auto& { return cache_->temperature; },
* [this]() { return expensive_temperature_calculation(); }
* );
* }
*
* void add_to_output(double contribution) {
* set_output(
* [this]() -> auto& { return output_variable; },
* contribution
* );
* }
* };
* @endcode
*
* @tparam CacheType A type derived from cache_base that provides storage
* for cached values. Must satisfy the CacheRules concept.
*/

struct cache_base
{
int64_t last_timestep = -1;

template<typename T>
static constexpr T default_value() {
if constexpr (std::is_floating_point_v<T>)
return std::numeric_limits<T>::quiet_NaN();
else if constexpr (std::is_integral_v<T>)
return std::numeric_limits<T>::min();
else
return T{};
};

};

namespace data_base_concepts {
template<typename C>
concept CacheRules = std::derived_from<C,cache_base>;

template<typename V>
concept ValueRules =
requires(V v) {
{v()} -> std::same_as<std::add_lvalue_reference_t<decltype(v())>>;
};

template<typename O,typename T>
concept OutputRules = ValueRules<O> &&
requires(O o,const T t)
{
{o()} -> std::convertible_to<T>;
{o() += t};
};
};

namespace pt = boost::property_tree;

template<data_base_concepts::CacheRules CacheType>
class data_base {

template<typename T>
bool constexpr is_unset(T t)
{
if constexpr (!std::is_floating_point_v<T>)
return t == cache_base::default_value<T>();
else
return std::isnan(t);
};

bool is_stale();

void init_cache();

protected:

data_base(const mesh_elem& face_in, const boost::shared_ptr<global> param,
const pt::ptree& cfg);
// Test constructor to skip checks of valid mesh_elem
data_base(const mesh_elem& face_in, const boost::shared_ptr<global> param,
const pt::ptree& cfg, bool istest);
~data_base() {};

const mesh_elem face{nullptr};
const boost::shared_ptr<global> global_param;
const pt::ptree& cfg_;
mutable std::optional<CacheType> cache_;

template<data_base_concepts::ValueRules Value,typename Fetch>
void update_value(Value&& value, const Fetch& fetch);

template<typename T,data_base_concepts::OutputRules<T> Output>
void set_output(Output&& output,const T t);

public:
void reset_cache() { cache_.reset(); };
const std::optional<CacheType>& get_cache() const { return cache_; };
};

template<data_base_concepts::CacheRules CacheType>
void data_base<CacheType>::init_cache() {
if (!cache_ || is_stale()) {
cache_.emplace();
cache_->last_timestep = global_param->timestep_counter;
}
}

template<data_base_concepts::CacheRules CacheType>
bool data_base<CacheType>::is_stale()
{
return cache_->last_timestep != global_param->timestep_counter;
}

template<data_base_concepts::CacheRules CacheType>
data_base<CacheType>::data_base(const mesh_elem& face_in, const boost::shared_ptr<global> param,
const pt::ptree& cfg) : face(face_in), global_param(param), cfg_(cfg)
{
if (!face->is_valid())
throw std::invalid_argument("Face handle points to an invalid face");

if (!global_param)
throw std::invalid_argument("global parameter holder is null");
};

template<data_base_concepts::CacheRules CacheType>
data_base<CacheType>::data_base(const mesh_elem& face_in, const boost::shared_ptr<global> param,
const pt::ptree& cfg, const bool istest) : face(face_in), global_param(param), cfg_(cfg)
{
if (!istest)
std::invalid_argument("data_base test constructor called with False istest flag. Should be true.");
};
template<data_base_concepts::CacheRules CacheType>
template<data_base_concepts::ValueRules Value,typename Fetch>
void data_base<CacheType>::update_value(Value&& value, const Fetch& fetch) {
init_cache();

auto& V = value();
if ( is_unset(V) )
{
V = fetch();
}

};

template<data_base_concepts::CacheRules CacheType>
template<typename T,data_base_concepts::OutputRules<T> Output>
void data_base<CacheType>::set_output(Output&& output,const T t)
{
init_cache();

output() += t;
};
Loading