diff --git a/docs/api/fdtd.md b/docs/api/fdtd.md index f3e7ac2..2ceaf56 100644 --- a/docs/api/fdtd.md +++ b/docs/api/fdtd.md @@ -18,3 +18,7 @@ CPML derivatives use electric widths for forward differences and magnetic widths for backward differences. `cpml_params(..., dx=1.0)` specifies the uniform physical cell width normal to a face; arbitrary nonuniform PML grading is not implemented by this constructor. Scale the timestep consistently with the grid. + +`ln_R_per_layer` must be finite and strictly negative. Zero attenuation raises +`ValueError`; disable individual CPML faces with `None` in the parameter table +passed to `updates_with_cpml`. diff --git a/meanas/fdtd/pml.py b/meanas/fdtd/pml.py index 679bb25..976368a 100644 --- a/meanas/fdtd/pml.py +++ b/meanas/fdtd/pml.py @@ -52,7 +52,9 @@ def cpml_params( `+1` for the high-index face). dt: Timestep used by the Yee update. thickness: Number of Yee cells occupied by the CPML region. - ln_R_per_layer: Logarithmic attenuation target per layer. + ln_R_per_layer: Finite, strictly negative logarithmic attenuation target + per layer. Zero attenuation is not supported; disable a face with + ``None`` in the updates_with_cpml parameter table instead. epsilon_eff: Effective permittivity used when choosing the CPML scaling. mu_eff: Effective permeability used when choosing the CPML scaling. m: Polynomial grading exponent for `sigma` and `kappa`. @@ -82,6 +84,9 @@ def cpml_params( if epsilon_eff <= 0: raise ValueError('epsilon_eff must be positive') + if not numpy.isfinite(ln_R_per_layer) or ln_R_per_layer >= 0: + raise ValueError('ln_R_per_layer must be finite and negative; use None to disable a CPML face') + if not numpy.isfinite(dx) or dx <= 0: raise ValueError('dx must be finite and positive') diff --git a/meanas/test/test_numerical_feedback.py b/meanas/test/test_numerical_feedback.py index 7c15091..1658bb7 100644 --- a/meanas/test/test_numerical_feedback.py +++ b/meanas/test/test_numerical_feedback.py @@ -400,3 +400,10 @@ def test_scpml_builders_agree_including_real_profiles_and_touching_faces(shape, for family in range(2): for axis in range(3): assert_allclose(uniform[family][axis], explicit[family][axis], rtol=1e-14, atol=1e-14) + + +@pytest.mark.parametrize('attenuation', [0.0, -0.0, .1, np.inf, -np.inf, np.nan]) +@pytest.mark.parametrize('alpha', [0, .2]) +def test_cpml_rejects_zero_or_invalid_attenuation_before_coefficient_evaluation(attenuation, alpha): + with np.errstate(divide='raise', invalid='raise'), pytest.raises(ValueError, match='ln_R_per_layer must be finite and negative'): + pml.cpml_params(0, 1, .1, ln_R_per_layer=attenuation, cfs_alpha=alpha)