Schrödinger equation minimally coupled to an electromagnetic potential
A Gaussian wave packet of a charge in a prescribed electromagnetic potential, after Schwichtenberg 4.3.
- Name:
charged_schrodinger - Field:
physics/waves - Formalism: ordinary differential equations
- References: Schwichtenberg 2020
Factory
@register_model(
field='physics/waves',
tags=('quantum', 'gauge field', 'electromagnetism', 'wave packet', 'spectral', 'pde'),
references=('Schwichtenberg 2020',),
defaults={'until': 8.0, 'dt': 0.1},
)
def charged_schrodinger(
n_sites: int = 256,
*,
length: float = 80.0,
D: float = 0.5,
q: float = 1.0,
momentum: float = 0.0,
width: float = 2.0,
A_x: float = 0.0,
A_t: float = 0.0,
E: float = 1.0,
duration: float = 2.0,
) -> System:
"""Return a Gaussian wave packet of a charge in a prescribed electromagnetic potential, after Schwichtenberg 4.3.
The wave function follows the Schrödinger equation with the covariant derivatives `d - i q A`,
`(d_t - i q A_t) psi = i D (d_x - i q A_x)^2 psi`, on a periodic line with spectral derivatives. A mode of wave
number `k` turns at the frequency `D (k - q A_x)^2 - q A_t`, so the packet moves at the group velocity
`2 D (k - q A_x)`. A constant `A_x` is a pure gauge that only shifts the canonical momentum, while the electric
field `E` switched on for `duration` changes `A_x` and accelerates the packet at the rate `2 D q E`.
Args:
n_sites:
The number of sites of the periodic lattice.
length:
The length of the periodic line, centered on 0.
D:
The coefficient of the Laplacian, `ħ / (2 m)`.
q:
The electric charge.
momentum:
The mean wave number `k` of the packet.
width:
The width of the packet.
A_x:
The vector potential at time 0.
A_t:
The scalar potential.
E:
The electric field.
duration:
The time the electric field is on.
Returns:
system:
The model, with the real part `packet.u` and the imaginary part `packet.w` of the wave function.
Examples:
>>> from skcomplex.models.physics.waves import charged_schrodinger
>>> charged_schrodinger(64, E=0.5).elements[0].values['E']
0.5
"""
spacing = length / n_sites
positions = spacing * np.arange(n_sites) - length / 2
envelope = np.exp(-(positions**2) / (4 * width**2)) / (2 * np.pi * width**2) ** 0.25
psi = envelope * np.exp(1j * momentum * positions)
values = {
'u': psi.real,
'w': psi.imag,
'D': D,
'q': q,
'A_x': A_x,
'A_t': A_t,
'E': E,
'duration': duration,
}
space = LatticeSpace(n_sites, spacing=spacing)
return System(
elements=[
FieldElement(
'packet',
element=ChargedWaveFunctionElement,
space=space,
values=values,
discretization='spectral',
),
],
)
Components
class ChargedWaveFunctionElement(Element):
"""Wave function `psi = u + i w` of a charge `q` in a uniform electromagnetic potential.
The wave function follows `(d_t - i q A_t) psi = i D (d_x - i q A_x)^2 psi`. The vector potential
`A_x(t) = A_x - E min(t, duration)` holds a uniform electric field `E` from time 0 to `duration`, and the scalar
potential `A_t` is constant. Expanded, the equation is
`psi_t = i D psi_xx + 2 D q A_x psi_x + i (q A_t - D q^2 A_x^2) psi`.
Args:
label:
The label of the site or the field.
visual:
How the element is drawn, or `None` for `default_visual_`.
u:
The initial real part.
w:
The initial imaginary part.
D:
The coefficient of the Laplacian, `ħ / (2 m)`.
q:
The electric charge.
A_x:
The vector potential at time 0.
A_t:
The scalar potential.
E:
The electric field.
duration:
The time the electric field is on.
Examples:
>>> from skcomplex.models.physics.waves import ChargedWaveFunctionElement
>>> ChargedWaveFunctionElement('site').q
1.0
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
u: Annotated[float, StateSpec()] = 0.0,
w: Annotated[float, StateSpec()] = 0.0,
D: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.5,
q: Annotated[float, ParameterSpec()] = 1.0,
A_x: Annotated[float, ParameterSpec()] = 0.0,
A_t: Annotated[float, ParameterSpec()] = 0.0,
E: Annotated[float, ParameterSpec()] = 0.0,
duration: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
) -> None:
super().__init__(label, visual=visual)
self.u = u
self.w = w
self.D = D
self.q = q
self.A_x = A_x
self.A_t = A_t
self.E = E
self.duration = duration
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the minimally coupled Schrödinger equation for the real and imaginary parts.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The laws of the component.
"""
potential = s.A_x - s.E * ramp(s.t, 0, s.duration, s.duration)
drift = 2 * s.D * s.q * potential
phase = s.q * s.A_t - s.D * s.q**2 * potential**2
return [
RateLaw(s.u, -s.D * laplacian(s.w) + drift * gradient(s.u, 1)[0] - phase * s.w),
RateLaw(s.w, s.D * laplacian(s.u) + drift * gradient(s.w, 1)[0] + phase * s.u),
]
Equations
packet.u[i]: diff(u_packet(t), t) == -2*(E_packet*where(t < 0, 0, where(t < duration_packet, t, duration_packet)) - A_x_packet)*D_packet*q_packet*partial(u_packet(t), 0) - D_packet*laplacian(w_packet(t)) + ((E_packet*where(t < 0, 0, where(t < duration_packet, t, duration_packet)) - A_x_packet)^2*D_packet*q_packet^2 - A_t_packet*q_packet)*w_packet(t)
packet.w[i]: diff(w_packet(t), t) == -2*(E_packet*where(t < 0, 0, where(t < duration_packet, t, duration_packet)) - A_x_packet)*D_packet*q_packet*partial(w_packet(t), 0) + D_packet*laplacian(u_packet(t)) - ((E_packet*where(t < 0, 0, where(t < duration_packet, t, duration_packet)) - A_x_packet)^2*D_packet*q_packet^2 - A_t_packet*q_packet)*u_packet(t)