aboutsummaryrefslogtreecommitdiff
path: root/Examples/Physics_applications
diff options
context:
space:
mode:
Diffstat (limited to 'Examples/Physics_applications')
-rw-r--r--Examples/Physics_applications/capacitive_discharge/PICMI_inputs_2d.py187
-rwxr-xr-xExamples/Physics_applications/capacitive_discharge/analysis.py18
2 files changed, 187 insertions, 18 deletions
diff --git a/Examples/Physics_applications/capacitive_discharge/PICMI_inputs_2d.py b/Examples/Physics_applications/capacitive_discharge/PICMI_inputs_2d.py
index b597a0f35..b9bc0bb3d 100644
--- a/Examples/Physics_applications/capacitive_discharge/PICMI_inputs_2d.py
+++ b/Examples/Physics_applications/capacitive_discharge/PICMI_inputs_2d.py
@@ -1,8 +1,16 @@
# --- Input file for MCC testing. There is already a test of the MCC
-# --- functionality so this just tests the PICMI interface
+# --- functionality. This tests the PICMI interface for the MCC and
+# --- provides an example of how an external Poisson solver can be
+# --- used for the field solve step.
+
+# _libwarpx requires the geometry be set before importing
+from pywarpx import geometry
+geometry.coord_sys = 0
+geometry.prob_lo = [0, 0]
import numpy as np
-from pywarpx import picmi
+from scipy.sparse import csc_matrix, linalg as sla
+from pywarpx import picmi, callbacks, _libwarpx, fields
constants = picmi.constants
@@ -31,12 +39,12 @@ DT = 1.0 / (400 * FREQ)
##########################
# --- Number of time steps
-max_steps = 10
-diagnostic_intervals = "::5"
+max_steps = 50
+diagnostic_intervals = "::50"
# --- Grid
nx = 128
-ny = 16
+ny = 8
xmin = 0.0
ymin = 0.0
@@ -45,6 +53,162 @@ ymax = D_CA / nx * ny
number_per_cell_each_dim = [32, 16]
+#############################
+# specialized Poisson solver
+# using superLU decomposition
+#############################
+
+class PoissonSolverPseudo1D(picmi.ElectrostaticSolver):
+
+ def __init__(self, grid, **kwargs):
+ """Direct solver for the Poisson equation using superLU. This solver is
+ useful for pseudo 1D cases i.e. diode simulations with small x extent.
+
+ Arguments:
+ grid (picmi.Cartesian2DGrid): Instance of the grid on which the
+ solver will be installed.
+ """
+ super(PoissonSolverPseudo1D, self).__init__(
+ grid=grid, method=kwargs.pop('method', 'Multigrid'),
+ required_precision=1, **kwargs
+ )
+ self.rho_wrapper = None
+ self.phi_wrapper = None
+ self.time_sum = 0.0
+
+ def initialize_inputs(self):
+ """Grab geometrical quantities from the grid.
+ """
+ self.right_voltage = self.grid.potential_xmax
+
+ # set WarpX boundary potentials to None since we will handle it
+ # ourselves in this solver
+ self.grid.potential_xmin = None
+ self.grid.potential_xmax = None
+ self.grid.potential_ymin = None
+ self.grid.potential_ymax = None
+ self.grid.potential_zmin = None
+ self.grid.potential_zmax = None
+
+ super(PoissonSolverPseudo1D, self).initialize_inputs()
+
+ self.nx = self.grid.nx
+ self.nz = self.grid.ny
+ self.dx = (self.grid.xmax - self.grid.xmin) / self.nx
+ self.dz = (self.grid.ymax - self.grid.ymin) / self.nz
+
+ if not np.isclose(self.dx, self.dz):
+ raise RuntimeError('Direct solver requires dx = dz.')
+
+ self.nxguardrho = 2
+ self.nzguardrho = 2
+ self.nxguardphi = 1
+ self.nzguardphi = 1
+
+ self.phi = np.zeros(
+ (self.nx + 1 + 2*self.nxguardphi,
+ self.nz + 1 + 2*self.nzguardphi)
+ )
+
+ self.decompose_matrix()
+
+ callbacks.installpoissonsolver(self._run_solve)
+
+ def decompose_matrix(self):
+ """Function to build the superLU object used to solve the linear
+ system."""
+ self.nxsolve = self.nx + 1
+ self.nzsolve = self.nz + 3
+
+ # Set up the computation matrix in order to solve A*phi = rho
+ A = np.zeros(
+ (self.nzsolve*self.nxsolve, self.nzsolve*self.nxsolve)
+ )
+ kk = 0
+ for ii in range(self.nxsolve):
+ for jj in range(self.nzsolve):
+ temp = np.zeros((self.nxsolve, self.nzsolve))
+
+ if ii == 0 or ii == self.nxsolve - 1:
+ temp[ii, jj] = 1.
+ elif ii == 1:
+ temp[ii, jj] = -2.0
+ temp[ii-1, jj] = 1.0
+ temp[ii+1, jj] = 1.0
+ elif ii == self.nxsolve - 2:
+ temp[ii, jj] = -2.0
+ temp[ii+1, jj] = 1.0
+ temp[ii-1, jj] = 1.0
+ elif jj == 0:
+ temp[ii, jj] = 1.0
+ temp[ii, -3] = -1.0
+ elif jj == self.nzsolve - 1:
+ temp[ii, jj] = 1.0
+ temp[ii, 2] = -1.0
+ else:
+ temp[ii, jj] = -4.0
+ temp[ii, jj+1] = 1.0
+ temp[ii, jj-1] = 1.0
+ temp[ii-1, jj] = 1.0
+ temp[ii+1, jj] = 1.0
+
+ A[kk] = temp.flatten()
+ kk += 1
+
+ A = csc_matrix(A, dtype=np.float32)
+ self.lu = sla.splu(A)
+
+ def _run_solve(self):
+ """Function run on every step to perform the required steps to solve
+ Poisson's equation."""
+
+ # get rho from WarpX
+ if self.rho_wrapper is None:
+ self.rho_wrapper = fields.RhoFPWrapper(0, True)
+ self.rho_data = self.rho_wrapper[Ellipsis][:,:,0]
+
+ self.solve()
+
+ if self.phi_wrapper is None:
+ self.phi_wrapper = fields.PhiFPWrapper(0, True)
+ self.phi_wrapper[Ellipsis] = self.phi
+
+ def solve(self):
+ """The solution step. Includes getting the boundary potentials and
+ calculating phi from rho."""
+ right_voltage = eval(
+ self.right_voltage,
+ {'t':_libwarpx.libwarpx.warpx_gett_new(0), 'sin':np.sin, 'pi':np.pi}
+ )
+ left_voltage = 0.0
+
+ rho = -self.rho_data[
+ self.nxguardrho:-self.nxguardrho, self.nzguardrho:-self.nzguardrho
+ ] / constants.ep0
+
+ # Construct b vector
+ nx, nz = np.shape(rho)
+ source = np.zeros((nx, nz+2), dtype=np.float32)
+ source[:,1:-1] = rho * self.dx**2
+
+ source[0] = left_voltage
+ source[-1] = right_voltage
+
+ # Construct b vector
+ b = source.flatten()
+
+ flat_phi = self.lu.solve(b)
+ self.phi[self.nxguardphi:-self.nxguardphi] = (
+ flat_phi.reshape(np.shape(source))
+ )
+
+ self.phi[:self.nxguardphi] = left_voltage
+ self.phi[-self.nxguardphi:] = right_voltage
+
+ # the electrostatic solver in WarpX keeps the ghost cell values as 0
+ self.phi[:,:self.nzguardphi] = 0
+ self.phi[:,-self.nzguardphi:] = 0
+
##########################
# physics components
##########################
@@ -136,14 +300,13 @@ grid = picmi.Cartesian2DGrid(
bc_ymax = 'periodic',
warpx_potential_hi_x = "%.1f*sin(2*pi*%.5e*t)" % (VOLTAGE, FREQ),
lower_boundary_conditions_particles=['absorbing', 'periodic'],
- upper_boundary_conditions_particles=['absorbing', 'periodic'],
- moving_window_velocity = None,
- warpx_max_grid_size = nx//4
+ upper_boundary_conditions_particles=['absorbing', 'periodic']
)
-solver = picmi.ElectrostaticSolver(
- grid=grid, method='Multigrid', required_precision=1e-6
-)
+# solver = picmi.ElectrostaticSolver(
+# grid=grid, method='Multigrid', required_precision=1e-6
+# )
+solver = PoissonSolverPseudo1D(grid=grid)
##########################
# diagnostics
@@ -153,7 +316,7 @@ field_diag = picmi.FieldDiagnostic(
name = 'diag1',
grid = grid,
period = diagnostic_intervals,
- data_list = ['rho_electrons', 'rho_he_ions', 'phi'],
+ data_list = ['rho_electrons', 'rho_he_ions'],
write_dir = '.',
warpx_file_prefix = 'Python_background_mcc_plt'
)
diff --git a/Examples/Physics_applications/capacitive_discharge/analysis.py b/Examples/Physics_applications/capacitive_discharge/analysis.py
index f37baf912..22d1c725a 100755
--- a/Examples/Physics_applications/capacitive_discharge/analysis.py
+++ b/Examples/Physics_applications/capacitive_discharge/analysis.py
@@ -2,11 +2,17 @@
# Copyright 2021 Modern Electron
-# This script simply checks that the PICMI_inputs_2d.py run output
-# diagnostics, which confirms that the PICMI MCC interface works otherwise
-# the run would've crashed.
+# This script checks that the PICMI_inputs_2d.py run more-or-less matches the
+# results from the non-PICMI run. The PICMI run is using an external Poisson
+# solver that directly solves the Poisson equation using matrix inversion
+# rather than the iterative approach from the MLMG solver.
-import glob
+import sys
+sys.path.append('../../../../warpx/Regression/Checksum/')
-files = sorted(glob.glob('Python_background_mcc_plt*'))[1:]
-assert len(files) > 0
+import checksumAPI
+
+my_check = checksumAPI.evaluate_checksum(
+ 'background_mcc', 'Python_background_mcc_plt00050',
+ do_particles=True, rtol=7.5e-2
+)