Example #1
0
    def __init__(self, diag_steps, diag_step_offset=0,
                 extended_interval_level=None,
                 manual_timesteps=None,
                 ):
        """Initialize timing. Will often be called within a child object's
        init.

        Arguments:
            diag_steps (int): Run the diagnostic with this period.
            diag_step_offset (int): Run when ``simulation_step % diag_steps =
                diag_step_offset``. Default 0.
            extended_interval_level (int): Enable an exponentially-increasing
                interval as the run progresses. If None (default), calculate
                every time period. Otherwise, the interval increases on an
                exponential scale. 0 is most aggressive at skipping diagnostic
                timesteps; 1, 2, etc. are progressively less aggressive but
                still exponential. Specifically, 0 executes once during each
                power-of-2 set of diagnostic steps; 1 twice; 2 four times, etc.
            manual_timesteps (list of ints): A list of timesteps to execute on
                within each diag_steps. If this list contains steps for the
                entire run, set diag_steps to a very large number. Default
                None.
        """
        self.diag_steps = int(round(diag_steps))
        self.diag_step_offset = int(round(diag_step_offset))

        self.extended_interval_level = extended_interval_level
        if self.extended_interval_level is not None:
            self.extended_interval_level = int(round(
                self.extended_interval_level))

        self.manual_timesteps = manual_timesteps
        if self.manual_timesteps is not None:
            self.manual_timesteps = [int(round(x))
                                     for x in self.manual_timesteps]

        if (
            (self.manual_timesteps is not None)
            and (self.extended_interval_level is not None)
        ):
            raise ValueError("manual_timesteps and extended_interval_level "
                             "cannot be used together; one must be None.")

        if mwxrun.initialized:
            raise RuntimeError("You must install all diagnostics before "
                               "initializing warpx")

        # handle creating the folder to save diagnostics
        callbacks.installafterinit(self._create_diag_folder)
Example #2
0
    def init(self, kw):
        """PICMI relies on C++ WarpX to translate charge & mass strings to
        floats. To get around that we have our own variables sq/sm (species
        charge/mass) that are always floats.

        Automatically adds the species to the simulation
        """

        super(Species, self).init(kw)

        if isinstance(self.charge, str):
            if self.charge == 'q_e':
                self.sq = picmi.constants.q_e
            elif self.charge == '-q_e':
                self.sq = -picmi.constants.q_e
            else:
                raise ValueError("Unrecognized charge {}".format(self.charge))
        else:
            self.sq = self.charge

        if isinstance(self.mass, str):
            if self.mass == 'm_e':
                self.sm = picmi.constants.m_e
            elif self.mass == 'm_p':
                self.sm = picmi.constants.m_p
            else:
                raise ValueError("Unrecognized mass {}".format(self.mass))
        else:
            self.sm = self.mass

        mwxrun.simulation.add_species(self,
                                      layout=picmi.GriddedLayout(
                                          n_macroparticle_per_cell=[0, 0],
                                          grid=mwxrun.grid))
        self.pids_initialized = False
        # Only keys are used; ensures pids are both unique and ordered.
        self.waiting_extra_pids = {}
        # add a callback to initialize the extra PIDs after sim init
        callbacks.installafterinit(self.init_pid_dict)
Example #3
0
    def __init__(self, diag_steps, preset_string='default',
                 custom_string=None, install=True, **kwargs):
        """Generate and install function to write out step #.

        Arguments:
            diag_steps (int): Number of steps between each output
            simulation (mespecies.Simulation): Main simulation object
            preset_string (str): Defaults to choose between:

              - ``default`` - just the step number and total particle num
              - ``perfdebug`` - like particledebug, plus interval wall time,
                step rate, and particle-step rate
              - ``memdebug`` - print out verbose memory usage information

            custom_string (str): Overrides preset_string if not None. The full
                string to output, with:

              - ``{step}`` formatting symbol for where the step number should
                go
              - ``{wall_time}`` run time of the last diag_steps steps
              - ``{step_rate}`` diag_steps / wall_time
              - ``{particle_step_rate}`` nplive * diag_steps / wall_time
              - ``{nplive}`` for number of live particles (global).
              - ``{npperspecies}`` for number of particles per species
                (global).
              - ``{iproc}`` for the current processor number
              - ``{system_memory}`` for verbose information on system memory
                usage.
              - ``{memory_usage}`` for memory usage of the current process
                only.

            install (bool): If False, don't actually install this into WarpX.
                Use if you want to call manually for debugging.
            kwargs: See :class:`mewarpx.mewarpx.diags_store.diag_base.WarpXDiagnostic`
                for more timing options.
        """
        self.defaults_dict = {
            'default': "Step #{step:6d}; {nplive:8d} particles",
            'perfdebug': ("Step #{step:6d}; {nplive:8d} particles "
                          "{npperspecies} "
                          "{wall_time:6.1f} s wall time; "
                          "{step_rate:4.2f} steps/s; "
                          "{particle_step_rate:4.2f} particle*steps/s in the last {diag_steps} steps; "
                          "{particle_step_rate_total:4.2f} particle*steps/s overall"),
            'memdebug': ("{system_memory}\n"
                         "Proc {iproc} usage:\n{memory_usage}"),
        }
        if custom_string is not None:
            self.diag_string = custom_string
        else:
            if preset_string not in self.defaults_dict:
                logger.warning(("Preset {} not found for set_step_diag, "
                                "using default").format(preset_string))
                preset_string = 'default'
            self.diag_string = self.defaults_dict[preset_string]

        super(TextDiag, self).__init__(diag_steps=diag_steps, **kwargs)

        callbacks.installafterinit(self.init_timers_and_counters)

        if install:
            callbacks.installafterstep(self.text_diag)
Example #4
0
    def __init__(self, diag_steps, runinfo, overwrite=True,
                 history_maxlen=5000, sig_figs=6,
                 printed_qtys=None,
                 check_charge_conservation=True,
                 print_per_diagnostic=True, print_total=False,
                 plot=True, save_csv=False, profile_decorator=None,
                 **kwargs):
        """Generate and install function to write out fluxes.

        Arguments:
            diag_steps (int): Number of steps between each output
            runinfo (:class:`mewarpx.runinfo.RunInfo`): RunInfo object is used
                to get the species, injectors, surfaces, and system area.
            overwrite (bool): If True the dill pickled save file will overwrite
                the previous diagnostic period's saved file.
            history_maxlen (int): Maximum length of full history to keep. If
                this is exceeded, history is resampled to a 2x lower frequency.
                Default 5000.
            sig_figs (int): Number of significant figures in text output.
                Default 6.
            printed_qtys (dict): Override individual values of
                default_printed_qtys; same input format but keys can be omitted
                to use defaults.
            check_charge_conservation (bool): Whether to check if charge is
                conserved in simulation.
            print_per_diagnostic (bool): Whether to print current results for
                the latest diagnostic period.
            print_total (bool): Whether to print total history of fluxes after a
                diagnostic period.
            plot (bool): Whether to save a plot of fluxes after each diagnostic
                period.
            save_csv (bool): Whether to save csv files of scraped / injected
                particles.
            profile_decorator (decorator): A decorator used to profile the
                timeseries update methods and related functions.
            kwargs: See :class:`mewarpx.diags_store.diag_base.WarpXDiagnostic`
                for more timing options.
        """
        # Save input variables
        self.runinfo = runinfo
        self.history_maxlen = history_maxlen
        self.history_dt = mwxrun.get_dt()
        self.check_charge_conservation = check_charge_conservation
        self.print_per_diagnostic = print_per_diagnostic
        self.print_total = print_total
        self.plot = plot

        if save_csv:
            csv_write_dir = os.path.join(self.DIAG_DIR, self.FLUX_DIAG_DIR)
        else:
            csv_write_dir = None

        if profile_decorator is not None:
            self.update_ts_dict = profile_decorator(self.update_ts_dict)
            self.update_fullhist_dict = (
                profile_decorator(self.update_fullhist_dict)
            )
            self.print_fluxes = profile_decorator(self.print_fluxes)
            self.plot_fluxes = profile_decorator(self.plot_fluxes)

        self.injector_dict = runinfo.injector_dict
        self.surface_dict = runinfo.surface_dict

        self.diags_dict = collections.OrderedDict()

        for key, val in self.injector_dict.items():
            self.injector_dict[key] = mwxutil.return_iterable(val)
            self.diags_dict[('inject', key)] = [
                InjectorFluxDiag(diag_steps=diag_steps,
                                 injector=injector,
                                 write_dir=csv_write_dir,
                                 **kwargs)
                for injector in self.injector_dict[key]
            ]

        for key, val in self.surface_dict.items():
            self.surface_dict[key] = mwxutil.return_iterable(val)
            self.diags_dict[('scrape', key)] = [
                SurfaceFluxDiag(diag_steps=diag_steps,
                                surface=surface,
                                write_dir=csv_write_dir,
                                **kwargs)
                for surface in self.surface_dict[key]
            ]

        # Initialize other variables
        self.last_run_step = 0

        super(FluxDiagnostic, self).__init__(
            diag_steps=diag_steps,
            runinfo=runinfo,
            overwrite=overwrite,
            sig_figs=sig_figs,
            printed_qtys=printed_qtys,
            **kwargs
        )

        self.check_scraping()

        # if this run is from a restart, try to load flux data up to the
        # current point
        # mwxrun.restart isn't set until mwxrun.init_run is called, so the
        # lambda function is needed here
        callbacks.installafterinit(
            lambda _=None:
            self._load_checkpoint_flux() if mwxrun.restart else None
        )

        callbacks.installafterstep(self._flux_ana)
Example #5
0
    def __init__(self,
                 injector_dict,
                 surface_dict,
                 electrode_params,
                 user_var_dict=None,
                 local_vars=None,
                 **kwargs):
        """**Initialization**: Set basic run parameters

        Call just before creating final diagnostics. This uses built-in WARP
        variables, in addition to the passed quantities, to save relevant
        parameters for diagnostic calculations and postprocessing.

        Arguments:
            injector_dict (dict): Dictionary where keys are strings for groups
                of surfaces or interaction physics (see component_labels keys
                for main possibilities) and values are injectors or lists of
                injectors.  Used by various diagnostics, so should be complete.
                ``cathode`` is a required key.
            surface_dict (dict): Dictionary where keys are groups of surfaces
                which scrape particles and values are surfaces or lists of
                surfaces.  Used by various diagnostics, so should be complete.
                ``cathode`` is a required key.
            user_var_dict (dict): A dictionary of all parameters set by user.
                This will depend on the given system being investigated.
            local_vars (dict): Instead of user_var_dict, pass locals() from the
                executing script. Then all UPPER_CASE variables will be saved to
                user_var_dict internally.
            electrode_params (dict): A dictionary of electrode parameters:

                * `CATHODE_A` (Amp/m^2/K^2)
                * `CATHODE_TEMP` (K)
                * `ANODE_TEMP` (K) (if back-emission used)
                * `ANODE_A` (Amp/m^2/K^2) (if back-emission used)
                * Additional elements if desired
            run_param_dict (dict): Optional, a dictionary of run parameters such
                as diag_timesteps and total_timesteps.
            run_file (str): Path to the main simulation file, in order to store
                it in run_param_dict for easy access later.
            area (float): Optional, cross-sectional area in m^2 to use for flux
                calculations from this simulation. If not supplied, will be
                calculated automatically from the geometry.
        """
        # Parameter dictionaries and injectors
        if local_vars is not None:
            if user_var_dict is not None:
                raise ValueError(
                    "Specify local_vars or user_var_dict, not both.")
            self.user_var_dict = self._process_local_vars(local_vars)
        else:
            self.user_var_dict = user_var_dict
        self.run_param_dict = kwargs.pop("run_param_dict", None)
        self.run_file = kwargs.pop("run_file", None)
        self.electrode_params = electrode_params

        self.init_species(mwxrun.simulation.species)

        self.init_injectors_surfaces(injector_dict, surface_dict)

        # Store cross-sectional area for flux calculations
        self.area = kwargs.pop('area', None)
        self.geom = mwxrun.geom_str
        self.pos_lims = [
            mwxrun.xmin, mwxrun.xmax, mwxrun.ymin, mwxrun.ymax, mwxrun.zmin,
            mwxrun.zmax
        ]
        self.nxyz = [mwxrun.nx, mwxrun.ny, mwxrun.nz]
        self.dt = mwxrun.get_dt()
        self.periodic = (
            #TODO: Should be implemented when needed
            #warp.w3d.boundxy is warp.periodic
            # The rwall in RZ simulations can be set even if boundxy is
            # periodic. It defaults to 1e36 so this should be fine.
            #and warp.top.prwall > 1e10
        )

        if self.area is None:
            self.area = mwxrun.get_domain_area()

        # All kwargs should have been popped, so raise an error if that isn't
        # the case.
        if len(kwargs) > 0:
            raise ValueError(f'Unrecognized kwarg(s) {list(kwargs.keys())}')

        # Shapes/injectors just adds all the lists of shapes together into one
        # list
        self.shapes = reduce((lambda x, y: x + y),
                             list(self.surface_dict.values()))
        self.injectors = reduce((lambda x, y: x + y),
                                list(self.injector_dict.values()))
        # _reserved_names ensures results dictionary will not have duplicated
        # entries that overwrite each other.
        self._reserved_names = (
            ["cathode_all", "anode_all", "aside", "aside_nodiel"] +
            [x + "_all" for x in self.component_list])
        callbacks.installafterinit(self._runinfo_after_init)
Example #6
0
    def init_grid(self,
                  lower_bound,
                  upper_bound,
                  number_of_cells,
                  use_rz=False,
                  **kwargs):
        """Function to set up the simulation grid.

        Arguments:
            lower_bound (list): Minimum coordinates for all direction; length
                of list has to equal number of dimensions.
            upper_bound (list): Maximum coordinates for all direction; length
                of list has to equal number of dimensions.
            number_of_cells (list): Number of grid cells in each direction.
            use_rz (bool): If True, cylindrical coordinates will be used.
            kwargs (dict): Dictionary containing extra arguments. These can
                include:
                - ``bc_fields_x/y/z/r_min/max``: field boundary condition
                   settings (can be 'periodic', 'dirichlet' or 'none').
                - ``bc_particles_x/y/z/r_min/max``: particle boundary
                  condition settings (can be 'periodic', 'absorbing' or
                  'reflecting').
                - ``min_tiles``: the minimum number of tiles. See function
                  ``_set_max_grid_size()`` below for details.
        """
        self.dim = len(lower_bound)

        # sanity check inputs
        assert len(upper_bound) == self.dim
        assert len(number_of_cells) == self.dim
        if use_rz and self.dim != 2:
            raise RuntimeError(
                f"Cannot use cylindrical coordinates in {self.dim} dimensions."
            )

        self._set_geom_str(use_rz)

        boundary_conditions = self._get_default_boundary_conditions()

        # update boundary conditions if provided
        for key in [key for key in kwargs.keys() if key.startswith('bc')]:
            kind, dim, side = key.split('_')[-3:]
            boundary_conditions[kind][side][self.coord_map[dim]] = kwargs[key]

        if self.geom_str == 'RZ':
            grid = picmi.CylindricalGrid
        elif self.geom_str == 'Z':
            grid = picmi.Cartesian1DGrid
        elif self.geom_str == 'XZ':
            grid = picmi.Cartesian2DGrid
        elif self.geom_str == 'XYZ':
            grid = picmi.Cartesian3DGrid

        self.grid = grid(
            number_of_cells=number_of_cells,
            lower_bound=lower_bound,
            upper_bound=upper_bound,
            lower_boundary_conditions=boundary_conditions['fields']['min'],
            upper_boundary_conditions=boundary_conditions['fields']['max'],
            lower_boundary_conditions_particles=boundary_conditions[
                'particles']['min'],
            upper_boundary_conditions_particles=boundary_conditions[
                'particles']['max'],
        )

        self._set_grid_params()
        self._set_max_grid_size(kwargs.get('min_tiles', None))
        self._print_grid_params()

        # there are a number of initialization tasks that have to happen
        # immediately after init so this function is installed as the
        # first afterinit callback
        if len(callbacks._afterinit) != 0:
            raise RuntimeError(
                "mwxrun._after_init must be the first afterinit callback to "
                "execute, but one was already installed. Please ensure "
                "init_grid is called before other items in the run script.")
        callbacks.installafterinit(self._after_init)