27constexpr double kPi = 3.141592653589793238462643383279502884;
33bool finitePositive(
double value)
noexcept
35 return std::isfinite(value) && value > 0.0;
44 return value != 0u && (value & (value - 1u)) == 0u;
52void fft1D(std::vector<std::complex<double> > &data,
bool inverse)
54 const std::size_t count = data.size();
56 throw std::invalid_argument(
"BoundStates1D: X grid must be a power of two");
60 for (std::size_t i = 1u; i < count; ++i) {
61 std::size_t bit = count >> 1u;
68 std::swap(data[i], data[j]);
72 const double sign = inverse ? 1.0 : -1.0;
73 for (std::size_t length = 2u; length <= count; length <<= 1u) {
74 const double angle = sign * 2.0 * kPi /
static_cast<double>(length);
75 const std::complex<double> wLength(std::cos(angle), std::sin(angle));
76 const std::size_t half = length >> 1u;
77 for (std::size_t block = 0u; block < count; block += length) {
78 std::complex<double> w(1.0, 0.0);
79 for (std::size_t offset = 0u; offset < half; ++offset) {
80 const std::complex<double> even = data[block + offset];
81 const std::complex<double> odd = data[block + offset + half] * w;
82 data[block + offset] = even + odd;
83 data[block + offset + half] = even - odd;
90 const double scale = 1.0 /
static_cast<double>(count);
91 for (std::complex<double> &value : data) {
102double weightedNorm(
const std::vector<std::complex<double> > &v,
double dx)
105 for (
const std::complex<double> &value : v) {
106 sum += std::norm(value);
108 return std::sqrt(std::max(0.0, sum * dx));
116bool normalize(std::vector<std::complex<double> > &v,
double dx)
119 if (!(n > 1.0e-30) || !std::isfinite(n)) {
122 const double inv = 1.0 / n;
123 for (std::complex<double> &value : v) {
129std::complex<double>
innerProduct(
const std::vector<std::complex<double> > &a,
130 const std::vector<std::complex<double> > &b,
133 std::complex<double> sum(0.0, 0.0);
134 for (std::size_t i = 0u; i < a.size(); ++i) {
135 sum += std::conj(a[i]) * b[i];
147 const std::vector<std::vector<std::complex<double> > > &basis,
151 for (
int pass = 0; pass < 2; ++pass) {
152 for (
const std::vector<std::complex<double> > &q : basis) {
153 const std::complex<double> projection =
innerProduct(q, v, dx);
154 for (std::size_t i = 0u; i < v.size(); ++i) {
155 v[i] -= projection * q[i];
166 state ^= state << 13;
167 state ^= state >> 17;
172std::vector<std::complex<double> >
makeTrial(std::size_t count,
173 const std::vector<double> &potential,
177 std::vector<std::complex<double> > trial(count, std::complex<double>(0.0, 0.0));
179 for (
double value : potential) {
180 spread = std::max(spread, value - vMin);
182 const double scale = std::max(1.0, 0.25 * spread);
183 std::uint32_t rng = 0x9E3779B9u ^
static_cast<std::uint32_t
>(0x85EBCA6Bu * (stateIndex + 1));
185 for (std::size_t i = 0u; i < count; ++i) {
186 const double u =
static_cast<double>(
nextRandom(rng) & 0x00FFFFFFu) /
187 static_cast<double>(0x01000000u);
188 const double randomValue = 2.0 * u - 1.0;
189 const double weight = std::exp(-0.35 * (potential[i] - vMin) / scale);
192 const double harmonic = std::cos(2.0 * kPi *
static_cast<double>(stateIndex + 1) *
193 static_cast<double>(i) /
static_cast<double>(count));
194 trial[i] = weight * (0.75 * randomValue + 0.25 * harmonic);
204 std::vector<double> kinetic(count, 0.0);
205 const double length =
static_cast<double>(count) * dx;
206 for (std::size_t k = 0u; k < count; ++k) {
207 const long signedIndex = (k <= count / 2u)
208 ?
static_cast<long>(k)
209 :
static_cast<long>(k) -
static_cast<long>(count);
210 const double waveNumber = 2.0 * kPi *
static_cast<double>(signedIndex) / length;
211 kinetic[k] = hbar * hbar * waveNumber * waveNumber / (2.0 * mass);
224 const std::vector<double> &potential,
225 const std::vector<double> &kinetic,
228 std::vector<std::complex<double> > kPsi = psi;
230 for (std::size_t i = 0u; i < kPsi.size(); ++i) {
231 kPsi[i] *= kinetic[i];
235 std::complex<double> e(0.0, 0.0);
236 for (std::size_t i = 0u; i < psi.size(); ++i) {
237 const std::complex<double> hPsi = kPsi[i] + potential[i] * psi[i];
238 e += std::conj(psi[i]) * hPsi;
240 return (e * dx).real();
249 float maximum = 0.0f;
250 for (
float value : amplitude) {
251 maximum = std::max(maximum, std::abs(value));
253 if (!(maximum > 0.0f)) {
256 const float threshold = maximum * 0.015f;
257 int previousSign = 0;
259 for (
float value : amplitude) {
260 if (std::abs(value) < threshold) {
263 const int sign = value >= 0.0f ? 1 : -1;
264 if (previousSign != 0 && sign != previousSign) {
277double edgeMean(
const std::vector<double> &values,
bool left)
279 if (values.empty()) {
282 const std::size_t count = std::max<std::size_t>(4u, values.size() / 10u);
285 for (std::size_t i = 0u; i < count; ++i) {
289 for (std::size_t i = values.size() - count; i < values.size(); ++i) {
293 return sum /
static_cast<double>(count);
302 for (
double value : values) {
303 if (!std::isfinite(value)) {
313 double tolerance)
noexcept
315 if (potential.empty() || potential.width() == 0u || potential.height() == 0u ||
316 !std::isfinite(tolerance) || tolerance < 0.0) {
320 const std::size_t referenceY = potential.height() / 2u;
322 for (std::size_t x = 0u; x < potential.width(); ++x) {
323 scale = std::max(scale, std::abs(
static_cast<double>(potential(x, referenceY))));
325 const double allowed = tolerance * scale;
327 for (std::size_t y = 0u; y < potential.height(); ++y) {
328 for (std::size_t x = 0u; x < potential.width(); ++x) {
329 const double delta = std::abs(
static_cast<double>(potential(x, y)) -
330 static_cast<double>(potential(x, referenceY)));
331 if (delta > allowed) {
343 std::uint64_t hash = 1469598103934665603ull;
344 const auto mixBytes = [&hash](
const void *data, std::size_t size) {
345 const unsigned char *bytes =
static_cast<const unsigned char *
>(data);
346 for (std::size_t i = 0u; i < size; ++i) {
347 hash ^=
static_cast<std::uint64_t
>(bytes[i]);
348 hash *= 1099511628211ull;
352 const std::size_t width = potential.width();
353 const std::size_t height = potential.height();
354 const float dx = potential.dx();
355 const float dy = potential.dy();
356 mixBytes(&width,
sizeof(width));
357 mixBytes(&height,
sizeof(height));
358 mixBytes(&dx,
sizeof(dx));
359 mixBytes(&dy,
sizeof(dy));
360 if (!potential.empty()) {
369 double hbar)
noexcept
371 return spectrum.valid &&
372 spectrum.sourceSignature == potentialSignature(potential) &&
373 std::abs(spectrum.mass - mass) <= 1.0e-12 * std::max(1.0, std::abs(mass)) &&
374 std::abs(spectrum.hbar - hbar) <= 1.0e-12 * std::max(1.0, std::abs(hbar)) &&
375 spectrum.x.size() == potential.width();
384 result.
dx = potential.
dx();
387 if (potential.
empty() || potential.
width() < 16u || potential.
height() < 1u) {
388 result.
message =
"Potential grid is empty or too small.";
391 if (!isPowerOfTwo(potential.
width())) {
392 result.
message =
"Bound-state analysis requires a radix-2 X grid.";
395 if (!finitePositive(params.
mass) || !finitePositive(params.
hbar) ||
399 result.
message =
"Invalid bound-state solver parameters.";
403 result.
message =
"Potential is not invariant in Y. Use a well extended across Y for this 1D bound-state analysis.";
407 const std::size_t n = potential.
width();
408 const std::size_t referenceY = potential.
height() / 2u;
411 for (std::size_t i = 0u; i < n; ++i) {
413 result.
potential[i] =
static_cast<double>(potential(i, referenceY));
416 result.
message =
"Potential contains non-finite values.";
420 const double dx =
static_cast<double>(potential.
dx());
421 const double vMin = *std::min_element(result.
potential.begin(), result.
potential.end());
424 const double potentialSpan = std::max(1.0,
426 const double boundMargin = 1.0e-5 * potentialSpan;
428 const std::vector<double> kinetic = spectralKinetic(n, dx, params.
mass, params.
hbar);
429 std::vector<double> halfPotentialDecay(n, 1.0);
430 std::vector<double> kineticDecay(n, 1.0);
431 for (std::size_t i = 0u; i < n; ++i) {
432 halfPotentialDecay[i] = std::exp(-(result.
potential[i] - vMin) *
434 (2.0 * params.
hbar));
438 std::vector<std::vector<std::complex<double> > > basis;
439 basis.reserve(
static_cast<std::size_t
>(params.
maxStates));
441 for (
int stateIndex = 0; stateIndex < params.
maxStates; ++stateIndex) {
442 std::vector<std::complex<double> > psi = makeTrial(n, result.
potential, vMin, stateIndex);
443 if (!orthonormalize(psi, basis, dx)) {
447 double previousEnergy = std::numeric_limits<double>::infinity();
448 int stableChecks = 0;
449 for (
int iteration = 0; iteration < params.
maxIterations; ++iteration) {
450 for (std::size_t i = 0u; i < n; ++i) {
451 psi[i] *= halfPotentialDecay[i];
454 for (std::size_t i = 0u; i < n; ++i) {
455 psi[i] *= kineticDecay[i];
458 for (std::size_t i = 0u; i < n; ++i) {
459 psi[i] *= halfPotentialDecay[i];
461 if (!orthonormalize(psi, basis, dx)) {
465 if ((iteration % 10) == 0) {
466 const double energy = expectationEnergy(psi, result.
potential, kinetic, dx);
467 const double scale = std::max(1.0, std::abs(energy));
468 if (std::isfinite(previousEnergy) &&
474 previousEnergy = energy;
475 if (iteration >= 120 && stableChecks >= 4) {
481 if (!orthonormalize(psi, basis, dx)) {
484 const double energy = expectationEnergy(psi, result.
potential, kinetic, dx);
485 if (!std::isfinite(energy)) {
496 std::size_t peakIndex = 0u;
497 double peakMagnitude = 0.0;
498 for (std::size_t i = 0u; i < n; ++i) {
499 const double magnitude = std::abs(psi[i]);
500 if (magnitude > peakMagnitude) {
501 peakMagnitude = magnitude;
505 if (peakMagnitude > 0.0) {
506 const double phase = std::arg(psi[peakIndex]);
507 const std::complex<double> rotation(std::cos(-phase), std::sin(-phase));
508 for (std::complex<double> &value : psi) {
517 for (std::size_t i = 0u; i < n; ++i) {
518 state.
amplitude[i] =
static_cast<float>(psi[i].real());
521 result.
states.push_back(state);
525 std::vector<std::complex<double> > realState(n);
526 for (std::size_t i = 0u; i < n; ++i) {
527 realState[i] =
static_cast<double>(state.
amplitude[i]);
529 normalize(realState, dx);
530 basis.push_back(realState);
535 result.
message =
"Bound states computed.";
537 result.
message =
"No bound eigenstate was found below the edge continuum threshold.";
546 if (!spectrum.
valid || stateIndex < 0 ||
547 stateIndex >=
static_cast<int>(spectrum.
states.size()) ||
548 field.
empty() || field.
width() != spectrum.
x.size()) {
557 const double yMeasure =
static_cast<double>(field.
height()) *
static_cast<double>(field.
dy());
558 if (!(yMeasure > 0.0)) {
561 const float yScale =
static_cast<float>(1.0 / std::sqrt(yMeasure));
562 for (std::size_t y = 0u; y < field.
height(); ++y) {
563 for (std::size_t x = 0u; x < field.
width(); ++x) {
574 double relativePhaseRadians)
576 if (!spectrum.
valid || stateIndexA < 0 || stateIndexB < 0 ||
577 stateIndexA == stateIndexB ||
578 stateIndexA >=
static_cast<int>(spectrum.
states.size()) ||
579 stateIndexB >=
static_cast<int>(spectrum.
states.size()) ||
580 !std::isfinite(relativePhaseRadians) || field.
empty() ||
581 field.
width() != spectrum.
x.size()) {
591 const double yMeasure =
static_cast<double>(field.
height()) *
static_cast<double>(field.
dy());
592 if (!(yMeasure > 0.0)) {
595 const double oneOverSqrtTwo = 1.0 / std::sqrt(2.0);
596 const std::complex<double> phase(std::cos(relativePhaseRadians),
597 std::sin(relativePhaseRadians));
598 const double yScale = 1.0 / std::sqrt(yMeasure);
600 for (std::size_t y = 0u; y < field.
height(); ++y) {
601 for (std::size_t x = 0u; x < field.
width(); ++x) {
602 const std::complex<double> value = oneOverSqrtTwo *
604 phase *
static_cast<double>(b.
amplitude[x])) * yScale;
606 static_cast<float>(value.imag()));
Declares the boundstates1d physics/numerical component used by QPE experiments.
static bool isYInvariant(const PotentialField2D &potential, double tolerance=1.0e-5) noexcept
Returns whether y invariant.
static bool prepareEigenstate(ComplexField2D &field, const BoundStateSpectrum1D &spectrum, int stateIndex)
Prepares eigenstate.
static bool matches(const BoundStateSpectrum1D &spectrum, const PotentialField2D &potential, double mass, double hbar=1.0) noexcept
Performs matches.
static BoundStateSpectrum1D solve(const PotentialField2D &potential, const BoundStateSolver1DParams ¶ms)
Performs solve.
static std::uint64_t potentialSignature(const PotentialField2D &potential) noexcept
Performs potential signature.
static bool prepareSuperposition(ComplexField2D &field, const BoundStateSpectrum1D &spectrum, int stateIndexA, int stateIndexB, double relativePhaseRadians=0.0)
Prepares superposition.
Owning Cartesian grid of complex scalar samples.
bool empty() const noexcept
Returns whether the field contains no samples.
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.
Implements the Potential Field2D component.
std::size_t height() const noexcept
Returns height.
double xCoordinate(std::size_t x) const noexcept
Performs x coordinate.
bool empty() const noexcept
Performs empty.
std::size_t width() const noexcept
Returns width.
float dx() const noexcept
Performs dx.
Declares the complex scalar field container representing two-dimensional wave functions.
std::vector< std::complex< double > > makeTrial(std::size_t count, const std::vector< double > &potential, double vMin, int stateIndex)
int countNodes(const std::vector< float > &litude)
Returns count nodes.
bool isPowerOfTwo(std::size_t value) noexcept
Returns whether power of two.
bool orthonormalize(std::vector< std::complex< double > > &v, const std::vector< std::vector< std::complex< double > > > &basis, double dx)
Performs orthonormalize.
double expectationEnergy(const std::vector< std::complex< double > > &psi, const std::vector< double > &potential, const std::vector< double > &kinetic, double dx)
Performs expectation energy.
std::vector< double > spectralKinetic(std::size_t count, double dx, double mass, double hbar)
bool normalize(std::vector< std::complex< double > > &v, double dx)
Returns normalize.
void fft1D(std::vector< std::complex< double > > &data, bool inverse)
Performs fft1 d.
std::complex< double > innerProduct(const std::vector< std::complex< double > > &a, const std::vector< std::complex< double > > &b, double dx)
bool finiteVector(const std::vector< double > &values) noexcept
Performs finite vector.
double weightedNorm(const std::vector< std::complex< double > > &v, double dx)
Performs weighted norm.
std::uint32_t nextRandom(std::uint32_t &state) noexcept
double edgeMean(const std::vector< double > &values, bool left)
Performs edge mean.
Declares the potentialfield2d physics/numerical component used by QPE experiments.
Data structure used by the Bound State1D component.
std::vector< float > amplitude
Configuration values for Bound State Solver1D Params.
double yInvarianceTolerance
Data structure used by the Bound State Spectrum1D component.
std::vector< BoundState1D > states
std::vector< double > potential
std::uint64_t sourceSignature
double continuumThreshold