Current solver

EPOCH uses the Villasenor and Buneman current calculating scheme which solves the additional equation ${\partial \rho}/{\partial t} = \nabla\cdot\vec{J}$ to calculate the current at each timestep. The main advantage of this scheme is that it conserves charge on the grid rather than just globally conserving charge on the particles. This means that the error in the solution to Gauss’s law is conserved, so if Gauss’s law is satisfied for $t = 0$ it remains satisfied for all time.

The Villasenor and Buneman scheme works because exactly the same charge added to one cell is subtracted from another cell, which in turn means that exactly the same current added to one cell is subtracted from another cell. This is intuitively correct since a point particle crossing a cell boundary would represent the loss of that particle’s contribution to the current from the source cell and the gain of that particle’s contribution to the current by the destination cell. In fact this simple type of cell boundary crossing current calculation was used in classical Buneman type PIC codes.

The scheme is messy, in practise, but simple. After the main particle push, the particle is advanced a further half timestep into the future to first order using the velocities calculated at the end of the particle push. The particle position at $t + dt/2$ were stored earlier, and combined with the newly calculated particle position at $t + {3dt}/{2}$ this allows a time centred evaluation of ${\partial \rho}/{\partial t}$ meaning that the current update is second order accurate in time. The spatial order of the scheme matches the spatial order of the particle weight function.

The weight functions for transferring particle properties onto the grid at the two timesteps are calculated including a shift when necessary to allow for the particle having crossed a cell boundary. Since the charge associated with the particle is spatially distributed using the weight function, all that is necessary to calculate ${\partial \rho}/{\partial t}$ is to subtract the two functions, multiply by the charge on the pseudoparticle and the pseudoparticle weight and finally divide by $dt$. The spatial derivative of $\vec{J}$ is then converted to a one sided finite difference form and solved directly. In multiple dimensions this is slightly complicated by the effects of offsets in directions other than the direction that a given current component is pointing in, with this adding additional weight factors based on the overlap of the shape functions in other directions. This is explained in full in the Villasenor and Buneman paper already quoted.

Currents in ignorable directions are simply calculated using $J = n\rho\vec{v}$ with the correct shape functions to ensure that the current is placed in the correct places.

Example

In EPOCH2D V4.19.2, the source-code for the current update looks like this:

  jyh = 0.0_num
    DO iy = ymin, ymax
      cy = cell_y1 + iy
      yfac1 =         gy(iy) + 0.5_num * hy(iy)
      yfac2 = third * hy(iy) + 0.5_num * gy(iy)

      hy_iy = hy(iy)

      jxh = 0.0_num
      DO ix = xmin, xmax
        cx = cell_x1 + ix
        xfac1 = gx(ix) + 0.5_num * hx(ix)

        wx = hx(ix) * yfac1
        wy = hy_iy  * xfac1
        wz = gx(ix) * yfac1 + hx(ix) * yfac2

        ! This is the bit that actually solves d(rho)/dt = -div(J)
        jxh = jxh - fjx * wx
        jyh(ix) = jyh(ix) - fjy * wy
        jzh = fjz * wz

        jx(cx, cy) = jx(cx, cy) + jxh
        jy(cx, cy) = jy(cx, cy) + jyh(ix)
        jz(cx, cy) = jz(cx, cy) + jzh
      END DO
    END DO

At this point in the code, gx and gy contain the weight distribution of the macro-particle at time $t+dt/2$, where the 0 index in these arrays correspond to the cell containing the macro-particle centre at this time (or the low-x, low-y corner for TOPHAT shapes). The hx and hy parameters contain the difference of weights in each cell between $t+3dt/2$ and $t+dt/2$. The loop occurs from gx index xmin to xmax, and gy index ymin to ymax. These min/max indices will describe an array which has the same size as the number of cells which the macro-particle shape has touched over the time-step.

As an example, consider the $j_x$ update. For cell index ix=xmin, we first calculate the average y-weight for each iy using the line:

      yfac1 =         gy(iy) + 0.5_num * hy(iy)

as gy(iy) is the initial y-weight, and gy(iy) + hy(iy) is the final y-weight. We assume the macro-particle moves at a constant speed when taking the average. Hence, the change in macro-particle weight due to motion in the $x$ direction from the ix=xmin cell is hx(xmin) * (gy(iy) + 0.5*hy(iy)). In the code, this is wx. We can multiply this by the macro-particle charge (charge * weight) to get the charge change due to $x$ motion, divide by dt to get a current, and divide by dy to get the current per unit area (as dz = 1m in EPOCH2D). These particle variables and simulation constants are contained in the fjx variable.

Finally, we must remember that this refers to the total current density change in the cell - we do not know the boundary this current flows through. In the xmin cell, we know no macro-particle weight enters xmin-1 by definition of xmin, so all the current density flows into the cell with ix=xmin+1. Hence, the current change is unambiguous here. If there is no current change in xmin+1, then an equal current must flow in and out. We have just calculated the xmin to xmin+1 current, so we can subtract this from the new cell to determine the current on the next boundary. Because we need to remember the current from the previous calculation, we must subtract jxh from the previously calculated jxh in:

jxh = jxh - fjx * wx

This calculation may then iterate through the particle shape, until the $j_x$ contribution from this macro-particle is recorded in all cells it influences. The remaining current density components can be calculated using similar logic.

Previous
Next