numerics
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1/// @file main.cpp
2/// @brief Real-time raylib visualisation of the 2-D TDSE solver.
3///
4/// Visualisation:
5/// Default mode -- probability density |psi|^2 coloured by quantum phase
6/// (HSV: hue = phase, brightness = prob, saturation = 1)
7/// Mode key D -- probability density only (black -> cyan -> white hot-map)
8/// Potential overlay drawn at 30% opacity over the wavefunction
9///
10/// Controls:
11/// SPACE pause / resume
12/// R reset wavepacket (click to reposition)
13/// P cycle potentials (Free -> Barrier -> DoubleSlit -> Harmonic -> CircularWell)
14/// E compute eigenmodes (Lanczos, runs in current thread ~1-2 s)
15/// 1-8 load eigenmode k (after E)
16/// D toggle density-only vs phase colour map
17/// Left click place wavepacket at cursor position
18/// Scroll up/dn increase / decrease initial momentum (kx)
19/// + / - substeps per frame
20/// ESC / Q quit
21
22#include "raylib.h"
23#include "tdse_solver.hpp"
24
25#include <vector>
26#include <cmath>
27#include <cstdio>
28#include <algorithm>
29
30// Colour maps
31
32/// Phase-amplitude HSV colour: hue = phase, value = sqrt(prob) / max_amp
33static Color phase_color(double prob, double phase, double max_prob) {
34 if (max_prob < 1e-20) return {0, 0, 0, 255};
35 float amp = static_cast<float>(std::sqrt(prob / max_prob));
36 amp = std::min(1.0f, amp);
37
38 // HSV: hue from phase, full saturation, brightness from amplitude
39 float hue = static_cast<float>((phase + M_PI) / (2.0 * M_PI)) * 360.0f;
40 float s = 0.95f;
41 float v = amp;
42
43 // HSV to RGB
44 float h6 = hue / 60.0f;
45 int hi = static_cast<int>(h6) % 6;
46 float f = h6 - static_cast<int>(h6);
47 float p = v * (1.0f - s);
48 float q = v * (1.0f - s * f);
49 float t = v * (1.0f - s * (1.0f - f));
50 float r, g, b;
51 switch (hi) {
52 case 0: r=v; g=t; b=p; break;
53 case 1: r=q; g=v; b=p; break;
54 case 2: r=p; g=v; b=t; break;
55 case 3: r=p; g=q; b=v; break;
56 case 4: r=t; g=p; b=v; break;
57 default:r=v; g=p; b=q; break;
58 }
59 return {static_cast<unsigned char>(r*255),
60 static_cast<unsigned char>(g*255),
61 static_cast<unsigned char>(b*255), 255};
62}
63
64/// Density-only: black -> teal -> white
65static Color density_color(double prob, double max_prob) {
66 if (max_prob < 1e-20) return {0, 0, 0, 255};
67 float t = static_cast<float>(prob / max_prob);
68 t = std::min(1.0f, std::pow(t, 0.45f)); // gamma for better range
69 // black -> cyan(0,255,255) -> white
70 if (t < 0.5f) {
71 float u = t * 2.0f;
72 return {0, static_cast<unsigned char>(u * 255),
73 static_cast<unsigned char>(u * 255), 255};
74 } else {
75 float u = (t - 0.5f) * 2.0f;
76 return {static_cast<unsigned char>(u * 255), 255, 255, 255};
77 }
78}
79
80// main
81
82int main(int argc, char* argv[]) {
83 int N_arg = (argc > 1) ? std::atoi(argv[1]) : 256;
84 if (N_arg < 32 || N_arg > 512) {
85 std::fprintf(stderr, "N must be in [32, 512]; got %d\n", N_arg);
86 return 1;
87 }
88 const int N = N_arg;
89
90 const double L = 10.0;
91 const double dt = 0.004;
92 tdse::TDSESolver solver(N, L, dt);
94 solver.init_gaussian(L * 0.2, L * 0.5, 5.0, 0.0, L * 0.06);
95
96 tdse::Potential pot_order[] = {
102 };
103 int pot_idx = 2; // start at DoubleSlit
104 double kx_init = 5.0;
105
106 const int WIN = 900;
107 SetConfigFlags(FLAG_MSAA_4X_HINT);
108 InitWindow(WIN, WIN, "TDSE -- 2D Quantum Wavepacket [P pot E eigenmodes 1-8 mode SPACE pause R reset LClick place Scroll kx]");
109 SetTargetFPS(60);
110
111 std::vector<Color> pixels(N * N);
112 Image img = {pixels.data(), N, N, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8};
113 Texture2D tex = LoadTextureFromImage(img);
114
115 // Potential overlay texture (computed once per potential change)
116 std::vector<Color> pot_pixels(N * N);
117 Image pot_img = {pot_pixels.data(), N, N, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8};
118 Texture2D pot_tex = LoadTextureFromImage(pot_img);
119 bool pot_dirty = true;
120
121 bool paused = false;
122 int substeps = 1;
123 float sim_time = 0.0f;
124 bool phase_mode = true; // true = phase-coloured, false = density-only
125 int active_mode = -1; // which eigenmode is displayed (-1 = wavepacket)
126 bool computing_modes = false;
127
128 auto rebuild_potential_tex = [&]() {
129 double V0 = 5000.0;
130 for (int i = 0; i < N; ++i) {
131 for (int j = 0; j < N; ++j) {
132 double v = solver.V[solver.at(i, j)];
133 unsigned char a = (v > V0 * 0.5) ? 200 : 0;
134 // White wall with partial alpha
135 pot_pixels[j * N + i] = {220, 220, 255, a};
136 }
137 }
138 UpdateTexture(pot_tex, pot_pixels.data());
139 pot_dirty = false;
140 };
141 rebuild_potential_tex();
142
143 while (!WindowShouldClose()) {
144
145 if (IsKeyPressed(KEY_SPACE)) paused = !paused;
146 if (IsKeyPressed(KEY_Q)) break;
147 if (IsKeyPressed(KEY_D)) phase_mode = !phase_mode;
148 if (IsKeyPressed(KEY_EQUAL) || IsKeyPressed(KEY_KP_ADD))
149 substeps = std::min(substeps + 1, 8);
150 if (IsKeyPressed(KEY_MINUS) || IsKeyPressed(KEY_KP_SUBTRACT))
151 substeps = std::max(substeps - 1, 1);
152
153 // Scroll to change momentum
154 float scroll = GetMouseWheelMove();
155 if (std::abs(scroll) > 0.1f) {
156 kx_init = std::clamp(kx_init + scroll * 0.5, 0.5, 20.0);
157 }
158
159 // Cycle potential
160 if (IsKeyPressed(KEY_P)) {
161 pot_idx = (pot_idx + 1) % 5;
162 solver.set_potential(pot_order[pot_idx]);
163 // Reset wavepacket to left side, aimed right
164 solver.init_gaussian(L * 0.2, L * 0.5, kx_init, 0.0, L * 0.06);
165 sim_time = 0.0f;
166 active_mode = -1;
167 pot_dirty = true;
168 }
169
170 // Reset
171 if (IsKeyPressed(KEY_R)) {
172 solver.init_gaussian(L * 0.2, L * 0.5, kx_init, 0.0, L * 0.06);
173 sim_time = 0.0f;
174 active_mode = -1;
175 }
176
177 // Place wavepacket at click position
178 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
179 Vector2 mp = GetMousePosition();
180 double x0 = (mp.x / WIN) * L;
181 double y0 = (mp.y / WIN) * L; // screen y goes down = physical y down
182 solver.init_gaussian(x0, y0, kx_init, 0.0, L * 0.06);
183 active_mode = -1;
184 sim_time = 0.0f;
185 }
186
187 // Compute eigenmodes (blocks for 1-3s)
188 if (IsKeyPressed(KEY_E)) {
189 computing_modes = true;
190 solver.compute_modes(8);
191 computing_modes = false;
192 // Update stats
193 solver.stats.n_modes = static_cast<int>(solver.modes.size());
194 }
195
196 // Load eigenmode
197 for (int k = 0; k < 8; ++k) {
198 if (IsKeyPressed(KEY_ONE + k)) {
199 if (solver.modes_ready && k < static_cast<int>(solver.modes.size())) {
200 solver.set_mode(k);
201 active_mode = k;
202 sim_time = 0.0f;
203 }
204 }
205 }
206
207 if (!paused) {
208 for (int s = 0; s < substeps; ++s) {
209 solver.step();
210 sim_time += static_cast<float>(dt);
211 }
212 }
213
214 if (pot_dirty) rebuild_potential_tex();
215
216 // Find max probability for colour normalisation
217 double max_prob = 1e-20;
218 for (int i = 0; i < N; ++i)
219 for (int j = 0; j < N; ++j)
220 max_prob = std::max(max_prob, solver.prob(i, j));
221
222 // Screen: row j = 0 at top -> physical y = (j+1)*h = small -> flip
223 for (int i = 0; i < N; ++i) {
224 for (int j = 0; j < N; ++j) {
225 double p = solver.prob(i, j);
226 int px = j * N + i; // texture: column = x, row = y (top-to-bottom)
227 // Flip y: screen row 0 = top = physical y = L
228 int screen_j = N - 1 - j;
229 px = screen_j * N + i;
230
231 if (phase_mode)
232 pixels[px] = phase_color(p, solver.phase_ang(i, j), max_prob);
233 else
234 pixels[px] = density_color(p, max_prob);
235 }
236 }
237 UpdateTexture(tex, pixels.data());
238
239 BeginDrawing();
240 ClearBackground(BLACK);
241
242 // Wavefunction
243 Rectangle src = {0, 0, static_cast<float>(N), static_cast<float>(N)};
244 Rectangle dst = {0, 0, static_cast<float>(WIN), static_cast<float>(WIN)};
245 DrawTexturePro(tex, src, dst, {0, 0}, 0.0f, WHITE);
246
247 // Potential walls overlay
248 DrawTexturePro(pot_tex, src, dst, {0, 0}, 0.0f, WHITE);
249
250 DrawFPS(WIN - 90, 8);
251 const int FS = 17;
252 const int FS2 = 14;
253 int y = 8;
254
255 DrawText(TextFormat("Grid %dx%d t = %.3f substeps %d [+/-]",
256 N, N, sim_time, substeps), 8, y, FS, RAYWHITE);
257 y += FS + 3;
258
259 DrawText(TextFormat("Potential: %s [P]", tdse::potential_name(pot_order[pot_idx])),
260 8, y, FS, {100, 220, 255, 255});
261 y += FS + 3;
262
263 DrawText(TextFormat("kx = %.1f [scroll] | Colourmap: %s [D]",
264 kx_init, phase_mode ? "phase-HSV" : "density"),
265 8, y, FS2, {180, 255, 180, 255});
266 y += FS2 + 3;
267
268 // Step stats
269 DrawText(TextFormat("step %.2f ms norm %.5f E = %.4f",
270 solver.stats.step_ms,
271 solver.stats.norm,
272 solver.stats.energy),
273 8, y, FS2, {220, 200, 100, 255});
274 y += FS2 + 6;
275
276 // Eigenmodes panel
277 if (solver.modes_ready) {
278 DrawText(TextFormat("Eigenmodes [%d] (1-8 to view):", solver.stats.n_modes),
279 8, y, FS2, {255, 200, 100, 255});
280 y += FS2 + 2;
281 int show = std::min(8, static_cast<int>(solver.modes.size()));
282 for (int k = 0; k < show; ++k) {
283 const auto& m = solver.modes[k];
284 Color c = (k == active_mode) ? Color{255, 255, 100, 255}
285 : Color{180, 180, 180, 255};
286 if (m.exact_energy >= 0)
287 DrawText(TextFormat(" [%d] E=%.4f exact=%.4f", k+1, m.energy, m.exact_energy),
288 8, y, FS2, c);
289 else
290 DrawText(TextFormat(" [%d] E=%.4f", k+1, m.energy), 8, y, FS2, c);
291 y += FS2 + 1;
292 }
293 } else {
294 DrawText("Press E to compute eigenmodes (Lanczos)", 8, y, FS2, {180, 180, 180, 255});
295 y += FS2 + 4;
296 }
297
298 if (active_mode >= 0)
299 DrawText(TextFormat("Eigenmode %d (SPACE to evolve, R to reset)", active_mode + 1),
300 8, WIN - 40, FS2, {255, 255, 100, 255});
301
302 if (paused)
303 DrawText("PAUSED", WIN/2 - 48, WIN/2, 32, YELLOW);
304
305 DrawText("SPACE pause P pot E modes 1-8 mode R reset LClick place D color ESC quit",
306 8, WIN - 20, 12, {130, 130, 130, 200});
307
308 EndDrawing();
309
310 // Update norm/energy once per frame (cheap)
311 solver.stats.norm = solver.compute_norm();
312 solver.stats.energy = solver.compute_energy();
313 }
314
315 UnloadTexture(tex);
316 UnloadTexture(pot_tex);
317 CloseWindow();
318 return 0;
319}
num::Vector V
NxN potential (real, length N^2)
double compute_energy() const
<psi|H|psi> using 5-point Laplacian and the potential.
void set_mode(int k)
Set psi to the k-th eigenmode (modes must be computed first).
void init_gaussian(double x0, double y0, double kx, double ky, double sigma)
Gaussian wavepacket: psi = A*exp(-(r-r0)^2/sigma^2)*exp(i*k*r), then normalised.
std::vector< EigenMode > modes
void compute_modes(int k=8)
double prob(int i, int j) const
void set_potential(Potential p, double param=0.0)
Build potential and recompute Thomas factorisations.
double compute_norm() const
integral|psi|^2 dA (Gauss-Legendre over each grid strip).
int at(int i, int j) const
double phase_ang(int i, int j) const
void step()
Advance one full time step (Strang splitting).
int main()
Definition main.cpp:84
@ DoubleSlit
Double-slit wall – interference.
@ Barrier
Single vertical barrier with one gap – tunnelling.
@ CircularWell
V = 0 inside radius R, V = V0 outside (Bessel eigenstates)
@ Harmonic
V = 1/2*omega^2*r^2 (coherent state dynamics)
@ Free
V = 0 (free particle)
const char * potential_name(Potential p)
double norm
integral|psi|^2 dA (should stay ~= 1)
int n_modes
Number of computed eigenmodes.
double step_ms
Wall time for one step()
double energy
<H> = integralpsi* H psi dA
2-D Time-Dependent Schrödinger Equation solver