Quantum Phenomena Explorer 7.9.8
Interactive quantum-phenomena Explorer Tutor — source documentation
Loading...
Searching...
No Matches
quantumvortex2d.cpp
Go to the documentation of this file.
1/**
2 * @file quantumvortex2d.cpp
3 * @brief Implements the quantumvortex2d physics/numerical component used by QPE experiments.
4 * @ingroup qpe_quantum
5 *
6 * Source: @c core/quantum/quantumvortex2d.cpp. This file is part of Quantum Phenomena Explorer (QPE),
7 * maintained by Officina Turini. See LICENSE_IT.txt / LICENSE.txt for the
8 * QPE freeware source-available terms and THIRD_PARTY_NOTICES.txt for
9 * third-party components.
10 */
11
12#include "quantumvortex2d.h"
13
14#include <algorithm>
15#include <cmath>
16#include <complex>
17#include <limits>
18#include <stdexcept>
19
20namespace qpe {
21namespace {
22
23const double kPi = 3.14159265358979323846;
24
25/**
26 * @brief Performs finite.
27 * @param v The v value used by the operation.
28 */
29bool finite(double v) noexcept
30{
31 return std::isfinite(v);
32}
33
34/**
35 * @brief Sets reason.
36 * @param reason The reason value used by the operation.
37 * @param text The text value used by the operation.
38 */
39void setReason(std::string *reason, const char *text)
40{
41 if (reason) *reason = text;
42}
43
44/**
45 * @brief Performs coordinate.
46 * @param i The i value used by the operation.
47 * @param n The n value used by the operation.
48 * @param spacing The spacing value used by the operation.
49 */
50double coordinate(std::size_t i, std::size_t n, double spacing) noexcept
51{
52 if (n == 0u) return 0.0;
53 return (static_cast<double>(i) - 0.5 * static_cast<double>(n - 1u)) * spacing;
54}
55
56/**
57 * @brief Performs wrap phase.
58 * @param phase The phase value used by the operation.
59 */
60double wrapPhase(double phase) noexcept
61{
62 const double twoPi = 2.0 * kPi;
63 while (phase <= -kPi) phase += twoPi;
64 while (phase > kPi) phase -= twoPi;
65 return phase;
66}
67
68} // namespace
69
71 std::string *reason)
72{
73 if (!finite(config.envelopeCenterX) || !finite(config.envelopeCenterY) ||
74 !finite(config.envelopeSigma) || !finite(config.coreX) || !finite(config.coreY)) {
75 setReason(reason, "QuantumVortex2D: configuration values must be finite");
76 return false;
77 }
78 if (!(config.envelopeSigma > 0.0) || config.envelopeSigma > 1000.0) {
79 setReason(reason, "QuantumVortex2D: envelope sigma must be positive");
80 return false;
81 }
82 if (config.charge != 1 && config.charge != -1) {
83 setReason(reason, "QuantumVortex2D: charge must be +1 or -1");
84 return false;
85 }
86 return true;
87}
88
91 std::string *reason)
92{
93 if (field.empty()) {
94 setReason(reason, "QuantumVortex2D: field must not be empty");
95 return false;
96 }
97 if (!validateConfig(config, reason)) return false;
98
99 const double sigma = config.envelopeSigma;
100 const double inv4Sigma2 = 1.0 / (4.0 * sigma * sigma);
101 const double dxGrid = static_cast<double>(field.dx());
102 const double dyGrid = static_cast<double>(field.dy());
103
104 for (std::size_t y = 0; y < field.height(); ++y) {
105 const double py = coordinate(y, field.height(), dyGrid);
106 const double ey = py - config.envelopeCenterY;
107 const double vy = py - config.coreY;
108 for (std::size_t x = 0; x < field.width(); ++x) {
109 const double px = coordinate(x, field.width(), dxGrid);
110 const double ex = px - config.envelopeCenterX;
111 const double vx = px - config.coreX;
112 const double envelope = std::exp(-(ex * ex + ey * ey) * inv4Sigma2);
113 field(x, y) = ComplexField2D::Value(
114 static_cast<float>(vx * envelope),
115 static_cast<float>(static_cast<double>(config.charge) * vy * envelope));
116 }
117 }
118
119 if (!field.normalize()) {
120 setReason(reason, "QuantumVortex2D: normalization failed");
121 return false;
122 }
123 return true;
124}
125
128 std::string *reason)
129{
130 trap = QuantumVortexTrap2D();
131 const PotentialObject2D *harmonic = nullptr;
132 for (const PotentialObject2D &object : scene.objects()) {
133 if (!object.enabled) continue;
134 if (object.type == PotentialObjectType2D::Harmonic) {
135 if (harmonic != nullptr) {
136 setReason(reason, "QuantumVortex2D: expected exactly one enabled harmonic trap");
137 return false;
138 }
139 harmonic = &object;
140 } else {
141 setReason(reason, "QuantumVortex2D: vortex guided analysis expects only one harmonic object");
142 return false;
143 }
144 }
145 if (!harmonic) {
146 setReason(reason, "QuantumVortex2D: harmonic trap not found");
147 return false;
148 }
149 const double kx = static_cast<double>(harmonic->curvatureX);
150 const double ky = static_cast<double>(harmonic->curvatureY);
151 if (!(kx > 0.0) || !(ky > 0.0) || !finite(kx) || !finite(ky)) {
152 setReason(reason, "QuantumVortex2D: trap curvature must be positive");
153 return false;
154 }
155 const double scale = std::max(1.0, std::max(std::abs(kx), std::abs(ky)));
156 if (std::abs(kx - ky) > 1.0e-6 * scale) {
157 setReason(reason, "QuantumVortex2D: guided vortex requires an isotropic harmonic trap");
158 return false;
159 }
160
161 trap.valid = true;
162 trap.centerX = static_cast<double>(harmonic->centerX);
163 trap.centerY = static_cast<double>(harmonic->centerY);
164 trap.curvature = 0.5 * (kx + ky);
165 return true;
166}
167
169 double mass)
170{
171 if (!trap.valid || !(trap.curvature > 0.0) || !(mass > 0.0) || !finite(mass)) return 0.0;
172 return std::sqrt(trap.curvature / mass);
173}
174
176 double mass)
177{
178 const double omega = trapAngularFrequency(trap, mass);
179 return omega > 0.0 ? 2.0 * kPi / omega : 0.0;
180}
181
183 const ComplexField2D &field,
185 const QuantumVortexTrap2D &trap)
186{
188 if (field.width() < 3u || field.height() < 3u || !trap.valid) return best;
189
190 const double maxRadius = std::max(2.5, 4.0 * config.envelopeSigma);
191 const double maxRadius2 = maxRadius * maxRadius;
192 double bestDensity = std::numeric_limits<double>::infinity();
193 const int expectedSign = config.charge >= 0 ? 1 : -1;
194
195 for (std::size_t y = 0; y + 1u < field.height(); ++y) {
196 for (std::size_t x = 0; x + 1u < field.width(); ++x) {
197 const double cx = 0.5 * (coordinate(x, field.width(), field.dx()) +
198 coordinate(x + 1u, field.width(), field.dx()));
199 const double cy = 0.5 * (coordinate(y, field.height(), field.dy()) +
200 coordinate(y + 1u, field.height(), field.dy()));
201 const double tx = cx - trap.centerX;
202 const double ty = cy - trap.centerY;
203 if (tx * tx + ty * ty > maxRadius2) continue;
204
205 const ComplexField2D::Value &p00 = field(x, y);
206 const ComplexField2D::Value &p10 = field(x + 1u, y);
207 const ComplexField2D::Value &p11 = field(x + 1u, y + 1u);
208 const ComplexField2D::Value &p01 = field(x, y + 1u);
209 const double windingPhase =
210 wrapPhase(std::arg(p10) - std::arg(p00)) +
211 wrapPhase(std::arg(p11) - std::arg(p10)) +
212 wrapPhase(std::arg(p01) - std::arg(p11)) +
213 wrapPhase(std::arg(p00) - std::arg(p01));
214 const int winding = static_cast<int>(std::lround(windingPhase / (2.0 * kPi)));
215 if (winding != expectedSign) continue;
216
217 const double density = 0.25 * (
218 static_cast<double>(std::norm(p00)) + static_cast<double>(std::norm(p10)) +
219 static_cast<double>(std::norm(p11)) + static_cast<double>(std::norm(p01)));
220 if (density < bestDensity) {
221 bestDensity = density;
222 best.valid = true;
223 best.x = cx;
224 best.y = cy;
225 best.winding = winding;
226 best.localDensity = density;
227 }
228 }
229 }
230 return best;
231}
232
234 double hbar,
235 double originX,
236 double originY)
237{
238 if (field.width() < 3u || field.height() < 3u || !(hbar > 0.0) || !finite(hbar)) return 0.0;
239 const double area = static_cast<double>(field.dx()) * static_cast<double>(field.dy());
240 const double inv2Dx = 1.0 / (2.0 * static_cast<double>(field.dx()));
241 const double inv2Dy = 1.0 / (2.0 * static_cast<double>(field.dy()));
242 double value = 0.0;
243 double norm = 0.0;
244
245 for (std::size_t y = 1u; y + 1u < field.height(); ++y) {
246 const double py = coordinate(y, field.height(), field.dy()) - originY;
247 for (std::size_t x = 1u; x + 1u < field.width(); ++x) {
248 const double px = coordinate(x, field.width(), field.dx()) - originX;
249 const std::complex<double> psi(field(x, y).real(), field(x, y).imag());
250 const std::complex<double> dPsiDx =
251 (std::complex<double>(field(x + 1u, y).real(), field(x + 1u, y).imag()) -
252 std::complex<double>(field(x - 1u, y).real(), field(x - 1u, y).imag())) * inv2Dx;
253 const std::complex<double> dPsiDy =
254 (std::complex<double>(field(x, y + 1u).real(), field(x, y + 1u).imag()) -
255 std::complex<double>(field(x, y - 1u).real(), field(x, y - 1u).imag())) * inv2Dy;
256 const std::complex<double> lzPsi = std::complex<double>(0.0, -hbar) *
257 (px * dPsiDy - py * dPsiDx);
258 value += std::real(std::conj(psi) * lzPsi) * area;
259 norm += std::norm(psi) * area;
260 }
261 }
262 return norm > 1.0e-30 ? value / norm : 0.0;
263}
264
267 const QuantumVortexTrap2D &trap,
268 double mass,
269 double &x,
270 double &y)
271{
272 x = config.coreX;
273 y = config.coreY;
274 const double omega = trapAngularFrequency(trap, mass);
275 if (!(omega > 0.0) || !finite(time)) return;
276 const double dx = config.coreX - trap.centerX;
277 const double dy = config.coreY - trap.centerY;
278 const double theta = static_cast<double>(config.charge) * omega * time;
279 const double c = std::cos(theta);
280 const double s = std::sin(theta);
281 x = trap.centerX + dx * c - dy * s;
282 y = trap.centerY + dx * s + dy * c;
283}
284
286 : maxSamples_(std::max<std::size_t>(32u, maxSamples))
287{
288}
289
297
299 const QuantumVortexTrap2D &trap)
300{
301 clear();
302 if (!trap.valid || !QuantumVortex2D::validateConfig(config, nullptr)) return;
303 config_ = config;
304 trap_ = trap;
305 configured_ = true;
306}
307
308bool QuantumVortexHistory2D::configured() const noexcept { return configured_; }
309bool QuantumVortexHistory2D::empty() const noexcept { return samples_.empty(); }
310
312 const ComplexField2D &field,
313 double hbar)
314{
315 if (!configured_ || field.empty() || !finite(time)) return;
317 sample.time = time;
318 sample.core = QuantumVortex2D::detectCore(field, config_, trap_);
319 sample.angularMomentumZ = QuantumVortex2D::angularMomentumZ(field, hbar,
321 sample.norm = field.norm2();
322 samples_.push_back(sample);
323 trim();
324}
325
327const QuantumVortexTrap2D &QuantumVortexHistory2D::trap() const noexcept { return trap_; }
328const std::vector<QuantumVortexSample2D> &QuantumVortexHistory2D::samples() const noexcept { return samples_; }
329
331{
332 if (samples_.size() <= maxSamples_) return;
333 const std::size_t removeCount = samples_.size() - maxSamples_;
334 samples_.erase(samples_.begin(), samples_.begin() + static_cast<std::ptrdiff_t>(removeCount));
335}
336
337} // namespace qpe
Owning Cartesian grid of complex scalar samples.
double norm2() const noexcept
Returns the discrete integral of |psi|^2 including dx*dy.
bool empty() const noexcept
Returns whether the field contains no samples.
float dx() const noexcept
Returns the X grid spacing.
std::size_t height() const noexcept
Returns the number of samples along Y.
bool normalize() noexcept
Scales the field to unit discrete probability norm.
std::complex< float > Value
float dy() const noexcept
Returns the Y grid spacing.
std::size_t width() const noexcept
Returns the number of samples along X.
Implements the Potential Scene2D component.
const std::vector< PotentialObject2D > & objects() const noexcept
Performs objects.
static double angularMomentumZ(const ComplexField2D &field, double hbar, double originX=0.0, double originY=0.0)
Performs angular momentum z.
static void idealCorePosition(double time, const QuantumVortexInitialState2DConfig &config, const QuantumVortexTrap2D &trap, double mass, double &x, double &y)
Returns ideal core position.
static bool validateConfig(const QuantumVortexInitialState2DConfig &config, std::string *reason=nullptr)
Validates config.
static bool identifyTrap(const PotentialScene2D &scene, QuantumVortexTrap2D &trap, std::string *reason=nullptr)
Returns identify trap.
static QuantumVortexCore2D detectCore(const ComplexField2D &field, const QuantumVortexInitialState2DConfig &config, const QuantumVortexTrap2D &trap)
Performs detect core.
static double trapAngularFrequency(const QuantumVortexTrap2D &trap, double mass)
Performs trap angular frequency.
static double trapPeriod(const QuantumVortexTrap2D &trap, double mass)
Performs trap period.
static bool initialize(ComplexField2D &field, const QuantumVortexInitialState2DConfig &config, std::string *reason=nullptr)
Initializes ialize.
void configure(const QuantumVortexInitialState2DConfig &config, const QuantumVortexTrap2D &trap)
Returns configure.
void clear()
Performs clear.
std::vector< QuantumVortexSample2D > samples_
const QuantumVortexTrap2D & trap() const noexcept
Performs trap.
QuantumVortexHistory2D(std::size_t maxSamples=1400u)
Performs quantum vortex history2 d.
bool configured() const noexcept
Returns configured.
const std::vector< QuantumVortexSample2D > & samples() const noexcept
Samples s.
bool empty() const noexcept
Performs empty.
QuantumVortexInitialState2DConfig config_
const QuantumVortexInitialState2DConfig & config() const noexcept
Returns config.
void append(double time, const ComplexField2D &field, double hbar)
Performs append.
double wrapPhase(double phase) noexcept
Performs wrap phase.
Declares the quantumvortex2d physics/numerical component used by QPE experiments.
Data structure used by the Potential Object2D component.
Data structure used by the Quantum Vortex Core2D component.
Configuration values for Quantum Vortex Initial State2D Config.
Data structure used by the Quantum Vortex Sample2D component.
Data structure used by the Quantum Vortex Trap2D component.