Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
712b20b
Base class for submodules: base_step.
djmallum Dec 4, 2025
110f6c3
Correcting `base_step` constness, concepts, and constructors
djmallum Dec 5, 2025
20e0ac7
Added documentation to `base_step`
djmallum Dec 5, 2025
b4b3ea7
Added `#pragma once` to `base_step.hpp`
djmallum Dec 5, 2025
d2d32fc
Added GNU License Header and author name
djmallum Dec 5, 2025
25a8f89
Data base class with finer control
djmallum Dec 4, 2025
23aac4e
Test of `data_base` and adding to CMakeLists
djmallum Dec 5, 2025
48805f3
Fixing comments
djmallum Dec 9, 2025
380fa3f
Updating `data_base` Concepts
djmallum Dec 9, 2025
9f066f2
Changed function name for clarity
djmallum Dec 9, 2025
d18f1c5
New test-only constructor for `data_base`
djmallum Dec 9, 2025
4c8f79f
get_cache() is now const
djmallum Dec 9, 2025
041b186
Removed name signature from base_step.hpp
djmallum Dec 9, 2025
a14e557
Added missing include
djmallum Dec 11, 2025
52f2ffa
Added new test for basic submodule, using base_step and data_base
djmallum Dec 11, 2025
de349bf
Adding .rst file with instructions
djmallum Jan 23, 2026
cb9bce2
Modified triangulation::make_module_data to accept variadic template
djmallum Jan 23, 2026
2e73a57
base_step: Added Derived as friend class
djmallum Feb 24, 2026
492316e
cfg is held as a pointer, not as a const reference, but as a pointer
djmallum Apr 1, 2026
05a7db0
Fixed typo in data_base constructor
djmallum Apr 1, 2026
d648478
added submodule test example, updated data_base test to use std::shar…
djmallum Apr 1, 2026
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.

19 changes: 11 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,19 @@ 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
Comment thread
Chrismarsh marked this conversation as resolved.
tests/submoduletests/test_data_base.cpp
tests/submoduletests/test_submodule.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() {};
};
227 changes: 227 additions & 0 deletions src/modules/submodules/data_base.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
//
// 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
Comment thread
djmallum marked this conversation as resolved.
//

#pragma once

#include "global.hpp"
#include "triangulation.hpp"
#include <limits>
#include <optional>
#include <boost/shared_ptr.hpp>
#include <boost/property_tree/ptree.hpp>
#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 std::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
{
cache_base() = default;
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 std::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 std::shared_ptr<global> param,
const pt::ptree* cfg, bool istest);
~data_base() {};

const mesh_elem face{nullptr};
const std::shared_ptr<global> global_param;
const pt::ptree* const 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 std::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 std::shared_ptr<global> param,
const pt::ptree* cfg, const bool istest) : face(face_in), global_param(param), cfg(cfg)
{
if (!istest)
throw std::invalid_argument("data_base test constructor called with False istest flag. Should be true.");

if (!global_param)
throw std::invalid_argument("global parameter holder is null");
};
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