Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tpl_ca_update_scheme.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
67#ifndef TPL_CA_UPDATE_SCHEME_H
68#define TPL_CA_UPDATE_SCHEME_H
69
70#include <array>
71#include <concepts>
72#include <cstddef>
73#include <cstdint>
74#include <random>
75#include <span>
76#include <type_traits>
77#include <utility>
78
79#include <ah-errors.H>
80#include <ca-rng.H>
81#include <ca-traits.H>
82#include <tpl_ca_concepts.H>
83#include <tpl_ca_neighborhood.H>
84
85namespace Aleph {
86namespace CA {
87
88namespace ca_scheme_detail {
89
91template <typename O>
92struct is_tile : std::false_type
93{
94};
95
96template <std::size_t W, std::size_t H>
97struct is_tile<Tile<W, H>> : std::true_type
98{
99};
100
101template <typename O>
102inline constexpr bool is_tile_v = is_tile<O>::value;
103
105template <typename Coord, typename Cb>
106inline void for_each_coord_1d(const ca_size_t n0, Cb &&cb)
107{
108 Coord c{};
109 for (ca_index_t i = 0; i < static_cast<ca_index_t>(n0); ++i)
110 {
111 c[0] = i;
112 cb(c);
113 }
114}
115
117template <typename Coord, typename Cb>
118inline void for_each_coord_2d_row(const ca_size_t n0, const ca_size_t n1, Cb &&cb)
119{
120 Coord c{};
121 for (ca_index_t i = 0; i < static_cast<ca_index_t>(n0); ++i)
122 for (ca_index_t j = 0; j < static_cast<ca_index_t>(n1); ++j)
123 {
124 c[0] = i;
125 c[1] = j;
126 cb(c);
127 }
128}
129
131template <typename Coord, typename Cb>
132inline void for_each_coord_2d_col(const ca_size_t n0, const ca_size_t n1, Cb &&cb)
133{
134 Coord c{};
135 for (ca_index_t j = 0; j < static_cast<ca_index_t>(n1); ++j)
136 for (ca_index_t i = 0; i < static_cast<ca_index_t>(n0); ++i)
137 {
138 c[0] = i;
139 c[1] = j;
140 cb(c);
141 }
142}
143
145template <std::size_t W, std::size_t H, typename Coord, typename Cb>
146inline void for_each_coord_2d_tile(const ca_size_t n0, const ca_size_t n1, Cb &&cb)
147{
148 Coord c{};
149 for (ca_size_t ti = 0; ti < n0; ti += H)
150 for (ca_size_t tj = 0; tj < n1; tj += W)
151 {
152 const ca_size_t i_end = ti + H < n0 ? ti + H : n0;
153 const ca_size_t j_end = tj + W < n1 ? tj + W : n1;
154 for (ca_size_t i = ti; i < i_end; ++i)
155 for (ca_size_t j = tj; j < j_end; ++j)
156 {
157 c[0] = static_cast<ca_index_t>(i);
158 c[1] = static_cast<ca_index_t>(j);
159 cb(c);
160 }
161 }
162}
163
165template <typename Coord, typename Cb>
166inline void for_each_coord_3d_row(const ca_size_t n0, const ca_size_t n1, const ca_size_t n2, Cb &&cb)
167{
168 Coord c{};
169 for (ca_index_t i = 0; i < static_cast<ca_index_t>(n0); ++i)
170 for (ca_index_t j = 0; j < static_cast<ca_index_t>(n1); ++j)
171 for (ca_index_t k = 0; k < static_cast<ca_index_t>(n2); ++k)
172 {
173 c[0] = i;
174 c[1] = j;
175 c[2] = k;
176 cb(c);
177 }
178}
179
181template <typename Order, typename Lattice, typename Cb>
182inline void for_each_coord(const Lattice &lat, Cb &&cb)
183{
184 using Coord = typename Lattice::coord_type;
185 if constexpr (Lattice::rank == 1)
186 {
187 for_each_coord_1d<Coord>(lat.size(0), std::forward<Cb>(cb));
188 }
189 else if constexpr (Lattice::rank == 2)
190 {
191 if constexpr (is_tile_v<Order>)
193 std::forward<Cb>(cb));
194 else if constexpr (std::is_same_v<Order, ColumnMajor>)
195 for_each_coord_2d_col<Coord>(lat.size(0), lat.size(1), std::forward<Cb>(cb));
196 else
197 for_each_coord_2d_row<Coord>(lat.size(0), lat.size(1), std::forward<Cb>(cb));
198 }
199 else if constexpr (Lattice::rank == 3)
200 {
201 for_each_coord_3d_row<Coord>(lat.size(0), lat.size(1), lat.size(2), std::forward<Cb>(cb));
202 }
203 else
204 {
205 static_assert(Lattice::rank <= 3, "Update schemes currently support rank <= 3");
206 }
207}
208
210template <typename Lattice>
211inline ca_size_t cell_count(const Lattice &lat) noexcept
212{
213 ca_size_t n = 1;
214 for (std::size_t d = 0; d < Lattice::rank; ++d)
215 n *= lat.size(d);
216 return n;
217}
218
220template <std::size_t Rank>
221inline Coord_Vec<Rank> coord_from_hash(std::uint64_t h, const std::array<ca_size_t, Rank> &ext) noexcept
222{
223 Coord_Vec<Rank> c{};
224 for (std::size_t d = 0; d < Rank; ++d)
225 {
226 const ca_size_t n = ext[d] == 0 ? 1 : ext[d];
227 c[d] = static_cast<ca_index_t>(h % n);
228 h /= n == 0 ? 1 : n;
229 h = splitmix64(h ^ (static_cast<std::uint64_t>(d) + 1));
230 }
231 return c;
232}
233
234} // namespace ca_scheme_detail
235
236// =======================================================================
237// Synchronous_Update
238// =======================================================================
239
249template <typename Order = RowMajor>
251{
253 static constexpr bool requires_double_buffer = true;
255 static constexpr bool requires_block_rule = false;
256
267 template <typename Engine, typename Rule>
268 void apply(Engine &e, Rule &r) const
269 {
270 using Lattice = typename Engine::lattice_type;
271 using State = typename Lattice::state_type;
272 using Coord = typename Lattice::coord_type;
273 using Neighborhood = typename Engine::neighborhood_type;
274 constexpr std::size_t K = Neighborhood::size_v;
275
276 auto &cur = e.current_buffer();
277 auto &nxt = e.next_buffer();
278 const auto &nh = e.neighborhood();
279 const std::size_t step = e.step_count();
280
281 std::array<State, K == 0 ? 1 : K> nbuf{};
282 const std::size_t neigh_sz = (std::min) (nh.size(), static_cast<std::size_t>(nbuf.size()));
283
284 ca_scheme_detail::for_each_coord<Order>(cur, [&](const Coord &c)
285 {
286 gather_neighbors(nh, cur, c, std::span<State>(nbuf.data(), neigh_sz));
287 const Cell_Context<Lattice::rank> ctx{step, c};
288 nxt.set(c, apply_rule(r, cur.at(c), Neighbor_View<State>(nbuf.data(), neigh_sz), ctx));
289 });
290
291 e.swap_buffers();
292 }
293};
294
295// =======================================================================
296// Sequential_Update
297// =======================================================================
298
309template <typename Order = RowMajor>
311{
313 static constexpr bool requires_double_buffer = false;
315 static constexpr bool requires_block_rule = false;
316
326 template <typename Engine, typename Rule>
327 void apply(Engine &e, Rule &r) const
328 {
329 using Lattice = typename Engine::lattice_type;
330 using State = typename Lattice::state_type;
331 using Coord = typename Lattice::coord_type;
332 using Neighborhood = typename Engine::neighborhood_type;
333 constexpr std::size_t K = Neighborhood::size_v;
334
335 auto &cur = e.current_buffer();
336 const auto &nh = e.neighborhood();
337 const std::size_t step = e.step_count();
338
339 std::array<State, K == 0 ? 1 : K> nbuf{};
340 const std::size_t neigh_sz = (std::min) (nh.size(), static_cast<std::size_t>(nbuf.size()));
341
342 ca_scheme_detail::for_each_coord<Order>(cur, [&](const Coord &c)
343 {
344 gather_neighbors(nh, cur, c, std::span<State>(nbuf.data(), neigh_sz));
345 const Cell_Context<Lattice::rank> ctx{step, c};
346 const State next = apply_rule(r, cur.at(c), Neighbor_View<State>(nbuf.data(), neigh_sz), ctx);
347 cur.set(c, next);
348 });
349 }
350};
351
352// =======================================================================
353// Random_Asynchronous_Update
354// =======================================================================
355
371template <typename Engine_Type = std::mt19937_64>
373{
375 static constexpr bool requires_double_buffer = false;
377 static constexpr bool requires_block_rule = false;
378
379 std::uint64_t master_seed = 0;
382 std::size_t sub_steps_per_step = 0;
383
391 constexpr Random_Asynchronous_Update(const std::uint64_t seed = 0,
392 const std::size_t sub_steps = 0) noexcept
394 {}
395
405 template <typename Engine, typename Rule>
406 void apply(Engine &e, Rule &r) const
407 {
408 using Lattice = typename Engine::lattice_type;
409 using State = typename Lattice::state_type;
410 using Coord = typename Lattice::coord_type;
411 using Neighborhood = typename Engine::neighborhood_type;
412 constexpr std::size_t K = Neighborhood::size_v;
413
414 auto &cur = e.current_buffer();
415 const auto &nh = e.neighborhood();
416 const std::size_t step = e.step_count();
417
419 const std::size_t subs
420 = sub_steps_per_step == 0 ? static_cast<std::size_t>(n_cells) : sub_steps_per_step;
421
422 std::array<State, K == 0 ? 1 : K> nbuf{};
423 const std::size_t neigh_sz = (std::min) (nh.size(), static_cast<std::size_t>(nbuf.size()));
424
425 for (std::size_t sub = 0; sub < subs; ++sub)
426 {
427 // Stable per-(step, sub) selection seed independent of the
428 // rule's own per-cell sub-stream.
429 const std::uint64_t pick_seed = mix_seed(
430 mix_seed(master_seed, static_cast<std::uint64_t>(step)), static_cast<std::uint64_t>(sub));
431 Coord c = ca_scheme_detail::coord_from_hash<Lattice::rank>(pick_seed, cur.extents());
432
433 gather_neighbors(nh, cur, c, std::span<State>(nbuf.data(), neigh_sz));
434 const Cell_Context<Lattice::rank> ctx{step, c};
435 const State next = apply_rule(r, cur.at(c), Neighbor_View<State>(nbuf.data(), neigh_sz), ctx);
436 cur.set(c, next);
437 }
438 }
439};
440
441// =======================================================================
442// Block_Synchronous_Update
443// =======================================================================
444
458template <std::size_t BlockSize = 4, typename Order = RowMajor>
460{
461 static_assert(BlockSize >= 1, "Block_Synchronous_Update requires BlockSize >= 1");
462
463 static constexpr bool requires_double_buffer = true;
464 static constexpr bool requires_block_rule = false;
465
466 static constexpr std::size_t block_size = BlockSize;
467
478 template <typename Engine, typename Rule>
479 void apply(Engine &e, Rule &r) const
480 {
481 using Lattice = typename Engine::lattice_type;
482 using State = typename Lattice::state_type;
483 using Coord = typename Lattice::coord_type;
484 using Neighborhood = typename Engine::neighborhood_type;
485 constexpr std::size_t K = Neighborhood::size_v;
486
487 auto &cur = e.current_buffer();
488 auto &nxt = e.next_buffer();
489 const auto &nh = e.neighborhood();
490 const std::size_t step = e.step_count();
491
492 // Number of blocks along each axis.
493 std::array<ca_size_t, Lattice::rank> blocks_per_axis{};
495 for (std::size_t d = 0; d < Lattice::rank; ++d)
496 {
497 const ca_size_t n = cur.size(d);
498 blocks_per_axis[d] = (n + BlockSize - 1) / BlockSize;
499 total_blocks *= (blocks_per_axis[d] == 0 ? 1 : blocks_per_axis[d]);
500 }
501
502 if (total_blocks == 0)
503 total_blocks = 1;
504 const std::size_t active = step % total_blocks;
505
506 // Decompose the active block index into per-axis tile coordinates.
507 std::array<ca_size_t, Lattice::rank> tile_coord{};
508 {
509 std::size_t rem = active;
510 for (std::size_t d = Lattice::rank; d-- > 0;)
511 {
512 const ca_size_t bn = blocks_per_axis[d] == 0 ? 1 : blocks_per_axis[d];
513 tile_coord[d] = rem % bn;
514 rem /= bn;
515 }
516 }
517
518 // First, copy current → next so cells outside the active block
519 // carry over unchanged after the swap.
520 ca_scheme_detail::for_each_coord<Order>(cur, [&](const Coord &c)
521 {
522 nxt.set(c, cur.at(c));
523 });
524
525 std::array<State, K == 0 ? 1 : K> nbuf{};
526 const std::size_t neigh_sz = (std::min) (nh.size(), static_cast<std::size_t>(nbuf.size()));
527
528 // Iterate over the active block only.
529 if constexpr (Lattice::rank == 1)
530 {
531 const ca_size_t i0 = tile_coord[0] * BlockSize;
532 const ca_size_t i_end = (std::min) (i0 + BlockSize, cur.size(0));
533 for (ca_size_t i = i0; i < i_end; ++i)
534 {
535 Coord c{static_cast<ca_index_t>(i)};
536 gather_neighbors(nh, cur, c, std::span<State>(nbuf.data(), neigh_sz));
537 const Cell_Context<Lattice::rank> ctx{step, c};
538 nxt.set(c, apply_rule(r, cur.at(c), Neighbor_View<State>(nbuf.data(), neigh_sz), ctx));
539 }
540 }
541 else if constexpr (Lattice::rank == 2)
542 {
543 const ca_size_t i0 = tile_coord[0] * BlockSize;
544 const ca_size_t j0 = tile_coord[1] * BlockSize;
545 const ca_size_t i_end = (std::min) (i0 + BlockSize, cur.size(0));
546 const ca_size_t j_end = (std::min) (j0 + BlockSize, cur.size(1));
547 for (ca_size_t i = i0; i < i_end; ++i)
548 for (ca_size_t j = j0; j < j_end; ++j)
549 {
550 Coord c{static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)};
551 gather_neighbors(nh, cur, c, std::span<State>(nbuf.data(), neigh_sz));
552 const Cell_Context<Lattice::rank> ctx{step, c};
553 nxt.set(c, apply_rule(r, cur.at(c), Neighbor_View<State>(nbuf.data(), neigh_sz), ctx));
554 }
555 }
556 else if constexpr (Lattice::rank == 3)
557 {
558 const ca_size_t i0 = tile_coord[0] * BlockSize;
559 const ca_size_t j0 = tile_coord[1] * BlockSize;
560 const ca_size_t k0 = tile_coord[2] * BlockSize;
561 const ca_size_t i_end = (std::min) (i0 + BlockSize, cur.size(0));
562 const ca_size_t j_end = (std::min) (j0 + BlockSize, cur.size(1));
563 const ca_size_t k_end = (std::min) (k0 + BlockSize, cur.size(2));
564 for (ca_size_t i = i0; i < i_end; ++i)
565 for (ca_size_t j = j0; j < j_end; ++j)
566 for (ca_size_t k = k0; k < k_end; ++k)
567 {
568 Coord c{static_cast<ca_index_t>(i), static_cast<ca_index_t>(j),
569 static_cast<ca_index_t>(k)};
570 gather_neighbors(nh, cur, c, std::span<State>(nbuf.data(), neigh_sz));
571 const Cell_Context<Lattice::rank> ctx{step, c};
572 nxt.set(c,
573 apply_rule(r, cur.at(c), Neighbor_View<State>(nbuf.data(), neigh_sz), ctx));
574 }
575 }
576
577 e.swap_buffers();
578 }
579};
580
581// =======================================================================
582// Margolus_Update
583// =======================================================================
584
609{
612 static constexpr bool requires_double_buffer = false;
614 static constexpr bool requires_block_rule = true;
615
617 [[nodiscard]] static constexpr ca_index_t origin_for_step(const std::size_t k) noexcept
618 {
619 return (k % 2 == 0) ? 0 : 1;
620 }
621
631 template <typename Engine, typename Block_Rule>
632 void apply_with_origin(Engine &e, Block_Rule &r, const ca_index_t origin) const
633 {
634 using Lattice = typename Engine::lattice_type;
635 using State = typename Lattice::state_type;
636 using Coord = typename Lattice::coord_type;
637 static_assert(Lattice::rank == 2, "Margolus_Update requires a 2D lattice");
638
639 auto &lat = e.current_buffer();
640 const ca_size_t n0 = lat.size(0);
641 const ca_size_t n1 = lat.size(1);
642 if (n0 < 2 or n1 < 2)
643 return;
644
645 // Sweep every 2×2 block of the active partition.
646 for (ca_index_t i = origin; i + 1 < static_cast<ca_index_t>(n0); i += 2)
647 for (ca_index_t j = origin; j + 1 < static_cast<ca_index_t>(n1); j += 2)
648 {
649 const Coord nw{i, j};
650 const Coord ne{i, j + 1};
651 const Coord sw{i + 1, j};
652 const Coord se{i + 1, j + 1};
653
654 std::array<State, 4> in{lat.at(nw), lat.at(ne), lat.at(sw), lat.at(se)};
655 const std::array<State, 4> out = r(in);
656
657 lat.set(nw, out[0]);
658 lat.set(ne, out[1]);
659 lat.set(sw, out[2]);
660 lat.set(se, out[3]);
661 }
662 }
663
670 template <typename Engine, typename Block_Rule>
671 void apply(Engine &e, Block_Rule &r) const
672 {
673 apply_with_origin(e, r, origin_for_step(e.step_count()));
674 }
675};
676
677} // namespace CA
678} // namespace Aleph
679
680#endif // TPL_CA_UPDATE_SCHEME_H
Exception handling system with formatted messages for Aleph-w.
long double h
Definition btreepic.C:154
size_t size_t int32_t * out
Definition ca-c-api.h:120
Reproducible random-number support for stochastic CA rules (Phase 8).
Common typedefs and tag types for the Cellular Automata module.
Lattice that adds boundary-aware access on top of a storage.
typename Storage::state_type state_type
typename Storage::coord_type coord_type
static constexpr std::size_t rank
A coordinate type with N integral components.
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_j0_function > > j0(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4110
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
Coord_Vec< Rank > coord_from_hash(std::uint64_t h, const std::array< ca_size_t, Rank > &ext) noexcept
Decode a 64-bit hash into a coordinate uniformly drawn over extents.
ca_size_t cell_count(const Lattice &lat) noexcept
Total cell count of a lattice (product of extents).
void for_each_coord_1d(const ca_size_t n0, Cb &&cb)
Iterate every coordinate of a rank-1 lattice in row order.
void for_each_coord_3d_row(const ca_size_t n0, const ca_size_t n1, const ca_size_t n2, Cb &&cb)
Iterate every coordinate of a rank-3 lattice in row-major order.
void for_each_coord(const Lattice &lat, Cb &&cb)
Visit every coordinate of lat honouring the chosen Order.
void for_each_coord_2d_row(const ca_size_t n0, const ca_size_t n1, Cb &&cb)
Iterate every coordinate of a rank-2 lattice in row-major order.
void for_each_coord_2d_tile(const ca_size_t n0, const ca_size_t n1, Cb &&cb)
Iterate a rank-2 lattice in H × W tiles (cache-friendly).
void for_each_coord_2d_col(const ca_size_t n0, const ca_size_t n1, Cb &&cb)
Iterate every coordinate of a rank-2 lattice in column-major order.
constexpr std::uint64_t mix_seed(const std::uint64_t a, const std::uint64_t b) noexcept
Combine two 64-bit values into a single deterministic hash.
Definition ca-rng.H:97
std::span< const T > Neighbor_View
Read-only view over a contiguous range of neighbour values.
Definition ca-traits.H:90
std::ptrdiff_t ca_index_t
Signed coordinate component used by lattices and neighborhoods.
Definition ca-traits.H:60
std::array< ca_index_t, N > Coord_Vec
Default coordinate vector.
Definition ca-traits.H:69
void gather_neighbors(const Nbh &nh, const L &lat, const typename L::coord_type &center, std::span< T > out)
Populate out[0..nh.size()) with neighbour values of center.
constexpr std::uint64_t splitmix64(std::uint64_t x) noexcept
64-bit SplitMix hash.
Definition ca-rng.H:82
std::size_t ca_size_t
Unsigned size component used for extents and counts.
Definition ca-traits.H:63
State apply_rule(const Rule &r, const State &s, Neighbor_View< State > v, const Cell_Context< Rank > &ctx)
Invoke a rule, optionally forwarding the per-cell context.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
void next()
Advance all underlying iterators (bounds-checked).
Definition ah-zip.H:171
Synchronous update over rotating sub-blocks.
static constexpr std::size_t block_size
void apply(Engine &e, Rule &r) const
Execute one block-synchronous step.
Per-cell context handed to rules that need to know "where" and "when" they are firing.
Definition ca-traits.H:106
Margolus 2×2 partition update for reversible CAs.
static constexpr bool requires_block_rule
Operates on Block_Rule, not on cell rules.
void apply_with_origin(Engine &e, Block_Rule &r, const ca_index_t origin) const
Apply the block rule to every 2×2 block of the active partition.
static constexpr bool requires_double_buffer
Margolus uses a single buffer; reads and writes the same lattice block by block.
static constexpr ca_index_t origin_for_step(const std::size_t k) noexcept
Origin offset used at step k: even → (0,0), odd → (1,1).
void apply(Engine &e, Block_Rule &r) const
Apply the block rule using the parity of the engine step.
Pick one cell at random per sub-step, update it in place.
std::size_t sub_steps_per_step
Number of single-cell sub-steps per step().
static constexpr bool requires_double_buffer
In-place updates → no second buffer.
static constexpr bool requires_block_rule
Operates on cell rules.
constexpr Random_Asynchronous_Update(const std::uint64_t seed=0, const std::size_t sub_steps=0) noexcept
Build a random asynchronous update strategy.
void apply(Engine &e, Rule &r) const
Execute one randomised step.
In-place sequential update (no double buffer).
static constexpr bool requires_block_rule
Operates on cell rules.
void apply(Engine &e, Rule &r) const
Execute one sequential in-place step.
static constexpr bool requires_double_buffer
Operates in place, no second buffer needed.
Classical double-buffer synchronous update.
static constexpr bool requires_block_rule
Operates on cell rules — not on block rules.
static constexpr bool requires_double_buffer
Engines must allocate a second buffer for this scheme.
void apply(Engine &e, Rule &r) const
Execute one synchronous step.
Iterate a 2D lattice in W x H tiles.
Definition ca-traits.H:200
Detect whether O is a Tile<W, H> instantiation.
ValueArg< size_t > seed
Definition testHash.C:53
static int * k
gsl_rng * r
C++20 concepts for the Cellular Automata module.
Neighborhoods catalogue for Aleph::CA.