Quantum Phenomena Explorer 7.9.8
Interactive quantum-phenomena Explorer Tutor — source documentation
Loading...
Searching...
No Matches
quantumanderson2d.cpp
Go to the documentation of this file.
1/**
2 * @file quantumanderson2d.cpp
3 * @brief Implements the quantumanderson2d physics/numerical component used by QPE experiments.
4 * @ingroup qpe_quantum
5 *
6 * Source: @c core/quantum/quantumanderson2d.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
13
15
16#include <algorithm>
17#include <cmath>
18#include <complex>
19#include <limits>
20
21namespace qpe {
22namespace {
23
24const double kPi = 3.14159265358979323846;
25
26/**
27 * @brief Sets reason.
28 * @param reason The reason value used by the operation.
29 * @param message The message value used by the operation.
30 */
31void setReason(std::string *reason, const char *message)
32{
33 if (reason) *reason = message;
34}
35
36/**
37 * @brief Performs finite.
38 * @param v The v value used by the operation.
39 */
40bool finite(double v) noexcept
41{
42 return std::isfinite(v);
43}
44
45struct Row
46{
47 double y = 0.0;
48 std::vector<const PotentialObject2D *> objects;
49};
50
51/**
52 * @brief Performs std dev.
53 * @param values The values value used by the operation.
54 */
55double stdDev(const std::vector<double> &values)
56{
57 if (values.empty()) return 0.0;
58 double mean = 0.0;
59 for (double v : values) mean += v;
60 mean /= static_cast<double>(values.size());
61 double var = 0.0;
62 for (double v : values) {
63 const double d = v - mean;
64 var += d * d;
65 }
66 return std::sqrt(var / static_cast<double>(values.size()));
67}
68
69} // namespace
70
72 std::string *reason)
73{
74 if (!finite(config.centerX) || !finite(config.cleanLaneY) ||
75 !finite(config.disorderLaneY) || !finite(config.sigma) ||
76 !finite(config.relativePhaseDegrees)) {
77 setReason(reason, "QuantumAnderson2D: initial-state values must be finite");
78 return false;
79 }
80 if (!(config.sigma > 0.0)) {
81 setReason(reason, "QuantumAnderson2D: sigma must be positive");
82 return false;
83 }
84 if (std::abs(config.cleanLaneY - config.disorderLaneY) < 4.0 * config.sigma) {
85 setReason(reason, "QuantumAnderson2D: the two lanes are too close for a clean comparison");
86 return false;
87 }
88 return true;
89}
90
93 std::string *reason)
94{
95 if (field.empty()) {
96 setReason(reason, "QuantumAnderson2D: field must not be empty");
97 return false;
98 }
99 if (!validateConfig(config, reason)) return false;
100
101 const double inv4Sigma2 = 1.0 / (4.0 * config.sigma * config.sigma);
102 const double phase = config.relativePhaseDegrees * kPi / 180.0;
103 const std::complex<double> disorderPhase(std::cos(phase), std::sin(phase));
104
105 for (std::size_t y = 0; y < field.height(); ++y) {
106 const double py = GaussianPacket2D::yCoordinate(field, y);
107 for (std::size_t x = 0; x < field.width(); ++x) {
108 const double px = GaussianPacket2D::xCoordinate(field, x);
109 const double dx = px - config.centerX;
110 const double dyClean = py - config.cleanLaneY;
111 const double dyDisorder = py - config.disorderLaneY;
112 const double a = std::exp(-(dx * dx + dyClean * dyClean) * inv4Sigma2);
113 const double b = std::exp(-(dx * dx + dyDisorder * dyDisorder) * inv4Sigma2);
114 const std::complex<double> value = std::complex<double>(a, 0.0) + disorderPhase * b;
115 field(x, y) = ComplexField2D::Value(static_cast<float>(value.real()),
116 static_cast<float>(value.imag()));
117 }
118 }
119
120 if (!field.normalize()) {
121 setReason(reason, "QuantumAnderson2D: normalization failed");
122 return false;
123 }
124 return true;
125}
126
129 std::string *reason)
130{
131 lattice = QuantumAndersonLattice2D();
132 std::vector<const PotentialObject2D *> gaussians;
133 for (const PotentialObject2D &object : scene.objects()) {
134 if (!object.enabled) continue;
135 if (object.type != PotentialObjectType2D::Gaussian) {
136 setReason(reason, "QuantumAnderson2D: guided comparison expects Gaussian lattice sites only");
137 return false;
138 }
139 gaussians.push_back(&object);
140 }
141 if (gaussians.size() < 14u || (gaussians.size() % 2u) != 0u) {
142 setReason(reason, "QuantumAnderson2D: two equal Gaussian rows are required");
143 return false;
144 }
145
146 std::sort(gaussians.begin(), gaussians.end(), [](const PotentialObject2D *a,
147 const PotentialObject2D *b) {
148 if (a->centerY != b->centerY) return a->centerY < b->centerY;
149 return a->centerX < b->centerX;
150 });
151
152 const double yLow = gaussians.front()->centerY;
153 const double yHigh = gaussians.back()->centerY;
154 if (std::abs(yHigh - yLow) < 1.0e-6) {
155 setReason(reason, "QuantumAnderson2D: two distinct lattice rows were not found");
156 return false;
157 }
158
159 Row low; low.y = yLow;
160 Row high; high.y = yHigh;
161 for (const PotentialObject2D *object : gaussians) {
162 if (std::abs(object->centerY - yLow) < 1.0e-5) low.objects.push_back(object);
163 else if (std::abs(object->centerY - yHigh) < 1.0e-5) high.objects.push_back(object);
164 else {
165 setReason(reason, "QuantumAnderson2D: more than two Gaussian rows were found");
166 return false;
167 }
168 }
169 if (low.objects.size() != high.objects.size() || low.objects.size() < 7u) {
170 setReason(reason, "QuantumAnderson2D: lattice rows must contain the same number of sites");
171 return false;
172 }
173
174 auto sortX = [](Row &row) {
175 std::sort(row.objects.begin(), row.objects.end(), [](const PotentialObject2D *a,
176 const PotentialObject2D *b) {
177 return a->centerX < b->centerX;
178 });
179 };
180 sortX(low); sortX(high);
181
182 const double spacing = low.objects[1]->centerX - low.objects[0]->centerX;
183 if (!(spacing > 0.0)) {
184 setReason(reason, "QuantumAnderson2D: invalid lattice spacing");
185 return false;
186 }
187
188 std::vector<double> lowDepths;
189 std::vector<double> highDepths;
190 for (std::size_t i = 0; i < low.objects.size(); ++i) {
191 const PotentialObject2D &a = *low.objects[i];
192 const PotentialObject2D &b = *high.objects[i];
193 if (std::abs(a.centerX - b.centerX) > 1.0e-4 ||
194 std::abs((a.centerX - low.objects[0]->centerX) - static_cast<double>(i) * spacing) > 2.0e-3 ||
195 std::abs((b.centerX - high.objects[0]->centerX) - static_cast<double>(i) * spacing) > 2.0e-3) {
196 setReason(reason, "QuantumAnderson2D: the two rows do not share one regular lattice");
197 return false;
198 }
199 if (!(a.sigmaX > 0.0f && a.sigmaY > 0.0f && b.sigmaX > 0.0f && b.sigmaY > 0.0f)) {
200 setReason(reason, "QuantumAnderson2D: Gaussian site widths must be positive");
201 return false;
202 }
203 lowDepths.push_back(static_cast<double>(a.value));
204 highDepths.push_back(static_cast<double>(b.value));
205 }
206
207 const double lowStd = stdDev(lowDepths);
208 const double highStd = stdDev(highDepths);
209 const Row *clean = lowStd <= highStd ? &low : &high;
210 const Row *disorder = lowStd <= highStd ? &high : &low;
211 const std::vector<double> &cleanDepths = lowStd <= highStd ? lowDepths : highDepths;
212 const std::vector<double> &disorderDepths = lowStd <= highStd ? highDepths : lowDepths;
213
214 double cleanMean = 0.0, disorderMean = 0.0;
215 for (double v : cleanDepths) cleanMean += v;
216 for (double v : disorderDepths) disorderMean += v;
217 cleanMean /= static_cast<double>(cleanDepths.size());
218 disorderMean /= static_cast<double>(disorderDepths.size());
219
220 if (stdDev(cleanDepths) > 1.0e-3 || stdDev(disorderDepths) < 1.0e-2) {
221 setReason(reason, "QuantumAnderson2D: one uniform row and one disordered row are required");
222 return false;
223 }
224
225 lattice.valid = true;
226 lattice.cleanLaneY = clean->y;
227 lattice.disorderLaneY = disorder->y;
228 lattice.spacing = spacing;
229 lattice.siteSigmaX = clean->objects.front()->sigmaX;
230 lattice.siteSigmaY = clean->objects.front()->sigmaY;
231 lattice.cleanDepth = cleanMean;
232 lattice.disorderDepthMean = disorderMean;
233 lattice.disorderDepthStdDev = stdDev(disorderDepths);
234 lattice.cleanDepths = cleanDepths;
235 lattice.disorderDepths = disorderDepths;
236 lattice.siteX.reserve(clean->objects.size());
237 for (const PotentialObject2D *object : clean->objects) lattice.siteX.push_back(object->centerX);
238 return true;
239}
240
242 const ComplexField2D &field,
243 const QuantumAndersonLattice2D &lattice,
244 bool cleanLane)
245{
247 if (field.empty() || !lattice.valid || lattice.siteX.empty()) return out;
248
249 const double splitY = 0.5 * (lattice.cleanLaneY + lattice.disorderLaneY);
250 const bool cleanAbove = lattice.cleanLaneY > lattice.disorderLaneY;
251 const double cellArea = static_cast<double>(field.dx()) * static_cast<double>(field.dy());
252 out.marginalX.assign(field.width(), 0.0f);
253 out.profileDx = static_cast<double>(field.dx());
255
256 for (std::size_t y = 0; y < field.height(); ++y) {
257 const double py = GaussianPacket2D::yCoordinate(field, y);
258 const bool above = py >= splitY;
259 if ((cleanLane && above != cleanAbove) || (!cleanLane && above == cleanAbove)) continue;
260 for (std::size_t x = 0; x < field.width(); ++x) {
261 const ComplexField2D::Value &v = field(x, y);
262 const double p = (static_cast<double>(v.real()) * v.real() +
263 static_cast<double>(v.imag()) * v.imag()) * cellArea;
264 out.probability += p;
265 out.marginalX[x] += static_cast<float>(p / static_cast<double>(field.dx()));
266 }
267 }
268 if (!(out.probability > 0.0)) return out;
269
270 double weightedX = 0.0;
271 for (std::size_t x = 0; x < field.width(); ++x) {
272 const double px = GaussianPacket2D::xCoordinate(field, x);
273 weightedX += static_cast<double>(out.marginalX[x]) * static_cast<double>(field.dx()) * px;
274 out.marginalX[x] = static_cast<float>(static_cast<double>(out.marginalX[x]) / out.probability);
275 }
276 out.meanX = weightedX / out.probability;
277
278 double variance = 0.0;
279 for (std::size_t x = 0; x < field.width(); ++x) {
280 const double px = GaussianPacket2D::xCoordinate(field, x);
281 const double p = static_cast<double>(out.marginalX[x]) * static_cast<double>(field.dx());
282 const double d = px - out.meanX;
283 variance += p * d * d;
284 }
285 out.sigmaX = std::sqrt(std::max(0.0, variance));
286
287 std::vector<double> siteProbabilities(lattice.siteX.size(), 0.0);
288 const double halfCell = 0.5 * lattice.spacing;
289 for (std::size_t x = 0; x < field.width(); ++x) {
290 const double px = GaussianPacket2D::xCoordinate(field, x);
291 std::size_t nearest = 0u;
292 double best = std::numeric_limits<double>::max();
293 for (std::size_t i = 0; i < lattice.siteX.size(); ++i) {
294 const double d = std::abs(px - lattice.siteX[i]);
295 if (d < best) { best = d; nearest = i; }
296 }
297 if (best <= halfCell) {
298 siteProbabilities[nearest] += static_cast<double>(out.marginalX[x]) * static_cast<double>(field.dx());
299 }
300 }
301 double sumSquares = 0.0;
302 for (double p : siteProbabilities) sumSquares += p * p;
303 if (sumSquares > 0.0) out.siteParticipation = 1.0 / sumSquares;
304 out.centralSiteProbability = siteProbabilities[siteProbabilities.size() / 2u];
305
306 if (!cleanLane) {
307 double sx = 0.0, sy = 0.0, sxx = 0.0, sxy = 0.0;
308 int count = 0;
309 const double centreX = lattice.siteX[lattice.siteX.size() / 2u];
310 for (std::size_t i = 0; i < siteProbabilities.size(); ++i) {
311 const double r = std::abs(lattice.siteX[i] - centreX);
312 const double p = siteProbabilities[i];
313 if (r < 0.75 * lattice.spacing || p < 1.0e-5) continue;
314 const double yy = std::log(p);
315 sx += r; sy += yy; sxx += r * r; sxy += r * yy; ++count;
316 }
317 const double denom = static_cast<double>(count) * sxx - sx * sx;
318 if (count >= 4 && std::abs(denom) > 1.0e-12) {
319 const double slope = (static_cast<double>(count) * sxy - sx * sy) / denom;
320 if (slope < -1.0e-6) {
321 out.localizationLength = -2.0 / slope; // P ~ exp(-2 |x| / xi)
323 }
324 }
325 }
326 return out;
327}
328
329double QuantumAnderson2D::freeGaussianSigma(double time, double sigma0,
330 double mass, double hbar) noexcept
331{
332 if (!(sigma0 > 0.0) || !(mass > 0.0) || !(hbar > 0.0) || !finite(time)) return 0.0;
333 const double q = hbar * time / (2.0 * mass * sigma0 * sigma0);
334 return sigma0 * std::sqrt(1.0 + q * q);
335}
336
338 : maxSamples_(std::max<std::size_t>(2u, maxSamples))
339{
340}
341
349
359
360bool QuantumAndersonHistory2D::configured() const noexcept { return configured_; }
361bool QuantumAndersonHistory2D::empty() const noexcept { return samples_.empty(); }
362
364{
365 if (!configured_ || field.empty()) return;
367 sample.time = time;
368 sample.clean = QuantumAnderson2D::measureLane(field, lattice_, true);
369 sample.disorder = QuantumAnderson2D::measureLane(field, lattice_, false);
370 sample.norm = sample.clean.probability + sample.disorder.probability;
371 samples_.push_back(sample);
372 trim();
373}
374
377const std::vector<QuantumAndersonSample2D> &QuantumAndersonHistory2D::samples() const noexcept { return samples_; }
378
380{
381 if (samples_.size() <= maxSamples_) return;
382 const std::size_t removeCount = samples_.size() - maxSamples_;
383 samples_.erase(samples_.begin(), samples_.begin() + static_cast<std::ptrdiff_t>(removeCount));
384}
385
386} // namespace qpe
Owning Cartesian grid of complex scalar samples.
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.
static double yCoordinate(const ComplexField2D &field, std::size_t y) noexcept
Performs y coordinate.
static double xCoordinate(const ComplexField2D &field, std::size_t x) noexcept
Performs x coordinate.
Implements the Potential Scene2D component.
const std::vector< PotentialObject2D > & objects() const noexcept
Performs objects.
static QuantumAndersonLaneSample2D measureLane(const ComplexField2D &field, const QuantumAndersonLattice2D &lattice, bool cleanLane)
Measures lane.
static double freeGaussianSigma(double time, double sigma0, double mass, double hbar) noexcept
Performs free gaussian sigma.
static bool initializeDualLane(ComplexField2D &field, const QuantumAndersonInitialState2DConfig &config, std::string *reason=nullptr)
Initializes dual lane.
static bool identifyLattice(const PotentialScene2D &scene, QuantumAndersonLattice2D &lattice, std::string *reason=nullptr)
Returns identify lattice.
static bool validateConfig(const QuantumAndersonInitialState2DConfig &config, std::string *reason=nullptr)
Validates config.
void append(double time, const ComplexField2D &field)
Performs append.
QuantumAndersonInitialState2DConfig config_
QuantumAndersonLattice2D lattice_
const QuantumAndersonInitialState2DConfig & config() const noexcept
Returns config.
const QuantumAndersonLattice2D & lattice() const noexcept
Performs lattice.
QuantumAndersonHistory2D(std::size_t maxSamples=1200u)
Performs quantum anderson history2 d.
void configure(const QuantumAndersonInitialState2DConfig &config, const QuantumAndersonLattice2D &lattice)
Returns configure.
const std::vector< QuantumAndersonSample2D > & samples() const noexcept
Samples s.
std::vector< QuantumAndersonSample2D > samples_
bool configured() const noexcept
Returns configured.
bool empty() const noexcept
Performs empty.
Declares the gaussianpacket2d physics/numerical component used by QPE experiments.
void setReason(std::string *reason, const char *message)
Sets reason.
double stdDev(const std::vector< double > &values)
Performs std dev.
Declares the quantumanderson2d physics/numerical component used by QPE experiments.
Data structure used by the Potential Object2D component.
Configuration values for Quantum Anderson Initial State2D Config.
Data structure used by the Quantum Anderson Lane Sample2D component.
Data structure used by the Quantum Anderson Lattice2D component.
std::vector< double > disorderDepths
std::vector< double > cleanDepths
Data structure used by the Quantum Anderson Sample2D component.
std::vector< const PotentialObject2D * > objects