Skip to content

Commit dd51a49

Browse files
committed
[feat] Add classical and quantum Hamiltonian functions with examples
1 parent a71618f commit dd51a49

1 file changed

Lines changed: 158 additions & 0 deletions

File tree

physics/hamiltonian.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
"""
2+
Hamiltonian functions for classical and quantum mechanics.
3+
4+
This module provides two educational, minimal implementations:
5+
6+
- classical_hamiltonian(m, p, V): Computes H = T + V for a particle/system, where
7+
T = p^2 / (2 m) is the kinetic energy expressed in terms of momentum p, and V is
8+
the potential energy (can be a scalar or an array broadcastable to p).
9+
10+
- quantum_hamiltonian_1d(m, hbar, V, dx): Builds the 1D Hamiltonian matrix for a
11+
particle in a potential V using second-order central finite differences for the
12+
kinetic energy operator: T = - (hbar^2 / 2m) d^2/dx^2 with Dirichlet boundaries.
13+
14+
These functions are intended for learners to quickly prototype and simulate basic
15+
physical systems.
16+
17+
References
18+
----------
19+
- Classical Hamiltonian mechanics: https://en.wikipedia.org/wiki/Hamiltonian_mechanics
20+
- Discrete 1D Schrödinger operator: https://en.wikipedia.org/wiki/Finite_difference_method
21+
"""
22+
23+
from __future__ import annotations
24+
25+
from typing import Any
26+
27+
import numpy as np
28+
29+
30+
def classical_hamiltonian(m: float, p: Any, V: Any) -> Any:
31+
"""
32+
Classical Hamiltonian H = T + V with T = p^2 / (2 m).
33+
34+
The function supports scalars or array-like inputs for momentum ``p`` and
35+
potential energy ``V``; NumPy broadcasting rules apply. If inputs are scalars,
36+
a float is returned; otherwise a NumPy array is returned.
37+
38+
Parameters
39+
----------
40+
m : float
41+
Mass (must be positive).
42+
p : array-like or scalar
43+
Canonical momentum.
44+
V : array-like or scalar
45+
Potential energy evaluated for the corresponding configuration.
46+
47+
Returns
48+
-------
49+
float | np.ndarray
50+
The Hamiltonian value(s) H = p^2/(2m) + V.
51+
52+
Examples
53+
--------
54+
Free particle with p = 3 kg·m/s and m = 2 kg (V = 0):
55+
>>> classical_hamiltonian(2.0, 3.0, 0.0)
56+
2.25
57+
58+
Harmonic oscillator snapshot with vectorized p and V:
59+
>>> m = 1.0
60+
>>> p = np.array([0.0, 1.0, 2.0])
61+
>>> V = np.array([0.5, 0.5, 0.5]) # e.g., 1/2 k x^2 at three positions
62+
>>> classical_hamiltonian(m, p, V).tolist()
63+
[0.5, 1.0, 2.5]
64+
"""
65+
if m <= 0:
66+
raise ValueError("Mass m must be positive.")
67+
68+
p_arr = np.asarray(p)
69+
V_arr = np.asarray(V)
70+
71+
T = (p_arr * p_arr) / (2.0 * m)
72+
H = T + V_arr
73+
74+
# Preserve scalar type when both inputs are scalar
75+
if np.isscalar(p) and np.isscalar(V):
76+
return float(H)
77+
return H
78+
79+
80+
def quantum_hamiltonian_1d(m: float, hbar: float, V: Any, dx: float) -> np.ndarray:
81+
"""
82+
Construct the 1D quantum Hamiltonian matrix using finite differences.
83+
84+
Discretizes the kinetic operator with second-order central differences and
85+
Dirichlet boundary conditions (wavefunction assumed zero beyond endpoints):
86+
87+
H = - (hbar^2 / 2m) d^2/dx^2 + V
88+
89+
On a uniform grid with spacing ``dx`` and N sites, the Laplacian is
90+
approximated by the tridiagonal matrix with main diagonal ``-2`` and
91+
off-diagonals ``+1``. The resulting kinetic term has main diagonal
92+
``(hbar^2)/(m*dx^2)`` and off-diagonals ``-(hbar^2)/(2*m*dx^2)``.
93+
94+
Parameters
95+
----------
96+
m : float
97+
Particle mass (must be positive).
98+
hbar : float
99+
Reduced Planck constant (can be set to 1.0 in natural units).
100+
V : array-like shape (N,)
101+
Potential energy values on the grid. Defines the matrix size.
102+
dx : float
103+
Grid spacing (must be positive).
104+
105+
Returns
106+
-------
107+
np.ndarray shape (N, N)
108+
The Hermitian Hamiltonian matrix.
109+
110+
Examples
111+
--------
112+
Free particle (V=0) on a small grid: main diagonal = 1/dx^2, off = -1/(2*dx^2) in units m=hbar=1.
113+
>>> N, dx = 5, 0.1
114+
>>> H = quantum_hamiltonian_1d(m=1.0, hbar=1.0, V=np.zeros(N), dx=dx)
115+
>>> float(H[0, 0])
116+
99.99999999999999
117+
>>> float(H[0, 1])
118+
-49.99999999999999
119+
120+
Add a harmonic-like site potential to the diagonal:
121+
>>> x = dx * (np.arange(N) - (N-1)/2)
122+
>>> V = 0.5 * x**2 # k=m=omega=1 for illustration
123+
>>> H2 = quantum_hamiltonian_1d(1.0, 1.0, V, dx)
124+
>>> np.allclose(np.diag(H2) - np.diag(H), V)
125+
True
126+
"""
127+
if m <= 0:
128+
raise ValueError("Mass m must be positive.")
129+
if dx <= 0:
130+
raise ValueError("Grid spacing dx must be positive.")
131+
132+
V_arr = np.asarray(V, dtype=float)
133+
if V_arr.ndim != 1:
134+
raise ValueError("V must be a 1D array-like of potential values.")
135+
136+
N = V_arr.size
137+
if N == 0:
138+
raise ValueError("V must contain at least one grid point.")
139+
140+
coeff_main = (hbar * hbar) / (m * dx * dx)
141+
coeff_off = -0.5 * (hbar * hbar) / (m * dx * dx)
142+
143+
# Build tridiagonal kinetic matrix
144+
H = np.zeros((N, N), dtype=float)
145+
np.fill_diagonal(H, coeff_main)
146+
idx = np.arange(N - 1)
147+
H[idx, idx + 1] = coeff_off
148+
H[idx + 1, idx] = coeff_off
149+
150+
# Add the potential on the diagonal
151+
H[np.arange(N), np.arange(N)] += V_arr
152+
return H
153+
154+
155+
if __name__ == "__main__":
156+
import doctest
157+
158+
doctest.testmod(verbose=True)

0 commit comments

Comments
 (0)