Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
ca_update_scheme_test.cc
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
51#include <array>
52#include <cstdint>
53#include <random>
54#include <vector>
55
56#include <gtest/gtest.h>
57
58#include <ca-traits.H>
59#include <tpl_ca_storage.H>
60#include <tpl_ca_lattice.H>
61#include <tpl_ca_neighborhood.H>
62#include <tpl_ca_rule.H>
63#include <tpl_ca_engine.H>
64#include <tpl_ca_async_engine.H>
66#include <tpl_ca_block_rule.H>
67
68using namespace Aleph;
69using namespace Aleph::CA;
70
71namespace
72{
73
75
77 double p_alive, std::uint32_t seed)
78{
79 std::mt19937 rng(seed);
80 std::uniform_real_distribution<double> u(0.0, 1.0);
81 Grid g({rows, cols}, 0);
82 for (ca_size_t i = 0; i < rows; ++i)
83 for (ca_size_t j = 0; j < cols; ++j)
84 g.set({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)},
85 u(rng) < p_alive ? 1 : 0);
86 return g;
87}
88
89bool grids_equal(const Grid &a, const Grid &b)
90{
91 if (a.size(0) != b.size(0) or a.size(1) != b.size(1))
92 return false;
93 for (ca_size_t i = 0; i < a.size(0); ++i)
94 for (ca_size_t j = 0; j < a.size(1); ++j)
95 if (a.at({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)})
96 != b.at({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)}))
97 return false;
98 return true;
99}
100
101std::size_t alive_count(const Grid &g)
102{
103 std::size_t n = 0;
104 for (ca_size_t i = 0; i < g.size(0); ++i)
105 for (ca_size_t j = 0; j < g.size(1); ++j)
106 if (g.at({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)}) != 0)
107 ++n;
108 return n;
109}
110
111} // namespace
112
113// ===================================================================
114// Regression: Synchronous_Update == Synchronous_Engine
115// ===================================================================
116
118{
119 // Random soup, run both engines side by side, frame must match
120 // every step (bit-for-bit).
121 Grid initial = make_random_grid(24, 24, 0.35, 0xC0FFEEu);
122
125
130
131 for (std::size_t t = 0; t < 30; ++t)
132 {
133 ASSERT_TRUE(grids_equal(legacy.frame(), schemed.frame()))
134 << "Mismatch at step " << t;
135 legacy.step();
136 schemed.step();
137 }
138 EXPECT_TRUE(grids_equal(legacy.frame(), schemed.frame()))
139 << "Mismatch after 30 steps";
140}
141
143{
147
148 const auto offsets = std::array<Offset_Vec<1>, 2>{{{-1}, {1}}};
149
150 Row initial({33}, 0);
151 initial.set({16}, 1);
152
154
156 initial, rule, Wolfram_Nh(offsets));
157
159 schemed(std::move(initial), rule, Wolfram_Nh(offsets),
161
162 for (std::size_t t = 0; t < 32; ++t)
163 {
164 for (ca_size_t i = 0; i < legacy.frame().size(0); ++i)
165 ASSERT_EQ(legacy.frame().at({static_cast<ca_index_t>(i)}),
166 schemed.frame().at({static_cast<ca_index_t>(i)}))
167 << "step " << t << ", cell " << i;
168 legacy.step();
169 schemed.step();
170 }
171}
172
173// ===================================================================
174// Sequential_Update: sandpile-like relaxation
175// ===================================================================
176
177namespace
178{
179
187struct Sandpile_Rule
188{
189 template <typename State>
190 State operator()(const State &s, Neighbor_View<State> nh) const noexcept
191 {
192 constexpr State threshold = 4;
193 State result = s;
194 if (result >= threshold)
195 result -= threshold;
196 for (const auto &n : nh)
197 if (n >= threshold)
198 result += 1; // each over-threshold neighbour pushes one grain
199 return result;
200 }
201};
202
203} // namespace
204
206{
207 Grid g({12, 12}, 0);
208 // Drop ten grains at the centre to seed an avalanche.
209 for (int k = 0; k < 50; ++k)
210 {
211 const auto cur = g.at({6, 6});
212 g.set({6, 6}, cur + 1);
213 // Run a single full sweep using Sequential_Update.
216 eng(std::move(g), Sandpile_Rule{}, Von_Neumann<2, 1>{},
218 // Run enough sweeps to relax the avalanche.
219 eng.run(40);
220 g = eng.frame();
221 }
222
223 // After many drops + relaxations every cell must be below the
224 // toppling threshold.
225 for (ca_size_t i = 0; i < g.size(0); ++i)
226 for (ca_size_t j = 0; j < g.size(1); ++j)
227 EXPECT_LT(g.at({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)}), 4)
228 << "cell (" << i << ", " << j << ") still over threshold";
229}
230
232{
233 // Strictly compile-time tag: Sequential_Update must declare it
234 // does not require a double buffer — saves ~50% memory.
237}
238
239// ===================================================================
240// Random_Asynchronous_Update: reproducibility
241// ===================================================================
242
244{
245 Grid initial = make_random_grid(24, 24, 0.30, 0xBEEFu);
246
250 Random_Asynchronous_Update<>{0xCAFE, 64});
253 b(std::move(initial), make_game_of_life_rule(), Moore<2, 1>{},
254 Random_Asynchronous_Update<>{0xCAFE, 64});
255
256 a.run(20);
257 b.run(20);
258 EXPECT_TRUE(grids_equal(a.frame(), b.frame()))
259 << "Same master seed must produce identical frames";
260}
261
263{
264 // After exactly k sub-steps at most k cells may have changed.
265 Grid initial = make_random_grid(20, 20, 0.30, 0xBADu);
266
271
272 Grid before = initial;
273 eng.step();
274 std::size_t diff = 0;
275 for (ca_size_t i = 0; i < before.size(0); ++i)
276 for (ca_size_t j = 0; j < before.size(1); ++j)
277 if (before.at({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)})
278 != eng.frame().at({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)}))
279 ++diff;
280 EXPECT_LE(diff, 5u)
281 << "5 sub-steps must touch at most 5 cells, got " << diff;
282}
283
284// ===================================================================
285// Block_Synchronous_Update
286// ===================================================================
287
289{
290 Grid initial = make_random_grid(8, 8, 0.50, 0x1234u);
291 Grid before = initial;
292
296
297 eng.step();
298
299 std::size_t changed_blocks = 0;
300 for (ca_size_t bi = 0; bi < 2; ++bi)
301 for (ca_size_t bj = 0; bj < 2; ++bj)
302 {
303 bool touched = false;
304 for (ca_size_t i = 0; i < 4 and not touched; ++i)
305 for (ca_size_t j = 0; j < 4 and not touched; ++j)
306 {
307 const auto r = static_cast<ca_index_t>(bi * 4 + i);
308 const auto c = static_cast<ca_index_t>(bj * 4 + j);
309 if (before.at({r, c}) != eng.frame().at({r, c}))
310 touched = true;
311 }
312 if (touched)
314 }
316}
317
318// ===================================================================
319// Margolus_Update + Block rules: reversibility
320// ===================================================================
321
322namespace
323{
324
325template <typename Block_Rule>
326void run_reversibility_test(Block_Rule rule, std::size_t N,
327 ca_size_t side, std::uint32_t seed,
328 const char *label)
329{
330 Grid initial = make_random_grid(side, side, 0.45, seed);
331 Grid baseline = initial;
332
334 eng(std::move(initial), std::move(rule), Null_Neighborhood<2>{},
336
337 // Forward N steps must complete without throwing.
338 ASSERT_NO_THROW(eng.run(N));
339 // Even in pathological cases, the rule must be invertible by
340 // running backwards the same number of steps.
341 ASSERT_NO_THROW(eng.run_back(N));
342
344 << label << ": forward(" << N << ") then backward(" << N
345 << ") must restore the initial state bit-by-bit";
346 EXPECT_EQ(eng.steps_run(), 0u);
347}
348
349} // namespace
350
352{
353 run_reversibility_test(Critters_Rule{}, 1000u, 32, 0xDEADBEEFu,
354 "Critters");
355}
356
361
366
368{
369 // The Critters rule swaps invert+rotate when popcount != 2 in the
370 // 2×2 block. Whole-lattice population is *not* conserved, but each
371 // step must still leave the grid in a valid binary state.
372 Grid g = make_random_grid(16, 16, 0.40, 0xC0DE);
374 eng(std::move(g), Critters_Rule{}, Null_Neighborhood<2>{});
375 for (std::size_t t = 0; t < 50; ++t)
376 {
377 eng.step();
378 for (ca_size_t i = 0; i < eng.frame().size(0); ++i)
379 for (ca_size_t j = 0; j < eng.frame().size(1); ++j)
380 {
381 const int v = eng.frame().at({static_cast<ca_index_t>(i),
382 static_cast<ca_index_t>(j)});
383 ASSERT_TRUE(v == 0 or v == 1)
384 << "non-binary cell after step " << t;
385 }
386 }
387}
388
390{
391 // BBM is a particle-conserving rule.
392 Grid g = make_random_grid(16, 16, 0.20, 0xB1B2u);
393 const auto initial_particles = alive_count(g);
395 std::move(g), BBM_Rule{}, Null_Neighborhood<2>{});
396 for (std::size_t t = 0; t < 100; ++t)
397 {
398 eng.step();
400 << "BBM lost particles at step " << t;
401 }
402}
403
405{
406 Grid g = make_random_grid(16, 16, 0.20, 0xEFEFu);
407 const auto initial_particles = alive_count(g);
409 std::move(g), TM_Gas_Rule{}, Null_Neighborhood<2>{});
410 for (std::size_t t = 0; t < 100; ++t)
411 {
412 eng.step();
414 << "TM Gas lost particles at step " << t;
415 }
416}
417
418// ===================================================================
419// Block_Rule adapter: cell rule wrapped as block rule
420// ===================================================================
421
423{
424 // Wrap a tiny "increment" cell-rule into a block rule.
425 struct Inc
426 {
427 int operator()(int s) const noexcept { return s + 1; }
428 };
430 const Block_2x2<int> in{0, 1, 2, 3};
431 const Block_2x2<int> out = wrapped(in);
432 EXPECT_EQ(out[0], 1);
433 EXPECT_EQ(out[1], 2);
434 EXPECT_EQ(out[2], 3);
435 EXPECT_EQ(out[3], 4);
436}
437
438// ===================================================================
439// Hooks
440// ===================================================================
441
443{
444 Grid initial = make_random_grid(6, 6, 0.50, 0x1u);
448
449 std::vector<std::size_t> pre, post;
450 eng.on_pre_step([&](std::size_t s, const Grid &) { pre.push_back(s); });
451 eng.on_post_step([&](std::size_t s, const Grid &) { post.push_back(s); });
452
453 eng.run(3);
454 EXPECT_EQ(pre, (std::vector<std::size_t>{0, 1, 2}));
455 EXPECT_EQ(post, (std::vector<std::size_t>{1, 2, 3}));
456}
size_t size_t int32_t * out
Definition ca-c-api.h:120
size_t * rows
Definition ca-c-api.h:112
size_t cols
Definition ca-c-api.h:105
Common typedefs and tag types for the Cellular Automata module.
Update-scheme aware engine.
void step()
Run one engine step using the configured update scheme.
Fredkin–Toffoli Billiard Ball Machine block rule.
Adapt a regular cell rule (with empty neighborhood) to a block rule.
Critters reversible CA block rule.
User-supplied list of offsets for arbitrary connectivity.
Lattice that adds boundary-aware access on top of a storage.
Precomputed transition table for (self, neighbours...).
Moore (Chebyshev) neighborhood of radius R in N dimensions.
Placeholder neighbourhood for engines that drive block rules.
Synchronous double-buffered engine.
Toffoli–Margolus (TM) lattice-gas block rule.
Von Neumann (L1) neighborhood of radius R in N dimensions.
#define TEST(name)
static mt19937 rng
#define N
Definition fib.C:294
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
constexpr Game_Of_Life_Rule make_game_of_life_rule() noexcept
Build the canonical Game of Life rule.
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::size_t ca_size_t
Unsigned size component used for extents and counts.
Definition ca-traits.H:63
std::array< State, 4 > Block_2x2
Fixed-size 2×2 block of cell values used by Margolus rules.
constexpr Lookup_Rule< 2, 2 > make_wolfram_elementary_rule(std::uint8_t rule_no) noexcept
Build the elementary 1D Wolfram rule rule_no (0..255) as a Lookup_Rule<2, 2> over neighbourhood {-1,...
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
size_t size(Node *root) noexcept
and
Check uniqueness with explicit hash + equality functors.
bool diff(const C1 &c1, const C2 &c2, Eq e=Eq())
Check if two containers differ.
Synchronous update over rotating sub-blocks.
Margolus 2×2 partition update for reversible CAs.
Pick one cell at random per sub-step, update it in place.
In-place sequential update (no double buffer).
Classical double-buffer synchronous update.
The lattice wraps around on every axis.
Definition ca-traits.H:124
ValueArg< size_t > seed
Definition testHash.C:53
static int * k
gsl_rng * r
Phase 13 update-scheme aware engine.
Block-based local rules for Aleph::CA (Phase 13).
Synchronous double-buffered engine for cellular automata.
Cellular automata lattice with pluggable boundary policies.
Neighborhoods catalogue for Aleph::CA.
Rule mechanisms for Aleph::CA.
Dense, contiguous storage for cellular automata cells (1D/2D/3D).
Phase 13 update-scheme strategies for Aleph::CA.