The mesh types aim at storing array data, but including additional information needed to put this data in context. There are several types of meshes:
cartesian mesh : an N-D fully regular mesh defined by an N-D data array and additional parameters that define the domain (i.e.: xmin, xmax) and other parameters like the number of cells in each dimension and the spacing of the data (i.e.: delta). The services provided should be like (with corresponding name changes, indicating the dimensionality of the data):
- allocation(new) and initialization functions,
- deletion,
- a copy constructor,
- computation of data interpolants (cubic splines for example),
- `get_value(mesh,x1,x2,...)`: where `x1`, `x2`,\... belong to the
corresponding interval `(x1_min, x1_max)`, `(x2_min, x2_max)`,
etc., and which returns the interpolated value at the desired
point. This operation launches spline interpolations under the
hood.
- `get_node_value(mesh,i,j,...)`: analogous to `get_value()` but
does not need to launch any interpolation as the indices are
integers and the requested value falls on a mesh node. This is
implemented by a macro.
- `set_node_value(mesh,i,j,...,val)`: sets the node datum at
i,j,\... with value. Implemented with a macro.
There are some pitfalls with the suggested interface:
-
the naming convention could get a little complicated depending on the type of data stored in the array (scalar, multiple-valued, etc.), as we had originally intended with the field/vec naming convention.
-
The interface may need to directly expose its underlying data, or pointers to sections of it for certain operations, like FFT. The whole data field of a mesh could need to be set to a whole data array in one step, during the initialization, such as after a remap operation. At least in these cases, the access to the data might be safer given the nature of the operations.
structured mesh
: a structured mesh is a mapped cartesian mesh in which the
coordinates are decoupled. An ND mesh data is stored as an ND array.
In addition, however, we need N 1D arrays to store the actual
coordinates of the node locations in each dimension. As an
alternative, we could use N functions
- allocation, initialization, deletion and copy.
- `get_node_value(mesh, i, j, ... )`: which reads directly from
the data array.
- `set_node_value(mesh, i, j, ... )`: which writes directly to the
data array.
- In an analogous way to the `get_value()` functions described
above, we could provide something like `get_value(mesh,`
$\eta_1$`, `$\eta_2$`)`. This would also trigger an
interpolation step using uniform splines generated with the ND
data. This would require the user to be *thinking* in terms of
the logical variables $\eta_i$.
- Similar functions could be provided such that the user could
also request values at points $x_i$. For this, we could launch
non-uniform splines, with the spacing determined by the $x_i$
arrays and the ND data.
- This type may also need to grant direct access to the data array
for use in operations like FFT and similar.
tensor product mesh
: This is a mapped cartesian mesh in which the coordinates are
coupled. That is, the mappings have the form:
- allocation, initialization, deletion and copy.
- `get_node_value(mesh, i, j, ... )`: which reads directly from
the data array.
- `set_node_value(mesh, i, j, ... )`: which writes directly to the
data array.
- `get_value(mesh,` $\eta_1$`, `$\eta_2$`, ... )` which would also
trigger uniform spline interpolations, and
- `get_value(mesh, x1, x2, ... )`, which would trigger nonuniform
spline interpolations.
We may need to include other operations here which would entail
inverse mappings (maybe also with splines), or NURBS or something
else. Need to fill in the details more here.
The physical quantities of interest are normally defined in a physical
space. For instance, the electric potential
(place picture of an example coordinate transformation here.)
Consider a scalar field and the services that it should offer to permit us to use it in our logical grid:
-
The scalar field is a derived type.
-
The type contains a simple array to store the data, i.e.: the values of the fields on a collection of points. Both, logical mesh values or physical mesh values can be stored in the same array.
-
The type contains an object that fully specifies the associated coordinate transformation (a mapped mesh). The transformation should provide all the needed services to permit moving the representation of the data from the physical space to the logical space and vice-versa:
- something
- something else
Here we present a simplified but hopefully reasonably clear step-by-step derivation of the quasi-neutral equation (the gyrokinetic Poisson equation) which this module solves. The intent is to make clear some of the assumptions that are built into this model. We follow the general argument given by Krommes (cite reference here for "Nonlinear gyrokinetics: a powerful tool for the description of microturbulence in magnetized plasma").
The model is based on the idea that the fast gyrations of particles around the magnetic field lines can be averaged away while still preserving the most important long-term physics (hence the name of this type of approach: gyrokinetics). The simplest model that we will look into first deals directly in the distribution function of the gyrocenters, instead of the particles'.
We start with the Poisson equation:
where
$$ -\nabla ^2 \phi = \frac{1}{\epsilon_0}(\rho^G_i + \rho^{pol}_i - \rho^G_e - \rho^{pol}_e), $$ (poisson_2)
where the indices
Here
$$ \frac{\partial \rho^{pol} }{\partial t} = - \nabla \cdot \vec{j}^{pol} = - \nabla \cdot (nZe \vec{v}^{pol})=- \nabla \cdot \bigg( n Ze \frac{1}{\omega_{ci}B}\frac{\partial \vec{E}_{\perp}}{\partial t}\bigg) . $$ (rho_pol_continuity)
Here,
$$ \rho^{pol}(\vec{x}) = \nabla \cdot \bigg( \frac{n_i(\vec{x})Ze}{\omega_{ci}(\vec{x}) B(\vec{x}) }\nabla_{\perp}\phi(\vec{x},t) \bigg). $$ (rho_pol)
Here we have introduced the assumption of rho_pol
, we can consider that the differential
operators act only in the directions perpendicular to the magnetic
field, as these are the only terms that will survive the taking of the
divergence. Finally, by multiplying by the proper unit factors and using
the relation
we can recast equation {eq}rho_pol
into
$$ \rho^{pol}(\vec{x}) = \epsilon_0 \nabla_{\perp} \cdot \big( \varepsilon^G(\vec{x})\nabla_{\perp}\phi(\vec{x},t) \big). $$ (rho_pol_epsilon) where
is called the dielectric constant of the gyrokinetic vacuum. With
equation
{eq}rho_pol_epsilon
, and neglecting the polarization of the
electrons, the modified Poisson equation {eq}poisson_2
can be written as:
In fusion applications,
The charge density of the electrons also receives a special treatment. The basic assumption here is that the electrons can move very quickly along a magnetic field line and thus are able to rapidly react to electric potential variations through changes in the electron particle density. Thus, it is assumed that along a field line, the electrons obey the Boltzmann relation:
$$ n_e(\vec{x},t) = \bar{n}e(\vec{x},0) \exp \bigg(\frac{e}{k_BT_e}(\phi(\vec{x},t) - <\phi(\vec{x},t)>{\ell})\bigg). $$ (boltzmann)
In equation {eq}boltzmann
,
$$ n_e(\vec{x},t) = \bar{n}e(\vec{x},0) \bigg(1+\frac{e}{k_BT_e}(\phi(\vec{x},t) - <\phi(\vec{x},t)>{\ell})\bigg). $$ (boltzmann_linear) At the risk of being too sloppy, we will equate the electron particle density with the electron gyrocenter density. A more careful step would involve gyroaveraging directly the original electron distribution function. With this assumption, our modified poisson becomes:
$$
- \nabla_{\perp} \cdot \big(\varepsilon ^G(\vec{x},t)\nabla_{\perp} \phi(\vec{x},t) \big) = \frac{1}{\epsilon_0}\bigg(\rho^G_i - \bar{\rho}{e0}^G\Big(1+\frac{e}{k_BT_e}(\phi(\vec{x},t) - <\phi(\vec{x},t)>{\ell})\Big)\bigg). $$
Rearranging terms and using the relation:
$$ \lambda_D=\bigg(\frac{\epsilon_0k_BT_e}{ne^2}\bigg)^\frac{1}{2}=\bigg(\frac{\epsilon_0k_BT_e}{\rho e}\bigg)^\frac{1}{2}, $$ (debye_length)
we arrive at:
$$
- \nabla_{\perp} \cdot \big(\varepsilon ^G(\vec{x},t)\nabla_{\perp} \phi(\vec{x},t) \big)+\frac{1}{\bar{\lambda}{D0}} (\phi(\vec{x},t) - <\phi(\vec{x},t)>{\ell})= \frac{1}{\epsilon_0}\Big(\rho^G_i - \bar{\rho}_{e0}^G\Big). $$ (gk_poisson)
But for a normalization of the variables, equation
{eq}gk_poisson
is virtually the same as the one stated in
the report by Latu (list here reference for "Scalable Quasineutral
solver for gyrokinetic simulation"). Equation
{eq}gk_poisson
is not yet ready to solve due to the
presence of the
$$
- \nabla_{\perp} \cdot \big(<\varepsilon ^G(\vec{x},t)\nabla_{\perp} \phi(\vec{x},t)>{\ell} \big)= \frac{1}{\epsilon_0}(<\rho^G_i>{\ell} - <\bar{\rho}{e0}^G>{\ell}). $$ (gk_poisson_aux)
By making two final assumptions: that the quantities involved in the
calculation of
$$
- \nabla_{\perp} \cdot \big(\varepsilon ^G\nabla_{\perp} <\phi(\vec{x},t)>{\ell} \big) = \frac{1}{\epsilon_0}(<\rho^G_i>{\ell} - <\bar{\rho}{e0}^G>{\ell}). $$
The average ion charge density can be computed from
Once we calculate gk_poisson_aux
we can use this to solve the final
version of our quasi neutral equation, after introducing all the
assumptions:
$$
- \nabla_{\perp} \cdot \big(\bar{\varepsilon}^G(\vec{x},0)\nabla_{\perp} \phi(\vec{x},t) \big)+\frac{1}{\bar{\lambda}D} (\phi(\vec{x},t) - <\phi(\vec{x},t)>{\ell})= \frac{1}{\epsilon_0}(\rho^G_i - \bar{\rho}_{i0}^G). $$ (gk_poisson_final)
Fundamental type: None. It is a function that operates on other top-level types. Function:
sll_solve_quasi_neutral_equation( electron_T_profile_2D,
initial_rho_ion_profile_2D,
charge_density,
phi )
Fundamental type:
sll_advection_field_3D_t
This implies that one of the options is to have multiple representations, for 3D, 2D, 1D.
Fundamental type: None. This is a function that operates on multiple top-level types. Function:
sll_advect( distribution_function,
advection_field,
dt,
space_mesh
scheme )
Above, scheme
is the functional parameterization of the various
methods in use (PSM, BSL, ...) and for which we need a standardized
interface. The above assumes that we can devise a standard functional
interface.