def setup_run(self, n=0, steps=None):
        """Setup run for the specific case (n) desired."""

        self.steps = steps

        # Case specific input parameters
        self.ANODE_VOLTAGE = f"{self.VOLTAGE[n]}*sin(2*pi*{self.FREQ:.5e}*t)"

        self.N_INERT = self.N_INERT[n]
        self.PLASMA_DENSITY = self.PLASMA_DENSITY[n]
        self.SEED_NPPC = self.SEED_NPPC[n]

        self.NZ = self.NZ[n]

        self.DT = self.DT[n]
        self.MAX_STEPS = int(self.TOTAL_TIME[n] / self.DT)
        self.DIAG_STEPS = int(self.DIAG_INTERVAL / self.DT)

        if self.steps is not None:
            self.MAX_STEPS = self.steps
            self.DIAG_STEPS = self.MAX_STEPS // 5

        #######################################################################
        # Set geometry, boundary conditions and timestep                      #
        #######################################################################

        mwxrun.init_grid(
            lower_bound=[-self.D_CA / self.NZ * self.NX / 2.0, 0],
            upper_bound=[self.D_CA / self.NZ * self.NX / 2.0, self.D_CA],
            number_of_cells=[self.NX, self.NZ],
            min_tiles=16)
        mwxrun.grid.potential_zmax = self.ANODE_VOLTAGE
        mwxrun.init_timestep(DT=self.DT)
        mwxrun.simulation.max_steps = self.MAX_STEPS
        mwxrun.simulation.load_balance_intervals = self.MAX_STEPS // 5000

        #######################################################################
        # Field solver                                                        #
        #######################################################################

        # self.solver = picmi.ElectrostaticSolver(
        #    grid=mwxrun.grid, method='Multigrid', required_precision=1e-12
        # )
        self.solver = poisson_solvers.PoissonSolverPseudo1D(grid=mwxrun.grid)
        mwxrun.simulation.solver = self.solver

        #######################################################################
        # Particle types setup                                                #
        #######################################################################

        self.electrons = mespecies.Species(particle_type='electron',
                                           name='electrons')
        self.ions = mespecies.Species(particle_type='He',
                                      name='he_ions',
                                      charge='q_e')

        #######################################################################
        # Collision  initialization                                           #
        #######################################################################

        self.mcc = mcc_wrapper.MCC(self.electrons,
                                   self.ions,
                                   T_INERT=self.T_INERT,
                                   N_INERT=self.N_INERT,
                                   exclude_collisions=['charge_exchange'])

        #######################################################################
        # Neutral plasma injection                                            #
        #######################################################################

        self.vol_emitter = emission.UniformDistributionVolumeEmitter(
            T=self.T_ELEC)

        self.plasma_injector = emission.PlasmaInjector(
            emitter=self.vol_emitter,
            species1=self.electrons,
            species2=self.ions,
            npart=2 * self.SEED_NPPC * self.NX * self.NZ,
            T_2=self.T_INERT,
            plasma_density=self.PLASMA_DENSITY)

        #######################################################################
        # Add diagnostics                                                     #
        #######################################################################

        self.text_diag = diag_base.TextDiag(diag_steps=self.DIAG_STEPS,
                                            preset_string='perfdebug')

        self.field_diag = FieldDiagnostic(diag_steps=self.DIAG_STEPS,
                                          barrier_slices=[0],
                                          save_pdf=False,
                                          style='roelof',
                                          min_dim=2.0)

        # array to save ion density
        self.rho_array = np.zeros(self.NZ + 1)

        #######################################################################
        # Initialize run and print diagnostic info                            #
        #######################################################################

        mwxrun.init_run()
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)
Beispiel #3
0
    def init_base(self):
        logger.info("### Init Diode Base Setup ###")
        # Set grid boundaries
        if self.dim == 1:
            # Translational symmetry in x & y, 1D simulation in z. Note warp
            # immediately sets xmin and ymin to 0 when
            # warp.w3d.initdecompositionw3d() is called, so we work with that
            # offset here, though nothing should depend on it.
            xmin = 0
            xmax = 1.0
        elif self.rz:
            # assume for now that PERIOD is used to set r_max
            xmin = 0
            xmax = self.PERIOD
        else:
            xmin = -self.PERIOD/2.0
            xmax = self.PERIOD/2.0

        if self.dim < 3 and not self.rz:
            ymin = 0
            ymax = 1.0
        else:
            ymin = -self.PERIOD/2.0
            ymax = self.PERIOD/2.0

        zmin = -self.OFFSET
        zmax = self.D_CA + self.OFFSET

        # Grid parameters - set grid counts

        # If NZ was passed in recalculate RES_LENGTH
        # otherwise calculate NX, NY, and NZ
        if self.NZ is not None:
            self.RES_LENGTH = (zmax-zmin)/self.NZ
        else:
            self.NZ = int(round((zmax - zmin)/self.RES_LENGTH))

        if self.NX is None:
            if self.dim == 1:
                self.NX = 0
            else:
                self.NX = int(round(self.PERIOD/self.RES_LENGTH))
        if self.NY is None:
            if self.dim < 3:
                self.NY = 0
            else:
                self.NY = int(round(self.PERIOD/self.RES_LENGTH))

        # create the grid
        if self.dim == 1:
            lower_bound = [zmin]
            upper_bound = [zmax]
            number_of_cells = [self.NZ]
        elif self.dim == 2:
            lower_bound = [xmin, zmin]
            upper_bound = [xmax, zmax]
            number_of_cells = [self.NX, self.NZ]
        elif self.dim == 3:
            raise NotImplementedError("3d grid not yet implemented.")
            lower_bound = [xmin, ymin, zmin]
            upper_bound = [xmax, ymax, zmax]
            number_of_cells = [self.NX, self.NY, self.NZ]

        mwxrun.init_grid(
            lower_bound=lower_bound, upper_bound=upper_bound,
            number_of_cells=number_of_cells, use_rz=self.rz,
            min_tiles=self.MIN_TILES
        )

        self.DT = mwxrun.init_timestep(
            DT=self.DT,
            CFL_factor=self.CFL_FACTOR,
            # NOTE: If back-emission is later introduced, the absolute value of
            # self.V_ANODE_CATHODE should be used instead.
            V_grid=max(self.V_ANODE_CATHODE, 1.0)
        )

        logger.info("Setting up simulation with")
        logger.info(f"  dt = {self.DT:.3e} s")
        logger.info(
            f"  Total time = {self.TOTAL_TIMESTEPS * self.DT:.3e} "
            f"s ({self.TOTAL_TIMESTEPS} timesteps)"
        )
        if self.DIAG_STEPS is not None:
            logger.info(
                f"  Diag time = {self.DIAG_STEPS * self.DT:.3e} s "
                f"({self.DIAG_STEPS} timesteps)"
            )
Beispiel #4
0
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)
    '''
Beispiel #5
0
    def setup_run(self):

        self.rmax = 1e-3
        self.rmin = self.rmax - self.D_CA - self.USE_EB * 10e-6
        self.zmax = (self.rmax - self.rmin) / self.NR * self.NZ / 2.0
        self.zmin = -self.zmax

        self.V_ANODE = self.V_CATHODE + self.V_ANODE_CATHODE

        if self.DIAG_STEPS is None:
            self.DIAG_STEPS = ((self.TOTAL_TIMESTEPS //
                                5) if self.TOTAL_TIMESTEPS > 10 else
                               self.TOTAL_TIMESTEPS)

        #######################################################################
        # Set geometry, boundary conditions and timestep                      #
        #######################################################################

        mwxrun.init_grid(
            lower_bound=[self.rmin, self.zmin],
            upper_bound=[self.rmax, self.zmax],
            number_of_cells=[self.NR, self.NZ],  # min_tiles=1,
            use_rz=True,
            bc_fields_z_min='periodic',
            bc_fields_z_max='periodic',
            bc_particles_z_min='periodic',
            bc_particles_z_max='periodic',
            bc_fields_r_min='dirichlet',
            bc_fields_r_max='dirichlet',
        )
        mwxrun.init_timestep(DT=self.DT)
        mwxrun.simulation.max_steps = self.TOTAL_TIMESTEPS
        mwxrun.simulation.load_balance_intervals = self.TOTAL_TIMESTEPS // 5000

        #######################################################################
        # Field solver                                                        #
        #######################################################################

        self.solver = picmi.ElectrostaticSolver(grid=mwxrun.grid,
                                                method='Multigrid',
                                                required_precision=1e-6,
                                                warpx_self_fields_verbosity=0,
                                                maximum_iterations=1000)
        mwxrun.simulation.solver = self.solver

        #######################################################################
        # Conductors setup and installation                                   #
        #######################################################################

        self.cathode = assemblies.CylinderZ(V=self.V_CATHODE,
                                            T=self.CATHODE_TEMP,
                                            WF=self.CATHODE_PHI,
                                            name='cathode',
                                            r_outer=self.rmax + 1e-6,
                                            r_inner=self.rmax)
        self.anode = assemblies.CylinderZ(
            V=self.V_ANODE,
            T=self.ANODE_TEMP,
            WF=self.ANODE_PHI,
            name='anode',
            r_outer=(self.rmin if not self.USE_EB else self.rmax - self.D_CA))

        #######################################################################
        # Particle types setup                                                #
        #######################################################################

        self.electrons = mespecies.Species(particle_type='electron',
                                           name='electrons')

        #######################################################################
        # Scraper setup                                                       #
        #######################################################################

        for species in [self.electrons]:
            species.save_particles_at_xhi = 1
            if self.USE_EB:
                species.save_particles_at_eb = 1
            else:
                species.save_particles_at_xlo = 1

        #######################################################################
        # Thermionic emission                                                 #
        #######################################################################

        self.cathode_emitter = emission.ZCylinderEmitter(
            conductor=self.cathode, rdir=-1)
        self.cathode_injector = emission.ThermionicInjector(
            emitter=self.cathode_emitter,
            species=self.electrons,
            A=self.CATHODE_A,
            npart_per_cellstep=self.NPPC,
            use_Schottky=self.USE_SCHOTTKY)

        #######################################################################
        # Clean up and output RunInfo                                         #
        #######################################################################

        runvars = CylinderVacuumTEC.__dict__.copy()
        runvars.update(self.__dict__)

        self.runinfo = runinfo.RunInfo(
            local_vars=runvars,
            electrode_params={
                "CATHODE_A": self.CATHODE_A,
                "CATHODE_TEMP": self.CATHODE_TEMP,
                "CATHODE_PHI": self.CATHODE_PHI,
                "ANODE_PHI": self.ANODE_PHI
            },
            surface_dict={
                'cathode': self.cathode,
                'anode': self.anode
            },
            injector_dict={'cathode': self.cathode_injector},
        )
        # overwrite runinfo area to properly normalize currents
        self.runinfo.area = self.cathode_emitter.area

        #######################################################################
        # Add diagnostics                                                     #
        #######################################################################

        self.text_diag = diags.TextDiag(diag_steps=self.DIAG_STEPS,
                                        preset_string='perfdebug')
        # self.field_diag = diags.FieldDiagnostic(
        #    diag_steps=self.DIAG_STEPS, style='roelof', save_pdf=False
        # )
        self.fluxdiag = diags.FluxDiagnostic(diag_steps=self.DIAG_STEPS,
                                             runinfo=self.runinfo,
                                             check_charge_conservation=False,
                                             overwrite=True)

        #######################################################################
        # Initialize run and print diagnostic info                            #
        #######################################################################

        mwxrun.init_run()