Source code for pops.fields.poisson
"""pops.fields.poisson -- Poisson-family field-problem shortcuts (Spec 5 sec.5.5).
Thin subclasses of :class:`pops.fields.problem.FieldProblem` that name the common elliptic
shapes:
* :class:`PoissonProblem` -- ``-laplacian(phi) == rhs`` (the standard self-consistent field);
* :class:`ScreenedPoissonProblem` -- adds a zeroth-order reaction term (Debye screening);
* :class:`AnisotropicPoissonProblem` -- a tensor / anisotropic principal coefficient.
They only refine the declared capabilities and the validation; the runtime / codegen treat
them as a :class:`FieldProblem`.
"""
from pops.math import Equation, principal_kinds
from .problem import FieldProblem
# An elliptic LHS must carry a principal operator: a Laplacian or a div(coeff*grad).
_PRINCIPAL_OPERATORS = {"laplacian", "div_coeff_grad"}
[docs]
class PoissonProblem(FieldProblem):
"""A standard Poisson problem ``-laplacian(phi) == rhs``.
Refines :class:`FieldProblem` by requiring the equation's principal operator to be an
elliptic Laplacian / div(coeff*grad) (so a non-elliptic equation is rejected up front).
"""
category = "poisson_problem"
def capabilities(self):
caps = super().capabilities()
caps["poisson"] = True
return caps
def validate(self, context=None):
super().validate(context)
if isinstance(self.equation, Equation):
if not (principal_kinds(self.equation.lhs) & _PRINCIPAL_OPERATORS):
raise ValueError(
"%s: a Poisson problem expects an elliptic principal operator on the "
"left-hand side (e.g. -laplacian(phi) == rhs or "
"-div(eps*grad(phi)) == rhs); got %r" % (self.name, self.equation.lhs))
return True
[docs]
class ScreenedPoissonProblem(PoissonProblem):
"""A screened Poisson problem ``-laplacian(phi) + k*phi == rhs`` (Debye screening).
Requires a zeroth-order reaction term (``k*phi``) on top of the principal operator.
"""
category = "screened_poisson_problem"
def capabilities(self):
caps = super().capabilities()
caps["screened"] = True
return caps
def validate(self, context=None):
super().validate(context)
if isinstance(self.equation, Equation):
if "reaction" not in principal_kinds(self.equation.lhs):
raise ValueError(
"%s: a screened Poisson expects a zeroth-order reaction term "
"(e.g. -laplacian(phi) + k*phi == rhs); got %r"
% (self.name, self.equation.lhs))
# Reject only a solver that declares it cannot serve a screened operator (criterion 11).
self._require_solver_capability("screened", "a screened operator", "GeometricMG()")
return True
[docs]
class AnisotropicPoissonProblem(PoissonProblem):
"""An anisotropic Poisson problem ``-div(A grad phi) == rhs`` (variable / tensor coefficient).
Requires a ``div(coeff*grad(phi))`` principal operator (the variable-coefficient form).
NOTE: the form is authorable + validated here, but lowering a variable / tensor
coefficient in the elliptic codegen is the coordinated follow-up (ADC-491); today the
native elliptic operator solves a constant-coefficient ``div(eps grad)``.
"""
category = "anisotropic_poisson_problem"
def capabilities(self):
caps = super().capabilities()
caps["anisotropic"] = True
return caps
def validate(self, context=None):
super().validate(context)
if isinstance(self.equation, Equation):
if "div_coeff_grad" not in principal_kinds(self.equation.lhs):
raise ValueError(
"%s: an anisotropic Poisson expects a div(coeff*grad(phi)) principal "
"operator (e.g. -div(eps*grad(phi)) == rhs); got %r"
% (self.name, self.equation.lhs))
# Reject only a solver that declares it cannot serve an anisotropic operator (criterion 11).
self._require_solver_capability(
"anisotropic", "an anisotropic operator", "GeometricMG()")
return True
__all__ = ["PoissonProblem", "ScreenedPoissonProblem", "AnisotropicPoissonProblem"]