Quantum Phenomena Explorer 7.9.8
Interactive quantum-phenomena Explorer Tutor — source documentation
Loading...
Searching...
No Matches
quantumtalbot2d.cpp
Go to the documentation of this file.
1/**
2 * @file quantumtalbot2d.cpp
3 * @brief Implements the quantumtalbot2d physics/numerical component used by QPE experiments.
4 * @ingroup qpe_quantum
5 *
6 * Source: @c core/quantum/quantumtalbot2d.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 "quantumtalbot2d.h"
13
15
16#include <algorithm>
17#include <cmath>
18#include <complex>
19#include <limits>
20#include <stdexcept>
21
22namespace qpe {
23namespace {
24
25constexpr double kPi = 3.141592653589793238462643383279502884;
26
27/**
28 * @brief Performs finite.
29 * @param v The v value used by the operation.
30 */
31bool finite(double v) noexcept
32{
33 return std::isfinite(v);
34}
35
36/**
37 * @brief Performs fail.
38 * @param reason The reason value used by the operation.
39 * @param message The message value used by the operation.
40 */
41void fail(std::string *reason, const char *message) noexcept
42{
43 if (reason) *reason = message;
44}
45
46/**
47 * @brief Performs wrapped distance.
48 * @param x X coordinate/value.
49 * @param period The period value used by the operation.
50 */
51double wrappedDistance(double x, double period) noexcept
52{
53 if (!(period > 0.0)) return 0.0;
54 return x - period * std::floor(x / period + 0.5);
55}
56
57} // namespace
58
60 std::string *reason) noexcept
61{
62 if (reason) reason->clear();
63 if (!(config.period > 0.0) || !finite(config.period)) {
64 fail(reason, "Talbot period must be finite and positive.");
65 return false;
66 }
67 if (!(config.toothSigma > 0.0) || !finite(config.toothSigma) ||
68 config.toothSigma >= 0.5 * config.period) {
69 fail(reason, "Talbot tooth sigma must be positive and smaller than half the period.");
70 return false;
71 }
72 if (!(config.sigmaY > 0.0) || !finite(config.sigmaY)) {
73 fail(reason, "Talbot longitudinal sigma must be finite and positive.");
74 return false;
75 }
76 if (!finite(config.offsetX) || !finite(config.centerY) || !finite(config.momentumY)) {
77 fail(reason, "Talbot initial-state position and momentum values must be finite.");
78 return false;
79 }
80 return true;
81}
82
85 double hbar,
86 std::string *reason)
87{
88 if (reason) reason->clear();
89 if (field.empty()) {
90 fail(reason, "Talbot initial state requires a non-empty field.");
91 return false;
92 }
93 if (!validateConfig(config, reason)) return false;
94 if (!(hbar > 0.0) || !finite(hbar)) {
95 fail(reason, "Talbot initial state requires positive finite hbar.");
96 return false;
97 }
98
99 const double physicalWidth = static_cast<double>(field.width()) * field.dx();
100 const double periods = physicalWidth / config.period;
101 const double nearest = std::floor(periods + 0.5);
102 if (nearest < 2.0 || std::fabs(periods - nearest) > 1.0e-5) {
103 fail(reason, "Talbot comb period must divide the FFT domain width exactly.");
104 return false;
105 }
106
107 const double periodCells = config.period / static_cast<double>(field.dx());
108 const double nearestCells = std::floor(periodCells + 0.5);
109 if (nearestCells < 4.0 || std::fabs(periodCells - nearestCells) > 1.0e-5 ||
110 (static_cast<long long>(nearestCells) % 2LL) != 0LL) {
111 fail(reason, "Talbot period must span an even integer number of grid cells.");
112 return false;
113 }
114
115 const double inv4SigmaX2 = 1.0 / (4.0 * config.toothSigma * config.toothSigma);
116 const double inv4SigmaY2 = 1.0 / (4.0 * config.sigmaY * config.sigmaY);
117 const double invHbar = 1.0 / hbar;
118
119 for (std::size_t y = 0u; y < field.height(); ++y) {
120 const double py = GaussianPacket2D::yCoordinate(field, y);
121 const double dy = py - config.centerY;
122 const double envelopeY = std::exp(-(dy * dy) * inv4SigmaY2);
123 const double phase = config.momentumY * py * invHbar;
124 const std::complex<float> phaseY(
125 static_cast<float>(std::cos(phase)),
126 static_cast<float>(std::sin(phase)));
127
128 for (std::size_t x = 0u; x < field.width(); ++x) {
129 const double px = GaussianPacket2D::xCoordinate(field, x);
130 const double d = wrappedDistance(px - config.offsetX, config.period);
131 const double comb = std::exp(-(d * d) * inv4SigmaX2);
132 field(x, y) = static_cast<float>(comb * envelopeY) * phaseY;
133 }
134 }
135
136 if (!field.normalize()) {
137 fail(reason, "Talbot initial-state normalization failed.");
138 return false;
139 }
140 return true;
141}
142
143double QuantumTalbot2D::talbotTime(double period,
144 double mass,
145 double hbar) noexcept
146{
147 if (!(period > 0.0) || !(mass > 0.0) || !(hbar > 0.0) ||
148 !finite(period) || !finite(mass) || !finite(hbar)) {
149 return 0.0;
150 }
151 return mass * period * period / (kPi * hbar);
152}
153
155{
156 std::vector<float> result(field.width(), 0.0f);
157 if (field.empty()) return result;
158
159 const double dy = static_cast<double>(field.dy());
160 for (std::size_t x = 0u; x < field.width(); ++x) {
161 double sum = 0.0;
162 for (std::size_t y = 0u; y < field.height(); ++y) {
163 const ComplexField2D::Value value = field(x, y);
164 sum += static_cast<double>(value.real()) * value.real() +
165 static_cast<double>(value.imag()) * value.imag();
166 }
167 result[x] = static_cast<float>(sum * dy);
168 }
169 return result;
170}
171
172double QuantumTalbot2D::profileCorrelation(const std::vector<float> &a,
173 const std::vector<float> &b,
174 double dx) noexcept
175{
176 if (a.empty() || a.size() != b.size() || !(dx > 0.0) || !finite(dx)) return 0.0;
177
178 double numerator = 0.0;
179 double normA = 0.0;
180 double normB = 0.0;
181 for (std::size_t i = 0u; i < a.size(); ++i) {
182 const double av = std::max(0.0, static_cast<double>(a[i]));
183 const double bv = std::max(0.0, static_cast<double>(b[i]));
184 numerator += av * bv;
185 normA += av * av;
186 normB += bv * bv;
187 }
188 if (!(normA > 0.0) || !(normB > 0.0)) return 0.0;
189 const double value = numerator / std::sqrt(normA * normB);
190 return std::max(0.0, std::min(1.0, value));
191}
192
193std::vector<float> QuantumTalbot2D::circularShiftHalfPeriod(const std::vector<float> &profile,
194 double period,
195 double dx)
196{
197 if (profile.empty() || !(period > 0.0) || !(dx > 0.0)) return std::vector<float>();
198 const double shiftExact = 0.5 * period / dx;
199 const long long shift = static_cast<long long>(std::floor(shiftExact + 0.5));
200 if (std::fabs(shiftExact - static_cast<double>(shift)) > 1.0e-5) {
201 return std::vector<float>();
202 }
203
204 std::vector<float> result(profile.size(), 0.0f);
205 const long long n = static_cast<long long>(profile.size());
206 for (long long i = 0; i < n; ++i) {
207 long long source = (i - shift) % n;
208 if (source < 0) source += n;
209 result[static_cast<std::size_t>(i)] = profile[static_cast<std::size_t>(source)];
210 }
211 return result;
212}
213
215 : maximumSamples_(std::max<std::size_t>(1u, maximumSamples))
216{
217}
218
220{
221 gridWidth_ = 0u;
222 gridHeight_ = 0u;
223 dx_ = 0.0f;
224 dy_ = 0.0f;
226 referenceMarginal_.clear();
227 halfShiftReference_.clear();
228 samples_.clear();
229}
230
233{
234 clear();
235 std::string reason;
236 if (field.empty() || !QuantumTalbot2D::validateConfig(config, &reason)) return false;
239 referenceMarginal_, config.period, static_cast<double>(field.dx()));
240 if (referenceMarginal_.empty() || halfShiftReference_.empty()) {
241 clear();
242 return false;
243 }
244 gridWidth_ = field.width();
245 gridHeight_ = field.height();
246 dx_ = field.dx();
247 dy_ = field.dy();
248 config_ = config;
249 return true;
250}
251
252void QuantumTalbotHistory2D::setMaximumSamples(std::size_t maximumSamples)
253{
254 maximumSamples_ = std::max<std::size_t>(1u, maximumSamples);
255 trimToLimit();
256}
257
258bool QuantumTalbotHistory2D::append(double time, const ComplexField2D &field)
259{
260 if (!hasReference() || field.empty() || field.width() != gridWidth_ ||
261 field.height() != gridHeight_ || std::fabs(field.dx() - dx_) > 1.0e-9f ||
262 std::fabs(field.dy() - dy_) > 1.0e-9f || !finite(time)) {
263 return false;
264 }
265
267 sample.time = time;
268 sample.marginalX = QuantumTalbot2D::marginalProbabilityX(field);
269 sample.directCorrelation = QuantumTalbot2D::profileCorrelation(
270 sample.marginalX, referenceMarginal_, static_cast<double>(dx_));
271 sample.halfShiftCorrelation = QuantumTalbot2D::profileCorrelation(
272 sample.marginalX, halfShiftReference_, static_cast<double>(dx_));
273 samples_.push_back(sample);
274 trimToLimit();
275 return true;
276}
277
279{
280 if (samples_.size() <= maximumSamples_) return;
281 const std::size_t removeCount = samples_.size() - maximumSamples_;
282 samples_.erase(samples_.begin(), samples_.begin() + static_cast<std::ptrdiff_t>(removeCount));
283}
284
285} // 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.
bool normalize() noexcept
Scales the field to unit discrete probability norm.
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 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.
static bool initialize(ComplexField2D &field, const QuantumTalbotInitialState2DConfig &config, double hbar=1.0, std::string *reason=nullptr)
Initializes ialize.
static double talbotTime(double period, double mass, double hbar=1.0) noexcept
Performs talbot time.
static double profileCorrelation(const std::vector< float > &a, const std::vector< float > &b, double dx=1.0) noexcept
Performs profile correlation.
static std::vector< float > marginalProbabilityX(const ComplexField2D &field)
Performs marginal probability x.
static bool validateConfig(const QuantumTalbotInitialState2DConfig &config, std::string *reason=nullptr) noexcept
Validates config.
static std::vector< float > circularShiftHalfPeriod(const std::vector< float > &profile, double period, double dx)
Performs circular shift half period.
const QuantumTalbotInitialState2DConfig & config() const noexcept
Returns config.
bool captureReference(const ComplexField2D &field, const QuantumTalbotInitialState2DConfig &config)
Carries out capture reference.
std::vector< QuantumTalbotSample2D > samples_
void trimToLimit()
Carries out trim to limit.
std::vector< float > halfShiftReference_
std::vector< float > referenceMarginal_
QuantumTalbotHistory2D(std::size_t maximumSamples=1000u)
Constructs a Quantum Talbot History 2D instance.
bool append(double time, const ComplexField2D &field)
Carries out append.
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::size_t maximumSamples() const noexcept
Carries out maximum samples.
bool hasReference() const noexcept
Returns whether reference.
QuantumTalbotInitialState2DConfig config_
Declares the gaussianpacket2d physics/numerical component used by QPE experiments.
double wrappedDistance(double x, double period) noexcept
Performs wrapped distance.
Declares the quantumtalbot2d physics/numerical component used by QPE experiments.
Configuration values for Quantum Talbot Initial State2D Config.
Data structure used by the Quantum Talbot Sample2D component.