24const double kPi = 3.141592653589793238462643383279502884;
33 const std::size_t half = count / 2u;
35 return static_cast<long long>(index);
37 return static_cast<long long>(index) -
static_cast<long long>(count);
42 return (shiftedIndex + count / 2u) % count;
48 if (coordinates.empty()) {
52 const std::vector<double>::const_iterator it =
53 std::lower_bound(coordinates.begin(), coordinates.end(), value);
54 if (it == coordinates.begin()) {
57 if (it == coordinates.end()) {
58 return coordinates.size() - 1u;
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)
79 const double re =
static_cast<double>(value.real());
80 const double im =
static_cast<double>(value.imag());
85 out.
phase.push_back(std::atan2(im, re));
94 throw std::invalid_argument(
"MomentumSpace2D: field must not be empty");
96 if (!(hbar > 0.0) || !std::isfinite(hbar)) {
97 throw std::invalid_argument(
"MomentumSpace2D: hbar must be finite and positive");
100 throw std::invalid_argument(
"MomentumSpace2D: FFT grid dimensions must be powers of two");
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);
117 static_cast<float>(dpX),
118 static_cast<float>(dpY));
122 for (std::size_t x = 0u; x < width; ++x) {
123 const std::size_t sourceX = shiftedSourceIndex(x, width);
125 static_cast<double>(signedFftIndex(sourceX, width)) * dpX;
127 for (std::size_t y = 0u; y < height; ++y) {
128 const std::size_t sourceY = shiftedSourceIndex(y, height);
130 static_cast<double>(signedFftIndex(sourceY, height)) * dpY;
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;
140 double peakProbability = -1.0;
142 for (std::size_t y = 0u; y < height; ++y) {
143 const std::size_t sourceY = shiftedSourceIndex(y, height);
145 for (std::size_t x = 0u; x < width; ++x) {
146 const std::size_t sourceX = shiftedSourceIndex(x, width);
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));
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()));
167 const double probability = std::norm(scaled);
168 const double weight = probability * dpX * dpY;
170 rawPx += px * weight;
171 rawPy += py * weight;
172 rawPx2 += px * px * weight;
173 rawPy2 += py * py * weight;
175 if (probability > peakProbability) {
176 peakProbability = probability;
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);
205 if (momentum.
empty() ||
212 const std::size_t y = nearestCoordinateIndex(momentum.
pyCoordinates, position);
219 for (std::size_t x = 0u; x < momentum.
spectrum.
width(); ++x) {
223 const std::size_t x = nearestCoordinateIndex(momentum.
pxCoordinates, position);
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.
static void forward(ComplexField2D &field)
Convenience wrapper for an in-place forward transform.
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.
std::vector< double > phase
std::vector< double > probability
std::vector< double > imaginaryPart
std::vector< double > realPart
std::vector< double > coordinate