Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tpl_ca_graph_automaton_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
46#include <cstdint>
47#include <random>
48#include <stdexcept>
49#include <vector>
50
51#include <gtest/gtest.h>
52
53#include <ca-traits.H>
54#include <tpl_ca_concepts.H>
56#include <tpl_ca_rule.H>
57
58using namespace Aleph;
59using namespace Aleph::CA;
60
61// ---------------------------------------------------------------------------
62// Concept conformance.
63// ---------------------------------------------------------------------------
64
65static_assert(LatticeLike<Graph_Lattice<int>>);
67
68// ---------------------------------------------------------------------------
69// Adjacency builders.
70// ---------------------------------------------------------------------------
71
73{
74 const auto adj = make_path_graph_adjacency(5);
75 ASSERT_EQ(adj.size(), 5u);
76 EXPECT_EQ(adj[0], (Array<std::size_t>{1}));
77 EXPECT_EQ(adj[1], (Array<std::size_t>{0, 2}));
78 EXPECT_EQ(adj[2], (Array<std::size_t>{1, 3}));
79 EXPECT_EQ(adj[3], (Array<std::size_t>{2, 4}));
80 EXPECT_EQ(adj[4], (Array<std::size_t>{3}));
81}
82
84{
85 const auto adj = make_path_graph_adjacency(4, /*cycle=*/true);
86 ASSERT_EQ(adj.size(), 4u);
87 EXPECT_EQ(adj[0], (Array<std::size_t>{3, 1}));
88 EXPECT_EQ(adj[1], (Array<std::size_t>{0, 2}));
89 EXPECT_EQ(adj[2], (Array<std::size_t>{1, 3}));
90 EXPECT_EQ(adj[3], (Array<std::size_t>{2, 0}));
91}
92
94{
95 const auto adj = make_grid_graph_adjacency(3, 4, /*periodic=*/false);
96 // Sum of degrees == 2 * |E|. For a 3x4 open grid, edges = 2*3*4 - 3 - 4 = 17.
97 std::size_t total_deg = 0;
98 for (const auto &row : adj)
99 total_deg += row.size();
100 EXPECT_EQ(total_deg, 2u * 17u);
101
102 // Corner has degree 2.
103 EXPECT_EQ(adj[0].size(), 2u);
104 // Centre cell (1, 1) has degree 4.
105 EXPECT_EQ(adj[1 * 4 + 1].size(), 4u);
106}
107
109{
110 const auto adj = make_grid_graph_adjacency(5, 5, /*periodic=*/true);
111 for (const auto &row : adj)
112 EXPECT_EQ(row.size(), 4u);
113}
114
115// ---------------------------------------------------------------------------
116// Graph_Lattice basics.
117// ---------------------------------------------------------------------------
118
120{
121 Array<Array<std::size_t>> bad = {{1}, {99}}; // 99 is invalid
123 std::out_of_range);
124}
125
127{
129 EXPECT_EQ(lat.size(), 6u);
130 EXPECT_EQ(lat.degree(0), 1u);
131 EXPECT_EQ(lat.degree(3), 2u);
132 EXPECT_EQ(lat.max_degree(), 2u);
133 for (std::size_t n = 0; n < lat.size(); ++n)
134 lat.set_node(n, static_cast<int>(n + 1));
135 for (std::size_t n = 0; n < lat.size(); ++n)
136 EXPECT_EQ(lat.at_node(n), static_cast<int>(n + 1));
137}
138
140{
143 EXPECT_EQ(lat.at_safe(coord_t{-1}), 0);
144 EXPECT_EQ(lat.at_safe(coord_t{99}), 0);
145 EXPECT_EQ(lat.at_safe(coord_t{1}), 7);
146 EXPECT_THROW((void)lat.at(coord_t{99}), std::out_of_range);
147}
148
150{
152 lat.set_node(0, 1);
153 lat.set_node(2, 1);
154 lat.fill(7);
155 for (std::size_t n = 0; n < lat.size(); ++n)
156 EXPECT_EQ(lat.at_node(n), 7);
157}
158
159// ---------------------------------------------------------------------------
160// Graph_Synchronous_Engine.
161// ---------------------------------------------------------------------------
162
163namespace {
164
165// Majority rule: cell becomes 1 iff strict majority of neighbours are 1.
166// Ties keep the current state.
167struct Majority_Functor
168{
169 [[nodiscard]] int operator()(int current, Neighbor_View<int> neigh) const noexcept
170 {
171 std::size_t alive = 0;
172 for (int v : neigh)
173 if (v != 0)
174 ++alive;
175 const std::size_t deg = neigh.size();
176 if (2 * alive > deg)
177 return 1;
178 if (2 * alive < deg)
179 return 0;
180 return current;
181 }
182};
183
184// Voter rule: each cell adopts the state of a uniformly random
185// neighbour. Determinism is achieved by hashing a monotonically
186// increasing per-call counter into the seed: every cell update
187// (across every step of the simulation) gets a unique sub-seed.
188class Voter_Rule_Driver
189{
190 std::uint64_t seed_;
191 mutable std::uint64_t calls_ = 0;
192
193public:
194 explicit Voter_Rule_Driver(std::uint64_t seed) noexcept : seed_(seed) {}
195
196 [[nodiscard]] int operator()(int current, Neighbor_View<int> neigh) const noexcept
197 {
198 if (neigh.empty())
199 return current;
200 std::uint64_t key = seed_;
201 key = (key * 1099511628211ull) ^ (calls_ + 1);
202 ++calls_;
203 std::mt19937_64 rng(key);
204 std::uniform_int_distribution<std::size_t> pick(0, neigh.size() - 1);
205 return neigh[pick(rng)];
206 }
207};
208
209} // namespace
210
212{
213 Graph_Lattice<int> seed(make_grid_graph_adjacency(4, 4, /*periodic=*/true), 0);
214 using Rule = Majority_Functor;
216 eng.run(10);
217 for (std::size_t n = 0; n < eng.frame().size(); ++n)
218 EXPECT_EQ(eng.frame().at_node(n), 0);
219}
220
222{
223 Graph_Lattice<int> seed(make_grid_graph_adjacency(3, 3, /*periodic=*/true), 1);
224 using Rule = Majority_Functor;
226 eng.run(10);
227 for (std::size_t n = 0; n < eng.frame().size(); ++n)
228 EXPECT_EQ(eng.frame().at_node(n), 1);
229}
230
232{
233 // Single 1 in a sea of zeros on a ring graph: under strict
234 // majority everyone goes back to 0 in one step (the one alive
235 // node has neighbours mostly 0 too).
237 seed.set_node(3, 1);
239 seed, Majority_Functor{});
240 eng.run(1);
241 for (std::size_t n = 0; n < eng.frame().size(); ++n)
242 EXPECT_EQ(eng.frame().at_node(n), 0);
243}
244
246{
247 // A "bowtie": three triangles glued at a common hub. The graph
248 // is connected and non-bipartite (every triangle is a 3-cycle),
249 // so the synchronous voter model cannot get trapped in a parity
250 // oscillation. With a fixed seed the chain reaches consensus
251 // within a few thousand steps.
253 auto edge = [&](std::size_t a, std::size_t b)
254 {
255 adj[a].append(b);
256 adj[b].append(a);
257 };
258 edge(0, 1); edge(0, 2); edge(1, 2);
259 edge(0, 3); edge(0, 4); edge(3, 4);
260 edge(0, 5); edge(0, 6); edge(5, 6);
261
262 Graph_Lattice<int> seed(adj, 0);
263 seed.set_node(1, 1); seed.set_node(2, 1);
264 seed.set_node(5, 1); seed.set_node(6, 1);
265
266 Voter_Rule_Driver driver(/*seed=*/2026u);
268 seed, driver);
269 eng.run(10000);
270
271 const int v0 = eng.frame().at_node(0);
272 for (std::size_t n = 1; n < eng.frame().size(); ++n)
273 EXPECT_EQ(eng.frame().at_node(n), v0)
274 << "voter model did not reach consensus on the bowtie graph";
275}
276
278{
280 seed.set_node(0, 1);
282 seed, Majority_Functor{});
283
284 std::size_t pre = 0;
285 std::size_t post = 0;
286 std::size_t last_pre = 0;
287 std::size_t last_post = 0;
288 eng.on_pre_step([&](std::size_t s, const Graph_Lattice<int> &) {
289 ++pre;
290 last_pre = s;
291 });
292 eng.on_post_step([&](std::size_t s, const Graph_Lattice<int> &) {
293 ++post;
294 last_post = s;
295 });
296
297 eng.run(4);
298 EXPECT_EQ(pre, 4u);
299 EXPECT_EQ(post, 4u);
300 EXPECT_EQ(last_pre, 3u);
301 EXPECT_EQ(last_post, 4u);
302 EXPECT_EQ(eng.steps_run(), 4u);
303}
304
306{
307 // A graph CA over a 4x4 toroidal grid graph + Outer_Totalistic_Rule
308 // with the GoL functor coincides with the rectangular-Lattice
309 // engine using a Von_Neumann<2,1> neighbourhood (4 neighbours,
310 // toroidal). This validates the graph engine against the existing
311 // rectangular pipeline.
312 using NodeState = int;
315
316 // Seed the same pattern in both engines.
317 std::vector<int> initial(16, 0);
318 initial[5] = 1;
319 initial[6] = 1;
320 initial[9] = 1;
321 initial[10] = 1; // a still-life block
322
323 GLat seed(make_grid_graph_adjacency(4, 4, /*periodic=*/true), 0);
324 for (std::size_t n = 0; n < seed.size(); ++n)
325 seed.set_node(n, initial[n]);
326
328 eng.run(5);
329
330 // Block is a still life: stable under any "B/S over 4 neighbours" rule
331 // that contains S2 in the survival set... GoL is B3/S23 so S2 counts.
332 // We just assert the four block cells stayed 1 and the rest stayed 0.
333 for (std::size_t n = 0; n < eng.frame().size(); ++n)
334 EXPECT_EQ(eng.frame().at_node(n), initial[n])
335 << "block at node " << n << " was not still under graph engine";
336}
size_t row
Definition ca-c-api.h:115
Common typedefs and tag types for the Cellular Automata module.
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:250
Graph lattice: one cell per node + precomputed adjacency.
ca_size_t size() const noexcept
Synchronous double-buffered engine for graph CAs.
void run(const std::size_t steps)
Run several synchronous steps.
void on_pre_step(F &&f)
Register a hook fired before every step().
Rule whose next state depends on (current, alive_count).
Definition tpl_ca_rule.H:99
#define TEST(name)
static mt19937 rng
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
std::span< const T > Neighbor_View
Read-only view over a contiguous range of neighbour values.
Definition ca-traits.H:90
Array< Array< std::size_t > > make_grid_graph_adjacency(std::size_t rows, std::size_t cols, bool periodic=false)
Build the adjacency of a 2D 4-neighbour grid graph.
Array< Array< std::size_t > > make_path_graph_adjacency(std::size_t n, bool cycle=false)
Build the adjacency of a path graph with n nodes.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
size_t size(Node *root) noexcept
Functor implementing Conway's Game of Life canonical rule (B3/S23).
ValueArg< size_t > seed
Definition testHash.C:53
C++20 concepts for the Cellular Automata module.
CA whose underlying topology is an arbitrary undirected graph.
Rule mechanisms for Aleph::CA.