Source code for struphy.propagators.variational_resistivity

import logging
from copy import deepcopy
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.feec.mass import L2Projector
from struphy.feec.variational_utilities import InternalEnergyEvaluator
from struphy.io.options import LiteralOptions, OptionsBase
from struphy.linear_algebra.solver import NonlinearSolverParameters, SolverParameters
from struphy.models.variables import FEECVariable
from struphy.propagators.base import Propagator
from struphy.utils.utils import check_option

logger = logging.getLogger("struphy")


[docs] class VariationalResistivity(Propagator): r""":ref:`FEEC <gempic>` discretization of the following equations: find :math:`s \in L^2` and :math:`\mathbf B \in H(\textrm{div})` such that .. math:: &\int_\Omega \partial_t \mathbf B \cdot \mathbf v \,\textrm d \mathbf x + \int_\Omega (\eta + \eta_a(\mathbf x)) \nabla \times \mathbf B \cdot \nabla \times \mathbf v \,\textrm d \mathbf x = 0 \qquad \forall \, \mathbf v \in H(\textrm{div}) \,, \\[4mm] &\int_\Omega \frac{\partial \mathcal U}{\partial s} \partial_t s \, q\,\textrm d \mathbf x - \int_\Omega (\eta + \eta_a(\mathbf x)) |\nabla \times \mathbf B|^2 \, q \,\textrm d \mathbf x = 0 \qquad \forall \, q \in L^2\, \text{if using } s, \\[4mm] &\int_\Omega \frac{1}{\gamma - 1} \partial_t p \, q\,\textrm d \mathbf x - \int_\Omega (\eta + \eta_a(\mathbf x)) |\nabla \times \mathbf B|^2 \, q \,\textrm d \mathbf x = 0 \qquad \forall \, q \in L^2\, \text{if using } p. With :math:`\eta_a(\mathbf x) = \eta_a |\nabla \times \mathbf B(\mathbf x)|` On the logical domain: .. math:: \begin{align} &\partial_t \hat{\boldsymbol B} - \eta \Delta \hat{\boldsymbol B} = 0 ~ , \\[2mm] &\int_{\hat{\Omega}} \partial_t (\hat{\rho} \mathcal U(\hat{\rho}, \hat{s})) \hat{w} \,\frac{1}{\sqrt g}\, \textrm d \boldsymbol \eta - \int_{\hat{\Omega}} (\eta + \eta_a(\mathbf \eta)) |DF^{-T}\tilde{\nabla} \times \hat{\boldsymbol B}|^2 \hat{w} \, \textrm d \boldsymbol \eta = 0 ~ , \text{if using } s, \\[2mm] &\int_{\hat{\Omega}} \partial_t (\frac{1}{\gamma -1} \hat{p} ) \hat{w} \,\frac{1}{\sqrt g}\, \textrm d \boldsymbol \eta - \int_{\hat{\Omega}} (\eta + \eta_a(\mathbf \eta)) |DF^{-T}\tilde{\nabla} \times \hat{\boldsymbol B}|^2 \hat{w} \, \textrm d \boldsymbol \eta = 0 ~, \text{if using } p. \end{align} It is discretized as .. math:: \begin{align} &\frac{\mathbf B^{n+1}-\mathbf B^n}{\Delta t} + \, \mathbb C \mathbb M_1^{-1} (\eta M_1 + \eta_a M_1[|\nabla \times \mathbf B|]) M_1^{-1} \mathbb C^T \mathbb M_2 \mathbf B^{n+1} = 0 ~ , \\[2mm] &\frac{P^{3}(\rho e(s^{n+1})- P^{3}(\rho e(s^{n}))}{\Delta t} - P^3((\eta + \eta_a(\mathbf x)) DF^{-T} \tilde{\mathbb C} \frac{ \mathbf B^{n+1}+\mathbf B^n}{2} \cdot DF^{-T} \tilde{\mathbb C} \mathbf B^{n+1}) = 0 ~ , \text{if using } s, \\[2mm] &\frac{1}{\gamma -1}\frac{p^{n+1}- p^{n}}{\Delta t} - P^3((\eta + \eta_a(\mathbf x)) DF^{-T} \tilde{\mathbb C} \frac{ \mathbf B^{n+1}+\mathbf B^n}{2} \cdot DF^{-T} \tilde{\mathbb C} \mathbf B^{n+1}) = 0 ~ , \text{if using } p. \end{align} where $P^3$ denotes the $L^2$ projection in the last space of the de Rham sequence. """
[docs] class Variables: """Container for variables advanced by :class:`VariationalResistivity`. Attributes ---------- s : FEECVariable Thermodynamic scalar variable in ``"L2"`` space. b : FEECVariable Magnetic-field variable in ``"Hdiv"`` space. """ def __init__(self): self._s: FEECVariable = None self._b: FEECVariable = None @property def s(self) -> FEECVariable: return self._s @s.setter def s(self, new): assert isinstance(new, FEECVariable) assert new.space == "L2" self._s = new @property def b(self) -> FEECVariable: return self._b @b.setter def b(self, new): assert isinstance(new, FEECVariable) assert new.space == "Hdiv" self._b = new
def __init__(self, rho: FEECVariable = None, pt3: FEECVariable = None): """ Parameters ---------- rho : FEECVariable, default=None Mass density 3-form (``"L2"`` space) weighting the thermodynamic equation. pt3 : FEECVariable, default=None Pressure or entropy 3-form (``"L2"`` space) evolved by the resistive heating. If ``None``, the thermodynamic equation is skipped. """ self.variables = self.Variables() self.rho = rho self.pt3 = pt3
[docs] @dataclass(repr=False) class Options(OptionsBase): """Configuration options for :class:`VariationalResistivity`. Parameters ---------- model : {"full", "full_p", "full_q", "linear_p", "linear_q", "deltaf_q"}, default="full" Thermodynamic model variant. gamma : float, default=5/3 Adiabatic index. solver : LiteralOptions.OptsSymmSolver, default="pcg" Linear solver for implicit subproblems. precond : LiteralOptions.OptsMassPrecond, default="MassMatrixDiagonalPreconditioner" Preconditioner used in linear solves. solver_params : SolverParameters, default=None Linear-solver controls. nonlin_solver : NonlinearSolverParameters, default=None Nonlinear iteration controls. linearize_current : bool, default=False If ``True``, linearize current terms around background fields. eta : float, default=0.0 Physical resistivity coefficient. eta_a : float, default=0.0 Artificial-resistivity coefficient. """ # specific literals OptsModel = Literal["full", "full_p", "full_q", "linear_p", "linear_q", "deltaf_q"] # propagator options model: OptsModel = "full" gamma: float = 5.0 / 3.0 solver: LiteralOptions.OptsSymmSolver = "pcg" precond: LiteralOptions.OptsMassPrecond = "MassMatrixDiagonalPreconditioner" solver_params: SolverParameters = None nonlin_solver: NonlinearSolverParameters = None linearize_current: bool = False eta: float = 0.0 eta_a: float = 0.0 def __post_init__(self): # checks check_option(self.model, self.OptsModel) check_option(self.solver, LiteralOptions.OptsSymmSolver) check_option(self.precond, LiteralOptions.OptsMassPrecond) # defaults if self.solver_params is None: self.solver_params = SolverParameters() if self.nonlin_solver is None: self.nonlin_solver = NonlinearSolverParameters(type="Newton")
@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): self._model = self.options.model self._gamma = self.options.gamma self._eta = self.options.eta self._eta_a = self.options.eta_a self._lin_solver = self.options.solver_params self._nonlin_solver = self.options.nonlin_solver self._linearize_current = self.options.linearize_current self._info = self._nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) # Femfields for the projector self.rhof = self.derham.create_spline_function("rhof", "L2") self.sf = self.derham.create_spline_function("sf", "L2") self.sf1 = self.derham.create_spline_function("sf1", "L2") self.bf = self.derham.create_spline_function("Bf", "Hdiv") self.bf1 = self.derham.create_spline_function("Bf1", "Hdiv") self.cbf1 = self.derham.create_spline_function("cBf", "Hcurl") self.cbf12 = self.derham.create_spline_function("cBf", "Hcurl") # Projector self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) self._initialize_projectors_and_mass() # bunch of temporaries to avoid allocating in the loop s = self.variables.s.spline.vector b = self.variables.b.spline.vector self._tmp_bn1 = b.space.zeros() self._tmp_bn12 = b.space.zeros() self._tmp_sn1 = s.space.zeros() self._tmp_sn_incr = s.space.zeros() self._tmp_sn_weak_diff = s.space.zeros() self._tmp_cb12 = self.derham.V1pol.zeros() self._tmp_cb1 = self.derham.V1pol.zeros() self._linear_form_tot_e = s.space.zeros() self._linear_form_en1 = s.space.zeros() self.tot_rhs = s.space.zeros() if True: # self._linearize_current: self._extracted_b2 = self.derham.boundary_ops["2"].dot( self.derham.extraction_ops["2"].dot(self.projected_equil.b2), ) def __call__(self, dt): if self._nonlin_solver.type == "Newton": self.__call_newton(dt) else: raise ValueError( "wrong value for solver type in VariationalResistivity", ) def __call_newton(self, dt): """Solve the non linear system for updating the variables using Newton iteration method""" # Compute dissipation implicitely sn = self.variables.s.spline.vector bn = self.variables.b.spline.vector if self._eta < 1.0e-15 and self._eta_a < 1.0e-15: self.update_feec_variables(s=sn, b=bn) return if self._info: logger.info("") logger.info("Computing the dissipation in VariationalResistivity") total_resistivity = self._update_artificial_resistivity(bn, dt) self._scaled_stiffness._scalar = dt * self._eta # self.evol_op._multiplicants[1]._addends[0]._scalar = -dt*self._eta/2. if self._linearize_current: bn1 = self.evol_op.dot( bn + dt * self._eta * self.curl.dot( self.Tcurl.dot(self._extracted_b2), ), out=self._tmp_bn1, ) else: bn1 = self.evol_op.dot(bn, out=self._tmp_bn1) if self._info: logger.info(f"information on the linear solver : {self.inv_lop._info}") if self._model == "linear_p" or (self._model == "linear_q" and self._nonlin_solver["fast"]): self.update_feec_variables(s=sn, b=bn1) return # Energy balance term # 1) Pointwize energy change energy_change = self._get_energy_change(bn, bn1, total_resistivity) # 2) Initial energy and linear form rho = self.rho.spline.vector self.rhof.vector = rho if self._model in ["deltaf_q", "linear_q"]: self.sf.vector = self.pt3.spline.vector else: self.sf.vector = sn sf_values = self.sf.eval_tp_fixed_loc( self.integration_grid_spans, self.integration_grid_bd, out=self._sf_values, ) if self._model == "full": rhof_values = self.rhof.eval_tp_fixed_loc( self.integration_grid_spans, self.integration_grid_bd, out=self._rhof_values, ) e_n = self._energy_evaluator.ener( rhof_values, sf_values, out=self._e_n, ) e_n *= self._energy_metric elif self._model in ["full_p", "linear_p", "delta_p"]: e_n = self._e_n e_n *= 0.0 e_n += sf_values e_n *= 1.0 / (self._gamma - 1.0) e_n *= self._energy_metric elif self._model in ["full_q"]: e_n = self._e_n e_n *= 0.0 e_n += sf_values e_n **= 2 e_n *= 1.0 / (self._gamma - 1.0) e_n *= self._energy_metric elif self._model in ["linear_q", "deltaf_q"]: e_n = self._e_n e_n *= 0.0 e_n += sf_values e_n *= self._q0_values e_n *= 2.0 / (self._gamma - 1.0) e_n *= self._energy_metric energy_change += e_n self._get_L2dofs_V3(energy_change, dofs=self._linear_form_tot_e) # 3) Newton iteration sn1 = sn.copy(out=self._tmp_sn1) tol = self._nonlin_solver["tol"] err = tol + 1 for it in range(self._nonlin_solver["maxiter"]): if self._model in ["deltaf_q", "linear_q"]: self.sf1.vector = self.pt3.spline.vector else: self.sf1.vector = sn1 sf1_values = self.sf1.eval_tp_fixed_loc( self.integration_grid_spans, self.integration_grid_bd, out=self._sf1_values, ) if self._model == "full": e_n1 = self._energy_evaluator.ener( rhof_values, sf1_values, out=self._e_n1, ) e_n1 *= self._energy_metric elif self._model in ["full_p", "linear_p", "delta_p"]: e_n1 = self._e_n1 e_n1 *= 0.0 e_n1 += sf1_values e_n1 *= 1.0 / (self._gamma - 1.0) e_n1 *= self._energy_metric elif self._model in ["full_q"]: e_n1 = self._e_n1 e_n1 *= 0.0 e_n1 += sf1_values e_n1 **= 2 e_n1 *= 1.0 / (self._gamma - 1.0) e_n1 *= self._energy_metric elif self._model in ["linear_q", "deltaf_q"]: e_n1 = self._e_n1 e_n1 *= 0.0 e_n1 += sf1_values e_n1 *= self._q0_values e_n1 *= 2.0 / (self._gamma - 1.0) e_n1 *= self._energy_metric self._get_L2dofs_V3(e_n1, dofs=self._linear_form_en1) self.tot_rhs *= 0.0 self.tot_rhs -= self._linear_form_en1 self.tot_rhs += self._linear_form_tot_e err = self._get_error_newton(self.tot_rhs) if self._info: logger.info(f"iteration : {it} error : {err}") if (err < tol**2 and it > 0) or xp.isnan(err): break if self._model == "full": deds = self._energy_evaluator.dener_ds( rhof_values, sf1_values, out=self._de_s1_values, ) deds *= self._mass_metric_term self.M_de_ds.assemble([[deds]]) self.pc_jac.update_mass_operator(self.M_de_ds) elif self._model in ["full_q", "linear_q", "deltaf_q"]: if self._model in ["deltaf_q", "linear_q"]: sf1_values = self._q0_values deds = self._de_s1_values deds *= 0.0 deds += sf1_values deds *= 2 / (self._gamma - 1.0) deds *= self._mass_metric_term self.M_de_ds.assemble([[deds]]) self.pc_jac.update_mass_operator(self.M_de_ds) incr = self.inv_jac.dot(self.tot_rhs, out=self._tmp_sn_incr) if self._info: logger.info(f"information on the linear solver : {self.inv_jac._info}") if self._model in ["deltaf_q", "linear_q"]: self.pt3 += incr else: sn1 += incr if it == self._nonlin_solver["maxiter"] - 1 or xp.isnan(err): logger.info( f"!!!Warning: Maximum iteration in VariationalResistivity reached - not converged:\n {err =} \n {tol**2 =}", ) self.update_feec_variables(s=sn1, b=bn1) # if self.pt3 is not None: # bn12 = bn.copy(out=self._tmp_bn12) # bn12 += bn1 # bn12 /= 2.0 # cb1 = self.Tcurl.dot(bn1, out=self._tmp_cb1) # cb12 = self.Tcurl.dot(bn12, out=self._tmp_cb12) # self.cbf12.vector = cb12 # self.cbf1.vector = cb1 # cb12_v = self.cbf12.eval_tp_fixed_loc( # self.integration_grid_spans, # self.integration_grid_curl, # out=self._cb12_values, # ) # cb1_v = self.cbf1.eval_tp_fixed_loc( # self.integration_grid_spans, # self.integration_grid_curl, # out=self._cb1_values, # ) # cb_sq_v = self._cb_sq_values # cb_sq_v *= 0.0 # for i in range(3): # for j in range(3): # cb_sq_v += cb12_v[i] * self._sq_term_metric[i, j] * cb1_v[j] # cb_sq_v *= self._cb_sq_values_init # # 2) Initial energy and linear form # self.sf.vector = self.pt3 # sf_values = self.sf.eval_tp_fixed_loc( # self.integration_grid_spans, # self.integration_grid_bd, # out=self._sf_values, # ) # e_n = self._e_n # e_n *= 0.0 # e_n += sf_values # e_n *= 1.0 / (self._gamma - 1.0) # e_n *= self._energy_metric # cb_sq_v += e_n # self._get_L2dofs_V3(cb_sq_v, dofs=self._linear_form_tot_e) # tol = self._nonlin_solver["tol"] # err = tol + 1 # for it in range(self._nonlin_solver["maxiter"]): # self.sf1.vector = self.pt3 # sf1_values = self.sf1.eval_tp_fixed_loc( # self.integration_grid_spans, # self.integration_grid_bd, # out=self._sf1_values, # ) # e_n1 = self._e_n1 # e_n1 *= 0.0 # e_n1 += sf1_values # e_n1 *= 1.0 / (self._gamma - 1.0) # e_n1 *= self._energy_metric # self._get_L2dofs_V3(e_n1, dofs=self._linear_form_en1) # self.tot_rhs *= 0.0 # self.tot_rhs -= self._linear_form_en1 # self.tot_rhs += self._linear_form_tot_e # err = self._get_error_newton(self.tot_rhs) # if self._info: # logger.info("iteration : ", it, " error : ", err) # if (err < tol**2 and it > 0) or xp.isnan(err): # break # incr = self.inv_jac.dot(self.tot_rhs, out=self._tmp_sn_incr) # if self._info: # logger.info("information on the linear solver : ", self.inv_jac._info) # self.pt3 += incr def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and needed to compute the bracket term""" pc_M1 = preconditioner.MassMatrixDiagonalPreconditioner( self.mass_ops.M1, ) inv_M1 = inverse( self.mass_ops.M1, "pcg", pc=pc_M1, tol=1e-16, maxiter=1000, verbose=False, ) pc_M3 = preconditioner.MassMatrixDiagonalPreconditioner( self.mass_ops.M3, ) self._inv_M3 = inverse( self.mass_ops.M3, "pcg", pc=pc_M3, tol=1e-16, maxiter=1000, verbose=False, ) M2 = self.mass_ops.M2 self.M_de_ds = self.mass_ops.create_weighted_mass("L2", "L2") D = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] self.M1_cb = self.mass_ops.create_weighted_mass("Hcurl", "Hcurl", weights=(D, "sqrt_g")) if self.options.precond is None: self.pc = None else: pc_class = getattr( preconditioner, self.options.precond, ) self.pc_jac = pc_class(self.M_de_ds) self.inv_jac = inverse( self.M_de_ds, "pcg", pc=self.pc_jac, tol=self._lin_solver.tol, maxiter=self._lin_solver.maxiter, verbose=False, recycle=True, ) self.curl = self.derham.curl self.Tcurl = inv_M1 @ self.curl.T @ M2 self.phy_stiffness = M2 @ self.curl @ inv_M1 @ self.curl.T @ M2 self.phy_cb_stiffness = self.Tcurl.T @ self.M1_cb @ self.Tcurl self._scaled_stiffness = 0.00001 * self.phy_stiffness self.r_op = M2 # - self._scaled_stiffness self.l_op = M2 + self._scaled_stiffness + self.phy_cb_stiffness if self.options.precond is None: self.pc = None else: pc_class = getattr( preconditioner, self.options.precond, ) self.pc = pc_class(M2) self.inv_lop = inverse( self.l_op, "pcg", pc=self.pc, tol=self._lin_solver.tol, maxiter=self._lin_solver.maxiter, verbose=False, recycle=True, ) self.evol_op = self.inv_lop @ self.r_op # self.evol_op = IdentityOperator(self.derham.Vvpol) integration_grid = [grid_1d.flatten() for grid_1d in self.derham.V3splines.quad_grid_pts[0]] self.integration_grid_spans, self.integration_grid_bn, self.integration_grid_bd = ( self.derham.prepare_eval_tp_fixed( integration_grid, ) ) self.integration_grid_curl = [ [self.integration_grid_bd[0], self.integration_grid_bn[1], self.integration_grid_bn[2]], [ self.integration_grid_bn[0], self.integration_grid_bd[1], self.integration_grid_bn[2], ], [self.integration_grid_bn[0], self.integration_grid_bn[1], self.integration_grid_bd[2]], ] grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) self._cb12_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] self._cb1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] self._cb_sq_values = xp.zeros(grid_shape, dtype=float) self._cb_sq_values_init = xp.zeros(grid_shape, dtype=float) self._sf_values = xp.zeros(grid_shape, dtype=float) self._sf1_values = xp.zeros(grid_shape, dtype=float) self._rhof_values = xp.zeros(grid_shape, dtype=float) self._e_n1 = xp.zeros(grid_shape, dtype=float) self._e_n = xp.zeros(grid_shape, dtype=float) self._de_s1_values = xp.zeros(grid_shape, dtype=float) self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) gam = self._gamma if self._model == "full": metric = xp.power( self.domain.jacobian_det( *integration_grid, ), -gam, ) self._mass_metric_term = deepcopy(metric) metric = xp.power( self.domain.jacobian_det( *integration_grid, ), 1 - gam, ) self._energy_metric = deepcopy(metric) elif self._model in ["full_p", "linear_p", "delta_p"]: metric = 1.0 / self.domain.jacobian_det( *integration_grid, ) self._mass_metric_term = deepcopy(metric) metric = ( 0 * self.domain.jacobian_det( *integration_grid, ) + 1.0 ) self._energy_metric = deepcopy(metric) # No need to compute this every iteration deds = self._de_s1_values deds *= 0.0 deds += 1 / (self._gamma - 1.0) deds *= self._mass_metric_term self.M_de_ds.assemble([[deds]]) self.pc_jac.update_mass_operator(self.M_de_ds) elif self._model in ["full_q", "linear_q", "deltaf_q"]: metric = xp.power( self.domain.jacobian_det( *integration_grid, ), -2, ) self._mass_metric_term = deepcopy(metric) metric = xp.power( self.domain.jacobian_det( *integration_grid, ), -1, ) self._energy_metric = deepcopy(metric) if self._model in ["linear_q", "deltaf_q"]: self.sf1.vector = self.projected_equil.q3 self._q0_values = self.sf1.eval_tp_fixed_loc(self.integration_grid_spans, self.integration_grid_bd) metric = self.domain.metric_inv( *integration_grid, ) * self.domain.jacobian_det(*integration_grid) self._sq_term_metric = deepcopy(metric) metric = self.domain.metric_inv( *integration_grid, ) self._sq_term_metric_no_jac = deepcopy(metric) self._get_L2dofs_V3 = L2Projector("L2", self.mass_ops).get_dofs def _get_error_newton(self, sn_diff): err_s = self._inv_M3.dot_inner(sn_diff, sn_diff) return err_s def _update_artificial_resistivity(self, bn, dt): """Update the artificial resistivity as the norm of the gradient of un. Update the associated mass matrix and return the total resistivity for later computation""" if self._eta_a > 1e-15: cb = self.Tcurl.dot(bn, out=self._tmp_cb1) self.cbf1.vector = cb cb_v = self.cbf1.eval_tp_fixed_loc( self.integration_grid_spans, self.integration_grid_curl, out=self._cb1_values, ) cb_sq_v = self._cb_sq_values_init cb_sq_v *= 0.0 for i in range(3): for j in range(3): cb_sq_v += cb_v[i] * self._sq_term_metric_no_jac[i, j] * cb_v[j] xp.sqrt(cb_sq_v, out=cb_sq_v) cb_sq_v *= dt * self._eta_a self.M1_cb.assemble( [ [ cb_sq_v * self._sq_term_metric[0, 0], cb_sq_v * self._sq_term_metric[0, 1], cb_sq_v * self._sq_term_metric[0, 2], ], [ cb_sq_v * self._sq_term_metric[1, 0], cb_sq_v * self._sq_term_metric[1, 1], cb_sq_v * self._sq_term_metric[1, 2], ], [ cb_sq_v * self._sq_term_metric[2, 0], cb_sq_v * self._sq_term_metric[2, 1], cb_sq_v * self._sq_term_metric[2, 2], ], ], ) cb_sq_v += dt * self._eta else: cb_sq_v = self._cb_sq_values_init cb_sq_v *= 0.0 cb_sq_v += dt * self._eta return cb_sq_v def _get_energy_change(self, bn, bn1, total_resistivity): """Return the total energy change caused by the resistivity""" bn12 = bn.copy(out=self._tmp_bn12) bn12 += bn1 bn12 /= 2.0 if self._linearize_current: cb1 = self.Tcurl.dot( bn1 - self._extracted_b2, out=self._tmp_cb1, ) else: cb1 = self.Tcurl.dot(bn1, out=self._tmp_cb1) if self._model in ["full", "full_p", "full_q"]: cb12 = self.Tcurl.dot(bn12, out=self._tmp_cb12) # elif self._model in ["linear_p", "linear_q"]: # cb12 = self.Tcurl.dot(self._extracted_b2, out=self._tmp_cb12) elif self._model in ["delta_p", "deltaf_q", "linear_p", "linear_q"]: # bn12 += self._extracted_b2 cb12 = self.Tcurl.dot(bn12, out=self._tmp_cb12) self.cbf12.vector = cb12 self.cbf1.vector = cb1 cb12_v = self.cbf12.eval_tp_fixed_loc( self.integration_grid_spans, self.integration_grid_curl, out=self._cb12_values, ) cb1_v = self.cbf1.eval_tp_fixed_loc( self.integration_grid_spans, self.integration_grid_curl, out=self._cb1_values, ) cb_sq_v = self._cb_sq_values cb_sq_v *= 0.0 for i in range(3): for j in range(3): cb_sq_v += cb12_v[i] * self._sq_term_metric[i, j] * cb1_v[j] cb_sq_v *= total_resistivity return cb_sq_v