SlideShare a Scribd company logo
⟩
⟨
⟨ ⟩ ᵢ ᵢ ᵢ
⟨ ⟩
⟨ ᵢ ⱼ⟩ ᵢⱼ
⟩ ⟩
python
import numpy as np
from scipy import linalg
# Define a simple two-level quantum state (qubit)
def qubit_state(alpha, beta):
"""Create a normalized qubit state α|0⟩ + β|1⟩"""
state = np.array([alpha, beta], dtype=complex)
# Normalize the state
norm = np.sqrt(np.abs(alpha)**2 + np.abs(beta)**2)
return state / norm
# Create basis states
ket_0 = qubit_state(1, 0) # |0⟩
ket_1 = qubit_state(0, 1) # |1⟩
# Create a superposition state
ket_plus = qubit_state(1/np.sqrt(2), 1/np.sqrt(2)) # |+⟩ = (|0⟩ + |1⟩)/√2
# Inner product
def inner_product(bra, ket):
"""Calculate ⟨bra|ket⟩"""
return np.vdot(bra, ket) # Conjugate of bra multiplied by ket
# Calculate probability amplitude
prob_amplitude = inner_product(ket_0, ket_plus)
probability = np.abs(prob_amplitude)**2
print(f"State |+⟩: {ket_plus}")
print(f"Inner product ⟨0|+⟩: {prob_amplitude}")
print(f"Probability of measuring |0⟩: {probability}")
# Tensor product (composite system of two qubits)
def tensor_product(state1, state2):
"""Calculate the tensor product |state1⟩ ⊗ |state2⟩"""
return np.kron(state1, state2)
# Create a Bell state (entangled state)
bell_state = (1/np.sqrt(2)) * tensor_product(ket_0, ket_0) + (1/np.sqrt(2)) * tensor_p
print(f"Bell state: {bell_state}")
⟩ ⟩
⟨ ⟩ ⟨ ⟩
⟩⟨
python
import numpy as np
# Pauli matrices (fundamental operators in quantum mechanics)
sigma_x = np.array([[0, 1], [1, 0]], dtype=complex) # σₓ
sigma_y = np.array([[0, -1j], [1j, 0]], dtype=complex) # σᵧ
sigma_z = np.array([[1, 0], [0, -1]], dtype=complex) # σᵧ
identity = np.array([[1, 0], [0, 1]], dtype=complex) # I
# State to operate on
psi = np.array([1/np.sqrt(2), 1/np.sqrt(2)], dtype=complex) # |+⟩
# Apply an operator to a state
def apply_operator(operator, state):
"""Apply operator to quantum state: operator|state⟩"""
return np.dot(operator, state)
# Calculate expectation value
def expectation_value(operator, state):
"""Calculate ⟨state|operator|state⟩"""
return np.vdot(state, apply_operator(operator, state))
# Test operators on the |+⟩ state
print(f"σₓ|+⟩ = {apply_operator(sigma_x, psi)}")
print(f"⟨+|σₓ|+⟩ = {expectation_value(sigma_x, psi)}")
print(f"⟨+|σᵧ|+⟩ = {expectation_value(sigma_y, psi)}")
print(f"⟨+|σᵧ|+⟩ = {expectation_value(sigma_z, psi)}")
# Check if an operator is Hermitian
def is_hermitian(operator):
"""Check if operator is Hermitian: A† = A"""
return np.allclose(operator, operator.conj().T)
# Check if an operator is unitary
def is_unitary(operator):
"""Check if operator is unitary: U†U = I"""
return np.allclose(np.dot(operator.conj().T, operator), np.eye(operator.shape[0]))
# Create a projection operator
def projection_operator(state):
"""Create projection operator |state⟩⟨state|"""
return np.outer(state, state.conj())
# Calculate commutator
def commutator(op_a, op_b):
"""Calculate [A,B] = AB - BA"""
return np.dot(op_a, op_b) - np.dot(op_b, op_a)
⟩ ⟩
⟩ ⟩
⟩ ⟩
# Check properties of operators
print(f"σₓ is Hermitian: {is_hermitian(sigma_x)}")
print(f"σₓ is unitary: {is_unitary(sigma_x)}")
# Compute the projection operator onto |0⟩
proj_0 = projection_operator(np.array([1, 0]))
print(f"Projection onto |0⟩:n{proj_0}")
# Compute the commutator [σₓ, σᵧ]
comm_xy = commutator(sigma_x, sigma_y)
print(f"[σₓ, σᵧ] = {comm_xy}")
python
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
from scipy import linalg
# Constants
hbar = 1.0 # Normalized units
# Define a simple Hamiltonian for a two-level system
def hamiltonian(t):
"""Time-dependent Hamiltonian for a two-level system"""
# Example: Rabi oscillations
omega = 1.0 # Rabi frequency
delta = 0.5 # Detuning
H = np.array([
[delta/2, omega/2 * np.exp(-1j * t)],
[omega/2 * np.exp(1j * t), -delta/2]
], dtype=complex)
return H
# Method 1: Direct time evolution with ODE solver
def schrodinger_equation(t, psi, h_func):
"""Right-hand side of the Schrödinger equation: d|ψ⟩/dt = -i/ H|ψ⟩"""
H = h_func(t)
dpsi_dt = -1j / hbar * np.dot(H, psi)
# Convert to real-valued for ODE solver (real and imaginary parts separately)
return np.concatenate([dpsi_dt.real, dpsi_dt.imag])
# Initial state: |0⟩
psi_0 = np.array([1.0, 0.0], dtype=complex)
# Convert to real-valued for ODE solver
psi_0_real = np.concatenate([psi_0.real, psi_0.imag])
# Time grid
t_span = (0, 10)
t_eval = np.linspace(0, 10, 100)
# Solve the Schrödinger equation
solution = solve_ivp(
lambda t, y: schrodinger_equation(t, y[:2] + 1j*y[2:], hamiltonian),
t_span, psi_0_real, t_eval=t_eval
)
# Extract the solution
psi_t = solution.y[:2] + 1j * solution.y[2:]
probabilities = np.abs(psi_t)**2
# Method 2: Time evolution with the unitary operator
def time_evolution_operator(t, h_func):
"""Calculate U(t) = exp(-i*H*t/ )"""
H = h_func(t)
return linalg.expm(-1j * H * t / hbar)
# Calculate time evolution using the unitary operator
psi_t_unitary = np.zeros((2, len(t_eval)), dtype=complex)
psi_t_unitary[:, 0] = psi_0
for i in range(1, len(t_eval)):
dt = t_eval[i] - t_eval[i-1]
U = time_evolution_operator(t_eval[i-1], hamiltonian)
psi_t_unitary[:, i] = np.dot(U, psi_t_unitary[:, i-1])
# Plot the results
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.title("ODE Solver Method")
plt.plot(t_eval, probabilities[0], label="|0⟩ probability")
plt.plot(t_eval, probabilities[1], label="|1⟩ probability")
plt.xlabel("Time")
plt.ylabel("Probability")
plt.legend()
plt.subplot(1, 2, 2)
plt.title("Unitary Operator Method")
plt.plot(t_eval, np.abs(psi_t_unitary[0])**2, label="|0⟩ probability")
plt.plot(t_eval, np.abs(psi_t_unitary[1])**2, label="|1⟩ probability")
plt.xlabel("Time")
plt.ylabel("Probability")
plt.legend()
plt.tight_layout()
# plt.show() # Uncomment to display
⟩⟨
ᵢ ᵢ ᵢ⟩⟨ ᵢ
⟨ ⟩
python
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)
# 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)
# 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)}")
⟨ ⟩ ℏ
ᵗᶠ ᵢ
⟨ ⟩
⟨ ⟩ ℏ ℏ
python
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
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
# 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
∈
ᵏ ᵏ ᵏ
ᵃ ᵇ ᵃᵇᶜ ᶜ ᵃᵇᶜ
python
import numpy as np
from scipy import linalg
# SU(2) Generators (Pauli matrices scaled by 1/2)
def su2_generators():
"""Return the generators of SU(2) group (angular momentum operators)"""
Jx = 0.5 * np.array([[0, 1], [1, 0]], dtype=complex)
Jy = 0.5 * np.array([[0, -1j], [1j, 0]], dtype=complex)
Jz = 0.5 * np.array([[1, 0], [0, -1]], dtype=complex)
return Jx, Jy, Jz
# Create group element (rotation) from generators
def su2_rotation(theta, nx, ny, nz):
"""
Create rotation operator e^(-i θ n·J)
Parameters:
- theta: Rotation angle
- nx, ny, nz: Components of rotation axis (unit vector)
Returns:
- Unitary rotation operator (2×2 matrix)
"""
# Normalize the rotation axis
norm = np.sqrt(nx**2 + ny**2 + nz**2)
nx, ny, nz = nx/norm, ny/norm, nz/norm
# Get generators
Jx, Jy, Jz = su2_generators()
# Construct n·J
n_dot_J = nx*Jx + ny*Jy + nz*Jz
# Compute the exponential
return linalg.expm(-1j * theta * n_dot_J)
# Calculate commutator of two operators
def commutator(op_a, op_b):
"""Calculate [A, B] = AB - BA"""
return np.dot(op_a, op_b) - np.dot(op_b, op_a)
# Verify the SU(2) commutation relations
def verify_su2_algebra():
"""Verify the SU(2) commutation relations: [Jᵢ, Jⱼ] = iεᵢⱼ J """
Jx, Jy, Jz = su2_generators()
# 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):
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():
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)
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)")
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()
python
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')
# 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
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
- 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)
# 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
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:
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
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}")
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")
plt.xlabel("Position")
plt.ylabel("Probability density")
plt.title("Final Walker Distribution")
plt.legend()
plt.grid(True)
plt.tight_layout()
# plt.show() # Uncomment to display
return positions, energies, E0
# Run examples
x_grid, energies, states = example_harmonic_oscillator()
t_grid, psi_t = example_wavepacket()
positions, e_history, e0 = example_dmc()

More Related Content

PDF
2018 MUMS Fall Course - Statistical Representation of Model Input (EDITED) - ...
PDF
Quantum algorithm for solving linear systems of equations
PDF
PDF
Boson Sampling
PDF
MMath Paper, Canlin Zhang
PDF
Matlab Sample Assignment Solution
PDF
Numerical solution of the Schr¨odinger equation
PDF
Litvinenko_RWTH_UQ_Seminar_talk.pdf
2018 MUMS Fall Course - Statistical Representation of Model Input (EDITED) - ...
Quantum algorithm for solving linear systems of equations
Boson Sampling
MMath Paper, Canlin Zhang
Matlab Sample Assignment Solution
Numerical solution of the Schr¨odinger equation
Litvinenko_RWTH_UQ_Seminar_talk.pdf

Similar to Quantum simulation Mathematics and Python examples (20)

PDF
Quantum Computation and the Stabilizer Formalism for Error Correction
PDF
Google TensorFlow Tutorial
PDF
M2 Internship report rare-earth nickelates
PDF
Operators n dirac in qm
DOC
An1 derivat.ro fizica-2_quantum physics 2 2010 2011_24125
DOC
An1 derivat.ro fizica-2_quantum physics 2 2010 2011_24125
PDF
MSC-2013-12
PPTX
Numerical calculation of high-order QED contributions to the electron anomalo...
PDF
PhDThesis
PDF
TENSOR DECOMPOSITION WITH PYTHON
PPTX
Density Matrix - Quantum and statistical mechanics
PDF
weights training of perceptron (using 3 training rules)
PDF
Language Language Models (in 2023) - OpenAI
PDF
Master Thesis
PDF
Hierarchical matrices for approximating large covariance matries and computin...
PPTX
Fourier project presentation
PDF
3 (a) calculate thy potential at point P located a distance z above .pdf
PDF
From NumPy to PyTorch
PDF
Scientific visualization with_gr
Quantum Computation and the Stabilizer Formalism for Error Correction
Google TensorFlow Tutorial
M2 Internship report rare-earth nickelates
Operators n dirac in qm
An1 derivat.ro fizica-2_quantum physics 2 2010 2011_24125
An1 derivat.ro fizica-2_quantum physics 2 2010 2011_24125
MSC-2013-12
Numerical calculation of high-order QED contributions to the electron anomalo...
PhDThesis
TENSOR DECOMPOSITION WITH PYTHON
Density Matrix - Quantum and statistical mechanics
weights training of perceptron (using 3 training rules)
Language Language Models (in 2023) - OpenAI
Master Thesis
Hierarchical matrices for approximating large covariance matries and computin...
Fourier project presentation
3 (a) calculate thy potential at point P located a distance z above .pdf
From NumPy to PyTorch
Scientific visualization with_gr
Ad

Recently uploaded (20)

PDF
cuic standard and advanced reporting.pdf
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
DOCX
The AUB Centre for AI in Media Proposal.docx
PPTX
Spectroscopy.pptx food analysis technology
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PDF
Machine learning based COVID-19 study performance prediction
PPT
Teaching material agriculture food technology
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
MIND Revenue Release Quarter 2 2025 Press Release
PDF
Spectral efficient network and resource selection model in 5G networks
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PDF
Encapsulation theory and applications.pdf
PDF
KodekX | Application Modernization Development
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
cuic standard and advanced reporting.pdf
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
The AUB Centre for AI in Media Proposal.docx
Spectroscopy.pptx food analysis technology
“AI and Expert System Decision Support & Business Intelligence Systems”
Machine learning based COVID-19 study performance prediction
Teaching material agriculture food technology
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
MIND Revenue Release Quarter 2 2025 Press Release
Spectral efficient network and resource selection model in 5G networks
Understanding_Digital_Forensics_Presentation.pptx
Advanced methodologies resolving dimensionality complications for autism neur...
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
Building Integrated photovoltaic BIPV_UPV.pdf
Encapsulation theory and applications.pdf
KodekX | Application Modernization Development
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Review of recent advances in non-invasive hemoglobin estimation
The Rise and Fall of 3GPP – Time for a Sabbatical?
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
Ad

Quantum simulation Mathematics and Python examples

  • 1. ⟩ ⟨ ⟨ ⟩ ᵢ ᵢ ᵢ ⟨ ⟩ ⟨ ᵢ ⱼ⟩ ᵢⱼ ⟩ ⟩
  • 2. python import numpy as np from scipy import linalg # Define a simple two-level quantum state (qubit) def qubit_state(alpha, beta): """Create a normalized qubit state α|0⟩ + β|1⟩""" state = np.array([alpha, beta], dtype=complex) # Normalize the state norm = np.sqrt(np.abs(alpha)**2 + np.abs(beta)**2) return state / norm # Create basis states ket_0 = qubit_state(1, 0) # |0⟩ ket_1 = qubit_state(0, 1) # |1⟩ # Create a superposition state ket_plus = qubit_state(1/np.sqrt(2), 1/np.sqrt(2)) # |+⟩ = (|0⟩ + |1⟩)/√2 # Inner product def inner_product(bra, ket): """Calculate ⟨bra|ket⟩""" return np.vdot(bra, ket) # Conjugate of bra multiplied by ket # Calculate probability amplitude prob_amplitude = inner_product(ket_0, ket_plus) probability = np.abs(prob_amplitude)**2 print(f"State |+⟩: {ket_plus}") print(f"Inner product ⟨0|+⟩: {prob_amplitude}") print(f"Probability of measuring |0⟩: {probability}") # Tensor product (composite system of two qubits) def tensor_product(state1, state2): """Calculate the tensor product |state1⟩ ⊗ |state2⟩""" return np.kron(state1, state2) # Create a Bell state (entangled state) bell_state = (1/np.sqrt(2)) * tensor_product(ket_0, ket_0) + (1/np.sqrt(2)) * tensor_p print(f"Bell state: {bell_state}")
  • 3. ⟩ ⟩ ⟨ ⟩ ⟨ ⟩ ⟩⟨
  • 5. import numpy as np # Pauli matrices (fundamental operators in quantum mechanics) sigma_x = np.array([[0, 1], [1, 0]], dtype=complex) # σₓ sigma_y = np.array([[0, -1j], [1j, 0]], dtype=complex) # σᵧ sigma_z = np.array([[1, 0], [0, -1]], dtype=complex) # σᵧ identity = np.array([[1, 0], [0, 1]], dtype=complex) # I # State to operate on psi = np.array([1/np.sqrt(2), 1/np.sqrt(2)], dtype=complex) # |+⟩ # Apply an operator to a state def apply_operator(operator, state): """Apply operator to quantum state: operator|state⟩""" return np.dot(operator, state) # Calculate expectation value def expectation_value(operator, state): """Calculate ⟨state|operator|state⟩""" return np.vdot(state, apply_operator(operator, state)) # Test operators on the |+⟩ state print(f"σₓ|+⟩ = {apply_operator(sigma_x, psi)}") print(f"⟨+|σₓ|+⟩ = {expectation_value(sigma_x, psi)}") print(f"⟨+|σᵧ|+⟩ = {expectation_value(sigma_y, psi)}") print(f"⟨+|σᵧ|+⟩ = {expectation_value(sigma_z, psi)}") # Check if an operator is Hermitian def is_hermitian(operator): """Check if operator is Hermitian: A† = A""" return np.allclose(operator, operator.conj().T) # Check if an operator is unitary def is_unitary(operator): """Check if operator is unitary: U†U = I""" return np.allclose(np.dot(operator.conj().T, operator), np.eye(operator.shape[0])) # Create a projection operator def projection_operator(state): """Create projection operator |state⟩⟨state|""" return np.outer(state, state.conj()) # Calculate commutator def commutator(op_a, op_b): """Calculate [A,B] = AB - BA""" return np.dot(op_a, op_b) - np.dot(op_b, op_a)
  • 6. ⟩ ⟩ ⟩ ⟩ ⟩ ⟩ # Check properties of operators print(f"σₓ is Hermitian: {is_hermitian(sigma_x)}") print(f"σₓ is unitary: {is_unitary(sigma_x)}") # Compute the projection operator onto |0⟩ proj_0 = projection_operator(np.array([1, 0])) print(f"Projection onto |0⟩:n{proj_0}") # Compute the commutator [σₓ, σᵧ] comm_xy = commutator(sigma_x, sigma_y) print(f"[σₓ, σᵧ] = {comm_xy}")
  • 8. import numpy as np import matplotlib.pyplot as plt from scipy.integrate import solve_ivp from scipy import linalg # Constants hbar = 1.0 # Normalized units # Define a simple Hamiltonian for a two-level system def hamiltonian(t): """Time-dependent Hamiltonian for a two-level system""" # Example: Rabi oscillations omega = 1.0 # Rabi frequency delta = 0.5 # Detuning H = np.array([ [delta/2, omega/2 * np.exp(-1j * t)], [omega/2 * np.exp(1j * t), -delta/2] ], dtype=complex) return H # Method 1: Direct time evolution with ODE solver def schrodinger_equation(t, psi, h_func): """Right-hand side of the Schrödinger equation: d|ψ⟩/dt = -i/ H|ψ⟩""" H = h_func(t) dpsi_dt = -1j / hbar * np.dot(H, psi) # Convert to real-valued for ODE solver (real and imaginary parts separately) return np.concatenate([dpsi_dt.real, dpsi_dt.imag]) # Initial state: |0⟩ psi_0 = np.array([1.0, 0.0], dtype=complex) # Convert to real-valued for ODE solver psi_0_real = np.concatenate([psi_0.real, psi_0.imag]) # Time grid t_span = (0, 10) t_eval = np.linspace(0, 10, 100) # Solve the Schrödinger equation solution = solve_ivp( lambda t, y: schrodinger_equation(t, y[:2] + 1j*y[2:], hamiltonian), t_span, psi_0_real, t_eval=t_eval ) # Extract the solution
  • 9. psi_t = solution.y[:2] + 1j * solution.y[2:] probabilities = np.abs(psi_t)**2 # Method 2: Time evolution with the unitary operator def time_evolution_operator(t, h_func): """Calculate U(t) = exp(-i*H*t/ )""" H = h_func(t) return linalg.expm(-1j * H * t / hbar) # Calculate time evolution using the unitary operator psi_t_unitary = np.zeros((2, len(t_eval)), dtype=complex) psi_t_unitary[:, 0] = psi_0 for i in range(1, len(t_eval)): dt = t_eval[i] - t_eval[i-1] U = time_evolution_operator(t_eval[i-1], hamiltonian) psi_t_unitary[:, i] = np.dot(U, psi_t_unitary[:, i-1]) # Plot the results plt.figure(figsize=(12, 5)) plt.subplot(1, 2, 1) plt.title("ODE Solver Method") plt.plot(t_eval, probabilities[0], label="|0⟩ probability") plt.plot(t_eval, probabilities[1], label="|1⟩ probability") plt.xlabel("Time") plt.ylabel("Probability") plt.legend() plt.subplot(1, 2, 2) plt.title("Unitary Operator Method") plt.plot(t_eval, np.abs(psi_t_unitary[0])**2, label="|0⟩ probability") plt.plot(t_eval, np.abs(psi_t_unitary[1])**2, label="|1⟩ probability") plt.xlabel("Time") plt.ylabel("Probability") plt.legend() plt.tight_layout() # plt.show() # Uncomment to display
  • 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)}")
  • 15. ⟨ ⟩ ℏ ᵗᶠ ᵢ ⟨ ⟩ ⟨ ⟩ ℏ ℏ
  • 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
  • 20. ∈ ᵏ ᵏ ᵏ ᵃ ᵇ ᵃᵇᶜ ᶜ ᵃᵇᶜ
  • 22. import numpy as np from scipy import linalg # SU(2) Generators (Pauli matrices scaled by 1/2) def su2_generators(): """Return the generators of SU(2) group (angular momentum operators)""" Jx = 0.5 * np.array([[0, 1], [1, 0]], dtype=complex) Jy = 0.5 * np.array([[0, -1j], [1j, 0]], dtype=complex) Jz = 0.5 * np.array([[1, 0], [0, -1]], dtype=complex) return Jx, Jy, Jz # Create group element (rotation) from generators def su2_rotation(theta, nx, ny, nz): """ Create rotation operator e^(-i θ n·J) Parameters: - theta: Rotation angle - nx, ny, nz: Components of rotation axis (unit vector) Returns: - Unitary rotation operator (2×2 matrix) """ # Normalize the rotation axis norm = np.sqrt(nx**2 + ny**2 + nz**2) nx, ny, nz = nx/norm, ny/norm, nz/norm # Get generators Jx, Jy, Jz = su2_generators() # Construct n·J n_dot_J = nx*Jx + ny*Jy + nz*Jz # Compute the exponential return linalg.expm(-1j * theta * n_dot_J) # Calculate commutator of two operators def commutator(op_a, op_b): """Calculate [A, B] = AB - BA""" return np.dot(op_a, op_b) - np.dot(op_b, op_a) # Verify the SU(2) commutation relations def verify_su2_algebra(): """Verify the SU(2) commutation relations: [Jᵢ, Jⱼ] = iεᵢⱼ J """ Jx, Jy, Jz = su2_generators()
  • 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")
  • 38. plt.xlabel("Position") plt.ylabel("Probability density") plt.title("Final Walker Distribution") plt.legend() plt.grid(True) plt.tight_layout() # plt.show() # Uncomment to display return positions, energies, E0 # Run examples x_grid, energies, states = example_harmonic_oscillator() t_grid, psi_t = example_wavepacket() positions, e_history, e0 = example_dmc()