Quantum Phenomena Explorer 7.9.8
Interactive quantum-phenomena Explorer Tutor — source documentation
Loading...
Searching...
No Matches
probabilitycontours2d.cpp
Go to the documentation of this file.
1/**
2 * @file probabilitycontours2d.cpp
3 * @brief Implements the probabilitycontours2d physics/numerical component used by QPE experiments.
4 * @ingroup qpe_quantum
5 *
6 * Source: @c core/quantum/probabilitycontours2d.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
14
15#include <algorithm>
16#include <cmath>
17
18namespace qpe {
19namespace {
20
21struct P2 { float x; float y; };
22
23/**
24 * @brief Returns probability.
25 * @param field Field data to read or modify.
26 * @param x X coordinate/value.
27 * @param y Y coordinate/value.
28 */
29float probability(const ComplexField2D &field, std::size_t x, std::size_t y)
30{
31 return static_cast<float>(std::norm(field(x, y)));
32}
33
34/**
35 * @brief Performs x coordinate.
36 * @param field Field data to read or modify.
37 * @param x X coordinate/value.
38 */
39float xCoordinate(const ComplexField2D &field, std::size_t x)
40{
41 const double centre = 0.5 * static_cast<double>(field.width() - 1u);
42 return static_cast<float>((static_cast<double>(x) - centre) * static_cast<double>(field.dx()));
43}
44
45/**
46 * @brief Performs y coordinate.
47 * @param field Field data to read or modify.
48 * @param y Y coordinate/value.
49 */
50float yCoordinate(const ComplexField2D &field, std::size_t y)
51{
52 const double centre = 0.5 * static_cast<double>(field.height() - 1u);
53 return static_cast<float>((static_cast<double>(y) - centre) * static_cast<double>(field.dy()));
54}
55
56/**
57 * @brief Performs interpolate.
58 * @param a The a value used by the operation.
59 * @param b The b value used by the operation.
60 * @param va The va value used by the operation.
61 * @param vb The vb value used by the operation.
62 * @param level The level value used by the operation.
63 */
64P2 interpolate(const P2 &a, const P2 &b, float va, float vb, float level)
65{
66 const float dv = vb - va;
67 float t = 0.5f;
68 if (std::fabs(dv) > 1.0e-30f) {
69 t = (level - va) / dv;
70 }
71 t = std::max(0.0f, std::min(1.0f, t));
72 P2 p;
73 p.x = a.x + t * (b.x - a.x);
74 p.y = a.y + t * (b.y - a.y);
75 return p;
76}
77
78/**
79 * @brief Adds segment.
80 * @param out The out value used by the operation.
81 * @param a The a value used by the operation.
82 * @param b The b value used by the operation.
83 * @param level The level value used by the operation.
84 * @param normalizedLevel The normalized level value used by the operation.
85 */
86void addSegment(ProbabilityContours2DResult &out,
87 const P2 &a,
88 const P2 &b,
89 float level,
90 float normalizedLevel)
91{
93 s.x1 = a.x;
94 s.y1 = a.y;
95 s.x2 = b.x;
96 s.y2 = b.y;
97 s.level = level;
98 s.normalizedLevel = normalizedLevel;
99 out.segments.push_back(s);
100}
101
102std::size_t automaticStride(std::size_t width,
103 std::size_t height,
104 std::size_t targetSamplesPerAxis)
105{
106 const std::size_t target = std::max<std::size_t>(32u, targetSamplesPerAxis);
107 const std::size_t largest = std::max(width, height);
108 if (largest <= target) {
109 return 1u;
110 }
111 return std::max<std::size_t>(1u, (largest + target - 1u) / target);
112}
113
114} // namespace
115
117 const ComplexField2D &field,
118 std::size_t requestedLevels,
120 float logDecades,
121 std::size_t targetSamplesPerAxis)
122{
124 out.spacing = spacing;
125 out.logDecades = std::max(0.25f, std::min(12.0f, logDecades));
126
127 if (field.empty() || field.width() < 2u || field.height() < 2u || requestedLevels == 0u) {
128 return out;
129 }
130
131 float maxProbability = 0.0f;
132 for (std::size_t y = 0u; y < field.height(); ++y) {
133 for (std::size_t x = 0u; x < field.width(); ++x) {
134 maxProbability = std::max(maxProbability, probability(field, x, y));
135 }
136 }
137 out.maximumProbability = maxProbability;
138 if (!(maxProbability > 1.0e-30f) || !std::isfinite(static_cast<double>(maxProbability))) {
139 return out;
140 }
141
142 const std::size_t count = std::max<std::size_t>(1u, requestedLevels);
143 out.levels.reserve(count);
144 for (std::size_t i = 0u; i < count; ++i) {
145 const float f = static_cast<float>(i + 1u) / static_cast<float>(count + 1u);
146 float level = 0.0f;
148 // f runs from faint to bright. End points stay strictly inside
149 // the data range so each level has a chance to produce a contour.
150 const float exponent = -out.logDecades * (1.0f - f);
151 level = maxProbability * std::pow(10.0f, exponent);
152 } else {
153 level = maxProbability * f;
154 }
155 out.levels.push_back(level);
156 }
157
158 out.samplingStride = automaticStride(field.width(), field.height(), targetSamplesPerAxis);
159 const std::size_t stride = out.samplingStride;
160
161 for (std::size_t li = 0u; li < out.levels.size(); ++li) {
162 const float level = out.levels[li];
163 // Colour rank is based on contour order rather than raw probability.
164 // With logarithmic levels the latter would compress nearly every faint
165 // contour into the same blue colour.
166 const float normalizedLevel =
167 static_cast<float>(li + 1u) / static_cast<float>(out.levels.size() + 1u);
168
169 for (std::size_t y0 = 0u; y0 + 1u < field.height(); y0 += stride) {
170 const std::size_t y1 = std::min(field.height() - 1u, y0 + stride);
171 if (y1 == y0) {
172 continue;
173 }
174 for (std::size_t x0 = 0u; x0 + 1u < field.width(); x0 += stride) {
175 const std::size_t x1 = std::min(field.width() - 1u, x0 + stride);
176 if (x1 == x0) {
177 continue;
178 }
179
180 const float v0 = probability(field, x0, y0); // bottom-left
181 const float v1 = probability(field, x1, y0); // bottom-right
182 const float v2 = probability(field, x1, y1); // top-right
183 const float v3 = probability(field, x0, y1); // top-left
184
185 int mask = 0;
186 if (v0 > level) mask |= 1;
187 if (v1 > level) mask |= 2;
188 if (v2 > level) mask |= 4;
189 if (v3 > level) mask |= 8;
190 if (mask == 0 || mask == 15) {
191 continue;
192 }
193
194 const P2 p0 = { xCoordinate(field, x0), yCoordinate(field, y0) };
195 const P2 p1 = { xCoordinate(field, x1), yCoordinate(field, y0) };
196 const P2 p2 = { xCoordinate(field, x1), yCoordinate(field, y1) };
197 const P2 p3 = { xCoordinate(field, x0), yCoordinate(field, y1) };
198
199 P2 edge[4];
200 edge[0] = interpolate(p0, p1, v0, v1, level);
201 edge[1] = interpolate(p1, p2, v1, v2, level);
202 edge[2] = interpolate(p2, p3, v2, v3, level);
203 edge[3] = interpolate(p3, p0, v3, v0, level);
204
205 switch (mask) {
206 case 1: case 14: addSegment(out, edge[3], edge[0], level, normalizedLevel); break;
207 case 2: case 13: addSegment(out, edge[0], edge[1], level, normalizedLevel); break;
208 case 3: case 12: addSegment(out, edge[3], edge[1], level, normalizedLevel); break;
209 case 4: case 11: addSegment(out, edge[1], edge[2], level, normalizedLevel); break;
210 case 6: case 9: addSegment(out, edge[0], edge[2], level, normalizedLevel); break;
211 case 7: case 8: addSegment(out, edge[3], edge[2], level, normalizedLevel); break;
212 case 5: {
213 const float centre = 0.25f * (v0 + v1 + v2 + v3);
214 if (centre > level) {
215 addSegment(out, edge[3], edge[2], level, normalizedLevel);
216 addSegment(out, edge[0], edge[1], level, normalizedLevel);
217 } else {
218 addSegment(out, edge[3], edge[0], level, normalizedLevel);
219 addSegment(out, edge[2], edge[1], level, normalizedLevel);
220 }
221 break;
222 }
223 case 10: {
224 const float centre = 0.25f * (v0 + v1 + v2 + v3);
225 if (centre > level) {
226 addSegment(out, edge[3], edge[0], level, normalizedLevel);
227 addSegment(out, edge[2], edge[1], level, normalizedLevel);
228 } else {
229 addSegment(out, edge[3], edge[2], level, normalizedLevel);
230 addSegment(out, edge[0], edge[1], level, normalizedLevel);
231 }
232 break;
233 }
234 default:
235 break;
236 }
237 }
238 }
239 }
240
241 return out;
242}
243
244} // 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.
float dy() const noexcept
Returns the Y grid spacing.
std::size_t width() const noexcept
Returns the number of samples along X.
static ProbabilityContours2DResult compute(const ComplexField2D &field, std::size_t requestedLevels, ProbabilityContourSpacing2D spacing, float logDecades=4.0f, std::size_t targetSamplesPerAxis=384u)
Performs compute.
Declares the complex scalar field container representing two-dimensional wave functions.
float yCoordinate(const ComplexField2D &field, std::size_t y)
Performs y coordinate.
float probability(const ComplexField2D &field, std::size_t x, std::size_t y)
Returns probability.
std::size_t automaticStride(std::size_t width, std::size_t height, std::size_t targetSamplesPerAxis)
float xCoordinate(const ComplexField2D &field, std::size_t x)
Performs x coordinate.
ProbabilityContourSpacing2D
Enumerates the supported Probability Contour Spacing2D values.
Declares the probabilitycontours2d physics/numerical component used by QPE experiments.
Data structure used by the Probability Contour Segment2D component.
Data record containing Probability Contours2D Result.
ProbabilityContourSpacing2D spacing
std::vector< ProbabilityContourSegment2D > segments