import cunumpy as xp
from feectools.linalg.block import BlockLinearOperator, BlockVector, BlockVectorSpace
from feectools.linalg.solvers import inverse
from numpy import zeros
import struphy.feec.utilities as util
from struphy.feec import preconditioner
from struphy.fields_background.equils import set_defaults
from struphy.pic.accumulation.particles_to_grid import Accumulator, AccumulatorVector
from struphy.polar.basic import PolarVector
from struphy.propagators.base import Propagator
[docs]
class FaradayExtended(Propagator):
r"""Equations: Faraday's law
.. math::
\begin{align*}
& \frac{\partial {\mathbf A}}{\partial t} = - \frac{\nabla \times (\nabla \times {\mathbf A} + {\mathbf B}_0) }{n} \times (\nabla \times {\mathbf A} + {\mathbf B}_0) - \frac{\int ({\mathbf A} - {\mathbf p}f \mathrm{d}{\mathbf p})}{n} \times (\nabla \times {\mathbf A} + {\mathbf B}_0), \\
& n = \int f \mathrm{d}{\mathbf p}.
\end{align*}
Mid-point rule:
.. math::
\begin{align*}
& \left[ \mathbb{M}_1 - \frac{\Delta t}{2} \mathbb{F}(\hat{n}^0_h, {\mathbf a}^{n+\frac{1}{2}}) \mathbb{M}_1^{-1} (\mathbb{P}_1^\top \mathbb{W} \mathbb{P}_1 + \mathbb{C}^\top \mathbb{M}_2 \mathbb{C} ) \right] {\mathbf a}^{n+1} \\
& = \mathbb{M}_1 {\mathbf a}^n + \frac{\Delta t}{2} \mathbb{F}(\hat{n}^0_h, {\mathbf a}^{n+\frac{1}{2}}) \mathbb{M}_1^{-1} (\mathbb{P}_1^\top \mathbb{W} \mathbb{P}_1 + \mathbb{C}^\top \mathbb{M}_2 \mathbb{C} ) {\mathbf a}^{n+1} \\
& - \Delta t \mathbb{F}(\hat{n}^0_h, {\mathbf a}^{n+\frac{1}{2}}) \mathbb{M}_1^{-1} \mathbb{P}_1^\top \mathbb{W} {\mathbf P}^n\\
& + \Delta t \mathbb{F}(\hat{n}^0_h, {\mathbf a}^{n+\frac{1}{2}}) \mathbb{M}_1^{-1} \mathbb{C}^\top \mathbb{M}_2 {\mathbf b}_0\\
& \mathbb{F}_{ij} = - \int \frac{1}{\hat{n}^0_h \sqrt{g}} G (\nabla \times {\mathbf A} + {\mathbf B}_0) \cdot (\Lambda^1_i \times \Lambda^1_j) \mathrm{d}{\boldsymbol \eta}.
\end{align*}
Parameters
----------
a : feectools.linalg.block.BlockVector
FE coefficients of vector potential.
**params : dict
Solver- and/or other parameters for this splitting step.
"""
def __init__(self, a, **params):
assert isinstance(a, (BlockVector, PolarVector))
# parameters
params_default = {
"a_space": None,
"beq": None,
"particles": None,
"quad_number": None,
"shape_degree": None,
"shape_size": None,
"solver_params": None,
"accumulate_density": None,
}
params = set_defaults(params, params_default)
self._a = a
self._a_old = self._a.copy()
self._a_space = params["a_space"]
assert self._a_space in {"Hcurl"}
self._beq = params["beq"]
self._particles = params["particles"]
self._nqs = params["quad_number"]
self.size1 = int(self.derham.domain_array[self.rank, int(2)])
self.size2 = int(self.derham.domain_array[self.rank, int(5)])
self.size3 = int(self.derham.domain_array[self.rank, int(8)])
self.weight_1 = zeros(
(self.size1 * self._nqs[0], self.size2 * self._nqs[1], self.size3 * self._nqs[2]),
dtype=float,
)
self.weight_2 = zeros(
(self.size1 * self._nqs[0], self.size2 * self._nqs[1], self.size3 * self._nqs[2]),
dtype=float,
)
self.weight_3 = zeros(
(self.size1 * self._nqs[0], self.size2 * self._nqs[1], self.size3 * self._nqs[2]),
dtype=float,
)
self._weight_pre = [self.weight_1, self.weight_2, self.weight_3]
self._ind = [
[self.derham.indN[0], self.derham.indD[1], self.derham.indD[2]],
[self.derham.indD[0], self.derham.indN[1], self.derham.indD[2]],
[self.derham.indD[0], self.derham.indD[1], self.derham.indN[2]],
]
# Initialize Accumulator object for getting density from particles
self._pts_x = 1.0 / (2.0 * self.derham.num_elements[0]) * xp.polynomial.legendre.leggauss(
self._nqs[0],
)[0] + 1.0 / (2.0 * self.derham.num_elements[0])
self._pts_y = 1.0 / (2.0 * self.derham.num_elements[1]) * xp.polynomial.legendre.leggauss(
self._nqs[1],
)[0] + 1.0 / (2.0 * self.derham.num_elements[1])
self._pts_z = 1.0 / (2.0 * self.derham.num_elements[2]) * xp.polynomial.legendre.leggauss(
self._nqs[2],
)[0] + 1.0 / (2.0 * self.derham.num_elements[2])
self._p_shape = params["shape_degree"]
self._p_size = params["shape_size"]
self._accum_density = params["accumulate_density"]
# Initialize Accumulator object for getting the matrix and vector related with vector potential
self._accum_potential = Accumulator(
self.mass_ops,
self.domain,
self._a_space,
"hybrid_fA_Arelated",
add_vector=True,
symmetry="symm",
)
self._solver_params = params["solver_params"]
# preconditioner
if self._solver_params["pc"] is None:
self._pc = None
else:
pc_class = getattr(preconditioner, self._solver_params["pc"])
self._pc = pc_class(self.mass_ops.M1)
self._Minv = inverse(self.mass_ops.M1, tol=1e-8)
self._CMC = self.derham.curl.T @ self.mass_ops.M2 @ self.derham.curl
self._M1 = self.mass_ops.M1
self._M2 = self.mass_ops.M2
@property
def variables(self):
return [self._a]
def __call__(self, dt):
# the loop of fixed point iteration, 100 iterations at most.
self._accum_density.accumulate(
self._particles,
xp.array(self.derham.num_elements),
xp.array(self._nqs),
xp.array(
self._pts_x,
),
xp.array(self._pts_y),
xp.array(self._pts_z),
xp.array(self._p_shape),
xp.array(self._p_size),
)
self._accum_potential.accumulate(self._particles)
self._L2 = -dt / 2 * self._Minv @ (self._accum_potential._operators[0].matrix + self._CMC)
self._RHS = -(self._L2.dot(self._a)) - dt * (
self._Minv.dot(
self._accum_potential._vectors[0] - self.derham.curl.T @ self._M2,
).dot(self._beq)
)
self._rhs = self._M1.dot(self._a)
for _ in range(10):
# logger.info('+++++=====++++++', self._accum_density._operators[0].matrix._data)
# set mid-value used in the fixed iteration
curla_mid = (
self.derham.curl.dot(
0.5 * (self._a_old + self._a),
)
+ self._beq
)
curla_mid.update_ghost_regions()
# initialize the curl A
# remember to check ghost region of curla_mid
util.create_weight_weightedmatrix_hybrid(
curla_mid,
self._weight_pre,
self.derham,
self._accum_density,
self.domain,
)
# self._weight = [[None, self._weight_pre[2], -self._weight_pre[1]], [None, None, self._weight_pre[0]], [None, None, None]]
self._weight = [
[0.0 * self._weight_pre[k] for k in range(3)],
[0.0 * self._weight_pre[k] for k in range(3)],
[0.0 * self._weight_pre[k] for k in range(3)],
]
# self._weight = [[self._weight_pre[0], self._weight_pre[2], self._weight_pre[1]], [self._weight_pre[2], self._weight_pre[1], self._weight_pre[0]], [self._weight_pre[1], self._weight_pre[0], self._weight_pre[2]]]
HybridM1 = self.mass_ops.create_weighted_mass("Hcurl", "Hcurl", weights=self._weight, assemble=True)
# next prepare for solving linear system
_LHS = self._M1 + HybridM1 @ self._L2
_RHS2 = HybridM1.dot(self._RHS) + self._rhs
# TODO: unknown function 'pcg', use new solver API
raise NotImplementedError("pcg solver not available, use new solver API!")
# a_new, info = pcg(
# _LHS,
# _RHS2,
# self._pc,
# x0=self._a,
# tol=self._solver_params["tol"],
# maxiter=self._solver_params["maxiter"],
# verbose=self._solver_params["verbose"],
# )
# write new coeffs into Propagator.variables
# max_da = self.feec_vars_update(a_new)
# logger.info("++++====check_iteration_error=====+++++", max_da)
# we can modify the diff function in in_place_update to get another type errors
# if max_da[0] < 10 ** (-6):
# break
@classmethod
def options(cls):
dct = {}
dct["solver"] = {
"type": [
("pcg", "MassMatrixPreconditioner"),
("cg", None),
],
"tol": 1.0e-8,
"maxiter": 3000,
"info": False,
"verbose": False,
"recycle": True,
}
return dct