Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
ca_checkpoint_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
48#include <atomic>
49#include <chrono>
50#include <cstdint>
51#include <filesystem>
52#include <fstream>
53#include <random>
54#include <sstream>
55#include <string>
56#include <vector>
57#if defined(_WIN32)
58# include <process.h>
59#else
60# include <unistd.h>
61#endif
62
63#include <gtest/gtest.h>
64
65#include <ca-checkpoint.H>
66#include <ca-rng.H>
68#include <ca-traits.H>
69#include <tpl_ca_engine.H>
70#include <tpl_ca_lattice.H>
71#include <tpl_ca_neighborhood.H>
72#include <tpl_ca_rule.H>
73#include <tpl_ca_storage.H>
75
76using namespace Aleph;
77using namespace Aleph::CA;
78
79namespace
80{
81
83
85long long process_id() noexcept
86{
87#if defined(_WIN32)
88 return static_cast<long long>(_getpid());
89#else
90 return static_cast<long long>(getpid());
91#endif
92}
93
94std::filesystem::path tmp_path(const std::string &name)
95{
96 // A steady_clock tick alone is unique *within* a process, but not
97 // across the several processes that actually run this suite (each
98 // TEST() is its own ctest process, and CI runs ctest with
99 // --parallel) -- two processes' calls landing in the same clock tick
100 // produce the identical name and race on the same file. Mixing in
101 // the process id and a per-process counter closes that gap
102 // regardless of clock resolution or call count.
103 static std::atomic<unsigned long long> counter{0};
104 const auto tick = std::chrono::steady_clock::now().time_since_epoch().count();
105 return std::filesystem::temp_directory_path()
106 / ("aleph_ca_chk_" + name + "_" + std::to_string(static_cast<long long>(tick)) +
107 "_" + std::to_string(process_id()) +
108 "_" + std::to_string(counter++));
109}
110
111Grid random_grid(ca_size_t side, std::uint32_t seed, double p_alive = 0.4)
112{
113 std::mt19937 rng(seed);
114 std::uniform_real_distribution<double> u(0.0, 1.0);
115 Grid g({side, side}, 0);
116 for (ca_size_t i = 0; i < side; ++i)
117 for (ca_size_t j = 0; j < side; ++j)
118 g.set({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)},
119 u(rng) < p_alive ? 1 : 0);
120 return g;
121}
122
123bool grids_equal(const Grid &a, const Grid &b)
124{
125 if (a.extents() != b.extents())
126 return false;
127 for (ca_size_t i = 0; i < a.size(0); ++i)
128 for (ca_size_t j = 0; j < a.size(1); ++j)
129 if (a.at({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)})
130 != b.at({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)}))
131 return false;
132 return true;
133}
134
135std::string read_file(const std::filesystem::path &p)
136{
137 std::ifstream in(p, std::ios::binary);
138 std::ostringstream ss;
139 ss << in.rdbuf();
140 return ss.str();
141}
142
143} // namespace
144
145// =====================================================================
146// Round-trip: save → load → save' is bit-identical
147// =====================================================================
148
150{
152
153 const auto dir = tmp_path("roundtrip");
154 std::filesystem::create_directories(dir);
155 const auto path1 = dir / "snap1.bin";
156 const auto path2 = dir / "snap2.bin";
157
159 eng1.run(7);
161
162 // Construct an engine of the same shape and load the snapshot.
163 Engine eng2(Grid({20, 20}, 0), make_game_of_life_rule(), Moore<2, 1>{});
165 EXPECT_EQ(token.header.step_count, 7u);
166
168
170 << "save → load → save must be bit-identical";
171
172 std::filesystem::remove_all(dir);
173}
174
175// =====================================================================
176// Resume reproducibility: run(N) + save + load + run(N) == run(2N)
177// =====================================================================
178
180{
182
183 const auto dir = tmp_path("resume");
184 std::filesystem::create_directories(dir);
185 const auto path = dir / "midpoint.bin";
186
187 // Reference: uninterrupted run for 2N steps.
189 ref.run(40);
190
191 // Resume run: N steps, save, fresh engine, load, N more steps.
193 resumed.run(20);
195
196 Engine restored(Grid({20, 20}, 0), make_game_of_life_rule(), Moore<2, 1>{});
197 const Resume_Token token = load_checkpoint_into(restored, path);
198 EXPECT_EQ(token.header.step_count, 20u);
199 EXPECT_EQ(restored.steps_run(), 20u);
200 restored.run(20);
201
202 EXPECT_EQ(restored.steps_run(), 40u);
203 EXPECT_TRUE(grids_equal(restored.frame(), ref.frame()))
204 << "restored run must match uninterrupted run frame-by-frame";
205
206 std::filesystem::remove_all(dir);
207}
208
209// =====================================================================
210// inspect_checkpoint surfaces the validated header
211// =====================================================================
212
214{
216
217 const auto dir = tmp_path("inspect");
218 std::filesystem::create_directories(dir);
219 const auto path = dir / "snap.bin";
220
222 eng.run(3);
223 save_checkpoint(eng, path);
224
226 EXPECT_EQ(h.format_version, 2u);
227 EXPECT_EQ(h.rank, 2u);
228 EXPECT_EQ(h.extents[0], 8u);
229 EXPECT_EQ(h.extents[1], 8u);
230 EXPECT_EQ(h.step_count, 3u);
231 EXPECT_EQ(h.cell_count, 64u);
232 EXPECT_EQ(h.has_rng, 0u); // Game_Of_Life_Rule is deterministic
233
234 std::filesystem::remove_all(dir);
235}
236
237// =====================================================================
238// Stochastic rule: master_seed is restored so the future trajectory
239// is identical to the uninterrupted run.
240// =====================================================================
241
243{
245
246 const auto dir = tmp_path("stoch");
247 std::filesystem::create_directories(dir);
248 const auto path = dir / "stoch.bin";
249
250 // Build a Schelling-style stochastic rule (carries a master seed).
251 auto make_rule = []
252 { return Schelling_Rule<>{0.5, 1.0, 1.0, /*master=*/0xCAFEBABE}; };
253
254 // Reference run for 30 steps.
255 Grid initial({16, 16}, 0);
256 for (ca_size_t i = 0; i < 16; ++i)
257 for (ca_size_t j = 0; j < 16; ++j)
258 initial.set({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)},
259 static_cast<int>((i + j) % 3));
261 ref.run(30);
262
263 // Resume run: 15 steps, save, new engine, load, 15 more.
264 Engine partial(initial, make_rule(), Moore<2, 1>{});
265 partial.run(15);
266 save_checkpoint(partial, path);
267
268 // Build resumed engine with a *wrong* master seed to prove the
269 // restore actually overwrites it.
270 Engine resumed(Grid({16, 16}, 0), Schelling_Rule<>{0.5, 1.0, 1.0, 0xDEADBEEF},
271 Moore<2, 1>{});
273 EXPECT_EQ(resumed.rule().master_seed(), 0xCAFEBABEu)
274 << "master seed must be restored from the snapshot";
275 resumed.run(15);
276
277 EXPECT_TRUE(grids_equal(resumed.frame(), ref.frame()))
278 << "stochastic resume must be bit-identical to uninterrupted run";
279
280 std::filesystem::remove_all(dir);
281}
282
283// =====================================================================
284// load_checkpoint_into rejects type-hash mismatches
285// =====================================================================
286
288{
294
295 const auto dir = tmp_path("badtype");
296 std::filesystem::create_directories(dir);
297 const auto path = dir / "snap.bin";
298
300 save_checkpoint(eng, path);
301
303 const auto offsets = std::array<Offset_Vec<1>, 2>{{{-1}, {1}}};
306 EXPECT_THROW(load_checkpoint_into(wrong, path), std::runtime_error);
307
308 std::filesystem::remove_all(dir);
309}
310
311// =====================================================================
312// Periodic_Checkpoint_Observer
313// =====================================================================
314
316{
318
319 const auto dir = tmp_path("periodic");
320 std::filesystem::create_directories(dir);
321
324 (dir / "snap_{step}.bin").string(),
325 /*zero_pad=*/4);
326
327 eng.on_post_step([&](std::size_t s, const Grid &f) { obs.on_step_end(s, f); });
328 eng.run(10);
329
330 // Steps 3, 6, 9 must have produced files.
331 EXPECT_TRUE(std::filesystem::exists(dir / "snap_0003.bin"));
332 EXPECT_TRUE(std::filesystem::exists(dir / "snap_0006.bin"));
333 EXPECT_TRUE(std::filesystem::exists(dir / "snap_0009.bin"));
334 // Steps 1, 2, 4, 5, 7, 8, 10 must not.
335 EXPECT_FALSE(std::filesystem::exists(dir / "snap_0001.bin"));
336 EXPECT_FALSE(std::filesystem::exists(dir / "snap_0002.bin"));
337 EXPECT_FALSE(std::filesystem::exists(dir / "snap_0010.bin"));
338
339 // Headers of the three files agree on extents/version.
340 const auto h3 = inspect_checkpoint(dir / "snap_0003.bin");
341 const auto h6 = inspect_checkpoint(dir / "snap_0006.bin");
342 EXPECT_EQ(h3.step_count, 3u);
343 EXPECT_EQ(h6.step_count, 6u);
344 EXPECT_EQ(h3.extents, h6.extents);
345
346 std::filesystem::remove_all(dir);
347}
348
349// =====================================================================
350// Tile_Cache: LRU eviction + disk round-trip
351// =====================================================================
352
354{
355 const auto dir = tmp_path("tile");
356 std::filesystem::remove_all(dir);
357
359 // 16×16 grid → 4×4 = 16 tiles total. Capacity 4 forces evictions.
360 Tiles tiles({16, 16}, dir, /*capacity=*/4);
361
362 // Stamp a checkerboard pattern across the whole grid: forces every
363 // tile to be touched at least once.
364 for (ca_size_t i = 0; i < 16; ++i)
365 for (ca_size_t j = 0; j < 16; ++j)
366 tiles.set(i, j, static_cast<int>((i + j) & 1));
367
368 EXPECT_LE(tiles.resident(), 4u) << "cache must respect capacity";
369 EXPECT_GE(tiles.stats().evictions, 12u)
370 << "16 distinct tiles touched, capacity 4 → >= 12 evictions";
371
372 // Persist any leftover dirty tiles, then re-read every cell. With
373 // the working set far exceeding capacity we will keep paging in
374 // and out — but every read must return the original checkerboard
375 // value.
376 tiles.flush();
377 for (ca_size_t i = 0; i < 16; ++i)
378 for (ca_size_t j = 0; j < 16; ++j)
379 EXPECT_EQ(tiles.at(i, j), static_cast<int>((i + j) & 1))
380 << "checkerboard mismatch at (" << i << "," << j << ")";
381
382 // On-disk file count: every distinct tile that was touched should
383 // have a corresponding file.
384 std::size_t file_count = 0;
385 for (auto &p : std::filesystem::directory_iterator(dir))
386 if (p.path().extension() == ".bin")
387 ++file_count;
388 EXPECT_EQ(file_count, 16u);
389
390 std::filesystem::remove_all(dir);
391}
392
394{
396 const auto dir = tmp_path("tile_bad");
397 std::filesystem::create_directories(dir);
398 EXPECT_THROW((Tiles({15, 16}, dir, 4)), std::domain_error);
399 EXPECT_THROW((Tiles({16, 16}, dir, 0)), std::domain_error);
400 std::filesystem::remove_all(dir);
401}
long double h
Definition btreepic.C:154
size_t row
Definition ca-c-api.h:115
Binary checkpoint format for Aleph::CA engines (Phase 15 + Phase 17 crash-safe / compress / async wri...
Reproducible random-number support for stochastic CA rules (Phase 8).
Phase 15 tile-based out-of-core storage for Aleph::CA.
Common typedefs and tag types for the Cellular Automata module.
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.
Observer that auto-saves the engine state every every steps to a templated path.
Local approximation of the Schelling segregation model.
Synchronous double-buffered engine.
Out-of-core 2D tile cache with LRU eviction.
void set(ca_size_t r, ca_size_t c, const State &v)
Write the cell at (r, c) and mark its tile dirty.
#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
constexpr Game_Of_Life_Rule make_game_of_life_rule() noexcept
Build the canonical Game of Life rule.
Checkpoint_Header inspect_checkpoint(const std::filesystem::path &path)
Read just the header from a checkpoint file.
std::size_t ca_size_t
Unsigned size component used for extents and counts.
Definition ca-traits.H:63
Resume_Token load_checkpoint_into(Engine &engine, const std::filesystem::path &path)
Restore an engine's state in-place from a checkpoint file.
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,...
void save_checkpoint(Engine &engine, const std::filesystem::path &path, const Checkpoint_Options &options={})
Write a complete engine snapshot to disk atomically.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
Public on-disk header surfaced by inspect_checkpoint.
Resume handle returned by load_checkpoint_into.
The lattice wraps around on every axis.
Definition ca-traits.H:124
static long counter
Definition test-splice.C:40
ValueArg< size_t > seed
Definition testHash.C:53
Synchronous double-buffered engine for cellular automata.
Cellular automata lattice with pluggable boundary policies.
Neighborhoods catalogue for Aleph::CA.
Rule mechanisms for Aleph::CA.
Reproducible stochastic CA rules (Phase 8).
Dense, contiguous storage for cellular automata cells (1D/2D/3D).