26using cplx = std::complex<double>;
30 : N(N_), L(L_), h(L_ / static_cast<double>(N_ + 1)), dt(dt_),
31 psi(static_cast<
num::idx>(N_ * N_),
cplx(0.0, 0.0)),
32 V(static_cast<
num::idx>(N_ * N_), 0.0)
38void TDSESolver::refactor_() {
45 current_potential_ = p;
48 const double cx =
L * 0.5, cy =
L * 0.5;
49 const double V0 = 5000.0;
53 double wall_x =
L * 0.55;
55 double gap_w =
L * 0.12;
56 int wall_th = std::max(1,
N / 60);
58 int ix0 =
static_cast<int>((wall_x /
L) *
N - wall_th);
59 int ix1 =
static_cast<int>((wall_x /
L) *
N + wall_th);
61 for (
int i = ix0; i <= std::min(ix1,
N-1); ++i) {
62 for (
int j = 0; j <
N; ++j) {
63 double y = (j + 1.0) *
h;
64 if (std::abs(y - gap_cy) > gap_w * 0.5)
70 double wall_x =
L * 0.5;
71 double gap_sep =
L * 0.12;
72 double gap_w =
L * 0.06;
73 int wall_th = std::max(1,
N / 60);
75 int ix0 =
static_cast<int>((wall_x /
L) *
N - wall_th);
76 int ix1 =
static_cast<int>((wall_x /
L) *
N + wall_th);
78 for (
int i = ix0; i <= std::min(ix1,
N-1); ++i) {
79 for (
int j = 0; j <
N; ++j) {
80 double y = (j + 1.0) *
h;
81 bool in_slit1 = std::abs(y - (cy - gap_sep * 0.5)) < gap_w * 0.5;
82 bool in_slit2 = std::abs(y - (cy + gap_sep * 0.5)) < gap_w * 0.5;
83 if (!in_slit1 && !in_slit2)
89 double omega = (param > 0) ? param : 1.5;
90 for (
int i = 0; i <
N; ++i) {
91 double x = (i + 1.0) *
h - cx;
92 for (
int j = 0; j <
N; ++j) {
93 double y = (j + 1.0) *
h - cy;
94 V[
at(i, j)] = 0.5 * omega * omega * (x*x + y*y);
99 double R = (param > 0) ? param :
L * 0.4;
100 for (
int i = 0; i <
N; ++i) {
101 double x = (i + 1.0) *
h - cx;
102 for (
int j = 0; j <
N; ++j) {
103 double y = (j + 1.0) *
h - cy;
104 if (std::sqrt(x*x + y*y) > R)
117 for (
int i = 0; i <
N; ++i) {
118 double x = (i + 1.0) *
h;
119 for (
int j = 0; j <
N; ++j) {
120 double y = (j + 1.0) *
h;
121 double dx = x - x0, dy = y - y0;
122 double env = std::exp(-(dx*dx + dy*dy) / (2.0 * sigma * sigma));
123 double phi = kx * dx + ky * dy;
124 psi[
at(i, j)] = env *
cplx(std::cos(phi), std::sin(phi));
132 throw std::runtime_error(
"tdse: modes not ready or k out of range");
133 const auto& phi =
modes[k].phi;
134 for (
int n2 = 0; n2 <
N *
N; ++n2)
141void TDSESolver::kick_V(
double tau) {
142 for (
int n2 = 0; n2 <
N *
N; ++n2) {
143 double angle = -
V[n2] * tau;
144 psi[n2] *=
cplx(std::cos(angle), std::sin(angle));
148void TDSESolver::sweep_x(
double tau) { adi_.
sweep(
psi,
true, tau); }
149void TDSESolver::sweep_y(
double tau) { adi_.
sweep(
psi,
false, tau); }
154 auto t0 = std::chrono::high_resolution_clock::now();
162 auto t1 = std::chrono::high_resolution_clock::now();
163 stats.
step_ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
179 const double c = 1.0 / (2.0 *
h *
h);
181 for (
int k = 0; k <
N *
N; ++k) {
183 E +=
psi[k].real() * Hpsi.real() +
psi[k].imag() * Hpsi.imag();
197 const int n2 =
N *
N;
198 const double c = 1.0 / (2.0 *
h *
h);
204 for (
int i = 0; i < n2; ++i)
205 y[i] = -c * lap_buf[i] +
V[i] * x[i];
213 int got =
static_cast<int>(res.ritz_values.size());
216 for (
int m = 0; m < got; ++m) {
218 em.
energy = res.ritz_values[m];
222 for (
int r = 0; r < n2; ++r) {
223 em.
phi[r] = res.ritz_vectors(r, m);
224 norm_sq += em.
phi[r] * em.
phi[r];
226 double inv = 1.0 / std::sqrt(norm_sq *
h *
h);
227 for (
double& v : em.
phi) v *= inv;
229 modes.push_back(std::move(em));
236 for (
int m = 0; m <= 2 && mode_idx < got; ++m) {
237 double bracket_lo = 1.0;
238 for (
int nz = 1; nz <= 3 && mode_idx < got; ++nz) {
240 double lo = bracket_lo, hi = lo + 0.5;
242 while (Jm(lo) * Jm(hi) > 0 && scan < 200) { lo = hi; hi += 0.5; ++scan; }
243 if (Jm(lo) * Jm(hi) > 0)
break;
246 double E_exact = (res2.root * res2.root) / (2.0 * R * R);
247 bracket_lo = hi + 0.01;
249 modes[mode_idx++].exact_energy = E_exact;
250 if (m > 0 && mode_idx < got)
251 modes[mode_idx++].exact_energy = E_exact;
Dense vector with optional GPU storage, templated over scalar type T.
constexpr idx size() const noexcept
int N
Interior grid points per axis.
num::Vector V
NxN potential (real, length N^2)
double L
Domain length ([0,L]x[0,L])
double compute_energy() const
<psi|H|psi> using 5-point Laplacian and the potential.
void set_mode(int k)
Set psi to the k-th eigenmode (modes must be computed first).
TDSESolver(int N, double L, double dt)
void init_gaussian(double x0, double y0, double kx, double ky, double sigma)
Gaussian wavepacket: psi = A*exp(-(r-r0)^2/sigma^2)*exp(i*k*r), then normalised.
std::vector< EigenMode > modes
void compute_modes(int k=8)
double h
Grid spacing = L/(N+1)
num::CVector psi
NxN wavefunction (complex, length N^2)
void set_potential(Potential p, double param=0.0)
Build potential and recompute Thomas factorisations.
double compute_norm() const
integral|psi|^2 dA (Gauss-Legendre over each grid strip).
int at(int i, int j) const
void step()
Advance one full time step (Strang splitting).
Lanczos algorithm – k extreme eigenvalues of a large sparse matrix.
Thin wrappers around C++17 <cmath> and <numeric> with readable names.
void laplacian_stencil_2d(const BasicVector< T > &x, BasicVector< T > &y, int N)
real bessel_j(real nu, real x)
J_nu(x) – Bessel function of the first kind.
RootResult brent(ScalarFn f, real a, real b, real tol=1e-10, idx max_iter=1000)
Brent's method (bisection + secant + inverse quadratic interpolation)
void scale(Vector &v, real alpha, Backend b=default_backend)
v *= alpha
real norm(const Vector &x, Backend b=default_backend)
Euclidean norm.
LanczosResult lanczos(MatVecFn matvec, idx n, idx k, real tol=1e-10, idx max_steps=0, Backend backend=Backend::seq)
Lanczos eigensolver for large sparse symmetric matrices.
@ DoubleSlit
Double-slit wall – interference.
@ Barrier
Single vertical barrier with one gap – tunnelling.
@ CircularWell
V = 0 inside radius R, V = V0 outside (Bessel eigenstates)
@ Harmonic
V = 1/2*omega^2*r^2 (coherent state dynamics)
std::complex< double > cplx
Root-finding methods for scalar equations f(x) = 0.
void sweep(CVector &psi, bool x_axis, double tau) const
std::vector< double > phi
NxN real wavefunction, L^2-normalised.
double energy
Ritz eigenvalue (~= energy)
int n_modes
Number of computed eigenmodes.
double step_ms
Wall time for one step()
2-D Time-Dependent Schrödinger Equation solver