Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
drossel_schwabl.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
18#include <array>
19#include <cstddef>
20#include <cstdint>
21#include <filesystem>
22#include <fstream>
23#include <iomanip>
24#include <iostream>
25#include <map>
26#include <stdexcept>
27#include <string>
28#include <vector>
29
32
33using namespace Aleph::CA;
34using namespace Aleph::CA::Reproductions;
35
36#ifndef ALEPH_REPRODUCTIONS_SOURCE_DIR
37# define ALEPH_REPRODUCTIONS_SOURCE_DIR "reproductions"
38#endif
39
40namespace
41{
42
43using State = std::uint8_t;
44using Neighbours = std::array<std::size_t, 8>;
45
46constexpr State empty = static_cast<State>(Forest_Cell::EMPTY);
47constexpr State tree = static_cast<State>(Forest_Cell::TREE);
48constexpr State burning = static_cast<State>(Forest_Cell::BURNING);
49
59class Fast_Forest_Fire
60{
61public:
68 Fast_Forest_Fire(const ca_size_t side,
69 const double p_growth,
70 const double p_lightning,
71 const std::uint64_t master_seed)
72 : side_(side), p_growth_(p_growth), p_lightning_(p_lightning),
73 master_seed_(master_seed), current_(side * side, empty),
74 next_(side * side, empty), neighbours_(side * side),
75 coord_hashes_(side * side)
76 {
77 for (ca_size_t row = 0; row < side_; ++row)
78 for (ca_size_t column = 0; column < side_; ++column)
79 {
80 const std::size_t index = row * side_ + column;
81 std::size_t k = 0;
82 for (ca_index_t dr = -1; dr <= 1; ++dr)
83 for (ca_index_t dc = -1; dc <= 1; ++dc)
84 {
85 if (dr == 0 and dc == 0)
86 continue;
87 const ca_size_t r = static_cast<ca_size_t>(
88 (static_cast<ca_index_t>(row) + static_cast<ca_index_t>(side_) + dr)
89 % static_cast<ca_index_t>(side_));
90 const ca_size_t c = static_cast<ca_size_t>(
91 (static_cast<ca_index_t>(column) + static_cast<ca_index_t>(side_) + dc)
92 % static_cast<ca_index_t>(side_));
93 neighbours_[index][k++] = r * side_ + c;
94 }
95 coord_hashes_[index] = cell_key_from_coord<2>(
96 {static_cast<ca_index_t>(row), static_cast<ca_index_t>(column)});
97 }
98 }
99
101 void step()
102 {
103 const std::uint64_t step_hash
104 = splitmix64(static_cast<std::uint64_t>(step_) + 0xa5a5a5a5a5a5a5a5ull);
105 for (std::size_t index = 0; index < current_.size(); ++index)
106 {
107 if (current_[index] == burning)
108 {
109 next_[index] = empty;
110 continue;
111 }
112 if (current_[index] == tree)
113 {
114 next_[index]
115 = has_burning_neighbour(current_, neighbours_[index])
116 or draw(index, step_hash) < p_lightning_ ? burning : tree;
117 continue;
118 }
119 next_[index] = draw(index, step_hash) < p_growth_ ? tree : empty;
120 }
121 current_.swap(next_);
122 ++step_;
123 }
124
128 [[nodiscard]] const std::vector<State> &frame() const noexcept
129 {
130 return current_;
131 }
132
136 [[nodiscard]] const std::vector<Neighbours> &neighbours() const noexcept
137 {
138 return neighbours_;
139 }
140
141private:
142 ca_size_t side_;
143 double p_growth_;
144 double p_lightning_;
145 std::uint64_t master_seed_;
146 std::size_t step_ = 0;
147 std::vector<State> current_;
148 std::vector<State> next_;
149 std::vector<Neighbours> neighbours_;
150 std::vector<std::uint64_t> coord_hashes_;
151
157 [[nodiscard]] static bool has_burning_neighbour(const std::vector<State> &frame,
158 const Neighbours &neighbours)
159 {
160 for (const std::size_t index : neighbours)
161 if (frame[index] == burning)
162 return true;
163 return false;
164 }
165
171 [[nodiscard]] double draw(const std::size_t index,
172 const std::uint64_t step_hash) const noexcept
173 {
174 const std::uint64_t bits = splitmix64(master_seed_ ^ step_hash ^ coord_hashes_[index]);
175 return static_cast<double>(bits >> 11) * (1.0 / 9007199254740992.0);
176 }
177};
178
184bool has_burning_neighbour(const std::vector<State> &frame,
185 const Neighbours &neighbours)
186{
187 for (const std::size_t index : neighbours)
188 if (frame[index] == burning)
189 return true;
190 return false;
191}
192
199void append_ignited_cluster_sizes(const std::vector<State> &before,
200 const std::vector<State> &after,
201 const std::vector<Neighbours> &neighbours,
202 std::vector<std::size_t> &sizes)
203{
204 std::vector<unsigned char> seen(before.size(), 0);
205 std::vector<std::size_t> queue;
206 for (std::size_t origin = 0; origin < before.size(); ++origin)
207 {
208 if (seen[origin] != 0 or before[origin] != tree
209 or after[origin] != burning
210 or has_burning_neighbour(before, neighbours[origin]))
211 continue;
212
213 queue.clear();
214 queue.push_back(origin);
215 seen[origin] = 1;
216 std::size_t count = 0;
217 for (std::size_t head = 0; head < queue.size(); ++head)
218 {
219 const std::size_t index = queue[head];
220 ++count;
221 for (const std::size_t next : neighbours[index])
222 if (seen[next] == 0 and before[next] == tree)
223 {
224 seen[next] = 1;
225 queue.push_back(next);
226 }
227 }
228 sizes.push_back(count);
229 }
230}
231
236void write_cluster_histogram_csv(const std::filesystem::path &path,
237 const std::vector<std::size_t> &sizes)
238{
239 if (const auto parent = path.parent_path(); not parent.empty())
240 std::filesystem::create_directories(parent);
241 std::map<std::size_t, std::size_t> histogram;
242 for (const std::size_t size : sizes)
243 ++histogram[size];
244 std::ofstream out(path);
245 if (not out)
246 throw std::runtime_error("Cannot write Drossel-Schwabl histogram");
247 out << "size,ignitions,cluster_number_weight\n";
248 for (const auto &[size, ignitions] : histogram)
249 out << size << ',' << ignitions << ','
250 << static_cast<double>(ignitions) / static_cast<double>(size) << '\n';
251}
252
253} // namespace
254
255int main(int argc, char **argv)
256{
257 const bool quick = argc == 2 and std::string(argv[1]) == "--quick";
258 if (argc > 1 and not quick)
259 {
260 std::cerr << "Usage: drossel_schwabl [--quick]\n";
261 return 1;
262 }
263 const ca_size_t side = quick ? 64 : 256;
264 const std::size_t warmup_steps = quick ? 500 : 5000;
265 const std::size_t measured_steps = quick ? 2000 : 50000;
266 constexpr double p_growth = 0.005;
267 constexpr double p_lightning = 0.0001;
268 constexpr std::uint64_t seed = 0xD20551992ull;
269
270 Fast_Forest_Fire model(side, p_growth, p_lightning, seed);
271 for (std::size_t step = 0; step < warmup_steps; ++step)
272 model.step();
273
274 std::vector<std::size_t> sizes;
275 for (std::size_t step = 0; step < measured_steps; ++step)
276 {
277 const std::vector<State> before = model.frame();
278 model.step();
279 append_ignited_cluster_sizes(before, model.frame(), model.neighbours(), sizes);
280 if (not quick and (step + 1) % 5000 == 0)
281 std::cout << "Drossel-Schwabl progress: " << step + 1 << '/'
282 << measured_steps << " measured steps\n";
283 }
284
285 // Lightning samples a cluster with probability proportional to its size.
286 // Weight by 1/s to recover the cluster-number distribution n(s). The
287 // 4..1024 scaling window excludes the finite-grid cutoff above 1024.
288 const Linear_Fit fit
289 = fit_log_log_histogram(sizes, 4, 1024, quick ? 12 : 18, 1.0);
290 const std::filesystem::path root = ALEPH_REPRODUCTIONS_SOURCE_DIR;
291 write_cluster_histogram_csv(root / "results" / "drossel_schwabl_fire_sizes.csv", sizes);
292 std::ofstream summary(root / "results" / "drossel_schwabl_summary.csv");
293 if (not summary)
294 {
295 std::cerr << "Cannot write Drossel-Schwabl summary\n";
296 return 1;
297 }
298 summary << "side,warmup_steps,measured_steps,p_growth,p_lightning,fires,slope,r_squared,fit_points\n"
299 << side << ',' << warmup_steps << ',' << measured_steps << ','
300 << p_growth << ',' << p_lightning << ',' << sizes.size() << ','
301 << std::setprecision(12) << fit.slope << ',' << fit.r_squared << ','
302 << fit.points << '\n';
303
304 std::cout << "Drossel-Schwabl: slope=" << std::fixed << std::setprecision(4)
305 << fit.slope << " r2=" << fit.r_squared << " fires=" << sizes.size() << '\n';
306 if (quick)
307 return 0;
308 if (fit.slope < -2.2 or fit.slope > -1.8)
309 {
310 std::cerr << "Drossel-Schwabl fire-size slope outside [-2.2, -1.8]\n";
311 return 2;
312 }
313 return 0;
314}
int main()
size_t size_t int32_t * out
Definition ca-c-api.h:120
size_t row
Definition ca-c-api.h:115
Internal helpers shared by the cellular-automata reproductions.
#define ALEPH_REPRODUCTIONS_SOURCE_DIR
__gmp_expr< T, __gmp_binary_expr< __gmp_expr< T, U >, unsigned long int, __gmp_root_function > > root(const __gmp_expr< T, U > &expr, unsigned long int l)
Definition gmpfrxx.h:4071
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
Linear_Fit fit_log_log_histogram(const std::vector< std::size_t > &samples, const std::size_t min_value, const std::size_t max_value, const std::size_t bins, const double sample_weight_power=0.0)
Fit a power-law exponent from logarithmically binned samples.
std::ptrdiff_t ca_index_t
Signed coordinate component used by lattices and neighborhoods.
Definition ca-traits.H:60
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
auto histogram(const Container &data, size_t num_bins) -> std::vector< std::pair< std::decay_t< decltype(*std::begin(data))>, size_t > >
Compute a histogram of the data.
Definition stat_utils.H:717
size_t size(Node *root) noexcept
and
Check uniqueness with explicit hash + equality functors.
void next()
Advance all underlying iterators (bounds-checked).
Definition ah-zip.H:171
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
Least-squares result for a log-log histogram.
std::size_t points
populated bins included in the fit.
double r_squared
coefficient of determination.
ValueArg< size_t > seed
Definition testHash.C:53
static int * k
gsl_rng * r
Reproducible stochastic CA rules (Phase 8).