24constexpr double kPi = 3.141592653589793238462643383279502884;
31void fail(std::string *reason,
const char *message)
noexcept
33 if (reason) *reason = message;
42bool near(
double a,
double b,
double scale = 1.0) noexcept
44 return std::fabs(a - b) <= 1.0e-4 * std::max(1.0, std::fabs(scale));
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;
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) {
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;
87 std::string *reason)
noexcept
90 if (reason) reason->clear();
92 std::vector<const PotentialObject2D *> vertical;
93 std::vector<const PotentialObject2D *> horizontal;
97 if (!
object.enabled)
continue;
99 if (
object.height >
object.width) vertical.push_back(&
object);
100 else if (
object.width >
object.height) horizontal.push_back(&
object);
102 fail(reason,
"Billiard walls must be clearly vertical or horizontal rectangles.");
106 if (circle !=
nullptr) {
107 fail(reason,
"Billiard analysis supports at most one circular scatterer.");
112 fail(reason,
"Billiard scene expects four positive Rectangle walls and optionally one positive Circle.");
117 if (vertical.size() != 2u || horizontal.size() != 2u) {
118 fail(reason,
"Billiard scene needs exactly two vertical and two horizontal walls.");
122 std::sort(vertical.begin(), vertical.end(),
124 return a->centerX < b->centerX;
126 std::sort(horizontal.begin(), horizontal.end(),
128 return a->centerY < b->centerY;
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.");
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.");
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.");
163 geometry.valid =
true;
164 geometry.centerX = centerX;
165 geometry.centerY = centerY;
166 geometry.innerWidth = width;
167 geometry.innerHeight = height;
168 geometry.wallPotential = wallValue;
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.");
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.");
187 geometry.hasObstacle =
true;
188 geometry.obstacleCenterX = circle->
centerX;
189 geometry.obstacleCenterY = circle->
centerY;
190 geometry.obstacleRadius = radius;
191 geometry.obstaclePotential = circle->
value;
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;
204 return std::max(0.0, area);
210 if (!sameGrid(reference, field))
return 0.0;
211 std::complex<double> overlap(0.0, 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;
221 if (!(na > 0.0) || !(nb > 0.0))
return 0.0;
222 return std::norm(overlap) / (na * nb);
230 if (field.empty() || !geometry.valid)
return out;
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())))
239 const double shellOuter = geometry.obstacleRadius + shellWidth;
240 const double shellOuter2 = shellOuter * shellOuter;
241 const double shellInner2 = geometry.obstacleRadius * geometry.obstacleRadius;
243 for (std::size_t y = 0u; y < field.height(); ++y) {
245 for (std::size_t x = 0u; x < field.width(); ++x) {
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;
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;
265 if (interiorNorm > 1.0e-20 && densitySquareIntegral > 1.0e-30) {
267 out.
effectiveArea = interiorNorm * interiorNorm / densitySquareIntegral;
268 const double area = accessibleArea(geometry);
272 if (reference !=
nullptr) out.
fidelity = fidelity(*reference, field);
277 : maximumSamples_(std::max<std::size_t>(1u, maximumSamples))
293 if (field.
empty() || !geometry.
valid)
return false;
322 for (std::size_t i = 0u; i < field.
size(); ++i) {
324 const double density =
static_cast<double>(v.real()) * v.real() +
325 static_cast<double>(v.imag()) * v.imag();
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.
std::size_t maximumSamples_
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.
ComplexField2D reference_
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_
std::size_t accumulatedFrames_
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.
double obstacleShellProbability
double accessibleProbability
Data structure used by the Quantum Billiard Sample2D component.