numerics
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1/// @file main.cpp
2/// @brief 2D Ising model -- classic mode + nucleation with umbrella sampling
3///
4/// Absorbs ~/projects/IsingNucleation into the numerics project.
5/// Exact match to IsingNucleation parameters:
6/// n=300, J=1, beta=0.58 (T~=1.724), F=0.1 (nucleation), F=0 (classic)
7/// Random spin selection per sweep (n^2 picks with replacement)
8/// DeltaE = 2J*s*(Sigma nbrs) - 2F*s (same sign convention as mc_step_boltz.c)
9///
10/// Optimizations vs. the original C implementation:
11/// 1. Boltzmann lookup table -- DeltaE discrete -> precomputed exp() table, no
12/// runtime exp() in the sweep hot path
13/// 2. Precomputed neighbor arrays -- up/dn/lt/rt[NN] avoid repeated modulo
14/// arithmetic per spin selection
15/// 3. Array-based BFS cluster detection -- iterative, pre-allocated flat
16/// queue; no recursion stack risk,
17/// no heap allocations per call
18/// 4. SparseMatrix adj retained for energy_per_spin() (SIMD sparse_matvec)
19///
20/// Two modes (TAB to switch):
21/// Classic -- free Metropolis; magnetisation and E/N^2 graphs
22/// Nucleation -- umbrella sampling; tracks largest spin-down cluster (nucleus)
23
24#include "numerics.hpp"
25#include "stats/stats.hpp" // num::RunningStats, num::Histogram, num::autocorr_time
26#include "analysis/roots.hpp" // num::newton (for mean-field solver)
27#include "stochastic/markov.hpp" // num::markov::metropolis_sweep_prob, umbrella_sweep_prob
28#include "spatial/pbc_lattice.hpp" // num::PBCLattice2D
29#include "spatial/connected_components.hpp" // num::ClusterResult, connected_components
30#include <raylib.h>
31#include <cmath>
32#include <random>
33#include <vector>
34#include <algorithm>
35#include <cstring>
36
37using namespace num;
38
39static constexpr int N = 300; // matches IsingNucleation/main.c
40static constexpr int NN = N * N;
41static constexpr real J = 1.0;
42static const real TC = 2.26918531421; // Onsager exact
43
44static constexpr int WIN_W = 1080;
45static constexpr int WIN_H = 720;
46static constexpr int SIM_SZ = 680;
47static constexpr int PAN_X = SIM_SZ + 20;
48static constexpr int PAN_W = WIN_W - PAN_X - 10;
49
50// BFS cluster detection uses num::ClusterResult + num::connected_components.
51
52struct RingBuffer {
53 std::vector<float> data;
54 int head = 0, count = 0;
55
56 explicit RingBuffer(int cap) : data(cap, 0.f) {}
57
58 void push(float v) {
59 data[head] = v;
60 head = (head + 1) % cap();
61 count = std::min(count + 1, cap());
62 }
63
64 float get(int i) const { return data[(head - count + i + cap() * 2) % cap()]; }
65 float back() const { return count ? get(count - 1) : 0.f; }
66 int size() const { return count; }
67 int cap() const { return static_cast<int>(data.size()); }
68};
69
71 Vector spins; // NN spins, values +/-1.0
72 Vector ones; // NN ones (for dot-product magnetisation)
73 Vector nbr_buf; // scratch buffer for energy_per_spin (sparse_matvec)
74 SparseMatrix adj; // 4-neighbor periodic CSR (used only for energy)
75
76 // Precomputed PBC neighbor indices -- avoids modulo in hot paths
78
79 // Boltzmann acceptance table.
80 // DeltaE = 2J*s*nbr_sum - 2F*s, sin{+/-1}, nbr_sumin{-4,-2,0,2,4}
81 // boltz[si][ni] = min(1, exp(-beta*DeltaE))
82 // si: 0 -> s=-1, 1 -> s=+1
83 // ni: (nbr_sum/2 + 2) in {0..4}
84 real boltz[2][5]{};
85
86 std::vector<real> save_buf; // pre-allocated for sweep_umbrella
87
88 std::mt19937 rng;
89 std::uniform_real_distribution<real> dist{0.0, 1.0}; // used in random_init()
90
91 real beta = 1.0 / (1.0 / 0.58); // beta=0.58, T~=1.724 (matches IsingNucleation/main.c)
92 real F = 0.0;
93
95 : spins(NN, 1.0)
96 , ones(NN, 1.0)
97 , nbr_buf(NN, 0.0)
98 , adj(build_adj())
99 , nbr_(N)
100 , save_buf(NN)
101 , rng(std::random_device{}()) // non-deterministic seed, like srand(time(0))
102 {
104 random_init();
105 }
106
107 void set_temperature(real T) { beta = (T > 1e-6) ? 1.0/T : 1e6; rebuild_boltz(); }
108 void set_field(real Fnew) { F = Fnew; rebuild_boltz(); }
109 real temperature() const { return (beta > 1e-6) ? 1.0/beta : 1e6; }
110
112 for (int si = 0; si < 2; ++si) {
113 real s = (si == 0) ? -1.0 : 1.0;
114 for (int ni = 0; ni < 5; ++ni) {
115 real ns = -4.0 + 2.0 * ni; // nbr_sum in {-4,-2,0,2,4}
116 real dE = 2.0 * J * s * ns - 2.0 * F * s;
118 }
119 }
120 }
121
122 void random_init() {
123 for (int i = 0; i < NN; ++i)
124 spins[i] = (dist(rng) < 0.5) ? 1.0 : -1.0;
125 }
126
127 void all_up() { for (int i = 0; i < NN; ++i) spins[i] = 1.0; }
128
129 // Square nucleus of spin-down spins at centre, area ~= r (as in IsingNucleation).
130 void generate_nucleus(real r = 100.0) {
131 all_up();
132 int half = static_cast<int>(std::sqrt(r) * 0.5);
133 int cx = N / 2;
134 for (int row = cx - half; row < cx + half; ++row)
135 for (int col = cx - half; col < cx + half; ++col)
136 spins[row * N + col] = -1.0;
137 }
138
139 // Standard Metropolis sweep: n^2 random spin picks (with replacement).
140 // Matches mc_step_boltz() in IsingNucleation/src/monte_carlo.c exactly.
141 // Delegates to num::markov::metropolis_sweep_prob -- Boltzmann table
142 // supplies the acceptance probability so no exp() runs at runtime.
143 void sweep() {
145 static_cast<idx>(NN),
146 [&](idx i) {
147 real s = spins[i];
148 real ns = spins[nbr_.up[i]] + spins[nbr_.dn[i]]
149 + spins[nbr_.lt[i]] + spins[nbr_.rt[i]];
150 int si = (s < 0.0) ? 0 : 1;
151 int ni = static_cast<int>(ns * 0.5 + 2.0);
152 return boltz[si][ni];
153 },
154 [&](idx i) { spins[i] = -spins[i]; },
155 rng);
156 }
157
158 // Umbrella sampling sweep: revert full state if nucleus size leaves [lo, hi].
159 // Delegates to num::markov::umbrella_sweep_prob; BFS cluster detection is
160 // passed as the measure_order callable so det.id is filled for rendering.
161 int sweep_umbrella(int lo, int hi, num::ClusterResult& det) {
163 static_cast<idx>(NN),
164 [&](idx i) {
165 real s = spins[i];
166 real ns = spins[nbr_.up[i]] + spins[nbr_.dn[i]]
167 + spins[nbr_.lt[i]] + spins[nbr_.rt[i]];
168 int si = (s < 0.0) ? 0 : 1;
169 int ni = static_cast<int>(ns * 0.5 + 2.0);
170 return boltz[si][ni];
171 },
172 [&](idx i) { spins[i] = -spins[i]; },
173 [&]() { for (int i = 0; i < NN; ++i) save_buf[i] = spins[i]; },
174 [&]() { for (int i = 0; i < NN; ++i) spins[i] = save_buf[i]; },
175 [&]() -> idx {
177 [&](int i) { return spins[i] < 0.0; },
178 [&](int i, auto&& visit) {
179 visit(nbr_.up[i]); visit(nbr_.dn[i]);
180 visit(nbr_.lt[i]); visit(nbr_.rt[i]);
181 });
182 return static_cast<idx>(det.largest_size);
183 },
184 num::markov::UmbrellaWindow{static_cast<idx>(lo), static_cast<idx>(hi)},
185 rng);
186 return static_cast<int>(result.order_param);
187 }
188
189 // |<m>| = |Sigma s_i| / N^2 via SIMD dot product
191 return std::abs(dot(spins, ones, Backend::seq)) / static_cast<real>(NN);
192 }
193
194 // <E>/N^2 using SparseMatrix adj and SIMD sparse_matvec + dot
197 real coup = -J * dot(spins, nbr_buf, Backend::seq) / (4.0 * NN);
198 real fld = -F * dot(spins, ones, Backend::seq) / static_cast<real>(NN);
199 return coup + fld;
200 }
201
202 // Mean-field order parameter: solve m = tanh(beta*4J*m + beta*F) via num::newton().
203 static real mean_field_m(real beta_val, real F_val) {
204 auto f = [=](real m) {
205 real arg = 4.0 * beta_val * J * m + beta_val * F_val;
206 return std::tanh(arg) - m;
207 };
208 auto df = [=](real m) {
209 real arg = 4.0 * beta_val * J * m + beta_val * F_val;
210 real th = std::tanh(arg);
211 return 4.0 * beta_val * J * (1.0 - th * th) - 1.0;
212 };
213 auto res = newton(f, df, 0.9);
214 return std::max(-1.0, std::min(1.0, res.root));
215 }
216
217private:
218 static SparseMatrix build_adj() {
219 std::vector<idx> rows_t, cols_t;
220 std::vector<real> vals_t;
221 rows_t.reserve(4 * NN);
222 cols_t.reserve(4 * NN);
223 vals_t.reserve(4 * NN);
224 for (int row = 0; row < N; ++row) {
225 for (int col = 0; col < N; ++col) {
226 int i = row * N + col;
227 int nbrs[4] = {
228 ((row-1+N)%N)*N + col, ((row+1)%N)*N + col,
229 row*N + (col-1+N)%N, row*N + (col+1)%N
230 };
231 for (int nb : nbrs) {
232 rows_t.push_back(static_cast<idx>(i));
233 cols_t.push_back(static_cast<idx>(nb));
234 vals_t.push_back(1.0);
235 }
236 }
237 }
238 return SparseMatrix::from_triplets(NN, NN, rows_t, cols_t, vals_t);
239 }
240};
241
242static void draw_graph(const RingBuffer& buf, int x, int y, int w, int h,
243 float y_lo, float y_hi, Color col, const char* label,
244 float ref_val = -999.f) {
245 DrawRectangle(x, y, w, h, {20, 20, 20, 255});
246 DrawRectangleLines(x, y, w, h, GRAY);
247 DrawText(label, x + 4, y + 4, 11, LIGHTGRAY);
248 if (buf.size() < 2) return;
249
250 float range = y_hi - y_lo;
251 if (range < 1e-6f) range = 1.f;
252
253 if (ref_val > -998.f) {
254 int ry = y + h - static_cast<int>((ref_val - y_lo) / range * h);
255 DrawLine(x, std::clamp(ry,y,y+h), x+w, std::clamp(ry,y,y+h), {255,220,0,160});
256 }
257
258 for (int i = 1; i < buf.size(); ++i) {
259 float x0f = static_cast<float>(i-1) / (buf.cap()-1) * w;
260 float x1f = static_cast<float>(i) / (buf.cap()-1) * w;
261 int py0 = y+h - static_cast<int>((buf.get(i-1)-y_lo)/range*h);
262 int py1 = y+h - static_cast<int>((buf.get(i) -y_lo)/range*h);
263 DrawLine(x+static_cast<int>(x0f), std::clamp(py0,y,y+h),
264 x+static_cast<int>(x1f), std::clamp(py1,y,y+h), col);
265 }
266}
267
268static real draw_slider(int x, int y, int w, real val, real lo, real hi,
269 float ref_frac, const char* fmt, Color knob_col) {
270 DrawRectangle(x, y-3, w, 6, DARKGRAY);
271 if (ref_frac >= 0.f) {
272 int rx = x + static_cast<int>(ref_frac * w);
273 DrawLine(rx, y-9, rx, y+9, YELLOW);
274 }
275 int kx = x + static_cast<int>(((val-lo)/(hi-lo)) * w);
276 DrawCircle(kx, y, 8, knob_col);
277 DrawText(TextFormat(fmt, static_cast<float>(val)), x, y-18, 12, LIGHTGRAY);
278
279 if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) {
280 Vector2 mp = GetMousePosition();
281 if (mp.x >= x && mp.x <= x+w && mp.y >= y-12 && mp.y <= y+12) {
282 float frac = (mp.x - x) / static_cast<float>(w);
283 val = lo + frac*(hi-lo);
284 val = std::max(lo, std::min(hi, val));
285 }
286 }
287 return val;
288}
289
290int main() {
291 InitWindow(WIN_W, WIN_H, "2D Ising -- Classic & Nucleation [TAB to switch]");
292 SetTargetFPS(60);
293
294 IsingLattice lat;
296 det.id.assign(NN, -2); // default: all excluded (safe before first sweep)
297 real T = 1.0 / 0.58; // beta=0.58 from IsingNucleation/main.c
298 lat.set_temperature(T);
299
300 std::vector<unsigned char> pixels(NN * 4, 255);
301 Image img = {pixels.data(), N, N, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8};
302 Texture2D tex = LoadTextureFromImage(img);
303
304 RingBuffer mag_hist(300);
305 RingBuffer eng_hist(300);
306 RingBuffer nuc_hist(300);
307
308 int sweeps_per_frame = 5;
309 bool paused = false;
310 int frame = 0;
311
312 enum class Mode { Classic, Nucleation } mode = Mode::Classic;
313
314 // Nucleation state -- matches run_sim_nucleation defaults:
315 // N_window=15, window_size=30 -> first window [0, 30], centre=15
316 int nuc_target = 15; // umbrella window centre (spins)
317 int nuc_half = 15; // half-width = window_size/2 = 30/2
318 int nuc_size = 0;
319
320 // Online statistics for nucleation observables (num::RunningStats, num::Histogram)
321 RunningStats nuc_stats;
322 RunningStats mag_stats;
323 Histogram nuc_histo(50, 0.0, 500.0); // nucleus size distribution
324
325 while (!WindowShouldClose()) {
326 if (IsKeyPressed(KEY_SPACE)) paused = !paused;
327 if (IsKeyPressed(KEY_R)) lat.random_init();
328 if (IsKeyPressed(KEY_H)) lat.all_up();
329 if (IsKeyPressed(KEY_N)) lat.generate_nucleus(static_cast<real>(nuc_target));
330 if (IsKeyPressed(KEY_UP) && sweeps_per_frame < 20) sweeps_per_frame++;
331 if (IsKeyPressed(KEY_DOWN) && sweeps_per_frame > 1) sweeps_per_frame--;
332 if (IsKeyPressed(KEY_TAB)) {
333 if (mode == Mode::Classic) {
334 mode = Mode::Nucleation;
335 nuc_target = 15; nuc_half = 15; // reset to run_sim_nucleation defaults
336 lat.set_field(0.1); // F=0.1 -- nucleation field
337 lat.generate_nucleus(static_cast<real>(nuc_target));
338 nuc_hist = RingBuffer(300);
339 mag_hist = RingBuffer(300);
340 nuc_stats.reset(); mag_stats.reset(); nuc_histo.reset();
341 } else {
342 mode = Mode::Classic;
343 lat.set_field(0.0);
344 lat.random_init();
345 mag_hist = RingBuffer(300);
346 eng_hist = RingBuffer(300);
347 }
348 }
349
350 if (!paused) {
351 if (mode == Mode::Classic) {
352 for (int s = 0; s < sweeps_per_frame; ++s) lat.sweep();
353 ++frame;
354 if (frame % 3 == 0) {
355 mag_hist.push(static_cast<float>(lat.magnetization()));
356 eng_hist.push(static_cast<float>(lat.energy_per_spin()));
357 }
358 } else {
359 int lo = nuc_target - nuc_half;
360 int hi = nuc_target + nuc_half;
361 for (int s = 0; s < sweeps_per_frame; ++s)
362 nuc_size = lat.sweep_umbrella(lo, hi, det);
363 ++frame;
364 if (frame % 3 == 0) {
365 nuc_hist.push(static_cast<float>(nuc_size));
366 mag_hist.push(static_cast<float>(lat.magnetization()));
367 nuc_stats.update(static_cast<real>(nuc_size));
368 mag_stats.update(lat.magnetization());
369 nuc_histo.fill(static_cast<real>(nuc_size));
370 }
371 }
372 }
373
374 if (mode == Mode::Classic) {
375 for (int i = 0; i < NN; ++i) {
376 unsigned char c = (lat.spins[i] > 0.0) ? 240 : 15;
377 pixels[4*i+0] = pixels[4*i+1] = pixels[4*i+2] = c;
378 pixels[4*i+3] = 255;
379 }
380 } else {
381 // White = spin+1, Red = nucleus (largest cluster), Dark = other spin-1
382 for (int i = 0; i < NN; ++i) {
383 if (lat.spins[i] > 0.0) {
384 pixels[4*i+0] = 240; pixels[4*i+1] = 240; pixels[4*i+2] = 240;
385 } else if (det.id[i] == det.largest_id) {
386 pixels[4*i+0] = 220; pixels[4*i+1] = 50; pixels[4*i+2] = 50;
387 } else {
388 pixels[4*i+0] = 15; pixels[4*i+1] = 15; pixels[4*i+2] = 15;
389 }
390 pixels[4*i+3] = 255;
391 }
392 }
393 UpdateTexture(tex, pixels.data());
394
395 BeginDrawing();
396 ClearBackground({10, 10, 10, 255});
397
398 DrawTexturePro(tex,
399 {0, 0, (float)N, (float)N},
400 {0, 0, (float)SIM_SZ, (float)SIM_SZ},
401 {0, 0}, 0.f, WHITE);
402 DrawRectangleLines(0, 0, SIM_SZ, SIM_SZ, DARKGRAY);
403
404 int px = PAN_X, cy = 10;
405
406 DrawText("2D Ising Model", px, cy, 17, WHITE); cy += 22;
407
408 // Mode badge
409 bool is_nuc = (mode == Mode::Nucleation);
410 Color mode_col = is_nuc ? Color{220,110,50,255} : SKYBLUE;
411 DrawText(is_nuc ? "NUCLEATION" : "CLASSIC", px, cy, 13, mode_col);
412 DrawText("[TAB]", px + 90, cy, 10, DARKGRAY); cy += 18;
413
414 DrawText(TextFormat("N=%d spd=%d", N, sweeps_per_frame), px, cy, 11, GRAY);
415 cy += 20;
416
417 if (!is_nuc) {
418 cy += 10;
419 float tc_frac = static_cast<float>((TC - 0.05) / (5.0 - 0.05));
420 T = draw_slider(px, cy, PAN_W, T, 0.05, 5.0, tc_frac, "T = %.3f", SKYBLUE);
421 lat.set_temperature(T);
422 DrawText("Tc", px + static_cast<int>(tc_frac * PAN_W) - 5, cy+10, 9, YELLOW);
423 cy += 30;
424
425 cy += 6;
426 real Fnew = draw_slider(px, cy, PAN_W, lat.F, 0.0, 0.2, -1.f,
427 "F = %.4f", PINK);
428 if (Fnew != lat.F) lat.set_field(Fnew);
429 cy += 30;
430
431 float cur_m = static_cast<float>(lat.magnetization());
432 float mf_m = static_cast<float>(IsingLattice::mean_field_m(lat.beta, lat.F));
433 cy += 6;
434 DrawText(TextFormat("|m| = %.4f", cur_m), px, cy, 13, WHITE);
435 DrawText(TextFormat("MF = %.4f", mf_m), px, cy + 16, 13, YELLOW);
436 DrawText(TextFormat("E/N = %.4f", eng_hist.back()), px, cy + 32, 13, LIGHTGRAY);
437 DrawText(TextFormat("beta = %.3f", (float)lat.beta), px, cy + 48, 13, GRAY);
438 cy += 68;
439
440 Color pc = (T < TC) ? Color{80,200,80,255} : Color{200,80,80,255};
441 DrawText((T < TC) ? "ORDERED" : "DISORDERED", px, cy, 14, pc);
442 cy += 20;
443
444 int gh = 128;
445 draw_graph(mag_hist, px, cy, PAN_W, gh, 0.f, 1.f, GREEN, "|m|", mf_m);
446 cy += gh + 10;
447 draw_graph(eng_hist, px, cy, PAN_W, gh, -2.2f, 0.f, ORANGE, "E/spin");
448 cy += gh + 10;
449
450 } else {
451 cy += 10;
452 // Temperature slider (still useful to explore metastability)
453 float tc_frac = static_cast<float>((TC - 0.05) / (5.0 - 0.05));
454 T = draw_slider(px, cy, PAN_W, T, 0.05, 5.0, tc_frac, "T = %.3f", SKYBLUE);
455 lat.set_temperature(T);
456 cy += 30;
457
458 // Field slider -- drives nucleation of spin-down phase
459 cy += 6;
460 real Fnew = draw_slider(px, cy, PAN_W, lat.F, 0.0, 0.3, -1.f,
461 "F = %.4f (nucleation field)", PINK);
462 if (Fnew != lat.F) lat.set_field(Fnew);
463 cy += 30;
464
465 // Umbrella target and half-width sliders
466 cy += 6;
467 {
468 int old_tgt = nuc_target;
469 real tgt = draw_slider(px, cy, PAN_W, static_cast<real>(nuc_target),
470 10, 2000, -1.f, "Nuc target = %.0f", {220,110,50,255});
471 nuc_target = static_cast<int>(tgt);
472 if (nuc_target != old_tgt) { nuc_stats.reset(); mag_stats.reset(); nuc_histo.reset(); }
473 }
474 cy += 30;
475
476 cy += 4;
477 {
478 int old_hw = nuc_half;
479 real hw = draw_slider(px, cy, PAN_W, static_cast<real>(nuc_half),
480 5, 200, -1.f, "Window +/- = %.0f", PURPLE);
481 nuc_half = static_cast<int>(hw);
482 if (nuc_half != old_hw) { nuc_stats.reset(); mag_stats.reset(); nuc_histo.reset(); }
483 }
484 cy += 30;
485
486 // Current nucleus stats (uses num::RunningStats)
487 int lo = nuc_target - nuc_half;
488 int hi = nuc_target + nuc_half;
489 cy += 6;
490 DrawText(TextFormat("N_nuc = %d", nuc_size), px, cy, 14, {220,80,80,255});
491 DrawText(TextFormat("Window [%d, %d]", lo, hi), px, cy + 14, 10, GRAY);
492 DrawText(TextFormat("<N> = %.1f sigma = %.1f",
493 (float)nuc_stats.mean,
494 (float)nuc_stats.std_dev()), px, cy + 26, 11, {220,150,80,255});
495 DrawText(TextFormat("<|m|> = %.4f sigma = %.4f",
496 (float)mag_stats.mean,
497 (float)mag_stats.std_dev()), px, cy + 38, 11, WHITE);
498 DrawText(TextFormat("n = %llu sweeps",
499 static_cast<unsigned long long>(nuc_stats.count)),
500 px, cy + 50, 10, GRAY);
501 DrawText(TextFormat("beta = %.3f", (float)lat.beta), px, cy + 62, 10, GRAY);
502 cy += 76;
503
504 // Nucleus size graph -- reference line at target
505 int gh = 118;
506 float graph_hi = static_cast<float>(nuc_target + nuc_half + 30);
507 draw_graph(nuc_hist, px, cy, PAN_W, gh, 0.f, graph_hi,
508 {220,80,80,255}, "Nucleus size (red cluster)",
509 static_cast<float>(nuc_target));
510 cy += gh + 8;
511
512 // Magnetisation graph
513 draw_graph(mag_hist, px, cy, PAN_W, gh, 0.f, 1.f, GREEN, "|m|");
514 cy += gh + 8;
515 }
516
517 // Controls (bottom of panel)
518 cy = WIN_H - 44;
519 DrawText("SPACE pause R rand H up N nucleus", px, cy, 9, DARKGRAY);
520 DrawText("UP/DOWN speed TAB mode", px, cy + 12, 9, DARKGRAY);
521
522 if (paused)
523 DrawText("PAUSED", SIM_SZ/2 - 45, SIM_SZ/2 - 12, 26, {255,100,100,220});
524
525 EndDrawing();
526 }
527
528 UnloadTexture(tex);
529 CloseWindow();
530 return 0;
531}
Sparse matrix in Compressed Sparse Row (CSR) format.
Definition sparse.hpp:15
static SparseMatrix from_triplets(idx n_rows, idx n_cols, const std::vector< idx > &rows, const std::vector< idx > &cols, const std::vector< real > &vals)
Build from coordinate (COO / triplet) lists.
Definition sparse.cpp:23
Iterative BFS connected-component labelling.
int main()
Definition main.cpp:290
MetropolisStats metropolis_sweep_prob(idx n_sites, ProbFn acceptance_prob, Apply apply_flip, RNG &rng)
Metropolis sweep with caller-supplied acceptance probabilities.
Definition mcmc_impl.hpp:55
double boltzmann_accept(double dE, double beta) noexcept
Metropolis acceptance probability min(1, exp(-β ΔE)).
UmbrellaStats umbrella_sweep_prob(idx n_sites, ProbFn acceptance_prob, Apply apply_flip, Save save_state, Restore restore_state, Measure measure_order, UmbrellaWindow window, RNG &rng)
Umbrella-sampled Metropolis sweep (precomputed probability variant).
double real
Definition types.hpp:10
ClusterResult connected_components(int n_sites, InCluster &&in_cluster, Neighbors &&neighbors)
std::size_t idx
Definition types.hpp:11
void sparse_matvec(const SparseMatrix &A, const Vector &x, Vector &y)
y = A * x
Definition sparse.cpp:110
RootResult newton(ScalarFn f, ScalarFn df, real x0, real tol=1e-10, idx max_iter=1000)
Newton-Raphson method.
Definition roots.cpp:25
real dot(const Vector &x, const Vector &y, Backend b=default_backend)
dot product
Definition vector.cpp:57
constexpr real e
Definition math.hpp:41
Precomputed periodic-boundary neighbor arrays for a 2D square lattice.
Root-finding methods for scalar equations f(x) = 0.
Online statistics for Monte Carlo observables.
num::PBCLattice2D nbr_
Definition main.cpp:77
void random_init()
Definition main.cpp:122
void all_up()
Definition main.cpp:127
real energy_per_spin()
Definition main.cpp:195
IsingLattice()
Definition main.cpp:94
void generate_nucleus(real r=100.0)
Definition main.cpp:130
int sweep_umbrella(int lo, int hi, num::ClusterResult &det)
Definition main.cpp:161
void set_temperature(real T)
Definition main.cpp:107
static real mean_field_m(real beta_val, real F_val)
Definition main.cpp:203
std::vector< real > save_buf
Definition main.cpp:86
std::mt19937 rng
Definition main.cpp:88
std::uniform_real_distribution< real > dist
Definition main.cpp:89
void sweep()
Definition main.cpp:143
real temperature() const
Definition main.cpp:109
void rebuild_boltz()
Definition main.cpp:111
Vector spins
Definition main.cpp:71
real magnetization() const
Definition main.cpp:190
void set_field(real Fnew)
Definition main.cpp:108
SparseMatrix adj
Definition main.cpp:74
Vector ones
Definition main.cpp:72
real boltz[2][5]
Definition main.cpp:84
Vector nbr_buf
Definition main.cpp:73
real beta
Definition main.cpp:91
int head
Definition main.cpp:54
void push(float v)
Definition main.cpp:58
RingBuffer(int cap)
Definition main.cpp:56
int cap() const
Definition main.cpp:67
float back() const
Definition main.cpp:65
float get(int i) const
Definition main.cpp:64
int count
Definition main.cpp:54
int size() const
Definition main.cpp:66
std::vector< float > data
Definition main.cpp:53
int largest_id
Index of largest cluster (-1 if none)
std::vector< int > id
Per-site label: -2 excluded, >=0 cluster index.
int largest_size
Size of largest cluster.
void reset()
Definition stats.hpp:78
void fill(real x, real weight=1.0)
Definition stats.hpp:73
4-neighbor periodic-boundary index arrays for an N×N lattice.
std::vector< int > up
std::vector< int > dn
std::vector< int > rt
up/dn = row±1, lt/rt = col±1 (PBC)
std::vector< int > lt
real std_dev() const
Definition stats.hpp:37
void update(real x)
Incorporate one new sample.
Definition stats.hpp:24
Window constraint for umbrella sampling.
Definition mcmc.hpp:54