Quantum Phenomena Explorer 7.9.8
Interactive quantum-phenomena Explorer Tutor — source documentation
Loading...
Searching...
No Matches
quantumscar2d.cpp
Go to the documentation of this file.
1/**
2 * @file quantumscar2d.cpp
3 * @brief Implements the quantumscar2d physics/numerical component used by QPE experiments.
4 * @ingroup qpe_quantum
5 *
6 * Source: @c core/quantum/quantumscar2d.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 "quantumscar2d.h"
13
15
16#include <algorithm>
17#include <cmath>
18#include <complex>
19
20namespace qpe {
21namespace {
22
23constexpr double kPi = 3.141592653589793238462643383279502884;
24
25/**
26 * @brief Performs fail.
27 * @param reason The reason value used by the operation.
28 * @param message The message value used by the operation.
29 */
30void fail(std::string *reason, const char *message) noexcept
31{
32 if (reason) *reason = message;
33}
34
35/**
36 * @brief Performs same grid.
37 * @param a The a value used by the operation.
38 * @param b The b value used by the operation.
39 */
40bool sameGrid(const ComplexField2D &a, const ComplexField2D &b) noexcept
41{
42 return !a.empty() && !b.empty() &&
43 a.width() == b.width() && a.height() == b.height() &&
44 std::fabs(static_cast<double>(a.dx()) - static_cast<double>(b.dx())) <= 1.0e-9 &&
45 std::fabs(static_cast<double>(a.dy()) - static_cast<double>(b.dy())) <= 1.0e-9;
46}
47
48/**
49 * @brief Performs enhancement from weights.
50 * @param bandWeight The band weight value used by the operation.
51 * @param totalWeight The total weight value used by the operation.
52 * @param geometry The geometry value used by the operation.
53 * @param halfWidth The half width value used by the operation.
54 */
55double enhancementFromWeights(double bandWeight,
56 double totalWeight,
57 const QuantumScarGeometry2D &geometry,
58 double halfWidth) noexcept
59{
60 if (!(totalWeight > 0.0)) return 0.0;
61 const double area = QuantumScars2D::accessibleArea(geometry);
62 const double bandArea = QuantumScars2D::orbitBandArea(geometry, halfWidth);
63 if (!(area > 0.0) || !(bandArea > 0.0)) return 0.0;
64 const double geometricFraction = std::min(1.0, bandArea / area);
65 if (!(geometricFraction > 0.0)) return 0.0;
66 return (bandWeight / totalWeight) / geometricFraction;
67}
68
69} // namespace
70
72 std::string *reason) noexcept
73{
74 if (reason) reason->clear();
75 if (!(config.orbitBandHalfWidth > 0.0) || !std::isfinite(config.orbitBandHalfWidth) ||
76 config.orbitBandHalfWidth > 20.0) {
77 fail(reason, "Scar orbit-band half-width must be finite and positive.");
78 return false;
79 }
80 if (!(config.referenceEnergy >= 0.0) || !std::isfinite(config.referenceEnergy) ||
81 config.referenceEnergy > 10000.0) {
82 fail(reason, "Scar reference energy must be finite and non-negative.");
83 return false;
84 }
85 return true;
86}
87
89 QuantumScarGeometry2D &geometry,
90 std::string *reason) noexcept
91{
92 geometry = QuantumScarGeometry2D();
93 if (reason) reason->clear();
94
95 const PotentialObject2D *stadium = nullptr;
96 for (const PotentialObject2D &object : scene.objects()) {
97 if (!object.enabled) continue;
98 if (object.type != PotentialObjectType2D::StadiumExterior) {
99 fail(reason, "Quantum Scar analysis expects one Stadium Exterior potential object.");
100 return false;
101 }
102 if (stadium != nullptr) {
103 fail(reason, "Quantum Scar analysis expects exactly one Stadium Exterior object.");
104 return false;
105 }
106 stadium = &object;
107 }
108
109 if (!stadium || stadium->value <= 0.0f || stadium->width <= 0.0f || stadium->radius <= 0.0f) {
110 fail(reason, "Stadium Exterior must have positive wall height, straight length and radius.");
111 return false;
112 }
113
114 geometry.valid = true;
115 geometry.centerX = stadium->centerX;
116 geometry.centerY = stadium->centerY;
117 geometry.straightLength = stadium->width;
118 geometry.radius = stadium->radius;
119 geometry.wallPotential = stadium->value;
120 return true;
121}
122
124 double x,
125 double y) noexcept
126{
127 if (!geometry.valid || !(geometry.straightLength > 0.0) || !(geometry.radius > 0.0)) {
128 return false;
129 }
130 const double lx = x - geometry.centerX;
131 const double ly = y - geometry.centerY;
132 const double halfStraight = 0.5 * geometry.straightLength;
133 if (std::fabs(lx) <= halfStraight) return std::fabs(ly) <= geometry.radius;
134 const double capX = lx > 0.0 ? halfStraight : -halfStraight;
135 const double dx = lx - capX;
136 return dx * dx + ly * ly <= geometry.radius * geometry.radius;
137}
138
140{
141 if (!geometry.valid || !(geometry.straightLength > 0.0) || !(geometry.radius > 0.0)) return 0.0;
142 return 2.0 * geometry.radius * geometry.straightLength +
143 kPi * geometry.radius * geometry.radius;
144}
145
147 double halfWidth) noexcept
148{
149 if (!geometry.valid || !(halfWidth > 0.0) || !(geometry.radius > 0.0)) return 0.0;
150 const double w = std::min(geometry.radius, halfWidth);
151 const double r = geometry.radius;
152 const double central = 2.0 * w * geometry.straightLength;
153 const double root = std::sqrt(std::max(0.0, r * r - w * w));
154 // The two semicircular caps together make one complete circle. This is the
155 // area of the horizontal |y|<=w band through that circle.
156 const double caps = 2.0 * (w * root + r * r * std::asin(std::min(1.0, w / r)));
157 return std::min(accessibleArea(geometry), central + caps);
158}
159
161{
162 if (!geometry.valid) return 0.0;
163 const double oneWay = geometry.straightLength + 2.0 * geometry.radius;
164 return 2.0 * oneWay;
165}
166
168 double speed) noexcept
169{
170 if (!(speed > 0.0)) return 0.0;
171 return axialOrbitRoundTripLength(geometry) / speed;
172}
173
175 const ComplexField2D &field) noexcept
176{
177 if (!sameGrid(reference, field)) return 0.0;
178 const double cellArea = static_cast<double>(field.dx()) * static_cast<double>(field.dy());
179 std::complex<double> overlap(0.0, 0.0);
180 for (std::size_t i = 0; i < field.size(); ++i) {
181 const std::complex<double> a(reference.data()[i].real(), reference.data()[i].imag());
182 const std::complex<double> b(field.data()[i].real(), field.data()[i].imag());
183 overlap += std::conj(a) * b * cellArea;
184 }
185 return std::norm(overlap);
186}
187
189 const QuantumScarGeometry2D &geometry,
190 double halfWidth) noexcept
191{
192 if (field.empty() || !geometry.valid || !(halfWidth > 0.0)) return 0.0;
193 double total = 0.0;
194 double band = 0.0;
195 const double cellArea = static_cast<double>(field.dx()) * static_cast<double>(field.dy());
196 for (std::size_t y = 0; y < field.height(); ++y) {
197 const double py = GaussianPacket2D::yCoordinate(field, y);
198 for (std::size_t x = 0; x < field.width(); ++x) {
199 const double px = GaussianPacket2D::xCoordinate(field, x);
200 if (!inside(geometry, px, py)) continue;
201 const double rho = static_cast<double>(std::norm(field(x, y))) * cellArea;
202 total += rho;
203 if (std::fabs(py - geometry.centerY) <= halfWidth) band += rho;
204 }
205 }
206 return enhancementFromWeights(band, total, geometry, halfWidth);
207}
208
210 : maximumSamples_(std::max<std::size_t>(1u, maximumSamples))
211{
212}
213
215{
216 configured_ = false;
220 referenceEnergy_ = 0.0;
221 hbar_ = 1.0;
222 samples_.clear();
223 meanDensitySum_.clear();
224 spectralAccumulator_.clear();
225 meanDensity_.clear();
226 filteredDensity_.clear();
229 totalTimeWeight_ = 0.0;
230 hasAccumulationTime_ = false;
231}
232
234 const QuantumScarGeometry2D &geometry,
235 const QuantumScarConfig2D &config,
236 double referenceEnergy,
237 double hbar)
238{
239 clear();
240 if (reference.empty() || !geometry.valid || !(config.orbitBandHalfWidth > 0.0) ||
241 !(referenceEnergy > 0.0) || !(hbar > 0.0) ||
242 !std::isfinite(referenceEnergy) || !std::isfinite(hbar)) {
243 return false;
244 }
245 reference_ = reference;
247 config_ = config;
249 hbar_ = hbar;
250 meanDensitySum_.assign(reference.size(), 0.0);
251 spectralAccumulator_.assign(reference.size(), std::complex<double>(0.0, 0.0));
252 meanDensity_.assign(reference.size(), 0.0f);
253 filteredDensity_.assign(reference.size(), 0.0f);
254 configured_ = true;
255 return true;
256}
257
258void QuantumScarHistory2D::setMaximumSamples(std::size_t maximumSamples)
259{
260 maximumSamples_ = std::max<std::size_t>(1u, maximumSamples);
261 trimToLimit();
262}
263
264bool QuantumScarHistory2D::append(double time, const ComplexField2D &field)
265{
266 if (!configured_ || !sameGrid(reference_, field) || !std::isfinite(time)) return false;
267
268 // Use elapsed simulation time as the quadrature weight. This keeps the
269 // finite-time projection invariant when the GUI speed changes the number
270 // of solver steps performed between display refreshes. The first frame gets
271 // a tiny seed weight so the map is immediately defined; it becomes
272 // negligible as soon as physical elapsed time accumulates.
273 double weight = hasAccumulationTime_ ? 0.0 : 1.0e-9;
275 const double elapsed = time - lastAccumulationTime_;
276 if (elapsed > 0.0 && std::isfinite(elapsed)) weight = elapsed;
277 }
280
281 const double phaseAngle = referenceEnergy_ * time / hbar_;
282 const std::complex<double> phase(std::cos(phaseAngle), std::sin(phaseAngle));
283 const std::size_t n = field.size();
284 for (std::size_t i = 0; i < n; ++i) {
285 const std::complex<double> psi(field.data()[i].real(), field.data()[i].imag());
286 const double rho = std::norm(psi);
287 meanDensitySum_[i] += rho * weight;
288 spectralAccumulator_[i] += psi * phase * weight;
289 }
290 totalTimeWeight_ += weight;
292
293 const double invWeight = totalTimeWeight_ > 0.0 ? 1.0 / totalTimeWeight_ : 0.0;
294 double filteredMax = 0.0;
295 for (std::size_t i = 0; i < n; ++i) {
296 meanDensity_[i] = static_cast<float>(meanDensitySum_[i] * invWeight);
297 const double v = std::norm(spectralAccumulator_[i]);
298 filteredMax = std::max(filteredMax, v);
299 }
300 const double invFilteredMax = filteredMax > 0.0 ? 1.0 / filteredMax : 0.0;
301 for (std::size_t i = 0; i < n; ++i) {
302 filteredDensity_[i] = static_cast<float>(std::norm(spectralAccumulator_[i]) * invFilteredMax);
303 }
304
305 QuantumScarSample2D sample;
306 sample.time = time;
307 sample.fidelity = QuantumScars2D::fidelity(reference_, field);
308 sample.instantaneousOrbitEnhancement =
310 sample.filteredOrbitEnhancement = filteredEnhancement();
311 samples_.push_back(sample);
312 trimToLimit();
313 return true;
314}
315
317{
318 if (!configured_ || spectralAccumulator_.empty()) return 0.0;
319 double total = 0.0;
320 double band = 0.0;
321 for (std::size_t y = 0; y < reference_.height(); ++y) {
322 const double py = GaussianPacket2D::yCoordinate(reference_, y);
323 for (std::size_t x = 0; x < reference_.width(); ++x) {
324 const double px = GaussianPacket2D::xCoordinate(reference_, x);
325 if (!QuantumScars2D::inside(geometry_, px, py)) continue;
326 const std::size_t i = y * reference_.width() + x;
327 const double w = std::norm(spectralAccumulator_[i]);
328 total += w;
329 if (std::fabs(py - geometry_.centerY) <= config_.orbitBandHalfWidth) band += w;
330 }
331 }
332 return enhancementFromWeights(band, total, geometry_, config_.orbitBandHalfWidth);
333}
334
336{
337 if (samples_.size() <= maximumSamples_) return;
338 const std::size_t excess = samples_.size() - maximumSamples_;
339 samples_.erase(samples_.begin(), samples_.begin() + static_cast<std::ptrdiff_t>(excess));
340}
341
342} // namespace qpe
Owning Cartesian grid of complex scalar samples.
std::size_t size() const noexcept
Returns the total number of complex samples.
bool empty() const noexcept
Returns whether the field contains no samples.
std::size_t height() const noexcept
Returns the number of samples along Y.
Value * data() noexcept
Returns mutable contiguous row-major sample storage.
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.
std::vector< std::complex< double > > spectralAccumulator_
bool append(double time, const ComplexField2D &field)
Carries out append.
double filteredEnhancement() const noexcept
Carries out filtered enhancement.
std::vector< float > meanDensity_
double referenceEnergy() const noexcept
Carries out reference energy.
std::vector< float > filteredDensity_
QuantumScarConfig2D config_
std::vector< double > meanDensitySum_
std::vector< QuantumScarSample2D > samples_
std::size_t maximumSamples() const noexcept
Carries out maximum samples.
QuantumScarGeometry2D geometry_
const QuantumScarGeometry2D & geometry() const noexcept
Carries out geometry.
void clear() noexcept
Clears stored state and returns the component to its empty/default state.
const QuantumScarConfig2D & config() const noexcept
Returns config.
void setMaximumSamples(std::size_t maximumSamples)
Sets maximum samples.
double hbar() const noexcept
Carries out hbar.
bool configure(const ComplexField2D &reference, const QuantumScarGeometry2D &geometry, const QuantumScarConfig2D &config, double referenceEnergy, double hbar=1.0)
Returns configure.
QuantumScarHistory2D(std::size_t maximumSamples=1600u)
Constructs a Quantum Scar History 2D instance.
void trimToLimit()
Carries out trim to limit.
static double orbitEnhancement(const ComplexField2D &field, const QuantumScarGeometry2D &geometry, double halfWidth) noexcept
Performs orbit enhancement.
static double axialOrbitPeriod(const QuantumScarGeometry2D &geometry, double speed) noexcept
Performs axial orbit period.
static double orbitBandArea(const QuantumScarGeometry2D &geometry, double halfWidth) noexcept
Performs orbit band area.
static double accessibleArea(const QuantumScarGeometry2D &geometry) noexcept
Performs accessible area.
static double fidelity(const ComplexField2D &reference, const ComplexField2D &field) noexcept
Performs fidelity.
static double axialOrbitRoundTripLength(const QuantumScarGeometry2D &geometry) noexcept
Performs axial orbit round trip length.
static bool inside(const QuantumScarGeometry2D &geometry, double x, double y) noexcept
Performs inside.
static bool identify(const PotentialScene2D &scene, QuantumScarGeometry2D &geometry, std::string *reason=nullptr) noexcept
Returns identify.
static bool validateConfig(const QuantumScarConfig2D &config, std::string *reason=nullptr) noexcept
Validates config.
Declares the gaussianpacket2d physics/numerical component used by QPE experiments.
double enhancementFromWeights(double bandWeight, double totalWeight, const QuantumScarGeometry2D &geometry, double halfWidth) noexcept
Performs enhancement from weights.
Declares the quantumscar2d physics/numerical component used by QPE experiments.
Data structure used by the Potential Object2D component.
bool valid() const noexcept
Performs valid.
Data structure used by the Quantum Scar Config2D component.
Data structure used by the Quantum Scar Geometry2D component.
Data structure used by the Quantum Scar Sample2D component.