|
| 1 | +""" |
| 2 | +test_gyromotion.py |
| 3 | +
|
| 4 | +Test that the Boris 1D3V integrator produces correct gyromotion in a uniform B field. |
| 5 | +
|
| 6 | +Physical expectation: |
| 7 | +--------------------- |
| 8 | +In a uniform magnetic field (B = Bz * e_z) and zero electric field, |
| 9 | +a charged particle with initial velocity perpendicular to B |
| 10 | +undergoes circular motion with constant |v|. |
| 11 | +
|
| 12 | +The cyclotron frequency is: |
| 13 | + ω_c = |q| * B / m |
| 14 | +
|
| 15 | +After one cyclotron period: |
| 16 | + T = 2π / ω_c |
| 17 | +the velocity vector should return to its initial direction (within numerical tolerance). |
| 18 | +""" |
| 19 | + |
| 20 | +from __future__ import annotations |
| 21 | + |
| 22 | +import numpy as np |
| 23 | + |
| 24 | +from test_particle_sim_1d.integrators import boris_1d3v_z |
| 25 | + |
| 26 | + |
| 27 | +def test_gyromotion_conservation(): |
| 28 | + # --- Physical constants --- |
| 29 | + q = np.array([-1.602e-19]) # electron charge [C] |
| 30 | + m = np.array([9.109e-31]) # electron mass [kg] |
| 31 | + B0 = 1.0 # magnetic field [T] |
| 32 | + E0 = 0.0 # no electric field |
| 33 | + |
| 34 | + # --- Initial conditions --- |
| 35 | + z = np.zeros(1) |
| 36 | + v0 = np.array([[1e6, 0.0, 0.0]]) # velocity purely in x (perpendicular to Bz) |
| 37 | + |
| 38 | + # --- Field definitions --- |
| 39 | + def E_field(z): |
| 40 | + return np.stack( |
| 41 | + [np.full_like(z, E0), np.full_like(z, 0.0), np.full_like(z, 0.0)], axis=1 |
| 42 | + ) |
| 43 | + |
| 44 | + def B_field(z): |
| 45 | + return np.stack( |
| 46 | + [np.full_like(z, 0.0), np.full_like(z, 0.0), np.full_like(z, B0)], axis=1 |
| 47 | + ) |
| 48 | + |
| 49 | + # --- Cyclotron frequency and time step --- |
| 50 | + omega_c = np.abs(q[0]) * B0 / m[0] # cyclotron frequency (rad/s) |
| 51 | + T_c = 2 * np.pi / omega_c # cyclotron period (s) |
| 52 | + n_steps = 1000 # number of integration steps to use for one full gyro orbit |
| 53 | + dt = T_c / n_steps # time step so that one period is divided into n_steps steps |
| 54 | + |
| 55 | + # --- Run one full gyro orbit --- |
| 56 | + _z_final, v_final = boris_1d3v_z(z, v0.copy(), q, m, E_field, B_field, dt, n_steps) |
| 57 | + |
| 58 | + # --- Expected: velocity magnitude conserved --- |
| 59 | + v_mag_initial = np.linalg.norm(v0) |
| 60 | + v_mag_final = np.linalg.norm(v_final) |
| 61 | + np.testing.assert_allclose(v_mag_final, v_mag_initial, rtol=1e-6) |
| 62 | + |
| 63 | + # --- Expected: velocity returns to (vx, vy) ≈ (v0, 0) after one full period --- |
| 64 | + np.testing.assert_allclose( |
| 65 | + v_final[0, 0], v0[0, 0], rtol=1e-4 |
| 66 | + ) # vx close to initial |
| 67 | + np.testing.assert_allclose(v_final[0, 1], 0.0, atol=1e-4 * v_mag_initial) # vy ≈ 0 |
| 68 | + np.testing.assert_allclose(v_final[0, 2], 0.0, atol=1e-12) # vz stays constant |
| 69 | + |
| 70 | + |
| 71 | +if __name__ == "__main__": |
| 72 | + test_gyromotion_conservation() |
0 commit comments