Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tpl_ca_continuous_rules.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
53#ifndef TPL_CA_CONTINUOUS_RULES_H
54#define TPL_CA_CONTINUOUS_RULES_H
55
56#include <algorithm>
57#include <array>
58#include <cmath>
59#include <cstddef>
60#include <limits>
61#include <type_traits>
62#include <utility>
63
64#include <ah-errors.H>
65
66#include <ca-kernels.H>
67#include <ca-traits.H>
68
69namespace Aleph {
70namespace CA {
71
72namespace ca_continuous_detail {
73
74template <typename Real>
75inline void require_finite(const Real value, const char *label)
76{
77 ah_runtime_error_if(not std::isfinite(static_cast<double>(value))) << label << " is not finite";
78}
79
80template <typename Real>
81inline void require_domain(const Real value, const Real lo, const Real hi, const char *label)
82{
83 require_finite(value, label);
85 << label << "=" << value << " outside [" << lo << ", " << hi << "]";
86}
87
88template <typename Real>
89inline void require_positive_dt(const Real dt, const Real max_dt, const char *label)
90{
91 require_finite(dt, label);
92 ah_domain_error_if(dt <= Real{}) << label << " must be positive";
93 ah_domain_error_if(dt > max_dt) << label << "=" << dt << " exceeds the documented stable maximum "
94 << max_dt;
95}
96
97template <typename Real>
98[[nodiscard]] inline Real clamp_unit_after_check(const Real value, const char *label)
99{
100 require_finite(value, label);
101 constexpr Real eps = Real{1024} * std::numeric_limits<Real>::epsilon();
103 << label << "=" << value << " outside the stable [0, 1] interval";
104 return std::clamp(value, Real{}, Real{1});
105}
106
107template <typename Real>
108[[nodiscard]] inline Real checked_bounded(const Real value, const Real max_abs, const char *label)
109{
110 require_finite(value, label);
112 << label << "=" << value << " outside [-" << max_abs << ", " << max_abs << "]";
113 return value;
114}
115
116} // namespace ca_continuous_detail
117
118// -----------------------------------------------------------------------
119// Continuous_Rule
120// -----------------------------------------------------------------------
121
138template <typename F, typename Kernel>
140{
141public:
143 using value_type = typename Kernel::value_type;
146
147private:
153
154public:
167 value_type min_value = -std::numeric_limits<value_type>::infinity(),
168 value_type max_value = std::numeric_limits<value_type>::infinity())
169 : f_(std::move(func)), kernel_(std::move(kernel)), dt_(dt), min_value_(min_value),
171 {
172 ca_continuous_detail::require_finite(dt_, "Continuous_Rule::dt");
173 ah_domain_error_if(dt_ <= value_type{}) << "Continuous_Rule: dt must be positive";
174 ah_domain_error_if(min_value_ > max_value_) << "Continuous_Rule: min_value exceeds max_value";
175 }
176
179 {
180 return kernel_;
181 }
182
185 {
186 return dt_;
187 }
188
191 {
192 return min_value_;
193 }
194
197 {
198 return max_value_;
199 }
200
211 template <typename State>
212 [[nodiscard]] State operator()(const State &current, Neighbor_View<State> neighbours) const
213 {
214 static_assert(std::is_floating_point_v<State>,
215 "Continuous_Rule requires float or double states");
216
217 const State conv = static_cast<State>(kernel_.apply(current, neighbours));
218 State next{};
219 if constexpr (std::is_invocable_r_v<State, const F &, State, State, value_type>)
220 next = f_(current, conv, dt_);
221 else if constexpr (std::is_invocable_r_v<State, const F &, State, State>)
222 next = f_(current, conv);
223 else
224 static_assert(std::is_invocable_r_v<State, const F &, State, State>,
225 "Continuous_Rule functor must accept (State, convolution[, dt])");
226
227 ca_continuous_detail::require_finite(next, "Continuous_Rule::next");
228 ah_runtime_error_if(next < static_cast<State>(min_value_) or next > static_cast<State>(max_value_))
229 << "Continuous_Rule::next=" << next << " outside [" << min_value_ << ", " << max_value_ << "]";
230 return next;
231 }
232};
233
246template <typename F, typename Kernel>
248 F f,
249 Kernel k,
250 typename Kernel::value_type dt = typename Kernel::value_type{1},
251 typename Kernel::value_type min_value
252 = -std::numeric_limits<typename Kernel::value_type>::infinity(),
253 typename Kernel::value_type max_value
254 = std::numeric_limits<typename Kernel::value_type>::infinity())
255{
256 return Continuous_Rule<F, Kernel>(std::move(f), std::move(k), dt, min_value, max_value);
257}
258
259// -----------------------------------------------------------------------
260// Reaction-diffusion state.
261// -----------------------------------------------------------------------
262
272template <typename Real = double>
274{
275 static_assert(std::is_floating_point_v<Real>, "Reaction_Diffusion_Cell requires float or double");
276
281
288 [[nodiscard]] constexpr bool operator==(const Reaction_Diffusion_Cell &rhs) const noexcept
289 {
290 return u == rhs.u and v == rhs.v;
291 }
292};
293
294// -----------------------------------------------------------------------
295// Gray-Scott reaction-diffusion.
296// -----------------------------------------------------------------------
297
316template <typename Real = double, typename Kernel = Kernel2D<Real, 3, 3>>
318{
319public:
324
325private:
332
333 [[nodiscard]] Real laplace_u(Neighbor_View<state_type> neighbours, const state_type &current) const
334 {
335 Real acc = current.u * lap_.center();
336 for (std::size_t k = 0; k < neighbours.size(); ++k)
337 acc += neighbours[k].u * lap_.neighbour_weight(k);
338 return acc;
339 }
340
341 [[nodiscard]] Real laplace_v(Neighbor_View<state_type> neighbours, const state_type &current) const
342 {
343 Real acc = current.v * lap_.center();
344 for (std::size_t k = 0; k < neighbours.size(); ++k)
345 acc += neighbours[k].v * lap_.neighbour_weight(k);
346 return acc;
347 }
348
349public:
364 Real kill,
365 Real du,
366 Real dv,
367 Real dt = Real{1},
369 : feed_(feed), kill_(kill), du_(du), dv_(dv), dt_(dt), lap_(std::move(laplacian))
370 {
371 ca_continuous_detail::require_domain(feed_, Real{}, Real{1}, "Gray_Scott_Rule::feed");
372 ca_continuous_detail::require_domain(kill_, Real{}, Real{1}, "Gray_Scott_Rule::kill");
373 ca_continuous_detail::require_domain(du_, Real{}, std::numeric_limits<Real>::max(),
374 "Gray_Scott_Rule::Du");
375 ca_continuous_detail::require_domain(dv_, Real{}, std::numeric_limits<Real>::max(),
376 "Gray_Scott_Rule::Dv");
377 ca_continuous_detail::require_positive_dt(dt_, Real{1}, "Gray_Scott_Rule::dt");
378 }
379
382 {
383 return feed_;
384 }
385
388 {
389 return kill_;
390 }
391
394 {
395 return du_;
396 }
397
400 {
401 return dv_;
402 }
403
406 {
407 return dt_;
408 }
409
412 {
413 return lap_;
414 }
415
427 Neighbor_View<state_type> neighbours) const
428 {
429 ah_length_error_if(neighbours.size() != Kernel::neighbour_count_v)
430 << "Gray_Scott_Rule: expected " << Kernel::neighbour_count_v << " neighbours, got "
431 << neighbours.size();
432
433 const Real lu = laplace_u(neighbours, current);
434 const Real lv = laplace_v(neighbours, current);
435 const Real uvv = current.u * current.v * current.v;
436
437 const Real next_u = current.u + dt_ * (du_ * lu - uvv + feed_ * (Real{1} - current.u));
438 const Real next_v = current.v + dt_ * (dv_ * lv + uvv - (feed_ + kill_) * current.v);
439
440 return {ca_continuous_detail::clamp_unit_after_check(next_u, "Gray_Scott_Rule::u"),
442 }
443};
444
445// -----------------------------------------------------------------------
446// FitzHugh-Nagumo reaction-diffusion.
447// -----------------------------------------------------------------------
448
466template <typename Real = double, typename Kernel = Kernel2D<Real, 3, 3>>
468{
469public:
474
475private:
484
485 [[nodiscard]] Real laplace_u(Neighbor_View<state_type> neighbours, const state_type &current) const
486 {
487 Real acc = current.u * lap_.center();
488 for (std::size_t k = 0; k < neighbours.size(); ++k)
489 acc += neighbours[k].u * lap_.neighbour_weight(k);
490 return acc;
491 }
492
493 [[nodiscard]] Real laplace_v(Neighbor_View<state_type> neighbours, const state_type &current) const
494 {
495 Real acc = current.v * lap_.center();
496 for (std::size_t k = 0; k < neighbours.size(); ++k)
497 acc += neighbours[k].v * lap_.neighbour_weight(k);
498 return acc;
499 }
500
501public:
518 Real b,
520 Real du,
521 Real dv,
522 Real dt = Real{0.01},
523 Real stimulus = Real{},
526 lap_(std::move(laplacian))
527 {
528 ca_continuous_detail::require_finite(a_, "FitzHugh_Nagumo_Rule::a");
529 ca_continuous_detail::require_domain(b_, std::numeric_limits<Real>::epsilon(),
530 std::numeric_limits<Real>::max(),
531 "FitzHugh_Nagumo_Rule::b");
532 ca_continuous_detail::require_domain(epsilon_, std::numeric_limits<Real>::epsilon(),
533 std::numeric_limits<Real>::max(),
534 "FitzHugh_Nagumo_Rule::epsilon");
535 ca_continuous_detail::require_domain(du_, Real{}, std::numeric_limits<Real>::max(),
536 "FitzHugh_Nagumo_Rule::Du");
537 ca_continuous_detail::require_domain(dv_, Real{}, std::numeric_limits<Real>::max(),
538 "FitzHugh_Nagumo_Rule::Dv");
539 ca_continuous_detail::require_finite(stimulus_, "FitzHugh_Nagumo_Rule::stimulus");
540 ca_continuous_detail::require_positive_dt(dt_, Real{0.25}, "FitzHugh_Nagumo_Rule::dt");
541 }
542
545 {
546 return a_;
547 }
548
551 {
552 return b_;
553 }
554
557 {
558 return epsilon_;
559 }
560
563 {
564 return du_;
565 }
566
569 {
570 return dv_;
571 }
572
575 {
576 return dt_;
577 }
578
581 {
582 return stimulus_;
583 }
584
587 {
588 return lap_;
589 }
590
602 Neighbor_View<state_type> neighbours) const
603 {
604 ah_length_error_if(neighbours.size() != Kernel::neighbour_count_v)
605 << "FitzHugh_Nagumo_Rule: expected " << Kernel::neighbour_count_v << " neighbours, got "
606 << neighbours.size();
607
608 const Real lu = laplace_u(neighbours, current);
609 const Real lv = laplace_v(neighbours, current);
610 const Real u3 = current.u * current.u * current.u;
611
612 const Real next_u
613 = current.u + dt_ * (du_ * lu + current.u - u3 / Real{3} - current.v + stimulus_);
614 const Real next_v = current.v + dt_ * (dv_ * lv + epsilon_ * (current.u + a_ - b_ * current.v));
615
616 return {ca_continuous_detail::checked_bounded(next_u, Real{100}, "FitzHugh_Nagumo_Rule::u"),
617 ca_continuous_detail::checked_bounded(next_v, Real{100}, "FitzHugh_Nagumo_Rule::v")};
618 }
619};
620
621// -----------------------------------------------------------------------
622// Per-cell history ring buffer.
623// -----------------------------------------------------------------------
624
634template <typename State, std::size_t Depth>
636{
637 static_assert(Depth >= 1, "History_Cell requires Depth >= 1");
638
639public:
641 using value_type = State;
643 static constexpr std::size_t depth_v = Depth;
644
645private:
646 std::array<State, Depth> values_{};
647 std::size_t head_ = 0;
648
649public:
655 History_Cell() = default;
656
662 explicit History_Cell(const State &value)
663 {
664 values_.fill(value);
665 }
666
673 History_Cell(const std::array<State, Depth> &values, const std::size_t head = 0)
675 {
677 << "History_Cell: head " << head_ << " outside [0, " << Depth << ")";
678 }
679
685 [[nodiscard]] State current() const
686 {
687 return values_[head_];
688 }
689
699 [[nodiscard]] State previous(const std::size_t age) const
700 {
702 << "History_Cell::previous: age " << age << " outside [0, " << Depth << ")";
703 const std::size_t idx = (head_ + Depth - age) % Depth;
704 return values_[idx];
705 }
706
712 void push(const State &value)
713 {
714 head_ = (head_ + 1) % Depth;
716 }
717
723 [[nodiscard]] std::size_t head() const noexcept
724 {
725 return head_;
726 }
727
733 [[nodiscard]] const std::array<State, Depth> &values() const noexcept
734 {
735 return values_;
736 }
737
745 [[nodiscard]] bool operator==(const History_Cell &rhs) const
746 {
747 return head_ == rhs.head_ and values_ == rhs.values_;
748 }
749};
750
768template <std::size_t Depth, typename F>
770{
771 static_assert(Depth >= 1, "History_Rule requires Depth >= 1");
772
774
775public:
781 explicit History_Rule(F func) : f_(std::move(func)) {}
782
791 template <typename State>
793 const History_Cell<State, Depth> &current,
795 {
796 using Cell = History_Cell<State, Depth>;
797
798 if constexpr (std::is_invocable_r_v<Cell, const F &, const Cell &, Neighbor_View<Cell>>)
799 {
800 return f_(current, neighbours);
801 }
802 else
803 {
804 State next{};
805 if constexpr (std::is_invocable_r_v<State, const F &, const Cell &, Neighbor_View<Cell>>)
806 next = f_(current, neighbours);
807 else if constexpr (std::is_invocable_r_v<State, const F &, State, const Cell &,
809 next = f_(current.current(), current, neighbours);
810 else if constexpr (std::is_invocable_r_v<State, const F &, State, State>)
811 next = f_(current.current(), current.previous(1));
812 else
813 static_assert(std::is_invocable_r_v<State, const F &, State, State>,
814 "History_Rule functor has an unsupported signature");
815
816 Cell ret = current;
817 ret.push(next);
818 return ret;
819 }
820 }
821};
822
831template <std::size_t Depth, typename F>
836
837} // namespace CA
838} // namespace Aleph
839
840#endif // TPL_CA_CONTINUOUS_RULES_H
Exception handling system with formatted messages for Aleph-w.
#define ah_length_error_if(C)
Throws std::length_error if condition holds.
Definition ah-errors.H:703
#define ah_out_of_range_error_if(C)
Throws std::out_of_range if condition holds.
Definition ah-errors.H:584
#define ah_domain_error_if(C)
Throws std::domain_error if condition holds.
Definition ah-errors.H:527
#define ah_runtime_error_if(C)
Throws std::runtime_error if condition holds.
Definition ah-errors.H:271
size_t size_t int32_t value
Definition ca-c-api.h:116
Fixed 2-D convolution kernels for continuous cellular automata.
Common typedefs and tag types for the Cellular Automata module.
Scalar continuous rule driven by a real convolution kernel.
typename Kernel::value_type value_type
Numeric type used by the kernel.
value_type min_value() const noexcept
value_type dt() const noexcept
value_type max_value() const noexcept
const Kernel & kernel() const noexcept
Continuous_Rule(F func, Kernel kernel, value_type dt=value_type{1}, value_type min_value=-std::numeric_limits< value_type >::infinity(), value_type max_value=std::numeric_limits< value_type >::infinity())
Build a scalar continuous rule.
State operator()(const State &current, Neighbor_View< State > neighbours) const
Compute the next scalar state.
FitzHugh-Nagumo excitable-media rule.
state_type operator()(const state_type &current, Neighbor_View< state_type > neighbours) const
Compute the next FitzHugh-Nagumo state.
Real laplace_v(Neighbor_View< state_type > neighbours, const state_type &current) const
FitzHugh_Nagumo_Rule(Real a, Real b, Real epsilon, Real du, Real dv, Real dt=Real{0.01}, Real stimulus=Real{}, Kernel laplacian=laplacian_5p_kernel< Real >())
Build a FitzHugh-Nagumo rule.
Real laplace_u(Neighbor_View< state_type > neighbours, const state_type &current) const
const Kernel & laplacian() const noexcept
Kernel kernel_type
Laplacian kernel type.
Gray-Scott reaction-diffusion rule.
state_type operator()(const state_type &current, Neighbor_View< state_type > neighbours) const
Compute the next Gray-Scott state.
Kernel kernel_type
Laplacian kernel type.
const Kernel & laplacian() const noexcept
Real laplace_u(Neighbor_View< state_type > neighbours, const state_type &current) const
Real laplace_v(Neighbor_View< state_type > neighbours, const state_type &current) const
Gray_Scott_Rule(Real feed, Real kill, Real du, Real dv, Real dt=Real{1}, Kernel laplacian=laplacian_5p_kernel< Real >())
Build a Gray-Scott rule.
Cell state carrying a fixed-depth ring buffer of past values.
History_Cell(const std::array< State, Depth > &values, const std::size_t head=0)
Construct a history from raw ring contents.
std::array< State, Depth > values_
bool operator==(const History_Cell &rhs) const
Compare two history cells exactly.
State previous(const std::size_t age) const
Return a previous value by age.
History_Cell()=default
Construct a zero/default history.
State current() const
Return the current value.
void push(const State &value)
Push a new current value into the ring.
History_Cell(const State &value)
Construct a history with every slot initialized to value.
State value_type
Base value type stored in the ring buffer.
const std::array< State, Depth > & values() const noexcept
Return the raw ring storage.
std::size_t head() const noexcept
Return the current ring head index.
static constexpr std::size_t depth_v
Number of retained states.
Rule adapter for History_Cell<State, Depth>.
History_Cell< State, Depth > operator()(const History_Cell< State, Depth > &current, Neighbor_View< History_Cell< State, Depth > > neighbours) const
Compute the next history-bearing cell state.
History_Rule(F func)
Construct a history rule from a functor.
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
void require_positive_dt(const Real dt, const Real max_dt, const char *label)
void require_domain(const Real value, const Real lo, const Real hi, const char *label)
void require_finite(const Real value, const char *label)
Real checked_bounded(const Real value, const Real max_abs, const char *label)
Real clamp_unit_after_check(const Real value, const char *label)
History_Rule< Depth, F > make_history_rule(F f)
Helper factory for History_Rule.
std::span< const T > Neighbor_View
Read-only view over a contiguous range of neighbour values.
Definition ca-traits.H:90
Continuous_Rule< F, Kernel > make_continuous_rule(F f, Kernel k, typename Kernel::value_type dt=typename Kernel::value_type{1}, typename Kernel::value_type min_value=-std::numeric_limits< typename Kernel::value_type >::infinity(), typename Kernel::value_type max_value=std::numeric_limits< typename Kernel::value_type >::infinity())
Helper factory for Continuous_Rule.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
auto min_value(const Container &data) -> std::decay_t< decltype(*std::begin(data))>
Compute minimum value.
Definition stat_utils.H:272
void next()
Advance all underlying iterators (bounds-checked).
Definition ah-zip.H:171
auto max_value(const Container &data) -> std::decay_t< decltype(*std::begin(data))>
Compute maximum value.
Definition stat_utils.H:294
STL namespace.
Two-field state for reaction-diffusion cellular automata.
Real v
Second concentration / inhibitor field.
Real u
First concentration / activator field.
constexpr bool operator==(const Reaction_Diffusion_Cell &rhs) const noexcept
Compare two cells exactly.
static int * k