Source code for struphy.models.curl_curl
import copy
import logging
from scope_profiler import ProfileManager
from struphy.io.options import BaseUnits, LiteralOptions
from struphy.models.base import StruphyModel
from struphy.models.species import (
FieldSpecies,
)
from struphy.models.variables import FEECVariable
from struphy.propagators.curl_curl_solve import CurlCurlSolve
from struphy.propagators.time_dependent_source import TimeDependentSource
logger = logging.getLogger("struphy")
[docs]
class CurlCurl(StruphyModel):
"""Weak discretization of the curl-curl problem with a mass (stabilization) term and an optional time-dependent right-hand side.
Parameters
----------
base_units: BaseUnits
Base units for normalization (default: BaseUnits())
with_t_dep_source: bool
Whether the right-hand side source term is time-dependent (default: False)
"""
@classmethod
def model_type(cls) -> LiteralOptions.ModelTypes:
return "Fluid"
## species
class EMFields(FieldSpecies):
def __init__(self):
self.e_field = FEECVariable(space="Hcurl")
# L2 projection: M1 @ coeffs is the exact load vector of the source; the commuting projection would
# add an O(h^p) gradient component to the right-hand side, which is amplified by 1/sigma in the solution
self.source = FEECVariable(space="Hcurl", projection="L2")
self.init_variables()
## propagators
class Propagators:
def __init__(self, j: FEECVariable = None, with_t_dep_source=False):
if with_t_dep_source:
self.source = TimeDependentSource()
self.curl_curl = CurlCurlSolve(j=j)
## abstract methods
def __init__(self, base_units: BaseUnits = BaseUnits(), with_t_dep_source=False):
self.with_t_dep_source = with_t_dep_source
# 0. store input parameters
self.params = copy.deepcopy(locals())
# 1. instantiate all species
self.em_fields = self.EMFields()
# 2. derive units (must be done after instantiating species to access charge and mass numbers)
self.setup_equation_params(base_units=base_units)
# 3. instantiate all propagators
self.propagators = self.Propagators(j=self.em_fields.source, with_t_dep_source=with_t_dep_source)
# 4. assign variables to propagators
if with_t_dep_source:
self.propagators.source.variables.source = self.em_fields.source
self.propagators.curl_curl.variables.e = self.em_fields.e_field
# 5. define scalars to be tracked during simulation
@property
def bulk_species(self):
return None
@property
def velocity_scale(self):
return None
def post_allocate(self):
"""Solve initial curl-curl problem.
:meta private:
"""
if self.with_t_dep_source:
# Solve to get initial field (before time stepping)
logger.info("\nSolving initial curl-curl problem (before time stepping)...")
# source at t = 0 (the time state is not yet attached to the propagators)
self.propagators.source.update_source(0.0)
with ProfileManager.profile_region("initial curl-curl solve", functions=[self.propagators.curl_curl]):
self.propagators.curl_curl(1.0)
logger.info("... Done.")
[docs]
@classmethod
def doc_pde(cls):
r"""**PDEs solved by model:**
Find :math:`\mathbf E \in H(\textnormal{curl})` such that
.. math::
\nabla \times \nabla \times \mathbf E + \sigma\, \mathbf E = \mathbf J(t, \mathbf{x})
where :math:`\sigma > 0` is a scalar and :math:`\mathbf J(t) : \Omega \to \mathbb{R}^3` is a
source parametrized by time :math:`t`. Boundary terms from integration by parts are assumed to vanish.
"""
[docs]
@classmethod
def doc_normalization(cls):
r"""The coefficient scaling is
.. math::
\hat \sigma = 1 / \hat x^2,\qquad \hat J = \hat E / \hat x^2.
No dedicated velocity normalization is used."""
[docs]
@classmethod
def doc_scalar_quantities(cls):
r"""**The following scalars are tracked during simulation:**
- No default scalar diagnostics are defined by this model."""
[docs]
@classmethod
def doc_discretization(cls):
"""Time integration is performed by the following propagators (in sequence):
1. :class:`~struphy.propagators.time_dependent_source.TimeDependentSource` (if :attr:`with_t_dep_source` is True)
2. :class:`~struphy.propagators.curl_curl_solve.CurlCurlSolve`
"""
doc = rf"""**1. TimeDependentSource:**
{TimeDependentSource.__doc__}
**2. CurlCurlSolve:**
{CurlCurlSolve.__doc__}
"""
return doc
[docs]
@classmethod
def doc_long_description(cls):
r"""CurlCurl is the standalone elliptic field-solve model for the stabilized curl-curl problem
in :math:`H(\textnormal{curl})`, e.g. from implicit time stepping of Maxwell's equations or
from magnetostatics. It is used to test and profile solvers and preconditioners for this
operator, whose large kernel (the gradients) makes standard preconditioners ineffective."""
[docs]
@classmethod
def doc_examples(cls):
r"""Create and initialize a CurlCurl model:
.. code-block:: python
from struphy.models import CurlCurl
model = CurlCurl()
model.em_fields.e_field
model.em_fields.source
"""
[docs]
@classmethod
def doc_use_cases(cls):
r"""This model is appropriate for:
- elliptic benchmark problems in :math:`H(\textnormal{curl})`
- testing and profiling curl-curl solvers and preconditioners
- field solves with prescribed current sources"""
[docs]
@classmethod
def doc_cannot_be_used_for(cls):
r"""This model is not suitable for:
- hyperbolic time-dependent wave propagation
- self-consistent kinetic plasma evolution on its own
- the singular case :math:`\sigma = 0` (it is regularized with :math:`\sigma = 10^{-14}`)"""