12. import numpy as np
from scipy import linalg
# Create a pure state density matrix
def pure_state_density_matrix(state):
"""Create density matrix ρ = |ψ⟩⟨ψ| for a pure state"""
return np.outer(state, state.conj())
# Create a mixed state density matrix
def mixed_state_density_matrix(states, probabilities):
"""Create density matrix ρ = ∑ᵢ pᵢ|ψᵢ⟩⟨ψᵢ| for a mixed state"""
rho = np.zeros((len(states[0]), len(states[0])), dtype=complex)
for state, prob in zip(states, probabilities):
rho += prob * pure_state_density_matrix(state)
return rho
# Check if a density matrix represents a pure state
def is_pure_state(rho, tol=1e-10):
"""Check if Tr(ρ²) = 1 (pure state) or < 1 (mixed state)"""
trace_rho_squared = np.trace(np.dot(rho, rho)).real
return abs(trace_rho_squared - 1.0) < tol
# Calculate von Neumann entropy
def von_neumann_entropy(rho):
"""Calculate S(ρ) = -Tr(ρ ln ρ) = -∑ᵢ λᵢ ln λᵢ"""
eigenvalues = np.linalg.eigvalsh(rho)
# Filter out extremely small eigenvalues (numerical precision issues)
eigenvalues = eigenvalues[eigenvalues > 1e-10]
return -np.sum(eigenvalues * np.log(eigenvalues))
# Calculate expectation value using density matrix
def expectation_value_dm(operator, rho):
"""Calculate ⟨A⟩ = Tr(ρA)"""
return np.trace(np.dot(rho, operator))
# Time evolution with the von Neumann equation
def von_neumann_equation(t, rho_vec, hamiltonian):
"""Implement von Neumann equation: dρ/dt = -i/ [H,ρ]"""
# Reshape vector to matrix
dim = int(np.sqrt(len(rho_vec) // 2))
rho = rho_vec[:dim*dim].reshape(dim, dim) + 1j * rho_vec[dim*dim:].reshape(dim, dim
# Calculate commutator [H, ρ]
H = hamiltonian(t)
comm = np.dot(H, rho) - np.dot(rho, H)
13. # von Neumann equation
drho_dt = -1j * comm
# Convert back to vector form (real and imaginary parts)
return np.concatenate([drho_dt.real.flatten(), drho_dt.imag.flatten()])
# Partial trace operation
def partial_trace(rho, system_dims, traced_systems):
"""
Calculate partial trace of a density matrix.
Parameters:
- rho: Density matrix of the composite system
- system_dims: List of dimensions for each subsystem
- traced_systems: List of subsystem indices to trace out
Returns:
- Reduced density matrix
"""
# Total number of subsystems
n_systems = len(system_dims)
# Systems to keep
keep_systems = [i for i in range(n_systems) if i not in traced_systems]
# Dimensions to keep and trace
keep_dims = [system_dims[i] for i in keep_systems]
traced_dims = [system_dims[i] for i in traced_systems]
# Reshape density matrix into tensor form
rho_tensor = rho.reshape(system_dims + system_dims)
# Create index arrays for einsum
a = list(range(n_systems))
b = list(range(n_systems, 2*n_systems))
# For each system to trace, set the same index for input and output legs
for i, sys in enumerate(traced_systems):
b[sys] = a[sys]
# Remaining indices for the output tensor
out_a = [a[i] for i in keep_systems]
out_b = [b[i] for i in keep_systems]
# Perform partial trace using einsum
traced_rho_tensor = np.einsum(rho_tensor, a + b, out_a + out_b)
14. # Reshape back to matrix form
traced_rho = traced_rho_tensor.reshape(np.prod(keep_dims), np.prod(keep_dims))
return traced_rho
# Example usage
# Define basis states
ket_0 = np.array([1, 0], dtype=complex)
ket_1 = np.array([0, 1], dtype=complex)
# Create a pure state (superposition)
psi = (ket_0 + ket_1) / np.sqrt(2)
rho_pure = pure_state_density_matrix(psi)
print(f"Pure state density matrix:n{rho_pure}")
# Create a mixed state (statistical mixture of |0⟩ and |1⟩)
mixed_states = [ket_0, ket_1]
probs = [0.7, 0.3] # 70% |0⟩, 30% |1⟩
rho_mixed = mixed_state_density_matrix(mixed_states, probs)
print(f"Mixed state density matrix:n{rho_mixed}")
# Check purity
print(f"Pure state is pure: {is_pure_state(rho_pure)}")
print(f"Mixed state is pure: {is_pure_state(rho_mixed)}")
# Calculate entropy
print(f"Entropy of pure state: {von_neumann_entropy(rho_pure)}")
print(f"Entropy of mixed state: {von_neumann_entropy(rho_mixed)}")
# Calculate expectation value of σₓ
print(f"⟨σₓ⟩ for pure state: {expectation_value_dm(sigma_x, rho_pure)}")
print(f"⟨σₓ⟩ for mixed state: {expectation_value_dm(sigma_x, rho_mixed)}")
# Create a Bell state (entangled state of two qubits)
bell_state = (np.kron(ket_0, ket_0) + np.kron(ket_1, ket_1)) / np.sqrt(2)
rho_bell = pure_state_density_matrix(bell_state)
# Calculate reduced density matrix by tracing out the second qubit
system_dims = [2, 2] # Two qubits
rho_reduced = partial_trace(rho_bell, system_dims, [1]) # Trace out second qubit
print(f"Reduced density matrix:n{rho_reduced}")
print(f"Reduced state is pure: {is_pure_state(rho_reduced)}")
17. import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import simps
# We'll implement a discrete path integral simulation for a particle in a potential
def action(path, dt, m, omega):
"""
Calculate the action S[x] for a given path
Parameters:
- path: Array of position values along the path
- dt: Time step
- m: Mass of the particle
- omega: Angular frequency (for harmonic oscillator)
Returns:
- The action value
"""
# Calculate velocity using central difference
velocity = np.zeros_like(path)
velocity[1:-1] = (path[2:] - path[:-2]) / (2*dt)
velocity[0] = (path[1] - path[0]) / dt
velocity[-1] = (path[-1] - path[-2]) / dt
# Lagrangian: L = T - V = (1/2)mv² - (1/2)mω²x²
kinetic = 0.5 * m * velocity**2
potential = 0.5 * m * omega**2 * path**2
lagrangian = kinetic - potential
# Action is the time integral of the Lagrangian
action = simps(lagrangian, dx=dt)
return action
def propagator_monte_carlo(x_i, x_f, t_i, t_f, m, omega, n_paths=1000, n_points=50):
"""
Calculate the quantum propagator using Monte Carlo path integration
Parameters:
- x_i, x_f: Initial and final positions
- t_i, t_f: Initial and final times
- m: Mass
- omega: Angular frequency
- n_paths: Number of random paths to sample
- n_points: Number of time points in each path
18. Returns:
- Estimated propagator value
"""
dt = (t_f - t_i) / (n_points - 1)
hbar = 1.0 # Normalized units
# Create time grid
times = np.linspace(t_i, t_f, n_points)
# Store all sampled paths
all_paths = np.zeros((n_paths, n_points))
amplitudes = np.zeros(n_paths, dtype=complex)
# Generate random paths with fixed endpoints
for i in range(n_paths):
# Generate a random path with fixed endpoints
path = np.zeros(n_points)
path[0] = x_i
path[-1] = x_f
# Linear path as a starting point
linear_path = np.linspace(x_i, x_f, n_points)
# Add random fluctuations in the middle (not at endpoints)
fluctuation_scale = np.sqrt(hbar / (m * omega)) # Typical quantum fluctuation
fluctuations = fluctuation_scale * np.random.randn(n_points-2)
path[1:-1] = linear_path[1:-1] + fluctuations
# Store the path
all_paths[i] = path
# Calculate action for this path
S = action(path, dt, m, omega)
# Calculate amplitude contribution e^(iS/ )
amplitudes[i] = np.exp(1j * S / hbar)
# Estimate propagator by averaging amplitudes
K = np.mean(amplitudes)
# Calculate analytical result for comparison
# For harmonic oscillator: K = sqrt(m*omega/(2πi* *sin(omega*t))) * exp[i*m*omega*
t = t_f - t_i
analytical_K = np.sqrt(m*omega/(2*np.pi*1j*hbar*np.sin(omega*t))) * np.exp(1j*m*ome
return K, analytical_K, all_paths
19. # Example: Harmonic oscillator
m = 1.0 # Mass
omega = 1.0 # Angular frequency
x_i = 0.0 # Initial position
x_f = 1.0 # Final position
t_i = 0.0 # Initial time
t_f = np.pi # Final time (one half-period)
# Calculate propagator
propagator_value, analytical_value, paths = propagator_monte_carlo(
x_i, x_f, t_i, t_f, m, omega, n_paths=500, n_points=30
)
print(f"Monte Carlo propagator: {propagator_value}")
print(f"Analytical propagator: {analytical_value}")
print(f"Ratio: {propagator_value/analytical_value}")
# Plot a subset of the paths
plt.figure(figsize=(10, 6))
time_points = np.linspace(t_i, t_f, paths.shape[1])
# Plot a few random paths
for i in range(min(20, paths.shape[0])):
plt.plot(time_points, paths[i], 'gray', alpha=0.3)
# Plot the classical path (solution to the Euler-Lagrange equation)
# For harmonic oscillator: x(t) = x_i * cos(ω(t_f-t))/sin(ωt_f) + x_f * sin(ωt)/sin(ωt_
classical_path = x_i * np.cos(omega*(t_f-time_points))/np.sin(omega*t_f) + x_f * np.sin
plt.plot(time_points, classical_path, 'r', linewidth=2, label='Classical path')
plt.grid(True)
plt.xlabel('Time')
plt.ylabel('Position')
plt.title('Path Integral: Sample Paths vs Classical Path')
plt.legend()
# plt.show() # Uncomment to display
23. # Calculate commutators
comm_xy = commutator(Jx, Jy)
comm_yz = commutator(Jy, Jz)
comm_zx = commutator(Jz, Jx)
# Expected results based on structure constants
expected_xy = 1j * Jz
expected_yz = 1j * Jx
expected_zx = 1j * Jy
# Check if they match
print("Testing SU(2) commutation relations:")
print(f"[Jx, Jy] = {comm_xy}")
print(f"Expected = {expected_xy}")
print(f"Match: {np.allclose(comm_xy, expected_xy)}")
print(f"[Jy, Jz] = {comm_yz}")
print(f"Expected = {expected_yz}")
print(f"Match: {np.allclose(comm_yz, expected_yz)}")
print(f"[Jz, Jx] = {comm_zx}")
print(f"Expected = {expected_zx}")
print(f"Match: {np.allclose(comm_zx, expected_zx)}")
# Calculate Clebsch-Gordan coefficients (for combining angular momenta)
def clebsch_gordan(j1, j2, j, m1, m2, m):
"""
Calculate Clebsch-Gordan coefficient ⟨j1,j2,m1,m2|j,m⟩
This is a simplified version for specific cases.
For a complete implementation, use sympy.physics.quantum.cg
"""
# Check if m = m1 + m2 (required by angular momentum conservation)
if abs(m1 + m2 - m) > 1e-10:
return 0
# Simple case: j1 = j2 = 1/2, j = 0 or 1
if abs(j1 - 0.5) < 1e-10 and abs(j2 - 0.5) < 1e-10:
# Combining two spin-1/2 particles
if abs(j - 0) < 1e-10: # Singlet state
return (-1)**(0.5-m1) / np.sqrt(2)
elif abs(j - 1) < 1e-10: # Triplet state
if abs(m - 1) < 1e-10: # m = 1 state
return 1.0 if abs(m1 - 0.5) < 1e-10 and abs(m2 - 0.5) < 1e-10 else 0.0
elif abs(m + 1) < 1e-10: # m = -1 state
return 1.0 if abs(m1 + 0.5) < 1e-10 and abs(m2 + 0.5) < 1e-10 else 0.0
elif abs(m - 0) < 1e-10: # m = 0 state
if (abs(m1 - 0.5) < 1e-10 and abs(m2 + 0.5) < 1e-10) or
(abs(m1 + 0.5) < 1e-10 and abs(m2 - 0.5) < 1e-10):
24. return 1.0 / np.sqrt(2)
return 0.0
# For other cases, a more comprehensive implementation would be needed
return None
# Transform a state under a symmetry operation
def transform_state(operator, state):
"""Apply symmetry transformation to quantum state"""
return np.dot(operator, state)
# Find invariant subspaces (multiplets) in a given representation
def find_multiplets(J_operators, dim):
"""
Find multiplets (irreducible subspaces) in a representation.
Parameters:
- J_operators: List of angular momentum operators [Jx, Jy, Jz]
- dim: Dimension of the representation
Returns:
- List of eigenvalues and eigenvectors of J² and Jz
"""
# Calculate total angular momentum operator J²
Jx, Jy, Jz = J_operators
J_squared = np.dot(Jx, Jx) + np.dot(Jy, Jy) + np.dot(Jz, Jz)
# Find eigenvalues and eigenvectors of J²
eigvals_J2, eigvecs_J2 = np.linalg.eigh(J_squared)
# Round eigenvalues to handle numerical issues
eigvals_J2 = np.round(eigvals_J2, 10)
# Group eigenvectors by J² eigenvalues
multiplets = {}
for i, val in enumerate(eigvals_J2):
j_val = (np.sqrt(4*val + 1) - 1) / 2 # Convert eigenvalue to j value
j_val = round(j_val, 5)
if j_val not in multiplets:
multiplets[j_val] = []
multiplets[j_val].append(eigvecs_J2[:, i])
# For each multiplet, find Jz eigenstates
result = []
for j_val, vectors in multiplets.items():
25. subspace = np.column_stack(vectors)
# Project Jz into this subspace
Jz_proj = subspace.T.conj() @ Jz @ subspace
# Find eigenvalues and eigenvectors of Jz in this subspace
eigvals_Jz, eigvecs_Jz = np.linalg.eigh(Jz_proj)
# Round eigenvalues to handle numerical issues
eigvals_Jz = np.round(eigvals_Jz, 10)
# Transform eigenvectors back to original space
eigvecs_Jz_full = subspace @ eigvecs_Jz
# Add results
for i, m_val in enumerate(eigvals_Jz):
result.append((j_val, m_val, eigvecs_Jz_full[:, i]))
# Sort by j and m values
result.sort(key=lambda x: (-x[0], -x[1]))
return result
# Example usage
Jx, Jy, Jz = su2_generators()
verify_su2_algebra()
# Create a rotation operator (90° about y-axis)
R = su2_rotation(np.pi/2, 0, 1, 0)
print(f"Rotation operator (90° about y-axis):n{R}")
# Apply to spin-up state
spin_up = np.array([1, 0], dtype=complex)
transformed = transform_state(R, spin_up)
print(f"Spin-up state: {spin_up}")
print(f"After rotation: {transformed}")
print(f"Probability amplitudes: {np.abs(transformed)**2}")
# Create spin-1 representation (3×3 matrices)
def spin1_operators():
"""Return the spin-1 representation of SU(2) generators"""
Jx = (1/np.sqrt(2)) * np.array([
[0, 1, 0],
[1, 0, 1],
[0, 1, 0]
], dtype=complex)
26. Jy = (1/np.sqrt(2)) * np.array([
[0, -1j, 0],
[1j, 0, -1j],
[0, 1j, 0]
], dtype=complex)
Jz = np.array([
[1, 0, 0],
[0, 0, 0],
[0, 0, -1]
], dtype=complex)
return Jx, Jy, Jz
# Find multiplets in a direct product representation
def tensor_product_analysis():
"""Analyze the decomposition of a tensor product representation"""
# Create spin-1/2 ⊗ spin-1/2 representation
Jx_half, Jy_half, Jz_half = su2_generators()
# Tensor product representation
dim_combined = 2 * 2 # 2×2 = 4-dimensional space
# Create operators in the tensor product space
I2 = np.eye(2, dtype=complex)
# Total angular momentum operators J = J₁ ⊗ I + I ⊗ J₂
Jx_combined = np.kron(Jx_half, I2) + np.kron(I2, Jx_half)
Jy_combined = np.kron(Jy_half, I2) + np.kron(I2, Jy_half)
Jz_combined = np.kron(Jz_half, I2) + np.kron(I2, Jz_half)
J_operators = [Jx_combined, Jy_combined, Jz_combined]
# Find multiplets
multiplets = find_multiplets(J_operators, dim_combined)
print("nMultiplet analysis of spin-1/2 ⊗ spin-1/2:")
for j, m, vec in multiplets:
print(f"State with j={j}, m={m}:")
# Express in the standard tensor product basis
basis_labels = ["|↑↑⟩", "|↑↓⟩", "|↓↑⟩", "|↓↓⟩"]
for i, (basis, coef) in enumerate(zip(basis_labels, vec)):
if abs(coef) > 1e-10:
print(f" {coef:.4f} × {basis}")
# Verify: spin-1/2 ⊗ spin-1/2 = spin-0 ⊕ spin-1
print("nExpected decomposition: 2 ⊗ 2 = 1 ⊕ 3 (singlet ⊕ triplet)")
27. print("Found in analysis:")
j_values = set(j for j, _, _ in multiplets)
for j in sorted(j_values):
dim = int(2*j + 1)
count = sum(1 for jj, _, _ in multiplets if abs(jj - j) < 1e-10)
print(f" Spin-{j} multiplet: {count}/{dim} states found")
tensor_product_analysis()
29. import numpy as np
import matplotlib.pyplot as plt
from scipy import sparse
from scipy.sparse.linalg import eigsh
from scipy.integrate import solve_ivp
# 1. Discretization and Eigenvalue Problems
def quantum_harmonic_oscillator_1d(n_points=100, x_max=10.0, m=1.0, omega=1.0):
"""
Solve the quantum harmonic oscillator using finite difference method.
Parameters:
- n_points: Number of grid points
- x_max: Maximum position value (grid goes from -x_max to +x_max)
- m: Mass
- omega: Angular frequency
Returns:
- x_grid: Position grid
- eigenenergies: First few energy eigenvalues
- eigenstates: First few wavefunctions
"""
# Constants
hbar = 1.0 # Natural units
# Create position grid
x_grid = np.linspace(-x_max, x_max, n_points)
dx = x_grid[1] - x_grid[0]
# Create potential energy matrix (diagonal)
V = 0.5 * m * omega**2 * x_grid**2
# Create kinetic energy matrix using finite difference for second derivative
# T = - ²/2m * d²/dx²
diagonals = [1, -2, 1] # For second derivative approximation
positions = [-1, 0, 1]
T_fd = sparse.diags(diagonals, positions, shape=(n_points, n_points))
T = -0.5 * hbar**2 * T_fd / (m * dx**2)
# Total Hamiltonian
H = T + sparse.diags(V)
# Find the lowest eigenstates
n_states = 5 # Number of states to compute
eigenenergies, eigenstates = eigsh(H, k=n_states, which='SM')
30. # Normalize eigenstates
for i in range(n_states):
norm = np.sqrt(dx * np.sum(np.abs(eigenstates[:, i])**2))
eigenstates[:, i] /= norm
# Analytical energies for comparison
analytical_energies = np.array([(n + 0.5) * hbar * omega for n in range(n_states)]
print("Quantum Harmonic Oscillator Results:")
print("Numerical vs Analytical Energies:")
for i, (num, ana) in enumerate(zip(eigenenergies, analytical_energies)):
print(f"State {i}: {num:.6f} vs {ana:.6f}, Difference: {abs(num-ana):.6f}")
return x_grid, eigenenergies, eigenstates
# 2. Time Evolution and Numerical Integration
def time_dependent_schrodinger(psi0, x_grid, V_func, t_span, n_steps=1000):
"""
Solve the time-dependent Schrödinger equation using split-operator method.
Parameters:
- psi0: Initial wavefunction
- x_grid: Position grid
- V_func: Potential energy function V(x, t)
- t_span: (t_start, t_end) time interval
- n_steps: Number of time steps
Returns:
- t_grid: Time grid
- psi_t: Wavefunction at each time step
"""
# Constants
hbar = 1.0
m = 1.0
# Grid properties
n_points = len(x_grid)
dx = x_grid[1] - x_grid[0]
# Time grid
t_start, t_end = t_span
dt = (t_end - t_start) / n_steps
t_grid = np.linspace(t_start, t_end, n_steps + 1)
# Momentum grid for kinetic energy in k-space
31. dk = 2 * np.pi / (n_points * dx)
k_grid = np.fft.fftfreq(n_points, dx) * 2 * np.pi
# Kinetic energy operator in k-space
K = 0.5 * hbar**2 * k_grid**2 / m
# Precompute kinetic propagator
exp_K = np.exp(-1j * dt * K / hbar)
# Store wavefunctions
psi_t = np.zeros((n_steps + 1, n_points), dtype=complex)
psi_t[0] = psi0
# Time evolution using split-operator method
for i in range(n_steps):
t = t_start + i * dt
# Current potential
V = V_func(x_grid, t)
# Half-step potential propagation
psi = psi_t[i] * np.exp(-1j * V * dt / (2 * hbar))
# Full-step kinetic propagation in momentum space
psi_k = np.fft.fft(psi)
psi_k = psi_k * exp_K
psi = np.fft.ifft(psi_k)
# Half-step potential propagation
V = V_func(x_grid, t + dt)
psi = psi * np.exp(-1j * V * dt / (2 * hbar))
# Store result
psi_t[i+1] = psi
return t_grid, psi_t
# 3. Monte Carlo Methods for Quantum Systems
def quantum_monte_carlo_1d(V_func, x_min, x_max, n_walkers=1000, n_steps=100, dt=0.01,
"""
Simple diffusion Monte Carlo for ground state calculations.
Parameters:
- V_func: Potential energy function V(x)
- x_min, x_max: Position boundaries
- n_walkers: Number of random walkers
32. - n_steps: Number of simulation steps
- dt: Time step
- tau: Reference energy adjustment rate
Returns:
- positions: Final walker positions
- energies: Energy estimate history
- E0: Ground state energy estimate
"""
# Constants
hbar = 1.0
m = 1.0
# Initialize walkers at random positions
positions = np.random.uniform(x_min, x_max, n_walkers)
# Reference energy (will be adjusted to maintain population)
E_ref = np.mean(V_func(positions))
# Store energy history
energies = np.zeros(n_steps)
# Run simulation
for step in range(n_steps):
# Diffusion: Random walk with standard deviation σ = √( ²dt/m)
sigma = np.sqrt(hbar**2 * dt / m)
positions += np.random.normal(0, sigma, n_walkers)
# Apply boundary conditions
positions = np.clip(positions, x_min, x_max)
# Calculate potential energies
V = V_func(positions)
# Calculate weights based on potential
weights = np.exp(-(V - E_ref) * dt / hbar)
# Branching: Replicate or kill walkers based on weights
new_positions = []
for i, pos in enumerate(positions):
# Number of copies (stochastic rounding)
n_copies = int(weights[i])
if np.random.rand() < (weights[i] - n_copies):
n_copies += 1
# Add copies to new population
new_positions.extend([pos] * n_copies)
33. # Resize population to maintain n_walkers
if len(new_positions) > 0:
# Randomly sample if too many walkers
if len(new_positions) > n_walkers:
indices = np.random.choice(len(new_positions), n_walkers, replace=False
positions = np.array([new_positions[i] for i in indices])
# Replicate randomly if too few walkers
elif len(new_positions) < n_walkers:
indices = np.random.choice(len(new_positions), n_walkers, replace=True
positions = np.array([new_positions[i] for i in indices])
else:
positions = np.array(new_positions)
# Update reference energy to control population
E_ref += tau * np.log(n_walkers / len(new_positions))
# Store current energy estimate
energies[step] = E_ref
# Final ground state energy estimate (average of last 20% of steps)
E0 = np.mean(energies[int(0.8 * n_steps):])
return positions, energies, E0
# 4. Tensor Networks (Matrix Product States)
def simple_mps_ground_state(H_local, L, D, n_sweeps=5):
"""
Find the ground state of a 1D system using matrix product states.
This is a simplified DMRG-like algorithm for pedagogical purposes.
Parameters:
- H_local: Local Hamiltonian term (e.g., for nearest-neighbor interactions)
- L: System length
- D: Maximum bond dimension
- n_sweeps: Number of optimization sweeps
Returns:
- mps: Optimized matrix product state
- energy: Ground state energy
"""
# Local Hilbert space dimension
d = H_local.shape[0] // 2
# Initialize random MPS
34. mps = []
for i in range(L):
if i == 0:
# First site: only right index
A = np.random.rand(d, min(d, D))
elif i == L-1:
# Last site: only left index
A = np.random.rand(min(d**(L-1), D), d)
else:
# Middle sites: left and right indices
D_left = min(d**i, D)
D_right = min(d**(L-i-1), D)
A = np.random.rand(D_left, d, D_right)
# Normalize
A /= np.linalg.norm(A)
mps.append(A)
# DMRG sweeps
energy = 0
for sweep in range(n_sweeps):
# Right-to-left sweep
for i in range(L-2, -1, -1):
# Local optimization (simplified)
# In a real implementation, we would solve the local eigenvalue problem
# Normalize the tensor
if i == 0:
shape = mps[i].shape
mps[i] = mps[i].reshape(shape[0]*shape[1])
mps[i] /= np.linalg.norm(mps[i])
mps[i] = mps[i].reshape(shape)
else:
mps[i] /= np.linalg.norm(mps[i])
# Left-to-right sweep
for i in range(L-1):
# Local optimization (simplified)
# In a real implementation, we would solve the local eigenvalue problem
# Normalize the tensor
if i == L-1:
shape = mps[i].shape
mps[i] = mps[i].reshape(shape[0]*shape[1])
mps[i] /= np.linalg.norm(mps[i])
mps[i] = mps[i].reshape(shape)
else:
35. mps[i] /= np.linalg.norm(mps[i])
# Calculate energy (placeholder)
# In a real implementation, we would contract the MPS with the MPO representati
energy = -1.0 * (sweep + 1) / n_sweeps # Placeholder
return mps, energy
# Example implementations
# Example 1: Quantum Harmonic Oscillator
def example_harmonic_oscillator():
x_grid, energies, states = quantum_harmonic_oscillator_1d(n_points=200, x_max=8.0)
# Plot the first few eigenstates
plt.figure(figsize=(12, 8))
for i in range(min(5, len(energies))):
plt.plot(x_grid, states[:, i] + energies[i], label=f"n={i}, E={energies[i]:.4f
# Plot the classical limits (turning points)
x_classical = np.sqrt(2 * energies[i])
plt.axvline(x_classical, color='gray', linestyle='--', alpha=0.5)
plt.axvline(-x_classical, color='gray', linestyle='--', alpha=0.5)
# Plot the potential
plt.plot(x_grid, 0.5 * x_grid**2, 'k-', label="V(x)")
plt.title("Quantum Harmonic Oscillator: Eigenstates")
plt.xlabel("Position")
plt.ylabel("Energy / Wavefunction")
plt.legend()
plt.grid(True)
# plt.show() # Uncomment to display
return x_grid, energies, states
# Example 2: Wavepacket dynamics
def example_wavepacket():
# Setup grid
x_min, x_max = -10, 10
n_points = 500
x_grid = np.linspace(x_min, x_max, n_points)
dx = x_grid[1] - x_grid[0]
# Initial wavepacket: Gaussian centered at x0 with momentum k0
x0 = -4.0
k0 = 2.0
36. sigma = 0.5
# Create wavepacket: ψ(x) = (2πσ²)^(-1/4) * exp(-(x-x0)²/(4σ²) + ik0x)
psi0 = (2*np.pi*sigma**2)**(-0.25) * np.exp(-(x_grid-x0)**2/(4*sigma**2)) * np.exp
# Normalize
psi0 /= np.sqrt(dx * np.sum(np.abs(psi0)**2))
# Define potential: double well V(x) = A * (x² - B)²
A = 0.1
B = 4.0
V_func = lambda x, t: A * (x**2 - B)**2
# Time evolution
t_span = (0, 10)
t_grid, psi_t = time_dependent_schrodinger(psi0, x_grid, V_func, t_span, n_steps=20
# Calculate probability densities
probs = np.abs(psi_t)**2
# Visualize the evolution (animation frames)
fig, ax = plt.subplots(figsize=(10, 6))
# Plot the potential (scaled for visibility)
V_plot = V_func(x_grid, 0)
V_max = np.max(V_plot)
V_scaled = 0.3 * V_plot / V_max
ax.plot(x_grid, V_scaled, 'k-', label="Potential (scaled)")
# Plot initial probability density
line, = ax.plot(x_grid, probs[0], 'r-', label="Probability density")
ax.set_ylim(0, np.max(probs) * 1.2)
ax.set_xlabel("Position")
ax.set_ylabel("Probability density")
ax.set_title(f"Wavepacket dynamics: t = 0")
ax.legend()
ax.grid(True)
# For static view, show a few snapshots
plt.figure(figsize=(12, 8))
times = [0, 50, 100, 150, 199] # Selected time indices
for i, t_idx in enumerate(times):
plt.subplot(len(times), 1, i+1)
plt.plot(x_grid, V_scaled, 'k-')
plt.plot(x_grid, probs[t_idx], label=f"t = {t_grid[t_idx]:.2f}")
plt.title(f"t = {t_grid[t_idx]:.2f}")
37. plt.ylim(0, np.max(probs) * 1.2)
if i == len(times)-1:
plt.xlabel("Position")
plt.ylabel("Density")
plt.grid(True)
plt.tight_layout()
# plt.show() # Uncomment to display
return t_grid, psi_t
# Example 3: Diffusion Monte Carlo for harmonic oscillator
def example_dmc():
# Harmonic oscillator potential
omega = 1.0
V_func = lambda x: 0.5 * omega**2 * x**2
# Run DMC simulation
positions, energies, E0 = quantum_monte_carlo_1d(
V_func, x_min=-5, x_max=5, n_walkers=1000, n_steps=200, dt=0.01, tau=0.05
)
# Analytical ground state energy for comparison
E_analytical = 0.5 # in natural units ( ω/2)
# Plot energy convergence
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(energies)
plt.axhline(E_analytical, color='r', linestyle='--', label=f"Analytical E₀ = {E_ana
plt.xlabel("Monte Carlo step")
plt.ylabel("Energy estimate")
plt.title(f"DMC Energy Convergence (E₀ = {E0:.4f})")
plt.legend()
plt.grid(True)
# Plot final walker distribution
plt.subplot(1, 2, 2)
plt.hist(positions, bins=50, density=True, alpha=0.7)
# Compare with analytical ground state wavefunction
x_plot = np.linspace(-5, 5, 1000)
psi_analytical = (omega/np.pi)**0.25 * np.exp(-omega * x_plot**2 / 2)
pdf_analytical = psi_analytical**2
plt.plot(x_plot, pdf_analytical, 'r-', label="Analytical")