def test_init(): grid = _test_grid("electrostatic_gaussian_sphere", num=50) # Cartesian source = (0 * u.mm, -10 * u.mm, 0 * u.mm) detector = (0 * u.mm, 200 * u.mm, 0 * u.mm) sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) # Test manually setting hdir and vdir hdir = np.array([1, 0, 1]) sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False, detector_hdir=hdir) # Test special case hdir == [0,0,1] source = (0 * u.mm, 0 * u.mm, -10 * u.mm) detector = (0 * u.mm, 0 * u.mm, 200 * u.mm) sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) assert all(sim.det_hdir == np.array([1, 0, 0])) # Test that hdir is calculated correctly if src-det axis is anti-parallel to z source = (0 * u.mm, 0 * u.mm, 10 * u.mm) detector = (0 * u.mm, 0 * u.mm, -200 * u.mm) sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) assert all(sim.det_hdir == np.array([1, 0, 0]))
def test_coordinate_systems(): """ Check that specifying the same point in different coordinate systems ends up with identical source and detector vectors. """ grid = _test_grid("empty") # Cartesian source = (-7.07 * u.mm, -7.07 * u.mm, 0 * u.mm) detector = (70.07 * u.mm, 70.07 * u.mm, 0 * u.mm) sim1 = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=True) # Cylindrical source = (-1 * u.cm, 45 * u.deg, 0 * u.mm) detector = (10 * u.cm, 45 * u.deg, 0 * u.mm) sim2 = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) # In spherical source = (-0.01 * u.m, 90 * u.deg, 45 * u.deg) detector = (0.1 * u.m, 90 * u.deg, 45 * u.deg) sim3 = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) assert np.allclose(sim1.source, sim2.source, atol=1e-2) assert np.allclose(sim2.source, sim3.source, atol=1e-2) assert np.allclose(sim1.detector, sim2.detector, atol=1e-2) assert np.allclose(sim2.detector, sim3.detector, atol=1e-2)
def test_run_options(): grid = _test_grid("electrostatic_gaussian_sphere", num=50) # Cartesian source = (0 * u.mm, -10 * u.mm, 0 * u.mm) detector = (0 * u.mm, 200 * u.mm, 0 * u.mm) sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=True) # Test that trying to call run() without creating particles # raises an exception with pytest.raises(ValueError): sim.run() sim.create_particles(1e4, 3 * u.MeV, max_theta=10 * u.deg) # Try running with nearest neighbor interpolator # Test manually setting a timestep sim.run(field_weighting="nearest neighbor", dt=1e-12 * u.s) # Test max_deflections sim.max_deflection # Test way too big of a max_theta sim.create_particles(1e4, 3 * u.MeV, max_theta=89 * u.deg) with pytest.warns(RuntimeWarning, match="of " "particles entered the field grid"): sim.run(field_weighting="nearest neighbor", dt=1e-12 * u.s) # Test extreme deflections -> warns user # This requires instatiating a whole new example field with a really # big B-field grid = _test_grid("constant_bz", num=50, B0=250 * u.T) source = (0 * u.mm, -10 * u.mm, 0 * u.mm) detector = (0 * u.mm, 200 * u.mm, 0 * u.mm) # Expectwarnings because these fields aren't well-behaved at the edges with pytest.warns( RuntimeWarning, match="Fields should go to zero at edges of grid to avoid "): sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) sim.create_particles(1e4, 3 * u.MeV, max_theta=0.1 * u.deg) with pytest.warns( RuntimeWarning, match="particles have been " "deflected away from the detector plane", ): sim.run(field_weighting="nearest neighbor", dt=1e-12 * u.s) # Calc max deflection: should be between 0 and pi/2 # Note: that's only true because max_theta is very small # More generally, max_deflection can be a bit bigger than pi/2 for # particles that begin at an angle then deflect all the way around. assert 0 < sim.max_deflection.to(u.rad).value < np.pi / 2
def test_load_particles(): grid = _test_grid("electrostatic_gaussian_sphere", num=50) # Cartesian source = (0 * u.mm, -10 * u.mm, 0 * u.mm) detector = (0 * u.mm, 200 * u.mm, 0 * u.mm) sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) sim.create_particles(1e3, 15 * u.MeV, max_theta=0.1 * u.rad, distribution="uniform") # Test adding unequal numbers of particles x = np.zeros([100, 3]) * u.m v = np.ones([150, 3]) * u.m / u.s with pytest.raises(ValueError): sim.load_particles(x, v) # Test creating particles with explict keywords x = sim.x * u.m v = sim.v * u.m / u.s # Try setting particles going the wrong direction with pytest.warns(RuntimeWarning): sim.load_particles(x, -v) # Try specifying a larger ion (not a proton or electron) sim.load_particles(x, v, particle="C-12 +3") # Run the tracker to make sure everything works sim.run(field_weighting="nearest neighbor")
def run_1D_example(name): """ Run a simulation through an example with parameters optimized to sum up to a lineout along x. The goal is to run a realtively fast sim with a quasi-1D field grid that can then be summed to get good enough statistics to use as a test. """ grid = _test_grid(name, L=1 * u.mm, num=50) # Cartesian source = (0 * u.mm, -10 * u.mm, 0 * u.mm) detector = (0 * u.mm, 200 * u.mm, 0 * u.mm) # Expect warnings because these fields aren't well-behaved at the edges with pytest.warns( RuntimeWarning, match="Fields should go to zero at edges of grid to avoid "): sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) sim.create_particles(1e4, 3 * u.MeV, max_theta=0.1 * u.deg) sim.run() size = np.array([[-1, 1], [-1, 1]]) * 10 * u.cm bins = [200, 60] hax, vax, values = sim.synthetic_radiograph(size=size, bins=bins) values = np.mean(values[:, 20:40], axis=1) return hax, values
def test_synthetic_radiograph(): # CREATE A RADIOGRAPH OBJECT grid = _test_grid("electrostatic_gaussian_sphere", num=50) source = (0 * u.mm, -10 * u.mm, 0 * u.mm) detector = (0 * u.mm, 200 * u.mm, 0 * u.mm) sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) sim.create_particles(1e4, 3 * u.MeV, max_theta=10 * u.deg) sim.run(field_weighting="nearest neighbor") size = np.array([[-1, 1], [-1, 1]]) * 30 * u.cm bins = [200, 60] # Test size is None, default bins h, v, i = sim.synthetic_radiograph() # Test optical density h, v, i = sim.synthetic_radiograph(size=size, bins=bins, optical_density=True)
def test_create_particles(): grid = _test_grid("electrostatic_gaussian_sphere", num=50) # Cartesian source = (0 * u.mm, -10 * u.mm, 0 * u.mm) detector = (0 * u.mm, 200 * u.mm, 0 * u.mm) sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) sim.create_particles(1e3, 15 * u.MeV, max_theta=0.1 * u.rad, distribution="monte-carlo") sim.create_particles(1e3, 15 * u.MeV, max_theta=0.1 * u.rad, distribution="uniform") # Test specifying particle charge = 3 * const.e.si mass = const.m_e.si sim.create_particles(1e3, 15 * u.MeV, particle="e")
def run_mesh_example( location=np.array([0, -2, 0]) * u.mm, extent=(2 * u.mm, 1.5 * u.mm), nwires=9, wire_diameter=20 * u.um, mesh_hdir=None, mesh_vdir=None, nparticles=1e4, problem="electrostatic_gaussian_sphere", ): """ Takes all of the add_wire_mesh parameters and runs a standard example problem simulation using them. Returns the sim object for use in additional tests """ grid = _test_grid(problem, num=100) source = (0 * u.mm, -10 * u.mm, 0 * u.mm) detector = (0 * u.mm, 200 * u.mm, 0 * u.mm) sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) sim.add_wire_mesh( location, extent, nwires, wire_diameter, mesh_hdir=mesh_hdir, mesh_vdir=mesh_vdir, ) sim.create_particles(nparticles, 3 * u.MeV, max_theta=10 * u.deg) sim.run(field_weighting="nearest neighbor") return sim
def test_gaussian_sphere_analytical_comparison(): """ This test runs a known example problem and compares to a theoretical model for small deflections. Still under construction (comparing the actual form of the radiograph is possible but tricky to implement). """ # The Gaussian sphere problem for small deflection potentials # is solved in Kugland2012relation, and the equations referenced # below are from that paper. # https://doi.org/10.1063/1.4750234 a = (1 * u.mm / 3).to(u.mm).value phi0 = 1.4e5 W = 15e6 l = 10 L = 200 # Define and run the problem # Setting b to be much larger than the problem so that the field is not # cut off at the edges. This is required to be directly # comparable to the theoretical result. grid = _test_grid( "electrostatic_gaussian_sphere", num=100, phi0=phi0 * u.V, a=a * u.mm, b=20 * u.mm, ) source = (0 * u.mm, -l * u.mm, 0 * u.mm) detector = (0 * u.mm, L * u.mm, 0 * u.mm) with pytest.warns( RuntimeWarning, match="Fields should go to zero at edges of grid to avoid "): sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) sim.create_particles(1e3, W * u.eV, max_theta=12 * u.deg) sim.run() size = np.array([[-1, 1], [-1, 1]]) * 4 * u.cm bins = [100, 100] h, v, i = sim.synthetic_radiograph(size=size, bins=bins) h = h.to(u.mm).value / sim.mag v = v.to(u.mm).value / sim.mag r0 = h # Calculate a lineout across the center of the plane (y=0) v0 = np.argmin(np.abs(v)) line = np.mean(i[:, v0 - 6:v0 + 6], axis=1) # Zero the edge of the radiograph line += -np.mean(line) line *= 1 / np.max(np.abs(line)) # Calculate the theoretical deflection angles (Eq. 28) theory = phi0 / W * np.sqrt(np.pi) * (r0 / a) * np.exp(-((r0 / a)**2)) max_deflection = np.max(np.abs(theory)) mu = np.sqrt(np.pi) * (phi0 / W) * (l / a) # sim_mu = sim.max_deflection.to(u.rad).value*(l/a) # Calculate the theoretical inversion (Eq. 31 ) theory_deflect = -2 * mu * (1 - (r0 / a)**2) * np.exp(-((r0 / a)**2)) theory_deflect *= 1 / np.max(np.abs(theory_deflect)) # Uncomment for debug """ print(f"Theory max deflection: {max_deflection:.6f}") print(f"Theory mu: {mu:.3f}") print(f"Sim max deflection: {sim.max_deflection.to(u.rad).value:.6f}") print(f"Sim mu: {sim_mu:.3f}") import matplotlib.pyplot as plt print(f"Theory max deflection: {max_deflection:.6f}") print(f"Theory mu: {mu:.3f}") print(f"Sim max deflection: {sim.max_deflection.to(u.rad).value:.6f}") print(f"Sim mu: {sim_mu:.3f}") fig, ax = plt.subplots() ax.pcolormesh(h, v, i.T, shading='auto', cmap='Blues_r') ax.set_aspect('equal') fig, ax = plt.subplots() ax.plot(h, line ) ax.plot(h, theory_deflect) """ assert np.isclose(max_deflection, sim.max_deflection.to(u.rad).value, atol=1e-3)
def test_input_validation(): """ Intentionally raise a number of errors. """ # ************************************************************************ # During initialization # ************************************************************************ grid = _test_grid("electrostatic_gaussian_sphere") source = (-10 * u.mm, 90 * u.deg, 45 * u.deg) detector = (100 * u.mm, 90 * u.deg, 45 * u.deg) # Check that an error is raised when an input grid has a nan or infty value # First check NaN Ex = grid["E_x"] Ex[0, 0, 0] = np.nan * u.V / u.m grid.add_quantities(E_x=Ex) with pytest.raises(ValueError): sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) Ex[0, 0, 0] = 0 * u.V / u.m Ex[0, 0, 0] = np.inf * u.V / u.m # Reset element for the rest of the tests grid.add_quantities(E_x=Ex) with pytest.raises(ValueError): sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) Ex[0, 0, 0] = 0 * u.V / u.m # Check what happens if a value is large realtive to the rest of the array Ex[0, 0, 0] = 0.5 * np.max(Ex) grid.add_quantities(E_x=Ex) # with pytest.raises(ValueError): with pytest.warns(RuntimeWarning): sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) Ex[0, 0, 0] = 0 * u.V / u.m # Raise error when source-to-detector vector doesn't pass through the # field grid source_bad = (10 * u.mm, -10 * u.mm, 0 * u.mm) detector_bad = (10 * u.mm, 100 * u.mm, 0 * u.mm) with pytest.raises(ValueError): sim = prad.SyntheticProtonRadiograph(grid, source_bad, detector_bad, verbose=False) # Test raises warning when one (or more) of the required fields is missing grid_bad = CartesianGrid(-1 * u.mm, 1 * u.mm, num=50) with pytest.warns(RuntimeWarning, match="is not specified for the provided grid."): sim = prad.SyntheticProtonRadiograph(grid_bad, source, detector, verbose=True) # ************************************************************************ # During create_particles # ************************************************************************ sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) sim.create_particles(1e3, 15 * u.MeV, max_theta=0.99 * np.pi / 2 * u.rad) # ************************************************************************ # During runtime # ************************************************************************ sim = prad.SyntheticProtonRadiograph(grid, source, detector, verbose=False) sim.create_particles(1e3, 15 * u.MeV) # Test an invalid field weighting keyword with pytest.raises(ValueError): sim.run(field_weighting="not a valid field weighting") # ************************************************************************ # During runtime # ************************************************************************ # SYNTHETIC RADIOGRAPH ERRORS sim.run() # Choose a very small synthetic radiograph size that misses most of the # particles with pytest.warns( RuntimeWarning, match="of the particles are shown on this synthetic radiograph."): size = np.array([[-1, 1], [-1, 1]]) * 1 * u.mm hax, vax, values = sim.synthetic_radiograph(size=size)