Source code for qscat.dvr.spec
"""Grid specification dataclasses for the FEM-DVR-ECS radial grid.
See `docs/physics/femdvr-ecs.md` for the construction this mirrors (ported from
eMoScat's FemDvrEcsGrid.cpp; the method is Rescigno & McCurdy, Phys. Rev. A 62,
032706 (2000)).
"""
from __future__ import annotations
from dataclasses import dataclass, field
from qscat.exceptions import GridError
__all__ = ["ElementSpec", "GridSpec"]
[docs]
@dataclass
class ElementSpec:
"""One finite element of the radial grid.
`length` is the (real, positive) physical length of the element along the
unscaled radial coordinate. `angle_deg` is the exterior-complex-scaling
rotation angle (degrees); 0.0 means a real (unscaled) element.
"""
length: float
angle_deg: float = 0.0
[docs]
@dataclass
class GridSpec:
"""Full grid specification: quadrature order (shared by all elements),
the ordered list of elements, and the inner boundary x_min.
Validates that complex (ECS) elements form a contiguous tail at the end
of the element list -- eMoScat's `complex_negative` (ECS at the inner
boundary) is deliberately not supported here.
Computes the ECS pivot `R0 = x_min + sum(real element lengths)`, which by
construction sits exactly on an element boundary.
Caveat: using multiple *different* nonzero `angle_deg` values across tail
elements (a bent/graded ECS contour) is REJECTED because it has never
been validated here -- the validated, actually-used case is a single ECS
tail angle shared by all complex elements.
"""
quadrature: int
elements: list[ElementSpec]
x_min: float = 0.0
R0: float = field(init=False)
def __post_init__(self) -> None:
"""Validate the spec (quadrature >= 2, ordered elements) and derive `R0`."""
if self.quadrature < 2:
raise GridError("quadrature must be >= 2")
if not self.elements:
raise GridError("elements must be non-empty")
seen_complex = False
real_length_sum = 0.0
distinct_nonzero_angles: set[float] = set()
for el in self.elements:
if el.length <= 0.0:
raise GridError("element length must be positive")
if el.angle_deg != 0.0:
seen_complex = True
distinct_nonzero_angles.add(el.angle_deg)
else:
if seen_complex:
raise GridError(
"complex (ECS) elements must be contiguous at the end of "
"the element list; found a real element after a complex one"
)
real_length_sum += el.length
if len(distinct_nonzero_angles) > 1:
raise GridError(
"a bent/graded ECS tail (more than one distinct nonzero "
"angle_deg among the elements) is not validated and is "
"rejected; use a single uniform ECS tail angle"
)
self.R0 = self.x_min + real_length_sum