aboutsummaryrefslogtreecommitdiff
path: root/Python/pywarpx/PICMI.py
blob: b1711926ff7c607d3f813936c2d93e239d4a901a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
"""Classes following the PICMI standard
"""
from PICMI_Base import *
import numpy as np
from pywarpx import *

codename = 'WarpX'

class Grid(PICMI_Grid):
    def init(self, **kw):

        amr.n_cell = [self.nx, self.ny, self.nz]

        # Maximum allowable size of each subdomain in the problem domain;
        #    this is used to decompose the domain for parallel calculations.
        amr.max_grid_size = kw.get('max_grid_size', 32)

        # Maximum level in hierarchy (for now must be 0, i.e., one level in total)
        amr.max_level = kw.get('max_level', 0)

        # Geometry
        geometry.coord_sys = kw.get('coord_sys', 0)  # 0: Cartesian
        geometry.is_periodic = '%d %d %d'%(self.bcxmin=='periodic', self.bcymin=='periodic', self.bczmin=='periodic')  # Is periodic?
        geometry.prob_lo = [self.xmin, self.ymin, self.zmin]  # physical domain
        geometry.prob_hi = [self.xmax, self.ymax, self.zmax]

        if self.moving_window_velocity is not None and np.any(self.moving_window_velocity != 0):
            warpx.do_moving_window = 1
            if self.moving_window_velocity[0] != 0.:
                warpx.moving_window_dir = 'x'
                warpx.moving_window_v = self.moving_window_velocity[0]/clight  # in units of the speed of light
            if self.moving_window_velocity[1] != 0.:
                warpx.moving_window_dir = 'y'
                warpx.moving_window_v = self.moving_window_velocity[1]/clight  # in units of the speed of light
            if self.moving_window_velocity[2] != 0.:
                warpx.moving_window_dir = 'z'
                warpx.moving_window_v = self.moving_window_velocity[2]/clight  # in units of the speed of light

    def getmins(self, **kw):
        return np.array([warpx.getProbLo(0), warpx.getProbLo(1), warpx.getProbLo(2)])

    def getmaxs(self, **kw):
        return np.array([warpx.getProbHi(0), warpx.getProbHi(1), warpx.getProbHi(2)])

    def getxmin(self):
        return warpx.getProbLo(0)
    def getxmax(self):
        return warpx.getProbHi(0)
    def getymin(self):
        return warpx.getProbLo(1)
    def getymax(self):
        return warpx.getProbHi(1)
    def getzmin(self):
        return warpx.getProbLo(2)
    def getzmax(self):
        return warpx.getProbHi(2)


class EM_solver(PICMI_EM_solver):
    def init(self, **kw):

        if self.current_deposition_algo is not None:
            algo.current_deposition = self.current_deposition_algo
        if self.charge_deposition_algo is not None:
            algo.charge_deposition = self.charge_deposition_algo
        if self.field_gathering_algo is not None:
            algo.field_gathering = self.field_gathering_algo
        if self.particle_pusher_algo is not None:
            algo.particle_pusher = self.particle_pusher_algo


class Gaussian_laser(PICMI_Gaussian_laser):
    def init(self, **kw):

        warpx.use_laser = 1
        laser.profile = "Gaussian"
        laser.wavelength = self.wavelength  # The wavelength of the laser (in meters)
        laser.e_max = self.E0  # Maximum amplitude of the laser field (in V/m)
        laser.polarization = [np.cos(self.pol_angle), np.sin(self.pol_angle), 0.]  # The main polarization vector
        laser.profile_waist = self.waist  # The waist of the laser (in meters)
        laser.profile_duration = self.duration  # The duration of the laser (in seconds)
        laser.profile_t_peak = (self.focal_position - self.z0)/clight  # The time at which the laser reaches its peak (in seconds)


class Laser_antenna(PICMI_Laser_antenna):
    def init(self, **kw):

        laser.position = [self.antenna_x0, self.antenna_y0, self.antenna_z0]  # This point is on the laser plane
        laser.direction = [self.antenna_xvec, self.antenna_yvec, self.antenna_zvec]  # The plane normal direction
        laser.profile_focal_distance = self.laser.focal_position - self.antenna_z0  # Focal distance from the antenna (in meters)


class Species(PICMI_Species):
    def init(self, **kw):

        self.species_number = particles.nspecies
        particles.nspecies = particles.nspecies + 1
        if particles.species_names is None:
            particles.species_names = self.name
        else:
            particles.species_names = particles.species_names + ' ' + self.name

        self.bucket = Bucket.Bucket(self.name, mass=self.mass, charge=self.charge, injection_style = 'python')
        Particles.particles_list.append(self.bucket)

    def add_particles(self, n=None,
                      x=None, y=None, z=None,
                      ux=None, uy=None, uz=None, w=None,
                      unique_particles=None, **kw):
        pid = np.array([w]).T
        add_particles(self.species_number, x, y, z, ux, uy, uz, pid, unique_particles)


class GaussianBeam(PICMI_GaussianBeam):
    def init(self, **kw):

        self.species.bucket.injection_style = "gaussian_beam"
        self.species.bucket.x_m = self.Xmean
        self.species.bucket.y_m = self.Ymean
        self.species.bucket.z_m = self.Zmean
        self.species.bucket.x_rms = self.Xrms
        self.species.bucket.y_rms = self.Yrms
        self.species.bucket.z_rms = self.Zrms
        self.species.bucket.npart = self.number_sim_particles
        self.species.bucket.q_tot = self.number_sim_particles*self.species.charge

        # --- These are unused but need to be set (maybe)
        self.species.bucket.profile = 'constant'
        self.species.bucket.density = 1

        # --- Momentum distribution
        if 'u_over_r' in kw:
            # --- Radial expansion
            self.species.bucket.momentum_distribution_type = "radial_expansion"
            self.species.bucket.u_over_r = kw['u_over_r']

        elif self.UXrms == 0. and self.UYrms == 0. and self.UZrms == 0.:
            # --- Constant velocity
            self.species.bucket.momentum_distribution_type = "constant"
            self.species.bucket.ux = self.UXmean
            self.species.bucket.uy = self.UYmean
            self.species.bucket.uz = self.UZmean

        else:
            # --- Gaussian velocity distribution
            self.species.bucket.momentum_distribution_type = "gaussian"
            self.species.bucket.ux_m = self.UXmean
            self.species.bucket.uy_m = self.UYmean
            self.species.bucket.uz_m = self.UZmean
            self.species.bucket.u_th = self.UZrms
            # !!! UXrms and UYrms are unused. Only an isotropic distribution is supported
            # !!! Maybe an error should be raised


class Plasma(PICMI_Plasma):
    def init(self, **kw):

        for species in self.species:
            species.bucket.injection_style = "NUniformPerCell"
            species.bucket.xmin = self.xmin
            species.bucket.xmax = self.xmax
            species.bucket.ymin = self.ymin
            species.bucket.ymax = self.ymax
            species.bucket.zmin = self.zmin
            species.bucket.zmax = self.zmax

            species.bucket.profile = 'constant'
            species.bucket.density = self.density

            if self.number_per_cell is not None:
                species.bucket.nrandompercell = self.number_per_cell
            elif self.number_per_cell_each_dim is not None:
                species.bucket.num_particles_per_cell_each_dim = self.number_per_cell_each_dim

            # --- Momentum distribution
            if 'u_over_r' in kw:
                # --- Radial expansion
                species.bucket.momentum_distribution_type = "radial_expansion"
                species.bucket.u_over_r = kw['u_over_r']

            elif self.vthx == 0. and self.vthy == 0. and self.vthz == 0.:
                # --- Constant velocity
                species.bucket.momentum_distribution_type = "constant"
                species.bucket.ux = self.vxmean
                species.bucket.uy = self.vymean
                species.bucket.uz = self.vzmean

            else:
                # --- Gaussian velocity distribution
                species.bucket.momentum_distribution_type = "gaussian"
                species.bucket.ux_m = self.vxmean
                species.bucket.uy_m = self.vymean
                species.bucket.uz_m = self.vzmean
                species.bucket.u_th = self.vthz
                # !!! vthx and vthy are unused. Only an isotropic distribution is supported
                # !!! Maybe an error should be raised


class ParticleList(PICMI_ParticleList):
    def init(self, **kw):

        assert len(self.x) == 1, "WarpX only supports initializing with a single particle"

        self.species.bucket.injection_style = "SingleParticle"
        self.species.bucket.single_particle_pos = [self.x[0], self.y[0], self.z[0]]
        self.species.bucket.single_particle_vel = [self.ux[0]/clight, self.uy[0]/clight, self.uz[0]/clight]
        self.species.bucket.single_particle_weight = self.weight


class Simulation(PICMI_Simulation):
    def set_warpx_attr(self, warpx_obj, attr, kw):
        value = kw.get(attr, None)
        if value is not None:
            setattr(warpx_obj, attr, value)
            setattr(self, attr, value)

    def init(self, **kw):

        warpx.verbose = self.verbose
        warpx.cfl = self.timestep_over_cfl
        if self.timestep == 0.:
            warpx.cfl = self.timestep_over_cfl
        else:
            warpx.const_dt = self.timestep
        amr.plot_int = self.plot_int

        self.initialized = False

    def initialize(self, inputs_name=None):
        if not self.initialized:
            self.initialized = True
            warpx.init()

    def write_inputs(self, inputs_name='inputs'):
        kw = {}
        if hasattr(self, 'max_step'):
            kw['max_step'] = self.max_step
        warpx.write_inputs(inputs_name, **kw)

    def step(self, nsteps=None):
        self.initialize()
        if nsteps is None:
            if self.max_step is not None:
                nsteps = self.max_step
            else:
                nsteps = -1
        warpx.evolve(nsteps)

    def finalize(self):
        warpx.finalize()