"Particle and FEEC variables are updated."
import logging
from dataclasses import dataclass
from typing import Literal
import cunumpy as xp
from feectools.ddm.mpi import mpi as MPI
from feectools.linalg.solvers import inverse
from line_profiler import profile
from struphy.feec import preconditioner
from struphy.io.options import LiteralOptions, OptionsBase
from struphy.linear_algebra.solver import DiscreteGradientSolverParameters, SolverParameters
from struphy.models.variables import FEECVariable, PICVariable
from struphy.ode.utils import ButcherTableau
from struphy.pic import utilities_kernels
from struphy.pic.accumulation import accum_kernels_gc
from struphy.pic.accumulation.filter import FilterParameters
from struphy.pic.accumulation.particles_to_grid import Accumulator, AccumulatorVector
from struphy.pic.pushing import pusher_kernels_gc
from struphy.propagators.base import Propagator
from struphy.utils.pyccel import Pyccelkernel
from struphy.utils.utils import check_option
logger = logging.getLogger("struphy")
[docs]
class CurrentCoupling5DGradB(Propagator):
r""":ref:`FEEC <gempic>` discretization of the following equations:
find :math:`\mathbf U \in \{H(\textnormal{curl}), H(\textnormal{div}), (H^1)^3\}` and :math:`\mathbf B \in H(\textnormal{div})` such that
.. math::
\left\{
\begin{aligned}
\int \rho_0 &\frac{\partial \tilde{\mathbf U}}{\partial t} \cdot \mathbf V \, \textnormal{d} \mathbf x = - \frac{A_\textnormal{h}}{A_b} \iint \mu \frac{f^\text{vol}}{B^*_\parallel} (\mathbf b_0 \times \nabla B_\parallel) \times \mathbf B \cdot \mathbf V \,\textnormal{d} \mathbf x \textnormal{d} v_\parallel \textnormal{d} \mu \quad \forall \,\mathbf V \in \{H(\textnormal{curl}), H(\textnormal{div}), (H^1)^3\}\,,
\\
&\frac{\partial f}{\partial t} = \frac{1}{B^*_\parallel} \left[ \mathbf b_0 \times (\tilde{\mathbf U} \times \mathbf B) \right] \cdot \nabla f\,.
\end{aligned}
\right.
:ref:`time_discret`: Explicit ('rk4', 'forward_euler', 'heun2', 'rk2', 'heun3').
.. math::
\begin{bmatrix}
\dot{\mathbf u}\\ \dot{\mathbf H}
\end{bmatrix}
=
\begin{bmatrix}
0 & (\mathbb{M}^{2,n})^{-1} \mathbb{L}² \frac{1}{\bar{\sqrt{g}}} \frac{1}{\bar B^{*0}_\parallel}\bar{B}^\times_f \bar{G}^{-1} \bar{b}^\times_0 \bar{G}^{-1}
\\
-\bar{G}^{-1} \bar{b}^\times_0 \bar{G}^{-1} \bar{B}^\times_f \frac{1}{\bar B^{*0}_\parallel} \frac{1}{\bar{\sqrt{g}}} (\mathbb{L}²)^\top (\mathbb{M}^{2,n})^{-1} & 0
\end{bmatrix}
\begin{bmatrix}
\mathbb M^{2,n} \mathbf u
\\
\frac{A_\textnormal{h}}{A_b} \bar M \bar W \overline{\nabla B}_\parallel
\end{bmatrix} \,.
For the detail explanation of the notations, see `2022_DriftKineticCurrentCoupling <https://gitlab.mpcdf.mpg.de/struphy/struphy-projects/-/blob/main/running-projects/2022_DriftKineticCurrentCoupling.md?ref_type=heads>`_.
"""
[docs]
class Variables:
"""Container for variables advanced by :class:`CurrentCoupling5DGradB`.
Attributes
----------
u : FEECVariable
Fluid-like FEEC variable in one of ``"Hcurl"``, ``"Hdiv"``, or
``"H1vec"``.
energetic_ions : PICVariable
Energetic-ion particle variable in ``"Particles5D"`` space.
"""
def __init__(self):
self._u: FEECVariable = None
self._energetic_ions: PICVariable = None
@property
def u(self) -> FEECVariable:
return self._u
@u.setter
def u(self, new):
assert isinstance(new, FEECVariable)
assert new.space in ("Hcurl", "Hdiv", "H1vec")
self._u = new
@property
def energetic_ions(self) -> PICVariable:
return self._energetic_ions
@energetic_ions.setter
def energetic_ions(self, new):
assert isinstance(new, PICVariable)
assert new.space == "Particles5D"
self._energetic_ions = new
def __init__(self, b_tilde: FEECVariable = None):
"""
Parameters
----------
b_tilde : FEECVariable, default=None
Magnetic perturbation 1-form (``"Hcurl"`` space) entering the grad-B
coupling term. If ``None``, only the equilibrium field is used.
"""
self.variables = self.Variables()
self.b_tilde = b_tilde
[docs]
@dataclass(repr=False)
class Options(OptionsBase):
"""Configuration options for :class:`CurrentCoupling5DGradB`.
Parameters
----------
ep_scale : float, default=1.0
Scaling factor applied in particle accumulation.
algo : {"discrete_gradient", "explicit"}, default="explicit"
Time integration algorithm.
- ``"explicit"``: explicit Runge-Kutta update with ``butcher``.
- ``"discrete_gradient"``: nonlinear discrete-gradient update.
butcher : ButcherTableau, default=None
Butcher tableau used in explicit mode. If ``None`` and
``algo="explicit"``, defaults to ``ButcherTableau()``.
u_space : LiteralOptions.OptsVecSpace, default="Hdiv"
FEEC space used for the unknown ``u`` variable.
solver : LiteralOptions.OptsSymmSolver, default="pcg"
Symmetric iterative solver used for mass-matrix inversions.
precond : LiteralOptions.OptsMassPrecond, default="MassMatrixPreconditioner"
Preconditioner used with the mass matrix.
solver_params : SolverParameters, default=None
Iterative-solver controls. If ``None``, defaults to
``SolverParameters()``.
filter_params : FilterParameters, default=None
Particle-to-grid filtering parameters used by the accumulator.
If ``None``, defaults to ``FilterParameters()``.
dg_solver_params : DiscreteGradientSolverParameters, default=None
Nonlinear solver controls for discrete-gradient mode. If ``None``
and ``algo="discrete_gradient"``, defaults to
``DiscreteGradientSolverParameters()``.
"""
# specific literals
OptsAlgo = Literal[
"discrete_gradient",
"explicit",
]
# propagator options
ep_scale: float = 1.0
algo: OptsAlgo = "explicit"
butcher: ButcherTableau = None
u_space: LiteralOptions.OptsVecSpace = "Hdiv"
solver: LiteralOptions.OptsSymmSolver = "pcg"
precond: LiteralOptions.OptsMassPrecond = "MassMatrixPreconditioner"
solver_params: SolverParameters = None
filter_params: FilterParameters = None
dg_solver_params: DiscreteGradientSolverParameters = None
def __post_init__(self):
# checks
check_option(self.algo, self.OptsAlgo)
check_option(self.u_space, LiteralOptions.OptsVecSpace)
check_option(self.solver, LiteralOptions.OptsSymmSolver)
check_option(self.precond, LiteralOptions.OptsMassPrecond)
assert isinstance(self.ep_scale, float)
# defaults
if self.algo == "explicit" and self.butcher is None:
self.butcher = ButcherTableau()
if self.algo == "discrete_gradient" and self.dg_solver_params is None:
self.dg_solver_params = DiscreteGradientSolverParameters()
if self.solver_params is None:
self.solver_params = SolverParameters()
if self.filter_params is None:
self.filter_params = FilterParameters()
@property
def options(self) -> Options:
if not hasattr(self, "_options"):
self._options = self.Options()
return self._options
@options.setter
def options(self, new):
assert isinstance(new, self.Options)
self._options = new
logger.info(f"\nNew options for propagator '{self.__class__.__name__}':\n{self._options}")
@profile
def allocate(self):
if self.options.u_space == "H1vec":
self._u_form_int = 0
else:
self._u_form_int = int(self.derham.space_to_form[self.options.u_space])
# call operatros
id_M = "M" + self.derham.space_to_form[self.options.u_space] + "n"
self._A = getattr(self.mass_ops, id_M)
self._PB = getattr(self.basis_ops, "PB")
# Preconditioner
if self.options.precond is None:
pc = None
else:
pc_class = getattr(preconditioner, self.options.precond)
pc = pc_class(getattr(self.mass_ops, id_M))
# linear solver
self._A_inv = inverse(
self._A,
self.options.solver,
pc=pc,
tol=self.options.solver_params.tol,
maxiter=self.options.solver_params.maxiter,
verbose=self.options.solver_params.verbose,
)
# magnetic equilibrium field
unit_b1 = self.projected_equil.unit_b1
curl_unit_b1 = self.projected_equil.curl_unit_b1
self._b2 = self.projected_equil.b2
gradB1 = self.projected_equil.gradB1
absB0 = self.projected_equil.absB0
# scaling factor
epsilon = self.variables.energetic_ions.species.equation_params.epsilon
if self.options.algo == "explicit":
# temporary vectors to avoid memory allocation
self._b_full = self._b2.space.zeros()
self._u_new = self.variables.u.spline.vector.space.zeros()
self._u_temp = self.variables.u.spline.vector.space.zeros()
self._ku = self.variables.u.spline.vector.space.zeros()
self._PB_b = self._PB.codomain.zeros()
self._grad_PB_b = self.derham.grad.codomain.zeros()
# define Accumulator and arguments
self._ACC = Accumulator(
self.variables.energetic_ions.particles,
self.options.u_space,
Pyccelkernel(accum_kernels_gc.cc_lin_mhd_5d_gradB),
self.mass_ops,
self.domain.args_domain,
add_vector=True,
symmetry="symm",
filter_params=self.options.filter_params,
)
self._args_accum_kernel = (
epsilon,
self.options.ep_scale,
self._b_full[0]._data,
self._b_full[1]._data,
self._b_full[2]._data,
unit_b1[0]._data,
unit_b1[1]._data,
unit_b1[2]._data,
curl_unit_b1[0]._data,
curl_unit_b1[1]._data,
curl_unit_b1[2]._data,
self._grad_PB_b[0]._data,
self._grad_PB_b[1]._data,
self._grad_PB_b[2]._data,
self._u_form_int,
)
# define Pusher
if self.options.u_space == "Hdiv":
self._pusher_kernel = pusher_kernels_gc.push_gc_cc_J2_stage_Hdiv
elif self.options.u_space == "H1vec":
self._pusher_kernel = pusher_kernels_gc.push_gc_cc_J2_stage_H1vec
else:
raise ValueError(
f'{self.options.u_space =} not valid, choose from "Hdiv" or "H1vec.',
)
# temp fix due to refactoring of ButcherTableau:
self._args_pusher_kernel = (
self.domain.args_domain,
self.derham.args_derham,
epsilon,
self._b_full[0]._data,
self._b_full[1]._data,
self._b_full[2]._data,
unit_b1[0]._data,
unit_b1[1]._data,
unit_b1[2]._data,
curl_unit_b1[0]._data,
curl_unit_b1[1]._data,
curl_unit_b1[2]._data,
self._u_temp[0]._data,
self._u_temp[1]._data,
self._u_temp[2]._data,
self.options.butcher.a_stage,
self.options.butcher.b,
self.options.butcher.c,
)
else:
# temporary vectors to avoid memory allocation
self._b_full = self._b2.space.zeros()
self._PB_b = self._PB.codomain.zeros()
self._grad_PB_b = self.derham.grad.codomain.zeros()
self._u_old = self.variables.u.spline.vector.space.zeros()
self._u_new = self.variables.u.spline.vector.space.zeros()
self._u_diff = self.variables.u.spline.vector.space.zeros()
self._u_mid = self.variables.u.spline.vector.space.zeros()
self._M2n_dot_u = self.variables.u.spline.vector.space.zeros()
self._ku = self.variables.u.spline.vector.space.zeros()
self._u_temp = self.variables.u.spline.vector.space.zeros()
# Call the accumulation and Pusher class
accum_kernel_init = accum_kernels_gc.cc_lin_mhd_5d_gradB_dg_init
accum_kernel = accum_kernels_gc.cc_lin_mhd_5d_gradB_dg
self._accum_kernel_en_fB_mid = utilities_kernels.eval_gradB_ediff
self._args_accum_kernel = (
epsilon,
self.options.ep_scale,
self.b_tilde.spline.vector[0]._data,
self.b_tilde.spline.vector[1]._data,
self.b_tilde.spline.vector[2]._data,
self._b2[0]._data,
self._b2[1]._data,
self._b2[2]._data,
unit_b1[0]._data,
unit_b1[1]._data,
unit_b1[2]._data,
curl_unit_b1[0]._data,
curl_unit_b1[1]._data,
curl_unit_b1[2]._data,
self._grad_PB_b[0]._data,
self._grad_PB_b[1]._data,
self._grad_PB_b[2]._data,
gradB1[0]._data,
gradB1[1]._data,
gradB1[2]._data,
self._u_form_int,
)
self._args_accum_kernel_en_fB_mid = (
self.domain.args_domain,
self.derham.args_derham,
gradB1[0]._data,
gradB1[1]._data,
gradB1[2]._data,
self._grad_PB_b[0]._data,
self._grad_PB_b[1]._data,
self._grad_PB_b[2]._data,
)
self._ACC_init = AccumulatorVector(
self.variables.energetic_ions.particles,
self.options.u_space,
accum_kernel_init,
self.mass_ops,
self.domain.args_domain,
filter_params=self.options.filter_params,
)
self._ACC = AccumulatorVector(
self.variables.energetic_ions.particles,
self.options.u_space,
accum_kernel,
self.mass_ops,
self.domain.args_domain,
filter_params=self.options.filter_params,
)
self._args_pusher_kernel_init = (
self.domain.args_domain,
self.derham.args_derham,
epsilon,
self._b_full[0]._data,
self._b_full[1]._data,
self._b_full[2]._data,
unit_b1[0]._data,
unit_b1[1]._data,
unit_b1[2]._data,
curl_unit_b1[0]._data,
curl_unit_b1[1]._data,
curl_unit_b1[2]._data,
self.variables.u.spline.vector[0]._data,
self.variables.u.spline.vector[1]._data,
self.variables.u.spline.vector[2]._data,
)
self._args_pusher_kernel = (
self.domain.args_domain,
self.derham.args_derham,
epsilon,
self._b_full[0]._data,
self._b_full[1]._data,
self._b_full[2]._data,
unit_b1[0]._data,
unit_b1[1]._data,
unit_b1[2]._data,
curl_unit_b1[0]._data,
curl_unit_b1[1]._data,
curl_unit_b1[2]._data,
self._u_mid[0]._data,
self._u_mid[1]._data,
self._u_mid[2]._data,
self._u_temp[0]._data,
self._u_temp[1]._data,
self._u_temp[2]._data,
)
self._pusher_kernel_init = pusher_kernels_gc.push_gc_cc_J2_dg_init_Hdiv
self._pusher_kernel = pusher_kernels_gc.push_gc_cc_J2_dg_Hdiv
def __call__(self, dt):
# current FE coeffs
un = self.variables.u.spline.vector
# particle markers and idx
particles = self.variables.energetic_ions.particles
holes = particles.holes
args_markers = particles.args_markers
markers = args_markers.markers
first_init_idx = args_markers.first_init_idx
first_free_idx = args_markers.first_free_idx
# clear buffer
markers[:, first_init_idx:-2] = 0.0
# save old marker positions
markers[:, first_init_idx : first_init_idx + 3] = markers[:, :3]
# sum up total magnetic field b_full1 = b_eq + b_tilde (in-place)
b_full = self._b2.copy(out=self._b_full)
b_full += self.b_tilde.spline.vector
b_full.update_ghost_regions()
if self.options.algo == "explicit":
PB_b = self._PB.dot(b_full, out=self._PB_b)
grad_PB_b = self.derham.grad.dot(PB_b, out=self._grad_PB_b)
grad_PB_b.update_ghost_regions()
# save old u
u_new = un.copy(out=self._u_new)
for stage in range(self.options.butcher.n_stages):
# accumulate
self._ACC(
*self._args_accum_kernel,
)
# push particles
self._pusher_kernel(
dt,
stage,
args_markers,
*self._args_pusher_kernel,
)
if particles.mpi_comm is not None:
particles.mpi_sort_markers()
else:
particles.apply_kinetic_bc()
# solve linear system for updating u coefficients
ku = self._A_inv.dot(self._ACC.vectors[0], out=self._ku)
info = self._A_inv._info
# calculate u^{n+1}_k
u_temp = un.copy(out=self._u_temp)
u_temp += ku * dt * self.options.butcher.a_stage[stage]
u_temp.update_ghost_regions()
# calculate u^{n+1}
u_new += ku * dt * self.options.butcher.b[stage]
if self.options.solver_params.info and MPI.COMM_WORLD.Get_rank() == 0:
logger.info(f"Stage: {stage}")
logger.info(f"Status for CurrentCoupling5DGradB: {info['success']}")
logger.info(f"Iterations for CurrentCoupling5DGradB: {info['niter']}")
logger.info("")
# update u coefficients
diffs = self.update_feec_variables(u=u_new)
# clear the buffer
markers[:, first_init_idx:-2] = 0.0
# update_weights
if self.variables.energetic_ions.species.weights_params.control_variate:
particles.update_weights()
if self.options.solver_params.info and MPI.COMM_WORLD.Get_rank() == 0:
logger.info(f"Maxdiff up for CurrentCoupling5DGradB: {diffs['u']}")
logger.info("")
else:
# total number of markers
n_mks_tot = particles.Np
# relaxation factor
alpha = self.options.dg_solver_params.relaxation_factor
# eval parallel tilde b and its gradient
PB_b = self._PB.dot(self.b_tilde.spline.vector, out=self._PB_b)
PB_b.update_ghost_regions()
grad_PB_b = self.derham.grad.dot(PB_b, out=self._grad_PB_b)
grad_PB_b.update_ghost_regions()
# save old u
u_old = un.copy(out=self._u_old)
u_new = un.copy(out=self._u_new)
# save en_U_old
self._A.dot(un, out=self._M2n_dot_u)
en_U_old = un.inner(self._M2n_dot_u) / 2.0
# save en_fB_old
particles.save_magnetic_energy(PB_b)
en_fB_old = xp.sum(markers[~holes, 8].dot(markers[~holes, 5])) * self.options.ep_scale
en_fB_old /= n_mks_tot
buffer_array = xp.array([en_fB_old])
if particles.mpi_comm is not None:
particles.mpi_comm.Allreduce(
MPI.IN_PLACE,
buffer_array,
op=MPI.SUM,
)
if particles.clone_config is not None:
particles.clone_config.inter_comm.Allreduce(
MPI.IN_PLACE,
buffer_array,
op=MPI.SUM,
)
en_fB_old = buffer_array[0]
en_tot_old = en_U_old + en_fB_old
# initial guess
self._ACC_init(*self._args_accum_kernel)
ku = self._A_inv.dot(self._ACC_init.vectors[0], out=self._ku)
u_new += ku * dt
u_new.update_ghost_regions()
# save en_U_new
self._A.dot(u_new, out=self._M2n_dot_u)
en_U_new = u_new.inner(self._M2n_dot_u) / 2.0
# push eta
self._pusher_kernel_init(
dt,
args_markers,
*self._args_pusher_kernel_init,
)
if particles.mpi_comm is not None:
particles.mpi_sort_markers(apply_bc=False)
# save en_fB_new
particles.save_magnetic_energy(PB_b)
en_fB_new = xp.sum(markers[~holes, 8].dot(markers[~holes, 5])) * self.options.ep_scale
en_fB_new /= n_mks_tot
buffer_array = xp.array([en_fB_new])
if particles.mpi_comm is not None:
particles.mpi_comm.Allreduce(
MPI.IN_PLACE,
buffer_array,
op=MPI.SUM,
)
if particles.clone_config is not None:
particles.clone_config.inter_comm.Allreduce(
MPI.IN_PLACE,
buffer_array,
op=MPI.SUM,
)
en_fB_new = buffer_array[0]
# fixed-point iterations
iter_num = 0
while True:
iter_num += 1
if self.options.dg_solver_params.verbose and MPI.COMM_WORLD.Get_rank() == 0:
logger.info(f"# of iteration: {iter_num}")
# calculate discrete gradient
# save u^{n+1, k}
u_old = u_new.copy(out=self._u_old)
u_diff = u_old.copy(out=self._u_diff)
u_diff -= un
u_diff.update_ghost_regions()
u_mid = u_old.copy(out=self._u_mid)
u_mid += un
u_mid /= 2.0
u_mid.update_ghost_regions()
# save H^{n+1, k}
markers[~holes, first_free_idx : first_free_idx + 3] = markers[~holes, 0:3]
# calculate denominator ||z^{n+1, k} - z^n||^2
sum_u_diff_loc = xp.sum((u_diff.toarray() ** 2))
sum_H_diff_loc = xp.sum(
(markers[~holes, :3] - markers[~holes, first_init_idx : first_init_idx + 3]) ** 2,
)
buffer_array = xp.array([sum_u_diff_loc])
if particles.mpi_comm is not None:
particles.mpi_comm.Allreduce(
MPI.IN_PLACE,
buffer_array,
op=MPI.SUM,
)
denominator = buffer_array[0]
buffer_array = xp.array([sum_H_diff_loc])
if particles.mpi_comm is not None:
particles.mpi_comm.Allreduce(
MPI.IN_PLACE,
buffer_array,
op=MPI.SUM,
)
if particles.clone_config is not None:
particles.clone_config.inter_comm.Allreduce(
MPI.IN_PLACE,
buffer_array,
op=MPI.SUM,
)
denominator += buffer_array[0]
# sorting markers at mid-point
if particles.mpi_comm is not None:
particles.mpi_sort_markers(apply_bc=False, alpha=0.5)
self._accum_kernel_en_fB_mid(
args_markers,
*self._args_accum_kernel_en_fB_mid,
first_free_idx + 3,
)
en_fB_mid = xp.sum(markers[~holes, first_free_idx + 3].dot(markers[~holes, 5])) * self.options.ep_scale
en_fB_mid /= n_mks_tot
buffer_array = xp.array([en_fB_mid])
if particles.mpi_comm is not None:
particles.mpi_comm.Allreduce(
MPI.IN_PLACE,
buffer_array,
op=MPI.SUM,
)
if particles.clone_config is not None:
particles.clone_config.inter_comm.Allreduce(
MPI.IN_PLACE,
buffer_array,
op=MPI.SUM,
)
en_fB_mid = buffer_array[0]
if denominator == 0.0:
const = 0.0
else:
const = (en_fB_new - en_fB_old - en_fB_mid) / denominator
# update u^{n+1, k}
self._ACC(*self._args_accum_kernel, const)
ku = self._A_inv.dot(self._ACC.vectors[0], out=self._ku)
u_new = un.copy(out=self._u_new)
u_new += ku * dt
u_new *= alpha
u_new += u_old * (1.0 - alpha)
u_new.update_ghost_regions()
# update en_U_new
self._A.dot(u_new, out=self._M2n_dot_u)
en_U_new = u_new.inner(self._M2n_dot_u) / 2.0
# update H^{n+1, k}
self._pusher_kernel(
dt,
args_markers,
*self._args_pusher_kernel,
const,
alpha,
)
sum_H_diff_loc = xp.sum(
xp.abs(markers[~holes, 0:3] - markers[~holes, first_free_idx : first_free_idx + 3]),
)
if particles.mpi_comm is not None:
particles.mpi_sort_markers(apply_bc=False)
# update en_fB_new
particles.save_magnetic_energy(PB_b)
en_fB_new = xp.sum(markers[~holes, 8].dot(markers[~holes, 5])) * self.options.ep_scale
en_fB_new /= n_mks_tot
buffer_array = xp.array([en_fB_new])
if particles.mpi_comm is not None:
particles.mpi_comm.Allreduce(
MPI.IN_PLACE,
buffer_array,
op=MPI.SUM,
)
if particles.clone_config is not None:
particles.clone_config.inter_comm.Allreduce(
MPI.IN_PLACE,
buffer_array,
op=MPI.SUM,
)
en_fB_new = buffer_array[0]
# calculate total energy difference
e_diff = xp.abs(en_U_new + en_fB_new - en_tot_old)
# calculate ||z^{n+1, k} - z^{n+1, k-1||
sum_u_diff_loc = xp.sum(xp.abs(u_new.toarray() - u_old.toarray()))
buffer_array = xp.array([sum_u_diff_loc])
if particles.mpi_comm is not None:
particles.mpi_comm.Allreduce(
MPI.IN_PLACE,
buffer_array,
op=MPI.SUM,
)
diff = buffer_array[0]
buffer_array = xp.array([sum_H_diff_loc])
if particles.mpi_comm is not None:
particles.mpi_comm.Allreduce(
MPI.IN_PLACE,
buffer_array,
op=MPI.SUM,
)
if particles.clone_config is not None:
particles.clone_config.inter_comm.Allreduce(
MPI.IN_PLACE,
buffer_array,
op=MPI.SUM,
)
diff += buffer_array[0]
# check convergence
if diff < self.options.dg_solver_params.tol:
if self.options.dg_solver_params.verbose and MPI.COMM_WORLD.Get_rank() == 0:
logger.info(f"converged diff: {diff}")
logger.info(f"converged e_diff: {e_diff}")
if particles.mpi_comm is not None:
particles.mpi_comm.Barrier()
break
else:
if self.options.dg_solver_params.verbose and MPI.COMM_WORLD.Get_rank() == 0:
logger.info(f"not converged diff: {diff}")
logger.info(f"not converged e_diff: {e_diff}")
if iter_num == self.options.dg_solver_params.maxiter:
if self.options.dg_solver_params.info and MPI.COMM_WORLD.Get_rank() == 0:
logger.info(
f"{iter_num =}, maxiter={self.options.dg_solver_params.maxiter} reached! diff: {diff}, e_diff: {e_diff}",
)
if particles.mpi_comm is not None:
particles.mpi_comm.Barrier()
break
# sorting markers
if particles.mpi_comm is not None:
particles.mpi_sort_markers()
else:
particles.apply_kinetic_bc()
# update u coefficients
diffs = self.update_feec_variables(u=u_new)
# clear the buffer
markers[:, first_init_idx:-2] = 0.0
# update_weights
if self.variables.energetic_ions.species.weights_params.control_variate:
particles.update_weights()
if self.options.dg_solver_params.info and MPI.COMM_WORLD.Get_rank() == 0:
logger.info(f"Maxdiff up for CurrentCoupling5DGradB: {diffs['u']}")
logger.info("")