Quantum Phenomena Explorer 7.9.8
Interactive quantum-phenomena Explorer Tutor — source documentation
Loading...
Searching...
No Matches
momentumspace2d.cpp
Go to the documentation of this file.
1/**
2 * @file momentumspace2d.cpp
3 * @brief Implements the momentumspace2d physics/numerical component used by QPE experiments.
4 * @ingroup qpe_quantum
5 *
6 * Source: @c core/quantum/momentumspace2d.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 "momentumspace2d.h"
13
14#include "core/math/fft2d.h"
15
16#include <algorithm>
17#include <cmath>
18#include <complex>
19#include <stdexcept>
20
21namespace qpe {
22namespace {
23
24const double kPi = 3.141592653589793238462643383279502884;
25
26/**
27 * @brief Performs signed fft index.
28 * @param index Zero-based index of the selected item.
29 * @param count The count value used by the operation.
30 */
31long long signedFftIndex(std::size_t index, std::size_t count)
32{
33 const std::size_t half = count / 2u;
34 if (index < half) {
35 return static_cast<long long>(index);
36 }
37 return static_cast<long long>(index) - static_cast<long long>(count);
38}
39
40std::size_t shiftedSourceIndex(std::size_t shiftedIndex, std::size_t count)
41{
42 return (shiftedIndex + count / 2u) % count;
43}
44
45std::size_t nearestCoordinateIndex(const std::vector<double> &coordinates,
46 double value)
47{
48 if (coordinates.empty()) {
49 return 0u;
50 }
51
52 const std::vector<double>::const_iterator it =
53 std::lower_bound(coordinates.begin(), coordinates.end(), value);
54 if (it == coordinates.begin()) {
55 return 0u;
56 }
57 if (it == coordinates.end()) {
58 return coordinates.size() - 1u;
59 }
60
61 const std::size_t right = static_cast<std::size_t>(it - coordinates.begin());
62 const std::size_t left = right - 1u;
63 return std::fabs(value - coordinates[left]) <=
64 std::fabs(coordinates[right] - value)
65 ? left
66 : right;
67}
68
69/**
70 * @brief Performs append sample.
71 * @param value Value to assign or evaluate.
72 * @param coordinate The coordinate value used by the operation.
73 * @param out The out value used by the operation.
74 */
76 double coordinate,
78{
79 const double re = static_cast<double>(value.real());
80 const double im = static_cast<double>(value.imag());
81 out.coordinate.push_back(coordinate);
82 out.realPart.push_back(re);
83 out.imaginaryPart.push_back(im);
84 out.probability.push_back(re * re + im * im);
85 out.phase.push_back(std::atan2(im, re));
86}
87
88} // namespace
89
91 double hbar)
92{
93 if (field.empty()) {
94 throw std::invalid_argument("MomentumSpace2D: field must not be empty");
95 }
96 if (!(hbar > 0.0) || !std::isfinite(hbar)) {
97 throw std::invalid_argument("MomentumSpace2D: hbar must be finite and positive");
98 }
99 if (!FFT2D::supportsSize(field.width(), field.height())) {
100 throw std::invalid_argument("MomentumSpace2D: FFT grid dimensions must be powers of two");
101 }
102
103 const std::size_t width = field.width();
104 const std::size_t height = field.height();
105 const double dx = static_cast<double>(field.dx());
106 const double dy = static_cast<double>(field.dy());
107 const double dpX = 2.0 * kPi * hbar / (static_cast<double>(width) * dx);
108 const double dpY = 2.0 * kPi * hbar / (static_cast<double>(height) * dy);
109 const double amplitudeScale = dx * dy / (2.0 * kPi * hbar);
110
111 ComplexField2D transformed = field;
112 FFT2D::forward(transformed);
113
115 out.hbar = hbar;
116 out.spectrum.resize(width, height,
117 static_cast<float>(dpX),
118 static_cast<float>(dpY));
119 out.pxCoordinates.resize(width);
120 out.pyCoordinates.resize(height);
121
122 for (std::size_t x = 0u; x < width; ++x) {
123 const std::size_t sourceX = shiftedSourceIndex(x, width);
124 out.pxCoordinates[x] =
125 static_cast<double>(signedFftIndex(sourceX, width)) * dpX;
126 }
127 for (std::size_t y = 0u; y < height; ++y) {
128 const std::size_t sourceY = shiftedSourceIndex(y, height);
129 out.pyCoordinates[y] =
130 static_cast<double>(signedFftIndex(sourceY, height)) * dpY;
131 }
132
133 const double xOriginIndex = 0.5 * static_cast<double>(width - 1u);
134 const double yOriginIndex = 0.5 * static_cast<double>(height - 1u);
135 double rawNorm = 0.0;
136 double rawPx = 0.0;
137 double rawPy = 0.0;
138 double rawPx2 = 0.0;
139 double rawPy2 = 0.0;
140 double peakProbability = -1.0;
141
142 for (std::size_t y = 0u; y < height; ++y) {
143 const std::size_t sourceY = shiftedSourceIndex(y, height);
144 const double py = out.pyCoordinates[y];
145 for (std::size_t x = 0u; x < width; ++x) {
146 const std::size_t sourceX = shiftedSourceIndex(x, width);
147 const double px = out.pxCoordinates[x];
148
149 // FFT2D assumes sample coordinate j*dx. The simulated spatial grid
150 // is centred around zero, so restore the corresponding Fourier
151 // phase. It does not change |psi_tilde|^2, but it makes the stored
152 // complex spectrum physically consistent for future extensions.
153 const double phase = 2.0 * kPi *
154 (static_cast<double>(sourceX) * xOriginIndex / static_cast<double>(width) +
155 static_cast<double>(sourceY) * yOriginIndex / static_cast<double>(height));
156 const std::complex<double> originPhase(std::cos(phase), std::sin(phase));
157
158 const ComplexField2D::Value &src = transformed(sourceX, sourceY);
159 const std::complex<double> value(
160 static_cast<double>(src.real()),
161 static_cast<double>(src.imag()));
162 const std::complex<double> scaled = value * originPhase * amplitudeScale;
164 static_cast<float>(scaled.real()),
165 static_cast<float>(scaled.imag()));
166
167 const double probability = std::norm(scaled);
168 const double weight = probability * dpX * dpY;
169 rawNorm += weight;
170 rawPx += px * weight;
171 rawPy += py * weight;
172 rawPx2 += px * px * weight;
173 rawPy2 += py * py * weight;
174
175 if (probability > peakProbability) {
176 peakProbability = probability;
177 out.peakPx = px;
178 out.peakPy = py;
179 }
180 }
181 }
182
183 out.norm2 = rawNorm;
184 if (rawNorm > 0.0 && std::isfinite(rawNorm)) {
185 out.meanPx = rawPx / rawNorm;
186 out.meanPy = rawPy / rawNorm;
187 const double variancePx = std::max(0.0, rawPx2 / rawNorm - out.meanPx * out.meanPx);
188 const double variancePy = std::max(0.0, rawPy2 / rawNorm - out.meanPy * out.meanPy);
189 out.sigmaPx = std::sqrt(variancePx);
190 out.sigmaPy = std::sqrt(variancePy);
191 }
192
193 return out;
194}
195
197 const MomentumSpace2DResult &momentum,
198 CrossSectionAxis axis,
199 double position)
200{
202 out.axis = axis;
203 out.requestedPosition = position;
204
205 if (momentum.empty() ||
206 momentum.pxCoordinates.size() != momentum.spectrum.width() ||
207 momentum.pyCoordinates.size() != momentum.spectrum.height()) {
208 return out;
209 }
210
211 if (axis == CrossSectionAxis::HorizontalY) {
212 const std::size_t y = nearestCoordinateIndex(momentum.pyCoordinates, position);
213 out.actualPosition = momentum.pyCoordinates[y];
214 out.coordinate.reserve(momentum.spectrum.width());
215 out.probability.reserve(momentum.spectrum.width());
216 out.realPart.reserve(momentum.spectrum.width());
217 out.imaginaryPart.reserve(momentum.spectrum.width());
218 out.phase.reserve(momentum.spectrum.width());
219 for (std::size_t x = 0u; x < momentum.spectrum.width(); ++x) {
220 appendSample(momentum.spectrum(x, y), momentum.pxCoordinates[x], out);
221 }
222 } else {
223 const std::size_t x = nearestCoordinateIndex(momentum.pxCoordinates, position);
224 out.actualPosition = momentum.pxCoordinates[x];
225 out.coordinate.reserve(momentum.spectrum.height());
226 out.probability.reserve(momentum.spectrum.height());
227 out.realPart.reserve(momentum.spectrum.height());
228 out.imaginaryPart.reserve(momentum.spectrum.height());
229 out.phase.reserve(momentum.spectrum.height());
230 for (std::size_t y = 0u; y < momentum.spectrum.height(); ++y) {
231 appendSample(momentum.spectrum(x, y), momentum.pyCoordinates[y], out);
232 }
233 }
234
235 return out;
236}
237
238} // 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.
void resize(std::size_t width, std::size_t height, float dx=1.0f, float dy=1.0f)
Reallocates the grid and resets all samples to zero.
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 bool supportsSize(std::size_t width, std::size_t height) noexcept
Returns whether both grid dimensions are supported by the radix-2 backend.
Definition fft2d.cpp:30
static void forward(ComplexField2D &field)
Convenience wrapper for an in-place forward transform.
Definition fft2d.cpp:59
static WaveFunctionCrossSection2DData sample(const MomentumSpace2DResult &momentum, CrossSectionAxis axis, double position)
Performs sample.
static MomentumSpace2DResult compute(const ComplexField2D &field, double hbar=1.0)
Performs compute.
Declares the two-dimensional FFT utility used by spectral quantum propagators and diagnostics.
Declares the momentumspace2d physics/numerical component used by QPE experiments.
std::size_t shiftedSourceIndex(std::size_t shiftedIndex, std::size_t count)
void appendSample(const ComplexField2D::Value &value, double coordinate, WaveFunctionCrossSection2DData &out)
Performs append sample.
std::size_t nearestCoordinateIndex(const std::vector< double > &coordinates, double value)
long long signedFftIndex(std::size_t index, std::size_t count)
Performs signed fft index.
CrossSectionAxis
Enumerates the supported Cross Section Axis values.
Data record containing Momentum Space2D Result.
std::vector< double > pyCoordinates
bool empty() const noexcept
Returns whether the object contains no data.
std::vector< double > pxCoordinates
Data structure used by the Wave Function Cross Section2D Data component.