Computational Physics Research Environment - Getting Started

Computational Physics Research Environment - Getting Started

Time to Complete: 20 minutes Cost: $14-22 for tutorial Skill Level: Beginner (no cloud experience needed)

What You’ll Build

By the end of this guide, you’ll have a working computational physics research environment that can:

  • Run high-performance computing simulations for particle physics
  • Process large-scale numerical calculations and Monte Carlo simulations
  • Analyze quantum mechanics problems and electromagnetic field calculations
  • Handle parallel computing workloads for physics modeling

Meet Dr. Michael Anderson

Dr. Michael Anderson is a theoretical physicist at CERN. He simulates particle interactions but waits weeks for supercomputer access. Each simulation requires processing billions of particle collisions and quantum state calculations.

Before: 3-week waits + 4-day computation = 4 weeks per physics simulation After: 15-minute setup + 6-hour computation = same day results Time Saved: 96% faster computational physics research cycle Cost Savings: $800/month vs $3,200 supercomputer allocation

Before You Start

What You Need

  • AWS account (free to create)
  • Credit card for AWS billing (charged only for what you use)
  • Computer with internet connection
  • 20 minutes of uninterrupted time

Cost Expectations

  • Tutorial cost: $14-22 (we’ll clean up resources when done)
  • Daily research cost: $35-70 per day when actively computing
  • Monthly estimate: $450-900 per month for typical usage
  • Free tier: Some compute included free for first 12 months

Skills Needed

  • Basic computer use (creating folders, installing software)
  • Copy and paste commands
  • No physics or programming experience required

Step 1: Install AWS Research Wizard

Choose your operating system:

macOS/Linux

curl -fsSL https://install.aws-research-wizard.com | sh

Windows

Download from: https://github.com/aws-research-wizard/releases/latest

What this does: Installs the research wizard command-line tool on your computer.

Expected result: You should see “Installation successful” message.

⚠️ If you see “command not found”: Close and reopen your terminal, then try again.

Step 2: Set Up AWS Account

If you don’t have an AWS account:

  1. Go to aws.amazon.com
  2. Click “Create an AWS Account”
  3. Follow the signup process
  4. Important: Choose the free tier options

What this does: Creates your personal cloud computing account.

Expected result: You receive email confirmation from AWS.

💰 Cost note: Account creation is free. You only pay for resources you use.

Step 3: Configure Your Credentials

aws-research-wizard config setup

The wizard will ask for:

  • AWS Access Key: Found in AWS Console → Security Credentials
  • Secret Key: Created with your access key
  • Region: Choose us-east-1 (recommended for computational physics with good CPU performance)

What this does: Connects the research wizard to your AWS account.

Expected result: “✅ AWS credentials configured successfully”

⚠️ If you see “Access Denied”: Double-check your access key and secret key are correct.

Step 4: Validate Your Setup

aws-research-wizard deploy validate --domain computational_physics --region us-east-1

What this does: Checks that everything is working before we spend money.

Expected result: “✅ All validations passed”

⚠️ If validation fails: Check your internet connection and AWS credentials.

Step 5: Deploy Your Research Environment

aws-research-wizard deploy create --domain computational_physics --region us-east-1 --instance-type c5.4xlarge

What this does: Creates a cloud computer with high-performance computational physics tools.

Expected result: You’ll see progress updates for about 8 minutes, then “✅ Environment ready”

💰 Billing starts now: About $0.68 per hour ($16.32 per day if left running)

⚠️ If deploy fails: Run the command again. AWS sometimes has temporary issues.

Step 6: Connect to Your Environment

aws-research-wizard connect --domain computational_physics

What this does: Opens a connection to your cloud research environment.

Expected result: You’ll see a terminal prompt like [physicist@ip-10-0-1-123 ~]$

🎉 Success: You’re now inside your computational physics research environment!

Step 7: Verify Your Tools

Let’s make sure all the physics tools are working:

# Check Python scientific computing tools
python3 -c "import numpy, scipy, matplotlib, numba; print('✅ Scientific computing tools ready')"

# Check parallel computing tools
python3 -c "import multiprocessing; print(f'✅ {multiprocessing.cpu_count()} CPU cores available')"

# Check MPI for parallel computing
mpiexec --version

Expected result: You should see “✅” messages confirming tools are installed.

⚠️ If tools are missing: Run sudo yum update && sudo yum install openmpi-devel then try again.

Step 8: Analyze Real Physics Data from AWS Open Data

Let’s analyze real experimental and theoretical physics data:

📊 Data Download Summary:

  • CERN Open Data: ~3.8 GB (particle collision data)
  • NASA Exoplanet Archive: ~1.2 GB (stellar and planetary data)
  • LIGO Gravitational Waves: ~2.4 GB (detector data)
  • Total download: ~7.4 GB
  • Estimated time: 15-20 minutes on typical broadband
# Create workspace
mkdir -p ~/physics_research/simulations
cd ~/physics_research/simulations

# Download real physics data from AWS Open Data
echo "Downloading CERN particle collision data (~3.8GB)..."
aws s3 cp s3://cern-open-data/Run2012B/SingleMuon/AOD/collision_data.root . --no-sign-request

echo "Downloading NASA Exoplanet Archive (~1.2GB)..."
aws s3 cp s3://nasa-exoplanet-archive/stellar_properties.csv . --no-sign-request

echo "Downloading LIGO gravitational wave data (~2.4GB)..."
aws s3 cp s3://ligo-open-data/GW150914/H-H1_LOSC_4_V2-1126259446-32.hdf5 . --no-sign-request

echo "Real physics data downloaded successfully!"

What this data contains:

  • CERN: Proton-proton collision data at 8 TeV from the LHC
  • NASA Exoplanet Archive: 5,000+ confirmed exoplanets with stellar properties
  • LIGO: Gravitational wave strain data from GW150914 black hole merger
  • Format: ROOT files, CSV tables, and HDF5 binary data

Particle Physics Simulation

Create this Python script for particle physics:

cat > particle_physics_sim.py << 'EOF'
#!/usr/bin/env python3
"""
Computational Physics Simulation Suite
Simulates particle interactions, quantum mechanics, and electromagnetic fields
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy import constants, integrate, optimize
from numba import jit, prange
import multiprocessing as mp
import time
import warnings
warnings.filterwarnings('ignore')

print("⚛️ Initializing particle physics simulation...")

# Physical constants
c = constants.c  # Speed of light
h = constants.h  # Planck constant
hbar = constants.hbar  # Reduced Planck constant
e = constants.e  # Elementary charge
m_e = constants.m_e  # Electron mass
m_p = constants.m_p  # Proton mass

print(f"Using {mp.cpu_count()} CPU cores for parallel computation")

# Monte Carlo particle collision simulation
print("\n🎯 Monte Carlo Particle Collision Simulation")
print("=" * 43)

@jit(nopython=True)
def simulate_particle_collision(n_particles, energy_range):
    """Simulate particle collisions using Monte Carlo method"""
    # Generate random particle momenta
    px = np.random.normal(0, 1, n_particles)
    py = np.random.normal(0, 1, n_particles)
    pz = np.random.normal(0, 1, n_particles)

    # Calculate particle energies (relativistic)
    p_magnitude = np.sqrt(px**2 + py**2 + pz**2)
    rest_energy = m_e * c**2
    energy = np.sqrt((p_magnitude * c)**2 + rest_energy**2)

    # Collision probability based on cross-section
    collision_prob = 0.1 * np.exp(-energy / (energy_range * 1e-13))  # Simplified cross-section

    # Determine which particles collide
    collisions = np.random.random(n_particles) < collision_prob

    # Calculate scattering angles for colliding particles
    n_collisions = np.sum(collisions)
    scattering_angles = np.pi * np.random.random(n_collisions)

    # Conservation of momentum and energy
    scattered_px = p_magnitude[collisions] * np.cos(scattering_angles)
    scattered_py = p_magnitude[collisions] * np.sin(scattering_angles)

    return {
        'n_particles': n_particles,
        'n_collisions': n_collisions,
        'collision_rate': n_collisions / n_particles,
        'avg_energy': np.mean(energy),
        'max_energy': np.max(energy),
        'scattering_angles': scattering_angles,
        'energies': energy
    }

# Run simulation
n_particles = 1000000
energy_range = 1000  # MeV

print(f"Simulating {n_particles:,} particle interactions...")
start_time = time.time()
results = simulate_particle_collision(n_particles, energy_range)
simulation_time = time.time() - start_time

print(f"Simulation completed in {simulation_time:.2f} seconds")
print(f"Results:")
print(f"  Total particles: {results['n_particles']:,}")
print(f"  Collisions: {results['n_collisions']:,}")
print(f"  Collision rate: {results['collision_rate']:.4f}")
print(f"  Average energy: {results['avg_energy']:.2e} J")
print(f"  Maximum energy: {results['max_energy']:.2e} J")

# Quantum mechanics simulation
print(f"\n🌊 Quantum Mechanics Simulation")
print("=" * 30)

def schrodinger_1d(x, psi, V, m, dt):
    """Solve time-dependent Schrödinger equation in 1D"""
    dx = x[1] - x[0]

    # Kinetic energy operator (second derivative)
    psi_xx = np.gradient(np.gradient(psi, dx), dx)

    # Hamiltonian
    H_psi = -hbar**2 / (2 * m) * psi_xx + V * psi

    # Time evolution (simplified)
    psi_new = psi - 1j * dt / hbar * H_psi

    return psi_new

# Set up quantum system
x_max = 10e-9  # 10 nm
n_points = 1000
x = np.linspace(-x_max, x_max, n_points)
dx = x[1] - x[0]

# Initial wave function (Gaussian wave packet)
x0 = 0
sigma = 1e-9
k0 = 1e10  # Initial momentum
psi_initial = np.exp(-(x - x0)**2 / (2 * sigma**2)) * np.exp(1j * k0 * x)
psi_initial = psi_initial / np.sqrt(np.trapz(np.abs(psi_initial)**2, x))

# Potential (square well)
V = np.zeros_like(x)
V[np.abs(x) > 3e-9] = 1e-18  # Potential barrier

# Time evolution
dt = 1e-18  # 1 attosecond
n_steps = 500

print(f"Quantum simulation parameters:")
print(f"  Spatial range: {x_max*1e9:.1f} nm")
print(f"  Grid points: {n_points}")
print(f"  Time steps: {n_steps}")
print(f"  Time step: {dt*1e18:.1f} attoseconds")

# Evolve wave function
psi = psi_initial.copy()
probability_density = []
expectation_x = []

for step in range(n_steps):
    psi = schrodinger_1d(x, psi, V, m_e, dt)

    # Normalize
    norm = np.sqrt(np.trapz(np.abs(psi)**2, x))
    psi = psi / norm

    # Calculate observables
    prob_density = np.abs(psi)**2
    exp_x = np.trapz(x * prob_density, x)

    probability_density.append(prob_density)
    expectation_x.append(exp_x)

print(f"Quantum evolution completed")
print(f"  Final position expectation: {expectation_x[-1]*1e9:.2f} nm")
print(f"  Position spread: {np.std(expectation_x)*1e9:.2f} nm")

# Electromagnetic field simulation
print(f"\n⚡ Electromagnetic Field Simulation")
print("=" * 33)

@jit(nopython=True)
def fdtd_update_e(Ex, Ey, Ez, Hx, Hy, Hz, dx, dy, dz, dt):
    """Update electric field using finite-difference time-domain method"""
    # Maxwell's equations: curl H = dE/dt
    c_dt = c * dt

    # Update Ex
    Ex[1:-1, 1:-1, 1:-1] += c_dt * (
        (Hz[1:-1, 1:, 1:-1] - Hz[1:-1, :-1, 1:-1]) / dy -
        (Hy[1:-1, 1:-1, 1:] - Hy[1:-1, 1:-1, :-1]) / dz
    )

    # Update Ey
    Ey[1:-1, 1:-1, 1:-1] += c_dt * (
        (Hx[1:-1, 1:-1, 1:] - Hx[1:-1, 1:-1, :-1]) / dz -
        (Hz[1:, 1:-1, 1:-1] - Hz[:-1, 1:-1, 1:-1]) / dx
    )

    # Update Ez
    Ez[1:-1, 1:-1, 1:-1] += c_dt * (
        (Hy[1:, 1:-1, 1:-1] - Hy[:-1, 1:-1, 1:-1]) / dx -
        (Hx[1:-1, 1:, 1:-1] - Hx[1:-1, :-1, 1:-1]) / dy
    )

    return Ex, Ey, Ez

@jit(nopython=True)
def fdtd_update_h(Ex, Ey, Ez, Hx, Hy, Hz, dx, dy, dz, dt):
    """Update magnetic field using finite-difference time-domain method"""
    # Maxwell's equations: curl E = -dH/dt
    c_dt = c * dt

    # Update Hx
    Hx[:-1, :-1, :-1] -= c_dt * (
        (Ez[:-1, 1:, :-1] - Ez[:-1, :-1, :-1]) / dy -
        (Ey[:-1, :-1, 1:] - Ey[:-1, :-1, :-1]) / dz
    )

    # Update Hy
    Hy[:-1, :-1, :-1] -= c_dt * (
        (Ex[:-1, :-1, 1:] - Ex[:-1, :-1, :-1]) / dz -
        (Ez[1:, :-1, :-1] - Ez[:-1, :-1, :-1]) / dx
    )

    # Update Hz
    Hz[:-1, :-1, :-1] -= c_dt * (
        (Ey[1:, :-1, :-1] - Ey[:-1, :-1, :-1]) / dx -
        (Ex[:-1, 1:, :-1] - Ex[:-1, :-1, :-1]) / dy
    )

    return Hx, Hy, Hz

# Set up electromagnetic simulation
nx, ny, nz = 50, 50, 50
dx = dy = dz = 1e-9  # 1 nm grid
dt = dx / (2 * c)  # Courant stability condition

# Initialize fields
Ex = np.zeros((nx, ny, nz))
Ey = np.zeros((nx, ny, nz))
Ez = np.zeros((nx, ny, nz))
Hx = np.zeros((nx, ny, nz))
Hy = np.zeros((nx, ny, nz))
Hz = np.zeros((nx, ny, nz))

# Initialize electromagnetic wave
frequency = 1e15  # 1 PHz (optical frequency)
wavelength = c / frequency
k = 2 * np.pi / wavelength

# Plane wave source
source_x = nx // 4
for j in range(ny):
    for k in range(nz):
        Ez[source_x, j, k] = np.sin(2 * np.pi * frequency * 0)

print(f"Electromagnetic simulation parameters:")
print(f"  Grid size: {nx}×{ny}×{nz}")
print(f"  Grid spacing: {dx*1e9:.1f} nm")
print(f"  Time step: {dt*1e15:.1f} fs")
print(f"  Wave frequency: {frequency:.2e} Hz")
print(f"  Wavelength: {wavelength*1e9:.1f} nm")

# Run electromagnetic simulation
n_time_steps = 100
field_energy = []

for step in range(n_time_steps):
    # Update electric field
    Ex, Ey, Ez = fdtd_update_e(Ex, Ey, Ez, Hx, Hy, Hz, dx, dy, dz, dt)

    # Update magnetic field
    Hx, Hy, Hz = fdtd_update_h(Ex, Ey, Ez, Hx, Hy, Hz, dx, dy, dz, dt)

    # Add source
    time_current = step * dt
    Ez[source_x, ny//2, nz//2] += np.sin(2 * np.pi * frequency * time_current)

    # Calculate field energy
    E_energy = 0.5 * constants.epsilon_0 * (Ex**2 + Ey**2 + Ez**2)
    H_energy = 0.5 * constants.mu_0 * (Hx**2 + Hy**2 + Hz**2)
    total_energy = np.sum(E_energy + H_energy)
    field_energy.append(total_energy)

print(f"Electromagnetic simulation completed")
print(f"  Average field energy: {np.mean(field_energy):.2e} J")
print(f"  Maximum field energy: {np.max(field_energy):.2e} J")

# Parallel computing demonstration
print(f"\n🔄 Parallel Computing Demonstration")
print("=" * 35)

def parallel_physics_calculation(n_tasks):
    """Demonstrate parallel physics calculation"""

    def compute_integral(args):
        """Compute a physics integral"""
        task_id, n_points = args

        # Monte Carlo integration of a physics function
        # Example: integrate exp(-x²) from 0 to infinity (should be sqrt(π)/2)
        np.random.seed(task_id)  # For reproducibility

        x = np.random.exponential(1.0, n_points)  # Importance sampling
        y = np.exp(-x**2) / np.exp(-x)  # Adjusted integrand

        result = np.mean(y)
        return task_id, result

    # Prepare tasks
    tasks = [(i, 100000) for i in range(n_tasks)]

    # Execute in parallel
    start_time = time.time()
    with mp.Pool(processes=mp.cpu_count()) as pool:
        results = pool.map(compute_integral, tasks)
    parallel_time = time.time() - start_time

    # Execute serially for comparison
    start_time = time.time()
    serial_results = [compute_integral(task) for task in tasks]
    serial_time = time.time() - start_time

    # Calculate statistics
    parallel_values = [result[1] for result in results]
    serial_values = [result[1] for result in serial_results]

    theoretical_value = np.sqrt(np.pi) / 2

    return {
        'n_tasks': n_tasks,
        'parallel_time': parallel_time,
        'serial_time': serial_time,
        'speedup': serial_time / parallel_time,
        'parallel_result': np.mean(parallel_values),
        'serial_result': np.mean(serial_values),
        'theoretical': theoretical_value,
        'parallel_error': abs(np.mean(parallel_values) - theoretical_value),
        'serial_error': abs(np.mean(serial_values) - theoretical_value)
    }

# Run parallel computation test
parallel_results = parallel_physics_calculation(mp.cpu_count() * 2)

print(f"Parallel computing results:")
print(f"  Tasks: {parallel_results['n_tasks']}")
print(f"  Parallel time: {parallel_results['parallel_time']:.3f} seconds")
print(f"  Serial time: {parallel_results['serial_time']:.3f} seconds")
print(f"  Speedup: {parallel_results['speedup']:.1f}x")
print(f"  Parallel result: {parallel_results['parallel_result']:.6f}")
print(f"  Theoretical value: {parallel_results['theoretical']:.6f}")
print(f"  Parallel error: {parallel_results['parallel_error']:.6f}")

# Generate comprehensive physics visualization
plt.figure(figsize=(16, 12))

# Particle energy distribution
plt.subplot(3, 3, 1)
plt.hist(results['energies'], bins=50, alpha=0.7, color='blue', density=True)
plt.title('Particle Energy Distribution')
plt.xlabel('Energy (J)')
plt.ylabel('Probability Density')
plt.yscale('log')

# Scattering angle distribution
plt.subplot(3, 3, 2)
plt.hist(results['scattering_angles'], bins=30, alpha=0.7, color='red')
plt.title('Scattering Angle Distribution')
plt.xlabel('Scattering Angle (radians)')
plt.ylabel('Frequency')

# Quantum wave function evolution
plt.subplot(3, 3, 3)
plt.plot(x * 1e9, np.abs(psi_initial)**2, 'b--', label='Initial', linewidth=2)
plt.plot(x * 1e9, probability_density[-1], 'r-', label='Final', linewidth=2)
plt.title('Quantum Wave Function Evolution')
plt.xlabel('Position (nm)')
plt.ylabel('Probability Density')
plt.legend()

# Expectation value of position
plt.subplot(3, 3, 4)
time_array = np.arange(n_steps) * dt * 1e18
plt.plot(time_array, np.array(expectation_x) * 1e9, 'g-', linewidth=2)
plt.title('Quantum Position Expectation Value')
plt.xlabel('Time (attoseconds)')
plt.ylabel('Position (nm)')

# Electromagnetic field energy
plt.subplot(3, 3, 5)
time_em = np.arange(n_time_steps) * dt * 1e15
plt.plot(time_em, field_energy, 'purple', linewidth=2)
plt.title('Electromagnetic Field Energy')
plt.xlabel('Time (femtoseconds)')
plt.ylabel('Energy (J)')

# Electric field snapshot
plt.subplot(3, 3, 6)
plt.imshow(Ez[:, :, nz//2], cmap='RdBu', aspect='auto')
plt.colorbar(label='Electric Field (V/m)')
plt.title('Electric Field (Ez) Snapshot')
plt.xlabel('Y Grid Point')
plt.ylabel('X Grid Point')

# Parallel computing performance
plt.subplot(3, 3, 7)
categories = ['Serial', 'Parallel']
times = [parallel_results['serial_time'], parallel_results['parallel_time']]
colors = ['red', 'green']
plt.bar(categories, times, color=colors)
plt.title('Parallel vs Serial Performance')
plt.ylabel('Execution Time (seconds)')

# Monte Carlo convergence
plt.subplot(3, 3, 8)
n_samples = np.logspace(2, 6, 20).astype(int)
convergence_errors = []
for n in n_samples:
    sample_energies = results['energies'][:n]
    theoretical_mean = np.mean(results['energies'])
    error = abs(np.mean(sample_energies) - theoretical_mean) / theoretical_mean
    convergence_errors.append(error)

plt.loglog(n_samples, convergence_errors, 'o-', color='orange')
plt.title('Monte Carlo Convergence')
plt.xlabel('Number of Samples')
plt.ylabel('Relative Error')

# Collision cross-section
plt.subplot(3, 3, 9)
energy_bins = np.linspace(0, np.max(results['energies']), 20)
cross_section = []
for i in range(len(energy_bins) - 1):
    mask = (results['energies'] >= energy_bins[i]) & (results['energies'] < energy_bins[i+1])
    if np.sum(mask) > 0:
        cs = 0.1 * np.exp(-np.mean(results['energies'][mask]) / (energy_range * 1e-13))
        cross_section.append(cs)
    else:
        cross_section.append(0)

plt.plot(energy_bins[:-1], cross_section, 'mo-', linewidth=2)
plt.title('Collision Cross-Section')
plt.xlabel('Energy (J)')
plt.ylabel('Cross-Section (arbitrary units)')

plt.tight_layout()
plt.savefig('computational_physics_analysis.png', dpi=300, bbox_inches='tight')
print(f"\n📊 Computational physics analysis saved as 'computational_physics_analysis.png'")

# Performance benchmarking
print(f"\n🏁 Performance Benchmarking")
print("=" * 26)

# CPU performance test
def cpu_benchmark():
    """Benchmark CPU performance with physics calculations"""
    n_iterations = 1000000

    # Matrix operations
    start_time = time.time()
    A = np.random.random((100, 100))
    for _ in range(1000):
        B = np.linalg.inv(A + 0.001 * np.eye(100))
    matrix_time = time.time() - start_time

    # FFT operations
    start_time = time.time()
    signal = np.random.random(10000)
    for _ in range(1000):
        fft_result = np.fft.fft(signal)
    fft_time = time.time() - start_time

    # Numerical integration
    start_time = time.time()
    for _ in range(1000):
        result = integrate.quad(lambda x: np.exp(-x**2), 0, 5)
    integration_time = time.time() - start_time

    return {
        'matrix_operations': matrix_time,
        'fft_operations': fft_time,
        'numerical_integration': integration_time,
        'total_time': matrix_time + fft_time + integration_time
    }

benchmark_results = cpu_benchmark()

print(f"CPU Performance Benchmark:")
print(f"  Matrix operations: {benchmark_results['matrix_operations']:.3f} seconds")
print(f"  FFT operations: {benchmark_results['fft_operations']:.3f} seconds")
print(f"  Numerical integration: {benchmark_results['numerical_integration']:.3f} seconds")
print(f"  Total benchmark time: {benchmark_results['total_time']:.3f} seconds")

# Memory usage assessment
import psutil
memory_info = psutil.virtual_memory()
print(f"\nMemory Usage Assessment:")
print(f"  Total memory: {memory_info.total / 1e9:.1f} GB")
print(f"  Available memory: {memory_info.available / 1e9:.1f} GB")
print(f"  Memory usage: {memory_info.percent:.1f}%")

# Physics simulation summary
print(f"\n✅ Computational Physics Analysis Complete!")
print("=" * 45)
print(f"Simulations performed:")
print(f"  • {results['n_particles']:,} particle collisions analyzed")
print(f"  • {n_steps} quantum time evolution steps")
print(f"  • {n_time_steps} electromagnetic field updates")
print(f"  • {parallel_results['n_tasks']} parallel physics calculations")
print(f"")
print(f"Performance achieved:")
print(f"  • {parallel_results['speedup']:.1f}x parallel speedup")
print(f"  • {1/simulation_time*n_particles:.0f} particles/second simulation rate")
print(f"  • {benchmark_results['total_time']:.3f} seconds total benchmark time")
EOF

chmod +x particle_physics_sim.py

3. Run the Physics Simulation

python3 particle_physics_sim.py

Expected output: You should see comprehensive computational physics simulation results.

4. Astrophysics Simulation Script

cat > astrophysics_sim.py << 'EOF'
#!/usr/bin/env python3
"""
Astrophysics Simulation Tool
Simulates stellar evolution, galaxy dynamics, and cosmological models
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy import integrate, constants
from numba import jit
import time
import warnings
warnings.filterwarnings('ignore')

# Physical constants
G = constants.G  # Gravitational constant
c = constants.c  # Speed of light
M_sun = 1.989e30  # Solar mass
R_sun = 6.96e8   # Solar radius
L_sun = 3.828e26  # Solar luminosity
k_B = constants.k  # Boltzmann constant
sigma_SB = constants.sigma  # Stefan-Boltzmann constant

print("🌌 Initializing astrophysics simulation...")

# Stellar evolution simulation
print("\n⭐ Stellar Evolution Simulation")
print("=" * 29)

def stellar_structure_equations(r, y, M, rho_c, T_c):
    """
    Stellar structure equations
    y = [m, P, T, L] (mass, pressure, temperature, luminosity)
    """
    m, P, T, L = y

    # Avoid division by zero
    if r <= 0:
        return [0, 0, 0, 0]

    # Density from ideal gas law (simplified)
    rho = rho_c * (P / (k_B * T * rho_c / (2 * constants.m_p)))

    # Energy generation rate (simplified CNO cycle)
    epsilon = 1e-5 * rho * (T / 2e7)**20 if T > 1e7 else 0

    # Opacity (simplified)
    kappa = 0.02 * (1 + 0.7) * (rho / 1e3) * (T / 1e6)**(-3.5)

    # Stellar structure equations
    dm_dr = 4 * np.pi * r**2 * rho
    dP_dr = -G * m * rho / r**2
    dT_dr = -3 * kappa * rho * L / (16 * np.pi * sigma_SB * T**3 * r**2)
    dL_dr = 4 * np.pi * r**2 * rho * epsilon

    return [dm_dr, dP_dr, dT_dr, dL_dr]

# Set up stellar parameters
M_star = M_sun  # Solar mass
R_star = R_sun  # Solar radius
T_c = 1.5e7    # Central temperature (K)
rho_c = 1e5    # Central density (kg/m³)

# Initial conditions at r=0 (use small initial radius)
r_initial = 0.01 * R_star
y_initial = [0.1 * M_star, 1e11, T_c, 0.1 * L_sun]

# Solve stellar structure
r_span = (r_initial, R_star)
r_eval = np.linspace(r_initial, R_star, 100)

print(f"Stellar parameters:")
print(f"  Mass: {M_star/M_sun:.1f} M_sun")
print(f"  Radius: {R_star/R_sun:.1f} R_sun")
print(f"  Central temperature: {T_c:.1e} K")
print(f"  Central density: {rho_c:.1e} kg/m³")

# Simplified stellar evolution (just the structure at one time)
try:
    sol = integrate.solve_ivp(
        lambda r, y: stellar_structure_equations(r, y, M_star, rho_c, T_c),
        r_span, y_initial, t_eval=r_eval, method='RK45'
    )

    if sol.success:
        radius_profile = sol.t
        mass_profile = sol.y[0]
        pressure_profile = sol.y[1]
        temperature_profile = sol.y[2]
        luminosity_profile = sol.y[3]

        print(f"Stellar structure calculation completed:")
        print(f"  Surface temperature: {temperature_profile[-1]:.0f} K")
        print(f"  Surface pressure: {pressure_profile[-1]:.2e} Pa")
        print(f"  Total luminosity: {luminosity_profile[-1]:.2e} W")
        print(f"  L/L_sun: {luminosity_profile[-1]/L_sun:.2f}")
    else:
        print("Stellar structure calculation failed")
        # Create dummy data for visualization
        radius_profile = r_eval
        mass_profile = np.linspace(0, M_star, len(r_eval))
        pressure_profile = np.logspace(11, 6, len(r_eval))
        temperature_profile = np.linspace(T_c, 5000, len(r_eval))
        luminosity_profile = np.linspace(0, L_sun, len(r_eval))

except Exception as e:
    print(f"Stellar structure calculation failed: {e}")
    # Create dummy data for visualization
    radius_profile = r_eval
    mass_profile = np.linspace(0, M_star, len(r_eval))
    pressure_profile = np.logspace(11, 6, len(r_eval))
    temperature_profile = np.linspace(T_c, 5000, len(r_eval))
    luminosity_profile = np.linspace(0, L_sun, len(r_eval))

# Galaxy dynamics simulation
print(f"\n🌌 Galaxy Dynamics Simulation")
print("=" * 27)

@jit(nopython=True)
def galaxy_n_body_simulation(positions, velocities, masses, dt, n_steps):
    """N-body simulation of galaxy dynamics"""
    n_particles = len(masses)

    # Arrays to store evolution
    kinetic_energy = np.zeros(n_steps)
    potential_energy = np.zeros(n_steps)

    for step in range(n_steps):
        # Calculate forces
        forces = np.zeros_like(positions)

        for i in range(n_particles):
            for j in range(i + 1, n_particles):
                # Distance vector
                r_vec = positions[j] - positions[i]
                r_mag = np.sqrt(np.sum(r_vec**2))

                # Avoid singularity
                if r_mag > 0:
                    # Gravitational force
                    F_mag = G * masses[i] * masses[j] / r_mag**2
                    F_vec = F_mag * r_vec / r_mag

                    forces[i] += F_vec
                    forces[j] -= F_vec

        # Update velocities and positions
        accelerations = forces / masses.reshape(-1, 1)
        velocities += accelerations * dt
        positions += velocities * dt

        # Calculate energies
        ke = 0.5 * np.sum(masses.reshape(-1, 1) * velocities**2)
        pe = 0.0
        for i in range(n_particles):
            for j in range(i + 1, n_particles):
                r_vec = positions[j] - positions[i]
                r_mag = np.sqrt(np.sum(r_vec**2))
                if r_mag > 0:
                    pe -= G * masses[i] * masses[j] / r_mag

        kinetic_energy[step] = ke
        potential_energy[step] = pe

    return positions, velocities, kinetic_energy, potential_energy

# Set up galaxy simulation
n_stars = 100
galaxy_radius = 1e20  # 100 light-years
galaxy_mass = 1e11 * M_sun  # 100 billion solar masses

# Initial positions (disk distribution)
np.random.seed(42)
r = np.random.exponential(galaxy_radius/3, n_stars)
theta = np.random.uniform(0, 2*np.pi, n_stars)
z = np.random.normal(0, galaxy_radius/20, n_stars)

positions = np.column_stack([
    r * np.cos(theta),
    r * np.sin(theta),
    z
])

# Initial velocities (circular orbits)
v_circular = np.sqrt(G * galaxy_mass / r)
velocities = np.column_stack([
    -v_circular * np.sin(theta),
    v_circular * np.cos(theta),
    np.zeros(n_stars)
])

# Star masses
masses = np.full(n_stars, galaxy_mass / n_stars)

# Simulation parameters
dt = 1e13  # 10,000 years
n_steps = 1000

print(f"Galaxy simulation parameters:")
print(f"  Number of stars: {n_stars}")
print(f"  Galaxy radius: {galaxy_radius/9.461e15:.1f} light-years")
print(f"  Total mass: {galaxy_mass/M_sun:.1e} M_sun")
print(f"  Time step: {dt/(365.25*24*3600):.0f} years")
print(f"  Simulation steps: {n_steps}")

# Run galaxy simulation
print("Running galaxy N-body simulation...")
start_time = time.time()
final_pos, final_vel, ke_history, pe_history = galaxy_n_body_simulation(
    positions, velocities, masses, dt, n_steps
)
galaxy_sim_time = time.time() - start_time

print(f"Galaxy simulation completed in {galaxy_sim_time:.2f} seconds")

# Total energy conservation check
total_energy = ke_history + pe_history
energy_drift = (total_energy[-1] - total_energy[0]) / abs(total_energy[0])
print(f"Energy conservation: {energy_drift:.1e} relative drift")

# Cosmological simulation
print(f"\n🌠 Cosmological Simulation")
print("=" * 24)

def friedmann_equation(t, y, H0, Omega_m, Omega_lambda):
    """
    Friedmann equation for cosmic expansion
    y = [a] where a is the scale factor
    """
    a = y[0]

    # Hubble parameter
    H = H0 * np.sqrt(Omega_m / a**3 + Omega_lambda)

    # Scale factor evolution
    da_dt = H * a

    return [da_dt]

# Cosmological parameters
H0 = 70  # km/s/Mpc (Hubble constant)
Omega_m = 0.3  # Matter density parameter
Omega_lambda = 0.7  # Dark energy density parameter

# Time span (from Big Bang to present)
t_universe = 13.8e9 * 365.25 * 24 * 3600  # 13.8 billion years in seconds
t_span = (1e6, t_universe)  # Start after Big Bang
t_eval = np.logspace(6, np.log10(t_universe), 1000)

# Initial conditions
a_initial = 1e-3  # Small initial scale factor

print(f"Cosmological parameters:")
print(f"  Hubble constant: {H0} km/s/Mpc")
print(f"  Matter density: {Omega_m}")
print(f"  Dark energy density: {Omega_lambda}")
print(f"  Universe age: {t_universe/(365.25*24*3600*1e9):.1f} billion years")

# Solve Friedmann equation
sol_cosmo = integrate.solve_ivp(
    lambda t, y: friedmann_equation(t, y, H0 * 1e3 / 3.086e22, Omega_m, Omega_lambda),
    t_span, [a_initial], t_eval=t_eval, method='RK45'
)

if sol_cosmo.success:
    time_cosmo = sol_cosmo.t
    scale_factor = sol_cosmo.y[0]

    # Calculate redshift
    redshift = 1 / scale_factor - 1

    # Present day values
    a_present = scale_factor[-1]
    z_present = redshift[-1]

    print(f"Cosmological evolution completed:")
    print(f"  Present scale factor: {a_present:.3f}")
    print(f"  Present redshift: {z_present:.3f}")
    print(f"  Scale factor range: {scale_factor[0]:.1e} to {scale_factor[-1]:.3f}")
else:
    print("Cosmological simulation failed")
    # Create dummy data
    time_cosmo = t_eval
    scale_factor = np.linspace(a_initial, 1, len(t_eval))
    redshift = 1 / scale_factor - 1

# Generate comprehensive astrophysics visualization
plt.figure(figsize=(16, 12))

# Stellar structure - Temperature profile
plt.subplot(3, 3, 1)
plt.plot(radius_profile/R_sun, temperature_profile/1e6, 'r-', linewidth=2)
plt.title('Stellar Temperature Profile')
plt.xlabel('Radius (R_sun)')
plt.ylabel('Temperature (MK)')
plt.grid(True, alpha=0.3)

# Stellar structure - Pressure profile
plt.subplot(3, 3, 2)
plt.semilogy(radius_profile/R_sun, pressure_profile, 'b-', linewidth=2)
plt.title('Stellar Pressure Profile')
plt.xlabel('Radius (R_sun)')
plt.ylabel('Pressure (Pa)')
plt.grid(True, alpha=0.3)

# Stellar structure - Mass profile
plt.subplot(3, 3, 3)
plt.plot(radius_profile/R_sun, mass_profile/M_sun, 'g-', linewidth=2)
plt.title('Stellar Mass Profile')
plt.xlabel('Radius (R_sun)')
plt.ylabel('Enclosed Mass (M_sun)')
plt.grid(True, alpha=0.3)

# Galaxy dynamics - Initial and final positions
plt.subplot(3, 3, 4)
plt.scatter(positions[:, 0]/9.461e15, positions[:, 1]/9.461e15,
           c='blue', s=20, alpha=0.6, label='Initial')
plt.scatter(final_pos[:, 0]/9.461e15, final_pos[:, 1]/9.461e15,
           c='red', s=20, alpha=0.6, label='Final')
plt.title('Galaxy Evolution (Top View)')
plt.xlabel('x (light-years)')
plt.ylabel('y (light-years)')
plt.legend()
plt.axis('equal')

# Galaxy dynamics - Energy conservation
plt.subplot(3, 3, 5)
time_galaxy = np.arange(n_steps) * dt / (365.25 * 24 * 3600)
plt.plot(time_galaxy, ke_history/abs(ke_history[0]), 'r-', label='Kinetic', linewidth=2)
plt.plot(time_galaxy, pe_history/abs(pe_history[0]), 'b-', label='Potential', linewidth=2)
plt.plot(time_galaxy, total_energy/abs(total_energy[0]), 'g-', label='Total', linewidth=2)
plt.title('Galaxy Energy Evolution')
plt.xlabel('Time (years)')
plt.ylabel('Normalized Energy')
plt.legend()
plt.grid(True, alpha=0.3)

# Cosmological evolution - Scale factor
plt.subplot(3, 3, 6)
plt.loglog(time_cosmo/(365.25*24*3600*1e9), scale_factor, 'purple', linewidth=2)
plt.title('Cosmic Scale Factor Evolution')
plt.xlabel('Time (billion years)')
plt.ylabel('Scale Factor')
plt.grid(True, alpha=0.3)

# Cosmological evolution - Redshift
plt.subplot(3, 3, 7)
plt.loglog(time_cosmo/(365.25*24*3600*1e9), redshift, 'orange', linewidth=2)
plt.title('Cosmic Redshift Evolution')
plt.xlabel('Time (billion years)')
plt.ylabel('Redshift (z)')
plt.grid(True, alpha=0.3)

# Hertzsprung-Russell diagram (simulated)
plt.subplot(3, 3, 8)
# Generate synthetic stellar data
stellar_masses = np.logspace(-1, 2, 100)  # 0.1 to 100 solar masses
stellar_temps = 5780 * (stellar_masses)**0.5  # Temperature-mass relation
stellar_luminosities = stellar_masses**3.5  # Luminosity-mass relation

plt.loglog(stellar_temps, stellar_luminosities, 'ko', markersize=3)
plt.title('Hertzsprung-Russell Diagram')
plt.xlabel('Temperature (K)')
plt.ylabel('Luminosity (L_sun)')
plt.grid(True, alpha=0.3)

# Radial velocity distribution
plt.subplot(3, 3, 9)
radial_distances = np.sqrt(np.sum(final_pos**2, axis=1))
radial_velocities = np.sqrt(np.sum(final_vel**2, axis=1))
plt.scatter(radial_distances/9.461e15, radial_velocities/1000,
           c='green', s=20, alpha=0.6)
plt.title('Galactic Velocity Profile')
plt.xlabel('Radial Distance (light-years)')
plt.ylabel('Velocity (km/s)')
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('astrophysics_simulation.png', dpi=300, bbox_inches='tight')
print(f"\n📊 Astrophysics simulation saved as 'astrophysics_simulation.png'")

# Performance analysis
print(f"\n🚀 Simulation Performance Analysis")
print("=" * 33)

# Calculate computational intensity
particles_per_second = n_stars * n_steps / galaxy_sim_time
interactions_per_second = n_stars * (n_stars - 1) / 2 * n_steps / galaxy_sim_time

print(f"Performance metrics:")
print(f"  Galaxy simulation time: {galaxy_sim_time:.2f} seconds")
print(f"  Particles simulated per second: {particles_per_second:.0f}")
print(f"  Gravitational interactions per second: {interactions_per_second:.0f}")
print(f"  Computational complexity: O(N²) for N={n_stars} particles")

# Memory usage estimate
memory_per_particle = 8 * 6  # 6 double precision values per particle
total_memory = memory_per_particle * n_stars / 1024**2  # MB
print(f"  Memory usage: {total_memory:.2f} MB for particle data")

# Astrophysics summary
print(f"\n✅ Astrophysics Simulation Complete!")
print("=" * 37)
print(f"Simulations performed:")
print(f"  • Stellar structure for {M_star/M_sun:.1f} M_sun star")
print(f"  • Galaxy dynamics with {n_stars} stars over {n_steps} time steps")
print(f"  • Cosmological evolution over {t_universe/(365.25*24*3600*1e9):.1f} billion years")
print(f"")
print(f"Physical insights:")
print(f"  • Stellar luminosity: {luminosity_profile[-1]/L_sun:.2f} L_sun")
print(f"  • Galaxy energy conservation: {abs(energy_drift):.1e} relative error")
print(f"  • Universe scale factor: {a_present:.3f} (present day)")
print(f"  • Computational performance: {interactions_per_second:.0f} interactions/sec")
EOF

chmod +x astrophysics_sim.py

5. Run Astrophysics Simulation

python3 astrophysics_sim.py

Expected output: Comprehensive astrophysics simulation with stellar evolution and galaxy dynamics.

What You’ve Accomplished

🎉 Congratulations! You’ve successfully:

  1. ✅ Created a computational physics research environment in the cloud
  2. ✅ Simulated particle physics interactions and quantum mechanics
  3. ✅ Performed electromagnetic field calculations using FDTD methods
  4. ✅ Conducted parallel computing demonstrations with high-performance computing
  5. ✅ Ran astrophysics simulations including stellar evolution and galaxy dynamics
  6. ✅ Generated comprehensive physics research reports and visualizations

Real Research Applications

Your environment can now handle:

  • Particle physics: Monte Carlo simulations, collision analysis, cross-section calculations
  • Quantum mechanics: Wave function evolution, Schrödinger equation solutions
  • Electromagnetic fields: FDTD simulations, wave propagation, antenna design
  • Astrophysics: Stellar evolution, galaxy dynamics, cosmological models
  • High-performance computing: Parallel algorithms, optimization, performance tuning

Next Steps for Advanced Research

# Install specialized physics packages
pip3 install fenics-dolfinx petsc4py mpi4py

# Set up physics simulation software
conda install -c conda-forge openfoam freefem++

# Configure high-performance computing tools
aws-research-wizard tools install --domain computational_physics --advanced

Monthly Cost Estimate

For typical computational physics research usage:

  • Light usage (30 hours/week): ~$450/month
  • Medium usage (50 hours/week): ~$700/month
  • Heavy usage (80 hours/week): ~$900/month

Clean Up Resources

Important: Always clean up to avoid unexpected charges!

# Exit your research environment
exit

# Destroy the research environment
aws-research-wizard deploy destroy --domain computational_physics

Expected result: “✅ Environment destroyed successfully”

💰 Billing stops: No more charges after cleanup

Step 9: Using Your Own Computational Physics Data

Instead of the tutorial data, you can analyze your own computational physics datasets:

Upload Your Data

# Option 1: Upload from your local computer
scp -i ~/.ssh/id_rsa your_data_file.* ec2-user@12.34.56.78:~/computational_physics-tutorial/

# Option 2: Download from your institution's server
wget https://your-institution.edu/data/research_data.csv

# Option 3: Access your AWS S3 bucket
aws s3 cp s3://your-research-bucket/computational_physics-data/ . --recursive

Common Data Formats Supported

  • Simulation output (.dat, .h5, .vtk): Numerical simulation results
  • Parameter files (.json, .xml, .cfg): Model configuration and input parameters
  • Time series data (.csv, .txt): Physical measurements and calculations
  • Grid data (.mesh, .msh): Finite element and finite difference grids
  • Binary output (.bin, .raw): High-performance simulation data files

Replace Tutorial Commands

Simply substitute your filenames in any tutorial command:

# Instead of tutorial data:
python3 analyze_simulation.py results.h5

# Use your data:
python3 analyze_simulation.py YOUR_SIMULATION.h5

Data Size Considerations

  • Small datasets (<10 GB): Process directly on the instance
  • Large datasets (10-100 GB): Use S3 for storage, process in chunks
  • Very large datasets (>100 GB): Consider multi-node setup or data preprocessing

Troubleshooting

Common Issues

Problem: “Numba compilation failed” Solution:

pip3 install numba numpy scipy --upgrade
# OR use conda
conda install -c conda-forge numba

Problem: “MPI not found” errors Solution:

sudo yum install openmpi-devel
pip3 install mpi4py

Problem: Out of memory errors during large simulations Solution:

# Use larger memory instance
aws-research-wizard deploy create --domain computational_physics --instance-type r5.8xlarge

Problem: Slow numerical integrations Solution:

# Install optimized numerical libraries
conda install -c conda-forge numpy scipy blas=*=openblas

Extend and Contribute

🚀 Help us expand AWS Research Wizard!

Missing a tool or domain? We welcome suggestions for:

  • New computational physics software (e.g., OpenFOAM, FEniCS, COMSOL, ANSYS, ParaView)
  • Additional domain packs (e.g., plasma physics, condensed matter, particle physics, nuclear physics)
  • New data sources or tutorials for specific research workflows

How to contribute:

This is an open research platform - your suggestions drive our development roadmap!

Getting Help

Emergency Stop

If something goes wrong and you want to stop all charges immediately:

aws-research-wizard emergency-stop --all

This will terminate everything and stop billing within 2 minutes.


⚛️ Happy computational physics research! You now have a professional-grade computational physics environment that scales with your simulation and modeling needs.