Quantum Phenomena Explorer 7.9.8
Interactive quantum-phenomena Explorer Tutor — source documentation
Loading...
Searching...
No Matches
quantumbilliards2d.cpp
Go to the documentation of this file.
1/**
2 * @file quantumbilliards2d.cpp
3 * @brief Implements the quantumbilliards2d physics/numerical component used by QPE experiments.
4 * @ingroup qpe_quantum
5 *
6 * Source: @c core/quantum/quantumbilliards2d.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 "quantumbilliards2d.h"
13
15
16#include <algorithm>
17#include <cmath>
18#include <complex>
19#include <vector>
20
21namespace qpe {
22namespace {
23
24constexpr double kPi = 3.141592653589793238462643383279502884;
25
26/**
27 * @brief Performs fail.
28 * @param reason The reason value used by the operation.
29 * @param message The message value used by the operation.
30 */
31void fail(std::string *reason, const char *message) noexcept
32{
33 if (reason) *reason = message;
34}
35
36/**
37 * @brief Performs near.
38 * @param a The a value used by the operation.
39 * @param b The b value used by the operation.
40 * @param scale The scale value used by the operation.
41 */
42bool near(double a, double b, double scale = 1.0) noexcept
43{
44 return std::fabs(a - b) <= 1.0e-4 * std::max(1.0, std::fabs(scale));
45}
46
47/**
48 * @brief Performs same grid.
49 * @param a The a value used by the operation.
50 * @param b The b value used by the operation.
51 */
52bool sameGrid(const ComplexField2D &a, const ComplexField2D &b) noexcept
53{
54 return !a.empty() && !b.empty() &&
55 a.width() == b.width() && a.height() == b.height() &&
56 std::fabs(a.dx() - b.dx()) <= 1.0e-9f &&
57 std::fabs(a.dy() - b.dy()) <= 1.0e-9f;
58}
59
60/**
61 * @brief Performs inside accessible.
62 * @param g The g value used by the operation.
63 * @param x X coordinate/value.
64 * @param y Y coordinate/value.
65 */
66bool insideAccessible(const QuantumBilliardGeometry2D &g, double x, double y) noexcept
67{
68 if (!g.valid) return false;
69 const double hx = 0.5 * g.innerWidth;
70 const double hy = 0.5 * g.innerHeight;
71 if (x < g.centerX - hx || x > g.centerX + hx ||
72 y < g.centerY - hy || y > g.centerY + hy) {
73 return false;
74 }
75 if (g.hasObstacle) {
76 const double dx = x - g.obstacleCenterX;
77 const double dy = y - g.obstacleCenterY;
78 if (dx * dx + dy * dy <= g.obstacleRadius * g.obstacleRadius) return false;
79 }
80 return true;
81}
82
83} // namespace
84
87 std::string *reason) noexcept
88{
89 geometry = QuantumBilliardGeometry2D();
90 if (reason) reason->clear();
91
92 std::vector<const PotentialObject2D *> vertical;
93 std::vector<const PotentialObject2D *> horizontal;
94 const PotentialObject2D *circle = nullptr;
95
96 for (const PotentialObject2D &object : scene.objects()) {
97 if (!object.enabled) continue;
98 if (object.type == PotentialObjectType2D::Rectangle && object.value > 0.0f) {
99 if (object.height > object.width) vertical.push_back(&object);
100 else if (object.width > object.height) horizontal.push_back(&object);
101 else {
102 fail(reason, "Billiard walls must be clearly vertical or horizontal rectangles.");
103 return false;
104 }
105 } else if (object.type == PotentialObjectType2D::Circle && object.value > 0.0f) {
106 if (circle != nullptr) {
107 fail(reason, "Billiard analysis supports at most one circular scatterer.");
108 return false;
109 }
110 circle = &object;
111 } else {
112 fail(reason, "Billiard scene expects four positive Rectangle walls and optionally one positive Circle.");
113 return false;
114 }
115 }
116
117 if (vertical.size() != 2u || horizontal.size() != 2u) {
118 fail(reason, "Billiard scene needs exactly two vertical and two horizontal walls.");
119 return false;
120 }
121
122 std::sort(vertical.begin(), vertical.end(),
123 [](const PotentialObject2D *a, const PotentialObject2D *b) {
124 return a->centerX < b->centerX;
125 });
126 std::sort(horizontal.begin(), horizontal.end(),
127 [](const PotentialObject2D *a, const PotentialObject2D *b) {
128 return a->centerY < b->centerY;
129 });
130
131 const PotentialObject2D &left = *vertical[0];
132 const PotentialObject2D &right = *vertical[1];
133 const PotentialObject2D &bottom = *horizontal[0];
134 const PotentialObject2D &top = *horizontal[1];
135
136 const double leftInner = static_cast<double>(left.centerX) + 0.5 * left.width;
137 const double rightInner = static_cast<double>(right.centerX) - 0.5 * right.width;
138 const double bottomInner = static_cast<double>(bottom.centerY) + 0.5 * bottom.height;
139 const double topInner = static_cast<double>(top.centerY) - 0.5 * top.height;
140 const double width = rightInner - leftInner;
141 const double height = topInner - bottomInner;
142 if (!(width > 0.0) || !(height > 0.0)) {
143 fail(reason, "Billiard walls overlap or leave no interior.");
144 return false;
145 }
146
147 const double wallValue = left.value;
148 if (!near(right.value, wallValue, wallValue) ||
149 !near(bottom.value, wallValue, wallValue) ||
150 !near(top.value, wallValue, wallValue)) {
151 fail(reason, "All billiard walls must use the same potential height.");
152 return false;
153 }
154
155 const double centerX = 0.5 * (leftInner + rightInner);
156 const double centerY = 0.5 * (bottomInner + topInner);
157 if (!near(left.centerY, centerY, height) || !near(right.centerY, centerY, height) ||
158 !near(bottom.centerX, centerX, width) || !near(top.centerX, centerX, width)) {
159 fail(reason, "Billiard walls are not centred on one rectangular interior.");
160 return false;
161 }
162
163 geometry.valid = true;
164 geometry.centerX = centerX;
165 geometry.centerY = centerY;
166 geometry.innerWidth = width;
167 geometry.innerHeight = height;
168 geometry.wallPotential = wallValue;
169
170 if (circle != nullptr) {
171 const double radius = static_cast<double>(circle->radius);
172 if (!(radius > 0.0)) {
173 fail(reason, "Circular billiard scatterer must have positive radius.");
174 geometry = QuantumBilliardGeometry2D();
175 return false;
176 }
177 const double hx = 0.5 * width;
178 const double hy = 0.5 * height;
179 if (circle->centerX - radius <= centerX - hx ||
180 circle->centerX + radius >= centerX + hx ||
181 circle->centerY - radius <= centerY - hy ||
182 circle->centerY + radius >= centerY + hy) {
183 fail(reason, "Circular scatterer must lie completely inside the billiard.");
184 geometry = QuantumBilliardGeometry2D();
185 return false;
186 }
187 geometry.hasObstacle = true;
188 geometry.obstacleCenterX = circle->centerX;
189 geometry.obstacleCenterY = circle->centerY;
190 geometry.obstacleRadius = radius;
191 geometry.obstaclePotential = circle->value;
192 }
193
194 return true;
195}
196
198{
199 if (!geometry.valid || !(geometry.innerWidth > 0.0) || !(geometry.innerHeight > 0.0)) return 0.0;
200 double area = geometry.innerWidth * geometry.innerHeight;
201 if (geometry.hasObstacle && geometry.obstacleRadius > 0.0) {
202 area -= kPi * geometry.obstacleRadius * geometry.obstacleRadius;
203 }
204 return std::max(0.0, area);
205}
206
208 const ComplexField2D &field) noexcept
209{
210 if (!sameGrid(reference, field)) return 0.0;
211 std::complex<double> overlap(0.0, 0.0);
212 double na = 0.0;
213 double nb = 0.0;
214 for (std::size_t i = 0u; i < field.size(); ++i) {
215 const std::complex<double> a(reference.data()[i].real(), reference.data()[i].imag());
216 const std::complex<double> b(field.data()[i].real(), field.data()[i].imag());
217 overlap += std::conj(a) * b;
218 na += std::norm(a);
219 nb += std::norm(b);
220 }
221 if (!(na > 0.0) || !(nb > 0.0)) return 0.0;
222 return std::norm(overlap) / (na * nb);
223}
224
226 const QuantumBilliardGeometry2D &geometry,
227 const ComplexField2D *reference) noexcept
228{
230 if (field.empty() || !geometry.valid) return out;
231
232 const double cellArea = static_cast<double>(field.dx()) * static_cast<double>(field.dy());
233 double interiorNorm = 0.0;
234 double densitySquareIntegral = 0.0;
235 double shellProbability = 0.0;
236 const double shellWidth = geometry.hasObstacle
237 ? std::max(0.45, 2.5 * std::max(static_cast<double>(field.dx()), static_cast<double>(field.dy())))
238 : 0.0;
239 const double shellOuter = geometry.obstacleRadius + shellWidth;
240 const double shellOuter2 = shellOuter * shellOuter;
241 const double shellInner2 = geometry.obstacleRadius * geometry.obstacleRadius;
242
243 for (std::size_t y = 0u; y < field.height(); ++y) {
244 const double py = GaussianPacket2D::yCoordinate(field, y);
245 for (std::size_t x = 0u; x < field.width(); ++x) {
246 const double px = GaussianPacket2D::xCoordinate(field, x);
247 const ComplexField2D::Value v = field(x, y);
248 const double density = static_cast<double>(v.real()) * v.real() +
249 static_cast<double>(v.imag()) * v.imag();
250 const double probability = density * cellArea;
251 if (insideAccessible(geometry, px, py)) {
252 interiorNorm += probability;
253 densitySquareIntegral += density * density * cellArea;
254 }
255 if (geometry.hasObstacle) {
256 const double dx = px - geometry.obstacleCenterX;
257 const double dy = py - geometry.obstacleCenterY;
258 const double r2 = dx * dx + dy * dy;
259 if (r2 > shellInner2 && r2 <= shellOuter2) shellProbability += probability;
260 }
261 }
262 }
263
264 out.accessibleProbability = interiorNorm;
265 if (interiorNorm > 1.0e-20 && densitySquareIntegral > 1.0e-30) {
266 // Renormalize density to the accessible probability before evaluating IPR.
267 out.effectiveArea = interiorNorm * interiorNorm / densitySquareIntegral;
268 const double area = accessibleArea(geometry);
269 if (area > 0.0) out.occupiedFraction = std::max(0.0, std::min(1.0, out.effectiveArea / area));
270 }
271 out.obstacleShellProbability = shellProbability;
272 if (reference != nullptr) out.fidelity = fidelity(*reference, field);
273 return out;
274}
275
277 : maximumSamples_(std::max<std::size_t>(1u, maximumSamples))
278{
279}
280
282{
284 samples_.clear();
287}
288
290 const QuantumBilliardGeometry2D &geometry)
291{
292 clear();
293 if (field.empty() || !geometry.valid) return false;
294 reference_ = field;
295 meanProbabilityDensity_.assign(field.size(), 0.0f);
296 return true;
297}
298
299void QuantumBilliardHistory2D::setMaximumSamples(std::size_t maximumSamples)
300{
301 maximumSamples_ = std::max<std::size_t>(1u, maximumSamples);
302 trimToLimit();
303}
304
306 const ComplexField2D &field,
307 const QuantumBilliardGeometry2D &geometry)
308{
309 if (!hasReference() || !sameGrid(reference_, field) || !geometry.valid) return false;
310
311 const QuantumBilliardMetrics2D metrics = QuantumBilliards2D::measure(field, geometry, &reference_);
313 sample.time = time;
314 sample.effectiveArea = metrics.effectiveArea;
315 sample.occupiedFraction = metrics.occupiedFraction;
316 sample.obstacleShellProbability = metrics.obstacleShellProbability;
317 sample.fidelity = metrics.fidelity;
318 samples_.push_back(sample);
319
321 const double n = static_cast<double>(accumulatedFrames_);
322 for (std::size_t i = 0u; i < field.size(); ++i) {
323 const ComplexField2D::Value v = field.data()[i];
324 const double density = static_cast<double>(v.real()) * v.real() +
325 static_cast<double>(v.imag()) * v.imag();
326 const double previous = meanProbabilityDensity_[i];
327 meanProbabilityDensity_[i] = static_cast<float>(previous + (density - previous) / n);
328 }
329
330 trimToLimit();
331 return true;
332}
333
335{
336 if (samples_.size() <= maximumSamples_) return;
337 const std::size_t removeCount = samples_.size() - maximumSamples_;
338 samples_.erase(samples_.begin(), samples_.begin() + static_cast<std::ptrdiff_t>(removeCount));
339}
340
341} // 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.
void clear() noexcept
Releases all samples and returns the field to the empty state.
Value * data() noexcept
Returns mutable contiguous row-major sample storage.
std::complex< float > Value
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::size_t maximumSamples() const noexcept
Carries out maximum samples.
QuantumBilliardHistory2D(std::size_t maximumSamples=1600u)
Constructs a Quantum Billiard History 2D instance.
bool captureReference(const ComplexField2D &field, const QuantumBilliardGeometry2D &geometry)
Carries out capture reference.
bool append(double time, const ComplexField2D &field, const QuantumBilliardGeometry2D &geometry)
Carries out append.
void trimToLimit()
Carries out trim to limit.
void clear() noexcept
Clears stored state and returns the component to its empty/default state.
void setMaximumSamples(std::size_t maximumSamples)
Sets maximum samples.
std::vector< float > meanProbabilityDensity_
bool hasReference() const noexcept
Returns whether reference.
std::vector< QuantumBilliardSample2D > samples_
static double accessibleArea(const QuantumBilliardGeometry2D &geometry) noexcept
Performs accessible area.
static QuantumBilliardMetrics2D measure(const ComplexField2D &field, const QuantumBilliardGeometry2D &geometry, const ComplexField2D *reference=nullptr) noexcept
Performs measure.
static double fidelity(const ComplexField2D &reference, const ComplexField2D &field) noexcept
Performs fidelity.
static bool identify(const PotentialScene2D &scene, QuantumBilliardGeometry2D &geometry, std::string *reason=nullptr) noexcept
Returns identify.
Declares the gaussianpacket2d physics/numerical component used by QPE experiments.
bool insideAccessible(const QuantumBilliardGeometry2D &g, double x, double y) noexcept
Performs inside accessible.
Declares the quantumbilliards2d physics/numerical component used by QPE experiments.
Data structure used by the Potential Object2D component.
Data structure used by the Quantum Billiard Geometry2D component.
Data structure used by the Quantum Billiard Metrics2D component.
Data structure used by the Quantum Billiard Sample2D component.