def test_create_checkpoints_with_fluxdiag(): testing_util.initialize_testingdir("test_create_checkpoints_with_fluxdiag") # use a fixed random seed np.random.seed(47239475) run = get_run() run.init_injectors() checkpoint = CheckPointDiagnostic(DIAG_STEPS, CHECKPOINT_NAME, clear_old_checkpoints=True) run.init_runinfo() run.init_fluxdiag() mwxrun.init_run(restart=False) checkpoint.flux_diag = run.fluxdiag # Run the main WARP loop mwxrun.simulation.step(MAX_STEPS) checkpoint_names = [ f"{CHECKPOINT_NAME}{i:05}" for i in range(DIAG_STEPS, MAX_STEPS + 1, DIAG_STEPS) ] for name in checkpoint_names: print(f"Looking for checkpoint file 'diags/{name}'...") assert os.path.isdir(os.path.join("diags", name)) assert os.path.isfile("diags/checkpoint00004/fluxdata.ckpt")
def test_infinite_cylinder_z(): name = "infinite_cylinder_z_scraping" # Include a random run number to allow parallel runs to not collide. Using # python randint prevents collisions due to numpy rseed below testing_util.initialize_testingdir(name) # Initialize each run with consistent, randomly-chosen, rseed. np.random.seed(42147820) import sys sys.path.append(testing_util.example_dir) from thermionic_diode_rz_cylinder import CylinderVacuumTEC run = CylinderVacuumTEC(V_ANODE_CATHODE=5.0, TOTAL_TIMESTEPS=1000, SAVE=True, USE_EB=True, DIAG_STEPS=500) run.NR = 256 run.setup_run() run.run_sim() key = ('scrape', 'anode', 'electrons') J_diode = run.fluxdiag.ts_dict[key].get_averagevalue_by_key('J') assert np.isclose(J_diode, 1.5039225852677167)
def test_timeseries(): name = "timeseries" # Include a random run number to allow parallel runs to not collide. Using # python randint prevents collisions due to numpy rseed below testing_util.initialize_testingdir(name) # Initialize each run with consistent, randomly-chosen, rseed, Use a random # seed instead for initial dataframe generation. # np.random.seed() np.random.seed(47239315) dt = 1e-12 data1 = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) data2 = np.array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) data3 = np.array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29]) datadict = {"a": data1, "b": data2, "c": data3} ts = timeseries.Timeseries(0, 10, dt, datadict) assert np.all(np.isclose(ts.get_timeseries_by_key("a", False), data1)) assert np.mean(data2) == ts.get_averagevalue_by_key("b") resampled_ts = ts.resample(dt * 2) assert np.all( np.isclose( np.array([[0.0e-12, 20], [2.0e-12, 22], [4.0e-12, 24], [6.0e-12, 25], [8.0e-12, 27]]), resampled_ts.get_timeseries_by_key("c", True)))
def test_write_results(): test_name = "write_results_test" testing_util.initialize_testingdir(test_name) # Initialize each run with consistent, randomly-chosen, rseed. Use a random # seed instead for initial dataframe generation. # np.random.seed() np.random.seed(83197410) STEPS = 1 D_CA = 0.067 # m FREQ = 13.56e6 # MHz VOLTAGE = 450.0 NX = 8 NZ = 128 DT = 1.0 / (400 * FREQ) run = diode_setup.DiodeRun_V1( GEOM_STR='XZ', V_ANODE_CATHODE=VOLTAGE, V_ANODE_EXPRESSION="%.1f*sin(2*pi*%.5e*t)" % (VOLTAGE, FREQ), D_CA=D_CA, INERT_GAS_TYPE='He', N_INERT=9.64e20, # m^-3 T_INERT=300.0, # K PLASMA_DENSITY=2.56e14, # m^-3 T_ELEC=30000.0, # K SEED_NPPC=10, NX=NX, NZ=NZ, DT=DT, TOTAL_TIMESTEPS=STEPS, ) run.setup_run( init_conductors=False, init_scraper=False, init_injectors=False, init_neutral_plasma=True, init_simcontrol=True, init_warpx=True, init_simulation=True ) def results_contents(): return f"The dimensions of this run were: {NX} x {NZ}" run.control.set_write_func(results_contents) mwxrun.step(run.control) results_path = os.path.join(WarpXDiagnostic.DIAG_DIR, "results.txt") assert os.path.isfile(results_path) with open(results_path, 'r') as results_file: assert results_file.readline().strip() == f"The dimensions of this run were: {NX} x {NZ}"
def test_restart_from_checkpoint(caplog, force, files_exist): caplog.set_level(logging.WARNING) testing_util.initialize_testingdir( f"test_restart_from_checkpoint_{force}_{files_exist}") run = get_run() if force: restart = True else: restart = None if not files_exist: prefix = "nonexistent_prefix" else: prefix = "checkpoint" try: mwxrun.init_run(restart=restart, checkpoint_dir=os.path.join(testing_util.test_dir, "checkpoint"), checkpoint_prefix=prefix) except RuntimeError as e: if ("There were no checkpoint directories starting with " "nonexistent_prefix!" in str(e)): # There should only be an exception if this restart was forced assert force # if the files didn't exist then we didn't restart, # so there's no need to verify the correct number of steps passed if files_exist: new_max_steps = mwxrun.simulation.max_steps start_step = mwxrun.get_it() if not force and not files_exist: log_lines = [r.msg for r in caplog.records] assert any([ f"There were no checkpoint directories starting with {prefix}!" in l for l in log_lines ]) mwxrun.simulation.step() end_step = mwxrun.get_it() assert end_step - start_step == new_max_steps
def test_checkpoints_fluxdiag(): testing_util.initialize_testingdir("test_checkpoints_fluxdiag") # use a fixed random seed np.random.seed(47239475) run = get_run() run.init_injectors() run.init_runinfo() run.init_fluxdiag() mwxrun.init_run( restart=True, checkpoint_dir=os.path.join(testing_util.test_dir, "checkpoint"), ) # Run the main WarpX loop mwxrun.simulation.step() # load flux diagnostic from a completed run for comparison basedir = os.path.join(testing_util.test_dir, "checkpoint") original_flux_file = os.path.join( testing_util.test_dir, "checkpoint/fluxes/fluxdata_0000000008.dpkl") original_flux = FluxDiagFromFile(basedir, original_flux_file) # compare injected flux from the restarted history with the original history flux_key = ('inject', 'cathode', 'electrons') for key in ['J', 'P', 'n']: # if key == 'J': # continue old = original_flux.fullhist_dict[flux_key].get_timeseries_by_key(key) new = run.fluxdiag.fullhist_dict[flux_key].get_timeseries_by_key(key) print("~~~~~~~~~~~~~~~~~~~~~~~~~~~") print(f"The key is {key}") print(f"old: \n {old}") print(f"new: \n {new}") assert np.allclose(old, new)
def test_extra_steps_after_restart(): testing_util.initialize_testingdir("test_extra_steps_after_restart") # use a fixed random seed np.random.seed(47239475) run = get_run() additional_steps = 8 # restart from checkpoint created by test_create_checkpoints mwxrun.init_run(restart=True, checkpoint_dir=os.path.join(testing_util.test_dir, "checkpoint"), additional_steps=additional_steps) start_step = mwxrun.get_it() mwxrun.simulation.step() end_step = mwxrun.get_it() assert start_step + additional_steps == end_step restart_net_charge_density = np.load( os.path.join(run.field_diag.write_dir, "Net_charge_density_0000000008.npy")) # compare against data from test_create_checkpoints original_net_charge_density = np.load( os.path.join(testing_util.test_dir, "checkpoint", "Net_charge_density_0000000008.npy")) assert np.allclose(restart_net_charge_density, original_net_charge_density, rtol=0.1)
def test_coulomb_scattering(): name = "coulomb_scattering_langevin" # Include a random run number to allow parallel runs to not collide. Using # python randint prevents collisions due to numpy rseed below testing_util.initialize_testingdir(name) # Initialize each run with consistent, randomly-chosen, rseed. Use a random # seed instead for initial dataframe generation. # np.random.seed() np.random.seed(92160000) class BeamEmitter(emission.UniformDistributionVolumeEmitter): """Child class of UniformDistributionVolumeEmitter used to overwrite the get_velocities function when sampling velocities for the electrons since we want to inject them with a delta function.""" def __init__(self, T, v_beam): self.v_beam = v_beam super(BeamEmitter, self).__init__(T=T) def _get_xv_coords(self, npart, m, rseed): """Get velocities and call specialized function for position.""" x_coords = self._get_x_coords(npart) if m == 1: # ion velocities can be Maxwellian v_coords = mwxutil.get_velocities( npart, self.T, m=m, emission_type='random', rseed=None) else: # electron velocities should be a delta function, we need to # have small non-zero values for x and y otherwise the Coulomb # scattering algorithm fails due to divide by zero errors v_coords = np.zeros((3, npart)) v_coords[0] = np.random.normal(0.0, 1.0, npart) v_coords[1] = np.random.normal(0.0, 1.0, npart) v_coords[2] = self.v_beam return ( x_coords[:, 0], x_coords[:, 1], x_coords[:, 2], v_coords[0], v_coords[1], v_coords[2] ) class VarianceTracker(diags.WarpXDiagnostic): def __init__(self, total_steps, diag_steps): """Diagnostic to record the electron velocity variance at every diagnostic step.""" self.i = 0 self.results_array = np.zeros((total_steps//diag_steps, 2)) super(VarianceTracker, self).__init__(diag_steps) callbacks.installafterstep(self._record_variance) def _record_variance(self): if not self.check_timestep(): return ux = np.concatenate(mwxrun.sim_ext.get_particle_ux('electrons')) self.results_array[self.i, 0] = mwxrun.get_t() self.results_array[self.i, 1] = np.std(ux) self.i += 1 ####################################################################### # Simulation parameters # ####################################################################### ZMAX = 250e-6 NZ = 256 NX = 8 DT = 1e-13 MAX_STEPS = 150 DIAG_STEPS = 15 SEED_DENSITY = 5e17 SEED_COUNT = 20000 LOGLAMBDA = 7.5 VBEAM = 1e6 # Derived quantities PERIOD = ZMAX / NZ * NX ####################################################################### # Set geometry, boundary conditions and timestep # ####################################################################### mwxrun.init_grid( lower_bound=[-PERIOD/2.0, 0.], upper_bound=[PERIOD/2.0, ZMAX], number_of_cells=[NX, NZ], bc_fields_z_min='periodic', bc_fields_z_max='periodic', bc_particles_z_min='periodic', bc_particles_z_max='periodic' ) mwxrun.init_timestep(DT=DT) mwxrun.simulation.max_steps = MAX_STEPS ####################################################################### # Dummy Poisson solver to effectively turn off the field solve # ####################################################################### solver = DummyPoissonSolver(grid=mwxrun.grid) mwxrun.simulation.solver = solver ####################################################################### # Particle types setup # ####################################################################### electrons = mespecies.Species( particle_type='electron', name='electrons' ) # artificially increase ion mass to match Lorentz gas better ions = mespecies.Species( particle_type='Xe', name='xe_ions', charge='q_e', mass=1.0 ) ####################################################################### # Seed simulation with quasi-neutral plasma # ####################################################################### volume_emitter = BeamEmitter(1.0, VBEAM) emission.PlasmaInjector( volume_emitter, electrons, ions, npart=SEED_COUNT*2, plasma_density=SEED_DENSITY ) ####################################################################### # Coulomb scattering # ####################################################################### langevin = coulomb_scattering.LangevinElectronIonScattering( electron_species=electrons, ion_species=ions, log_lambda=LOGLAMBDA, subcycling_steps=1 ) ####################################################################### # Diagnostics # ####################################################################### diags.TextDiag(MAX_STEPS // 5, preset_string='perfdebug') variance_tracker = VarianceTracker(MAX_STEPS, DIAG_STEPS) ####################################################################### # Run simulation # ####################################################################### mwxrun.init_run() mwxrun.simulation.step() ####################################################################### # Compare results to theory # ####################################################################### D = ( SEED_DENSITY * mwxconstants.e**4 * LOGLAMBDA / (4 * np.pi * mwxconstants.epsilon_0**2 * mwxconstants.m_e**2 * VBEAM) ) times = variance_tracker.results_array[:, 0] calculated_values = variance_tracker.results_array[:, 1] / VBEAM expected_values = np.sqrt(D*times) / VBEAM print("Calculated ratio: ", calculated_values ) print("Expected ratio: ", expected_values) assert np.allclose(calculated_values, expected_values, rtol=0.01) '''
def test_superLU_solver(): name = "superLU_solver" # Include a random run number to allow parallel runs to not collide. Using # python randint prevents collisions due to numpy rseed below testing_util.initialize_testingdir(name) # Initialize each run with consistent, randomly-chosen, rseed. Use a random # seed instead for initial dataframe generation. # np.random.seed() np.random.seed(92160881) # set to False to regenerate the reference data DIRECT_SOLVER = True # Specific numbers match older run for consistency FREQ = 13.56e6 # MHz DT = 1.0 / (400 * FREQ) DIAG_STEPS = 50 DIAG_INTERVAL = DIAG_STEPS * DT VOLTAGE = 450.0 D_CA = 0.067 # m NX = 16 NZ = 128 run = diode_setup.DiodeRun_V1( GEOM_STR='XZ', DIRECT_SOLVER=DIRECT_SOLVER, V_ANODE_EXPRESSION=f"{VOLTAGE}*sin(2*pi*{FREQ:.5e}*t)", D_CA=D_CA, INERT_GAS_TYPE='He', N_INERT=9.64e20, # m^-3 T_INERT=300.0, # K PLASMA_DENSITY=2.56e14, # m^-3 T_ELEC=30000.0, # K SEED_NPPC=16 * 32, NX=NX, NZ=NZ, DT=DT, TOTAL_TIMESTEPS=50, DIAG_STEPS=DIAG_STEPS, DIAG_INTERVAL=DIAG_INTERVAL) # Only the functions we change from defaults are listed here run.setup_run(init_conductors=True, init_scraper=False, init_injectors=False, init_mcc=True, init_neutral_plasma=True, init_field_diag=False, init_simcontrol=True, init_warpx=True) # Run the main WARP loop while run.control.check_criteria(): mwxrun.simulation.step() ####################################################################### # Check phi results against reference data from MLMG solver # ####################################################################### phi_data = mwxrun.get_gathered_phi_grid(include_ghosts=False) data = np.mean(phi_data, axis=0) # uncomment to generate reference data # np.save('reference_data.npy', data) ref_data = np.load( os.path.join(testing_util.test_dir, 'direct_solver', 'reference_data.npy')) assert np.allclose(data, ref_data, rtol=0.001)
def test_extra_pid(caplog): caplog.set_level(logging.INFO) name = "mespecies_extra_pid" # Include a random run number to allow parallel runs to not collide. Using # python randint prevents collisions due to numpy rseed below testing_util.initialize_testingdir(name) # Initialize each run with consistent, randomly-chosen, rseed. Use a random # seed instead for initial dataframe generation. # np.random.seed() np.random.seed(9216001) # Specific numbers match older run for consistency DIAG_STEPS = 2 D_CA = 0.05 # m NX = 16 NZ = 128 run = diode_setup.DiodeRun_V1(GEOM_STR='XZ', V_ANODE_CATHODE=450.0, D_CA=D_CA, NX=NX, NZ=NZ, DT=1.0e-10, TOTAL_TIMESTEPS=25, DIAG_STEPS=DIAG_STEPS, DIRECT_SOLVER=True) # Only the functions we change from defaults are listed here run.setup_run(init_conductors=True, init_injectors=False, init_simcontrol=True, init_warpx=True) # add new pid for the ions run.electrons.add_pid('extra_pid') def check_particle_nums(): return not (mwxrun.get_npart() < 950) nps = 1000 w = np.random.randint(low=1, high=100, size=nps) mwxrun.sim_ext.add_particles(run.electrons.name, x=np.random.random(nps) * D_CA / NZ * NX, y=np.zeros(nps), z=np.random.random(nps) * D_CA, ux=np.random.normal(scale=1e4, size=nps), uy=np.random.normal(scale=1e4, size=nps), uz=np.random.normal(scale=1e4, size=nps), w=w, extra_pid=np.copy(w) * 10.0) run.control.add_checker(check_particle_nums) # Run the main WARP loop while run.control.check_criteria(): mwxrun.simulation.step(DIAG_STEPS) ####################################################################### # Cleanup and final output # ####################################################################### all_log_output = "" records = caplog.records for record in records: all_log_output += record.msg + "\n" # make sure out isn't empty outstr = "SimControl: Termination from criteria: check_particle_nums" assert outstr in all_log_output weights = run.electrons.get_array_from_pid('w') extra_pid = run.electrons.get_array_from_pid('extra_pid') for ii in range(len(weights)): assert np.allclose(extra_pid[ii] / weights[ii], 10.0)
def test_field_diag(plot_on_diag_steps): # We test either post processing or plotting on diag steps, not both. post_processing = not plot_on_diag_steps plot_diag_str = "_with_diag_plotting" if plot_on_diag_steps else "" post_proc_str = "_with_post_processing" if post_processing else "" test_name = "field_diag_test" + plot_diag_str + post_proc_str testing_util.initialize_testingdir(test_name) # Initialize each run with consistent, randomly-chosen, rseed. Use a random # seed instead for initial dataframe generation. # np.random.seed() np.random.seed(83197410) STEPS = 10 D_CA = 0.067 # m FREQ = 13.56e6 # MHz VOLTAGE = 450.0 DT = 1.0 / (400 * FREQ) DIAG_STEPS = 2 * (int(post_processing) + 1) DIAG_DATA_LIST = ['rho_electrons', 'rho_he_ions', 'phi'] #DIAG_SPECIES_LIST = ["electrons", "he_ions"] DIAG_SPECIES_LIST = None run = diode_setup.DiodeRun_V1( GEOM_STR='XZ', V_ANODE_CATHODE=VOLTAGE, V_ANODE_EXPRESSION="%.1f*sin(2*pi*%.5e*t)" % (VOLTAGE, FREQ), D_CA=D_CA, INERT_GAS_TYPE='He', N_INERT=9.64e20, # m^-3 T_INERT=300.0, # K PLASMA_DENSITY=2.56e14, # m^-3 T_ELEC=30000.0, # K SEED_NPPC=10, NX=8, NZ=128, DT=DT, TOTAL_TIMESTEPS=STEPS, DIAG_STEPS=DIAG_STEPS, FIELD_DIAG_DATA_LIST=DIAG_DATA_LIST, FIELD_DIAG_SPECIES_LIST=DIAG_SPECIES_LIST, FIELD_DIAG_PLOT=plot_on_diag_steps, FIELD_DIAG_INSTALL_WARPX=post_processing, FIELD_DIAG_PLOT_AFTER_RUN=post_processing ) run.setup_run( init_conductors=False, init_scraper=False, init_injectors=False, init_neutral_plasma=True, init_field_diag=True, init_warpx=True, init_simulation=True ) mwxrun.simulation.step() # verify that the plot images were created. if plot_on_diag_steps: print("Verifying that all data and plot files were created...") phi_data_n = len(glob.glob( os.path.join( run.field_diag.write_dir, "Electrostatic_potential_*.npy" ) )) phi_plots_n = len(glob.glob( os.path.join( run.field_diag.write_dir, "Electrostatic_potential_*.png" ) )) assert phi_data_n == 5 assert phi_plots_n == 5 for species in [species.name for species in mwxrun.simulation.species]: n_data = len(glob.glob( os.path.join( run.field_diag.write_dir, f"{species}_particle_density_*.npy" ) )) n_plots = len(glob.glob( os.path.join( run.field_diag.write_dir, f"{species}_particle_density_*.png" ) )) assert n_data == 5 assert n_plots == 5 print("All plots exist!") # verify that the post processing image was created if post_processing: print("Verifying that all plots were created...") # Start at 1st diagnostic (at step 0 all data arrays are 0 and are # therefore skipped) for i in range(DIAG_STEPS, STEPS - 1, DIAG_STEPS): for param in DIAG_DATA_LIST: assert os.path.isfile( os.path.join( run.field_diag.write_dir, param + f"_{i:05d}.png" ) ), param + "_" + f"{i:06d}.png doesn't exist" print("All plots exist!")
def test_injector_flux_diagnostic(): name = "injectorfluxDiagnostic" # Include a random run number to allow parallel runs to not collide. Using # python randint prevents collisions due to numpy rseed below testing_util.initialize_testingdir(name) # Initialize each run with consistent, randomly-chosen, rseed, Use a random # seed instead for initial dataframe generation. # np.random.seed() np.random.seed(47239475) TOTAL_TIME = 1e-10 # s DIAG_INTERVAL = 1e-10 # s DT = 1e-12 # s P_INERT = 1 # torr T_INERT = 300 # K D_CA = 5e-4 # m VOLTAGE = 25 # V CATHODE_TEMP = 1100 + 273.15 # K CATHODE_PHI = 2.1 # work function in eV NX = 8 NZ = 128 DIRECT_SOLVER = True max_steps = int(TOTAL_TIME / DT) diag_steps = int(DIAG_INTERVAL / DT) run = diode_setup.DiodeRun_V1(GEOM_STR='XZ', CATHODE_TEMP=CATHODE_TEMP, CATHODE_PHI=CATHODE_PHI, V_ANODE_CATHODE=VOLTAGE, D_CA=D_CA, P_INERT=P_INERT, T_INERT=T_INERT, NPPC=50, NX=NX, NZ=NZ, DIRECT_SOLVER=DIRECT_SOLVER, DT=DT, TOTAL_TIMESTEPS=max_steps, DIAG_STEPS=diag_steps, DIAG_INTERVAL=DIAG_INTERVAL, CHECK_CHARGE_CONSERVATION=False) # Only the functions we change from defaults are listed here run.setup_run(init_conductors=True, init_scraper=True, init_runinfo=True, init_fluxdiag=False, init_warpx=False) run.fluxdiag = flux_diagnostic.FluxDiagnostic( diag_steps=run.DIAG_STEPS, runinfo=run.runinfo, check_charge_conservation=run.CHECK_CHARGE_CONSERVATION, overwrite=False, save_csv=True) run.init_warpx() mwxrun.simulation.step(max_steps) # check that current in CSV is correct filename = "diags/fluxes/thermionic_injector_electrons_injected.csv" assert os.path.isfile(filename), "Could not find output CSV." df = pandas.read_csv(filename) q = df["q"] cathode_area = run.emitter.area J = mwxutil.J_RD(run.injector.T, run.injector.WF, run.injector.A) current = J * cathode_area * -1 assert np.allclose(q / DT, current, rtol=0.01, atol=0) # check that pickle file current is correct with open("diags/fluxes/fluxdata_{:010d}.dpkl".format(100), "rb") as pfile: flux_diags = dill.load(pfile) # leave off first element (time step 0) where there is 0 current J_pickle = (flux_diags['fullhist_dict'][( 'inject', 'cathode', 'electrons')].get_timeseries_by_key("J", False)[1:]) # convert to cm^2 J /= -1.0e4 assert np.allclose(J_pickle, J, rtol=0.01, atol=0) # check that plotfile exists assert os.path.isfile("diags/fluxes/flux_plots_{:010d}.png".format(100))
def test_flux_diag_accuracy(caplog): caplog.set_level(logging.INFO) name = "FluxDiagRun" testing_util.initialize_testingdir(name) # Initialize each run with consistent, randomly-chosen, rseed. For initial # statistics, instead use a plain random seed (commented out here). np.random.seed(18672033) # these values come from the default values in the mewarp diode setup TOTAL_TIME = 3e-10 # s DIAG_INTERVAL = 1e-10 # s DT = 1e-12 # s not from mewarp diode setup CATHODE_TEMP = 1550 # K ANODE_TEMP = 773 # K P_INERT = 4 # torr T_INERT = .5 * (CATHODE_TEMP + ANODE_TEMP) # K NPPC = 2 D_CA = 1.0e-4 CATHODE_PHI = 2.4 # work function in eV NX = 8 # not from mewarp diode setup NZ = 128 # not from mewarp diode setup DIRECT_SOLVER = True max_steps = int(TOTAL_TIME / DT) diag_steps = int(DIAG_INTERVAL / DT) # Run with relatively large bias to have stronger signals. run = diode_setup.DiodeRun_V1(GEOM_STR='XZ', CATHODE_TEMP=CATHODE_TEMP, CATHODE_PHI=CATHODE_PHI, V_ANODE_CATHODE=20., D_CA=D_CA, P_INERT=P_INERT, T_INERT=T_INERT, NPPC=NPPC, NX=NX, NZ=NZ, DIRECT_SOLVER=DIRECT_SOLVER, DT=DT, TOTAL_TIMESTEPS=max_steps, DIAG_STEPS=diag_steps, DIAG_INTERVAL=DIAG_INTERVAL, CHECK_CHARGE_CONSERVATION=False) run.setup_run(init_mcc=True, init_runinfo=True) run.fluxdiag = flux_diagnostic.FluxDiagnostic( diag_steps=run.DIAG_STEPS, runinfo=run.runinfo, check_charge_conservation=run.CHECK_CHARGE_CONSERVATION, overwrite=False, save_csv=True) run.init_warpx() mwxrun.simulation.step(max_steps) all_log_output = "" records = caplog.records for record in records: all_log_output += record.msg + "\n" # Check expected print output # Match a pattern to allow for numbers to change pattern = (r"""Cathode Electrons Current Emitted: -4\.5\d* A/cm\^2 Cathode Electrons Current Collected: 2\.[0-3]\d* A/cm\^2 Cathode Electrons Current Net: -2\.[234]\d* A/cm\^2 Cathode Ar_Ions Current Collected: 0 A/cm\^2 Anode Electrons Current Collected: 2\.[123]\d* A/cm\^2 Anode Ar_Ions Current Collected: 0 A/cm\^2 mcc Electrons Current Emitted: -0\.0\d* A/cm\^2 mcc Ar_Ions Current Emitted: 0\.0\d* A/cm\^2 Total Current Emitted: -4\.5\d* A/cm\^2 Total Current Collected: 4\.[3-8]\d* A/cm\^2 Total Current Net: -?0\.[01]\d* A/cm\^2 Total Power Net: -4[0-7]\.\d* W/cm\^2 """).replace("\n", " ") match = re.search(pattern, all_log_output.replace("\n", " ")) assert match is not None print("Diagnostic output match:") print(match.group(0)) #Check diagnostic files are present filelist = [ 'diags/fluxes/Anode_scraped.csv', 'diags/fluxes/Cathode_scraped.csv', 'diags/fluxes/flux_plots_0000000100.png', 'diags/fluxes/flux_plots_0000000200.png', 'diags/fluxes/flux_plots_0000000300.png', 'diags/fluxes/fluxdata_0000000100.dpkl', 'diags/fluxes/fluxdata_0000000200.dpkl', 'diags/fluxes/fluxdata_0000000300.dpkl', 'diags/fluxes/mcc_electrons_ar_ions_injected.csv', 'diags/fluxes/thermionic_injector_electrons_injected.csv', ] for filename in filelist: assert os.path.isfile(filename) # Check that Qs plus powers sum to about 0 for most recent period Q_emit = run.fluxdiag.ts_dict[('inject', 'cathode', 'electrons')].get_averagevalue_by_key('dQ') Q_abs_cathode = run.fluxdiag.ts_dict[( 'scrape', 'cathode', 'electrons')].get_averagevalue_by_key('dQ') Q_abs_anode = run.fluxdiag.ts_dict[( 'scrape', 'anode', 'electrons')].get_averagevalue_by_key('dQ') P_anode = run.fluxdiag.ts_dict[('scrape', 'anode', 'electrons')].get_averagevalue_by_key('P') conservation = Q_emit + Q_abs_cathode + Q_abs_anode - P_anode print(f"Q_emit: {Q_emit} W/cm^2") print(f"Q_abs_cathode: {Q_abs_cathode} W/cm^2") print(f"Q_abs_anode: {Q_abs_anode} W/cm^2") print(f"P_anode: {P_anode} W/cm^2") print(f"Conservation: {conservation} W/cm^2") assert abs(conservation) < 0.4 # Gather results to check. The index call here ensures there's one row to # assign into in the new DataFrame. df = pandas.DataFrame(index=list(range(1))) for fluxtype in ['J', 'P', 'dQ', 'n']: df['inject_cathode_' + fluxtype] = run.fluxdiag.fullhist_dict[( 'inject', 'cathode', 'electrons')].get_averagevalue_by_key(fluxtype) df['scrape_cathode_' + fluxtype] = run.fluxdiag.fullhist_dict[( 'scrape', 'cathode', 'electrons')].get_averagevalue_by_key(fluxtype) df['scrape_anode_' + fluxtype] = run.fluxdiag.fullhist_dict[( 'scrape', 'anode', 'electrons')].get_averagevalue_by_key(fluxtype) df['mcc_' + fluxtype] = run.fluxdiag.fullhist_dict[( 'inject', 'mcc', 'electrons')].get_averagevalue_by_key(fluxtype) assert testing_util.test_df_vs_ref(name, df) # Check that loaded results are identical fluxdiag_2 = flux_diagnostic.FluxDiagFromFile() for key in run.fluxdiag.fullhist_dict: for fluxtype in ['J', 'P', 'dQ', 'n']: assert np.isclose( run.fluxdiag.fullhist_dict[key].get_averagevalue_by_key( fluxtype), fluxdiag_2.fullhist_dict[key].get_averagevalue_by_key( fluxtype)) assert np.isclose( run.fluxdiag.ts_dict[key].get_averagevalue_by_key(fluxtype), fluxdiag_2.ts_dict[key].get_averagevalue_by_key(fluxtype)) reftext = fluxdiag_2.print_fluxes(fluxdiag_2.ts_dict) print(reftext) match = re.search(pattern, reftext.replace("\n", " ")) assert match is not None print("Postprocess output match:") print(match.group(0))
def test_capacitive_discharge_multigrid(caplog, name): caplog.set_level(logging.INFO) # Include a random run number to allow parallel runs to not collide. Using # python randint prevents collisions due to numpy rseed below testing_util.initialize_testingdir(name) # Initialize each run with consistent, randomly-chosen, rseed. Use a random # seed instead for initial dataframe generation. # np.random.seed() np.random.seed(92160881) GEOM_STR = name.split('_')[-1] # Specific numbers match older run for consistency FREQ = 13.56e6 # MHz DT = 1.0 / (400 * FREQ) DIAG_STEPS = 2 DIAG_INTERVAL = DIAG_STEPS * DT VOLTAGE = 450.0 D_CA = 0.067 # m run = diode_setup.DiodeRun_V1( GEOM_STR=GEOM_STR, V_ANODE_CATHODE=VOLTAGE, V_ANODE_EXPRESSION="%.1f*sin(2*pi*%.5e*t)" % (VOLTAGE, FREQ), D_CA=D_CA, INERT_GAS_TYPE='He', N_INERT=9.64e20, # m^-3 T_INERT=300.0, # K PLASMA_DENSITY=2.56e14, # m^-3 T_ELEC=30000.0, # K SEED_NPPC=16 * 32, NX=16, NZ=128, DT=DT, TOTAL_TIMESTEPS=10, DIAG_STEPS=DIAG_STEPS, DIAG_INTERVAL=DIAG_INTERVAL) # Only the functions we change from defaults are listed here run.setup_run(init_conductors=False, init_injectors=False, init_neutral_plasma=True, init_mcc=True, init_field_diag=True, init_simcontrol=True, init_warpx=True) # Run the main WARP loop while run.control.check_criteria(): mwxrun.simulation.step() ####################################################################### # Cleanup and final output # ####################################################################### all_log_output = "" records = caplog.records for record in records: all_log_output += record.msg + "\n" print(all_log_output) # make sure out isn't empty outstr = "SimControl: Termination from criteria: eval_total_steps" assert outstr in all_log_output
def test_embedded_rectangle(): name = "Embedded_rectangle_solve" # Include a random run number to allow parallel runs to not collide. Using # python randint prevents collisions due to numpy rseed below testing_util.initialize_testingdir(name) # Initialize each run with consistent, randomly-chosen, rseed. Use a random # seed instead for initial dataframe generation. # np.random.seed() np.random.seed(92160881) # Specific numbers match older run for consistency D_CA = 1 # m run = diode_setup.DiodeRun_V1( GEOM_STR='XZ', D_CA=D_CA, NX=64, NZ=64, DT=1e-6, TOTAL_TIMESTEPS=1, DIAG_STEPS=1, FIELD_DIAG_DATA_LIST=['phi'], ) # Only the functions we change from defaults are listed here run.setup_run(init_conductors=False, init_scraper=False, init_electrons=False, init_solver=False, init_injectors=False, init_field_diag=True, init_simcontrol=True, init_simulation=False) # Install the embedded boundary cylinder = assemblies.Rectangle(center_x=0.0, center_z=0.5, length_x=0.3, length_z=0.3, V=-2.0, T=300, WF=4.7, name="Box") # Initialize solver run.init_solver() run.init_conductors() # Initialize the simulation run.init_simulation() run.init_warpx() # Run the main WARP loop while run.control.check_criteria(): mwxrun.simulation.step() ####################################################################### # Check phi results against reference data # ####################################################################### phi = mwxrun.get_gathered_phi_grid(include_ghosts=False) # np.save('embedded_rectangle_phi.npy', phi) ref_phi = np.load( os.path.join(testing_util.test_dir, 'embedded_boundary', 'embedded_rectangle_phi.npy')) assert np.allclose(phi, ref_phi, rtol=0.001)
def test_particle_diag(): test_name = "particle_diag_test_with_post_processing" testing_util.initialize_testingdir(test_name) # Initialize each run with consistent, randomly-chosen, rseed, Use a random # seed instead for initial dataframe generation. # np.random.seed() np.random.seed(47239475) DT = 0.5e-12 # s P_INERT = 1 # torr T_INERT = 300 # K D_CA = 5e-4 # m VOLTAGE = 25 # V CATHODE_TEMP = 1100 + 273.15 # K CATHODE_PHI = 2.1 # work function in eV NX = 8 NZ = 128 max_steps = 10 diag_steps = 2 DATA_LIST = ['position', 'momentum', 'weighting'] PLOT_SPECIES = ['electrons'] DIAG_PLOT_DATA_LIST = ["particle_position_x", "particle_momentum_x"] run = diode_setup.DiodeRun_V1( GEOM_STR='XZ', CATHODE_TEMP=CATHODE_TEMP, CATHODE_PHI=CATHODE_PHI, V_ANODE_CATHODE=VOLTAGE, D_CA=D_CA, P_INERT=P_INERT, T_INERT=T_INERT, NPPC=50, NX=NX, NZ=NZ, DT=DT, TOTAL_TIMESTEPS=max_steps, DIAG_STEPS=diag_steps, PARTICLE_DIAG_DATA_LIST=DATA_LIST, PARTICLE_PLOT_SPECIES=PLOT_SPECIES, PARTICLE_DIAG_PLOT_DATA_LIST=DIAG_PLOT_DATA_LIST, PARTICLE_DIAG_PLOT_AFTER_RUN=True) # Only the functions we change from defaults are listed here run.setup_run(init_conductors=True, init_particle_diag=True, init_warpx=True) mwxrun.simulation.step(max_steps) # verify that the plot images were created. print('Verifying that all plots were created...') for i in range(diag_steps, max_steps - 1, diag_steps): for specimen in PLOT_SPECIES: for param in DIAG_PLOT_DATA_LIST: file_name = os.path.join( run.particle_diag.write_dir, specimen + '_' + param + f'_{i:05d}.png') print(file_name) assert os.path.isfile(file_name), f"{file_name} not found" print('All plots exist!')
def test_utils_pulsing_sim(): name = "pulsing_utility_test" # Include a random run number to allow parallel runs to not collide. Using # python randint prevents collisions due to numpy rseed below testing_util.initialize_testingdir(name) # Initialize each run with consistent, randomly-chosen, rseed. Use a random # seed instead for initial dataframe generation. # np.random.seed() np.random.seed(9216024) nx = 8 nz = 128 D_CA = 1e-4 # m xmin = 0.0 zmin = 0.0 xmax = D_CA / nz * nx zmax = D_CA max_steps = 50 DT = 1e-10 ##################################### # grid, solver and timesteps ##################################### mwxrun.init_grid([xmin, zmin], [xmax, zmax], [nx, nz]) solver = PoissonSolverPseudo1D(grid=mwxrun.grid) mwxrun.simulation.solver = solver mwxrun.init_timestep(DT=DT) mwxrun.simulation.max_steps = max_steps pulse_expr = pulsing.linear_pulse_function( V_off=-1.0, V_on=7.5, pulse_period=20e-9, pulse_length=2e-9, t_rise=1e-9, t_fall=1e-9, wait_time=0.5e-9, plot=True ) anode = assemblies.Anode(D_CA, pulse_expr, 100, 3.0) electrons = mespecies.Species( particle_type='electron', name='electrons', ) # mwxrun.simulation.write_input_file() # exit() ################################# # simulation setup ################################ mwxrun.init_run() # check that plot was saved successfully assert os.path.isfile('diags/circuit/pulse_schematic.png') ################################ # Simulation run ################################ times = np.arange(max_steps)*DT Vs = np.zeros(max_steps) # Run the main loop for ii in range(max_steps): Vs[ii] = anode.getvoltage() mwxrun.simulation.step(1) print(repr(Vs)) ref_Vs = np.array( [-1. , -1. , -1. , -1. , -1. , -1. , -1. , -0.15, 0.7 , 1.55, 2.4 , 3.25, 4.1 , 4.95, 5.8 , 6.65, 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 7.5 , 6.65, 5.8 , 4.95, 4.1 , 3.25, 2.4 , 1.55, 0.7 , -0.15, -1. , -1. , -1. , -1. ] ) assert np.allclose(Vs, ref_Vs, atol=0)
def test_two_embedded_cylinders_scraping(): name = "two_embedded_cylinders_scraping" # Include a random run number to allow parallel runs to not collide. Using # python randint prevents collisions due to numpy rseed below testing_util.initialize_testingdir(name) # Initialize each run with consistent, randomly-chosen, rseed. Use a random # seed instead for initial dataframe generation. # np.random.seed() np.random.seed(42147820) # Specific numbers match older run for consistency D_CA = 0.025 # m run = diode_setup.DiodeRun_V1( GEOM_STR='XZ', #V_ANODE_CATHODE=VOLTAGE, D_CA=D_CA, NX=64, NZ=64, DT=1e-10, TOTAL_TIMESTEPS=15, DIAG_STEPS=15, FIELD_DIAG_DATA_LIST=['phi'], # FIELD_DIAG_PLOT=True, INERT_GAS_TYPE='positron') # Only the functions we change from defaults are listed here run.setup_run(init_conductors=False, init_scraper=False, init_electrons=True, init_inert_gas=True, init_solver=False, init_injectors=False, init_field_diag=True, init_simcontrol=True, init_simulation=False) # Install the embedded boundaries cylinder1 = assemblies.InfCylinderY(center_x=-0.25 * D_CA, center_z=0.5 * D_CA, radius=0.1 * D_CA, V=-0.5, T=300, WF=4.7, name="Cylinder1") cylinder2 = assemblies.InfCylinderY(center_x=0.25 * D_CA, center_z=0.5 * D_CA, radius=0.1 * D_CA, V=0.2, T=300, WF=4.7, name="Cylinder2") # Initialize solver run.init_solver() run.init_conductors() run.electrons.save_particles_at_eb = 1 run.ions.save_particles_at_eb = 1 # Inject particles in the simulation volemitter = emission.ZSinDistributionVolumeEmitter( T=3000, zmin=0, zmax=run.D_CA, ) emission.PlasmaInjector( emitter=volemitter, species1=run.electrons, species2=run.ions, npart=4000, plasma_density=1e14, ) # Initialize the simulation run.init_simulation() run.init_warpx() # Run the main WARP loop while run.control.check_criteria(): mwxrun.simulation.step() ####################################################################### # Check flux on each cylinder # ####################################################################### cylinder1.init_scrapedparticles(cylinder1.fields) cylinder1.record_scrapedparticles() cyl1_scraped = cylinder1.get_scrapedparticles() cylinder2.init_scrapedparticles(cylinder2.fields) cylinder2.record_scrapedparticles() cyl2_scraped = cylinder2.get_scrapedparticles() assert np.allclose(cyl1_scraped['n'], np.array([2, 0])) assert np.allclose(cyl2_scraped['n'], np.array([1, 2])) ####################################################################### # Check rho results against reference data # ####################################################################### rho = mwxrun.get_gathered_rho_grid(include_ghosts=False)[:, :, 0] # np.save('two_embedded_cylinders_rho.npy', rho) ref_rho = np.load( os.path.join(testing_util.test_dir, 'embedded_boundary', 'two_embedded_cylinders_rho.npy')) assert np.allclose(rho, ref_rho)