Moving Grids¶
For problems that optimize wavepacket dynamics across large physical distances or where the maximum possible momentum \(k_{\max}\) is expected to be large, it can be advantageous to use moving grids. In this case, instead of having a static grid in both coordinate (\(r\)) and momentum (\(k\)) space (which we will refer to together as phase space), the grid follows the movement of the wavepacket. Not only does this allow one to have dynamics that take the wavepacket outside of the initially defined phase-space boundaries, but it has the additional benefit that the wavefunction has fewer \(r\)-dependent phase components, which makes its representation more efficient, and easier to handle for propagation.
Turning on moving grids¶
To actually make use of moving grids is very straight-forward. First, one must turn on the use of moving grids in the config file by adding the parameter
moveable = true
in the grid section. The alternative spelling movable
is also
accepted. For example,
grid: n = 1, system = trap
1 : rmin = -10 rmax = 10, n = 256, moveable = true
This tells the grid that, when doing propagation, it should update the
position of the grid at each time step. Once the grid moves, it is likely
that operators in coordinate and phase space will have to be refreshed,
since we have effectively changed our coordinate system. This is
achieved by using a hook function, called upd_hook
, which must be
passed to any routine that performs propagation, for example
prop_step
or oct_step
.
The upd_hook
function¶
This function should be implemented as an external procedure in the program. It should follow the interface definition
interface
subroutine info_hook(psi, grid, ham, ti, work, para)
use def_mod
type(psi_t), intent(in) :: psi
type(grid_t), intent(in) :: grid
type(ham_t), intent(in) :: ham
integer, intent(in) :: ti
type(work_t), intent(inout) :: work
type(para_t), intent(in) :: para
end subroutine
end interface
This allows a great deal of flexibility in what takes place after each
movement of the grid. Typically, we will simply want to refresh our
operators on the modified grid by calling init
again. In this case,
there is a built-in function, init_ops
, which does just that. Hence
prop_step
could be called in the following manner:
prop_step(psi, grid, ham, work, para, ti, bw, pulses, upd_hook=init_ops)
For more detail on the exact implementation, we refer the reader to the
example moving_grid
in the examples folder.
Implementation¶
The implementation of the moving grid can be divided into two parts: the movement of the grid in coordinate space, and the movement in momentum space. Both these transformations are handled differently.
Movement in r¶
The implementation of the moving grid in \(r\) is very simple. At each time step, we calculate the expectation value of the position operator for the wavefunction \(\psi(t)\);
This defines the “middle” of the potential, which we assume should correspond to the center of the \(r\)-grid. We then calculate the difference between \(\avg{r}\) and the actual \(r\)-grid center
where \(r_{\max}\) and \(r_{\min}\) are the boundaries of the current \(r\)-grid.
Some \(r\)-dependent operators, such as potentials, will be defined externally in a data file. Usually, this data is defined as pairs of points, which describe the value of the potential at a given position. In order to preserve the alignment of the grid to this data, we restrict ourselves to only moving the \(r\)-grid by an integer multiple of the grid spacing \(dr\). Hence we round \(d_r\) to the nearest multiple of \(dr\), which gives us \(\tilde{d}\_r\). Dividing by \(dr\), we return an integer, \(i_r\).
We now have the number of grid-spacings \(i_r\) between the current \(r\)-grid position, and its goal position \(\tilde{d}\_r\). We then remove this many points from one side of the wavefunction, and add grid points to the other side (with a value of 0). This effectively translates the wavefunction. To complete the grid transformation, we add \(\tilde{d}\_r\) to each point of the grid. This transformation is depicted in the following figure.
Movement in k¶
Since the \(k\)-grid has only one operator on it, namely the kinetic operator \(\hbar^2 k^2/2 m\), and this operator is analytical, we aren’t constrained to moving the \(k\)-grid by integer multiples of the \(k\)-grid spacing \(dk\). Hence we follow a slightly different procedure.
Any translation in momentum space can be represented as a phase in coordinate space:
where \(\tilde{\psi}(k)\) is the wavefunction in momentum space. At the first propagation step, we calculate the expectation value of the momentum operator
and store this value as \(k_0\). We then multiply the wavefunction in coordinate space such that
and then set the wavefunction \(\psi(x,t) = \psi^\prime(x,t)\). We then do the propagation, making sure that we apply the momentum operator on the shifted \(k\)-grid; in other words, the momentum operator becomes \(\hat{T}\frac{\hbar^2}{2m}\left(k - k_0\right)^2\).