from __future__ import annotations
import numpy as np
import pytest
from rustmatrix import Scatterer as RsScatterer
pytestmark = pytest.mark.parity
@pytest.fixture(scope="module")
def PyScatterer():
pytmatrix = pytest.importorskip("pytmatrix.tmatrix")
return pytmatrix.Scatterer
def _compare(py, rs, s_tol=1e-4, z_tol=1e-4):
S_ref, Z_ref = py.get_SZ()
S_got, Z_got = rs.get_SZ()
np.testing.assert_allclose(S_got, S_ref, rtol=s_tol, atol=s_tol)
np.testing.assert_allclose(Z_got, Z_ref, rtol=z_tol, atol=z_tol)
@pytest.mark.parametrize(
"radius, wavelength, mrr, mri",
[
(0.5, 6.283185307, 1.33, 0.0),
(1.0, 6.283185307, 1.33, 0.01),
(2.0, 6.283185307, 1.5, 0.001),
],
)
def test_sphere_parity(PyScatterer, radius, wavelength, mrr, mri):
geom = (90.0, 90.0, 0.0, 180.0, 0.0, 0.0)
py = PyScatterer(radius=radius, wavelength=wavelength, axis_ratio=1.0,
m=complex(mrr, mri), ddelt=1e-4, ndgs=2)
py.set_geometry(geom)
rs = RsScatterer(radius=radius, wavelength=wavelength, axis_ratio=1.0,
m=complex(mrr, mri), ddelt=1e-4, ndgs=2)
rs.set_geometry(geom)
_compare(py, rs, s_tol=1e-3, z_tol=1e-3)
@pytest.mark.parametrize(
"radius, wavelength, axis_ratio",
[
(1.0, 6.283185307, 0.5), (1.0, 6.283185307, 2.0), (1.5, 6.283185307, 1.5),
],
)
def test_spheroid_parity(PyScatterer, radius, wavelength, axis_ratio):
geom = (90.0, 90.0, 0.0, 180.0, 0.0, 0.0)
m = complex(1.5, 0.01)
py = PyScatterer(radius=radius, wavelength=wavelength, axis_ratio=axis_ratio,
m=m, ddelt=1e-4, ndgs=2)
py.set_geometry(geom)
rs = RsScatterer(radius=radius, wavelength=wavelength, axis_ratio=axis_ratio,
m=m, ddelt=1e-4, ndgs=2)
rs.set_geometry(geom)
_compare(py, rs, s_tol=5e-3, z_tol=5e-3)
def test_cylinder_parity(PyScatterer):
geom = (90.0, 90.0, 0.0, 180.0, 0.0, 0.0)
m = complex(1.5, 0.01)
py = PyScatterer(radius=1.0, wavelength=6.283185307, axis_ratio=0.7,
shape=PyScatterer.SHAPE_CYLINDER, m=m, ddelt=1e-4, ndgs=2)
py.set_geometry(geom)
rs = RsScatterer(radius=1.0, wavelength=6.283185307, axis_ratio=0.7,
shape=RsScatterer.SHAPE_CYLINDER, m=m, ddelt=1e-4, ndgs=2)
rs.set_geometry(geom)
_compare(py, rs, s_tol=5e-3, z_tol=5e-3)
def test_psd_integration_parity(PyScatterer):
from pytmatrix import psd as py_psd
from rustmatrix import psd as rs_psd
geom = (90.0, 90.0, 0.0, 180.0, 0.0, 0.0)
m = complex(1.5, 0.5)
py = PyScatterer(wavelength=6.5, m=m, axis_ratio=1.0)
py.set_geometry(geom)
py.psd_integrator = py_psd.PSDIntegrator()
py.psd_integrator.num_points = 64
py.psd_integrator.D_max = 10.0
py.psd = py_psd.GammaPSD(D0=1.0, Nw=1e3, mu=4)
py.psd_integrator.init_scatter_table(py)
rs = RsScatterer(wavelength=6.5, m=m, axis_ratio=1.0)
rs.set_geometry(geom)
rs.psd_integrator = rs_psd.PSDIntegrator()
rs.psd_integrator.num_points = 64
rs.psd_integrator.D_max = 10.0
rs.psd = rs_psd.GammaPSD(D0=1.0, Nw=1e3, mu=4)
rs.psd_integrator.init_scatter_table(rs)
_compare(py, rs, s_tol=1e-3, z_tol=1e-3)
def test_radar_observables_backscatter_parity(PyScatterer):
from pytmatrix import radar as py_radar
from rustmatrix import radar as rs_radar
from rustmatrix.tmatrix_aux import dsr_thurai_2007, geom_horiz_back, wl_X
m = complex(7.94, 2.33)
D_eq = 2.0 axis_ratio = 1.0 / dsr_thurai_2007(D_eq)
py = PyScatterer(radius=D_eq / 2.0, wavelength=wl_X, m=m,
axis_ratio=axis_ratio, ddelt=1e-4, ndgs=2)
py.set_geometry(geom_horiz_back)
rs = RsScatterer(radius=D_eq / 2.0, wavelength=wl_X, m=m,
axis_ratio=axis_ratio, ddelt=1e-4, ndgs=2)
rs.set_geometry(geom_horiz_back)
np.testing.assert_allclose(
rs_radar.radar_xsect(rs, h_pol=True),
py_radar.radar_xsect(py, h_pol=True),
rtol=5e-3,
)
np.testing.assert_allclose(
rs_radar.radar_xsect(rs, h_pol=False),
py_radar.radar_xsect(py, h_pol=False),
rtol=5e-3,
)
np.testing.assert_allclose(rs_radar.Zdr(rs), py_radar.Zdr(py), rtol=5e-3)
np.testing.assert_allclose(
rs_radar.rho_hv(rs), py_radar.rho_hv(py), rtol=5e-3, atol=5e-3,
)
np.testing.assert_allclose(
rs_radar.delta_hv(rs), py_radar.delta_hv(py), atol=5e-3,
)
def test_radar_observables_forward_parity(PyScatterer):
from pytmatrix import radar as py_radar
from rustmatrix import radar as rs_radar
from rustmatrix.tmatrix_aux import dsr_thurai_2007, geom_horiz_forw, wl_X
m = complex(7.94, 2.33)
D_eq = 2.0
axis_ratio = 1.0 / dsr_thurai_2007(D_eq)
py = PyScatterer(radius=D_eq / 2.0, wavelength=wl_X, m=m,
axis_ratio=axis_ratio, ddelt=1e-4, ndgs=2)
py.set_geometry(geom_horiz_forw)
rs = RsScatterer(radius=D_eq / 2.0, wavelength=wl_X, m=m,
axis_ratio=axis_ratio, ddelt=1e-4, ndgs=2)
rs.set_geometry(geom_horiz_forw)
np.testing.assert_allclose(rs_radar.Kdp(rs), py_radar.Kdp(py), rtol=5e-3)
np.testing.assert_allclose(
rs_radar.Ai(rs, h_pol=True), py_radar.Ai(py, h_pol=True), rtol=5e-3,
)
np.testing.assert_allclose(
rs_radar.Ai(rs, h_pol=False), py_radar.Ai(py, h_pol=False), rtol=5e-3,
)
def test_refractive_mg_bruggeman_parity():
py_refr = pytest.importorskip("pytmatrix.refractive")
from rustmatrix import refractive as rs_refr
m_ice = complex(1.78, 2e-3)
m_air = complex(1.0, 0.0)
m_water = complex(7.94, 2.33)
for m_inc, frac in [(m_ice, 0.3), (m_water, 0.1), (m_ice, 0.6)]:
np.testing.assert_allclose(
rs_refr.mg_refractive((m_air, m_inc), (1 - frac, frac)),
py_refr.mg_refractive((m_air, m_inc), (1 - frac, frac)),
rtol=1e-10,
)
np.testing.assert_allclose(
rs_refr.bruggeman_refractive((m_air, m_inc), (1 - frac, frac)),
py_refr.bruggeman_refractive((m_air, m_inc), (1 - frac, frac)),
rtol=1e-10,
)
np.testing.assert_allclose(
rs_refr.mg_refractive((m_air, m_ice, m_water), (0.5, 0.3, 0.2)),
py_refr.mg_refractive((m_air, m_ice, m_water), (0.5, 0.3, 0.2)),
rtol=1e-10,
)
def test_orient_averaged_fixed_parity(PyScatterer):
from pytmatrix import orientation as py_orient
from rustmatrix import orientation as rs_orient
geom = (90.0, 90.0, 0.0, 180.0, 0.0, 0.0)
m = complex(1.5, 0.01)
py = PyScatterer(radius=1.0, wavelength=6.283185307, axis_ratio=2.0,
m=m, ddelt=1e-4, ndgs=2)
py.set_geometry(geom)
py.or_pdf = py_orient.gaussian_pdf(std=20.0, mean=90.0)
py.orient = py_orient.orient_averaged_fixed
py.n_alpha = 4
py.n_beta = 8
rs = RsScatterer(radius=1.0, wavelength=6.283185307, axis_ratio=2.0,
m=m, ddelt=1e-4, ndgs=2)
rs.set_geometry(geom)
rs.or_pdf = rs_orient.gaussian_pdf(std=20.0, mean=90.0)
rs.orient = rs_orient.orient_averaged_fixed
rs.n_alpha = 4
rs.n_beta = 8
_compare(py, rs, s_tol=5e-3, z_tol=5e-3)
def test_psd_integration_orient_averaged_parity(PyScatterer):
from pytmatrix import orientation as py_orient, psd as py_psd
from rustmatrix import orientation as rs_orient, psd as rs_psd
geom = (90.0, 90.0, 0.0, 180.0, 0.0, 0.0)
m = complex(1.5, 0.01)
py = PyScatterer(wavelength=6.283185307, m=m, axis_ratio=2.0,
ddelt=1e-4, ndgs=2)
py.set_geometry(geom)
py.or_pdf = py_orient.gaussian_pdf(std=20.0, mean=90.0)
py.orient = py_orient.orient_averaged_fixed
py.n_alpha = 4
py.n_beta = 8
py.psd_integrator = py_psd.PSDIntegrator()
py.psd_integrator.num_points = 16
py.psd_integrator.D_max = 4.0
py.psd = py_psd.GammaPSD(D0=1.0, Nw=1e3, mu=4)
py.psd_integrator.init_scatter_table(py)
rs = RsScatterer(wavelength=6.283185307, m=m, axis_ratio=2.0,
ddelt=1e-4, ndgs=2)
rs.set_geometry(geom)
rs.or_pdf = rs_orient.gaussian_pdf(std=20.0, mean=90.0)
rs.orient = rs_orient.orient_averaged_fixed
rs.n_alpha = 4
rs.n_beta = 8
rs.psd_integrator = rs_psd.PSDIntegrator()
rs.psd_integrator.num_points = 16
rs.psd_integrator.D_max = 4.0
rs.psd = rs_psd.GammaPSD(D0=1.0, Nw=1e3, mu=4)
rs.psd_integrator.init_scatter_table(rs)
_compare(py, rs, s_tol=5e-3, z_tol=5e-3)
def test_psd_integration_angular_parity(PyScatterer):
from pytmatrix import psd as py_psd
from rustmatrix import psd as rs_psd
geom = (90.0, 90.0, 0.0, 180.0, 0.0, 0.0)
m = complex(1.5, 0.01)
kw = dict(wavelength=6.283185307, m=m, axis_ratio=1.0,
ddelt=1e-4, ndgs=2)
py = PyScatterer(**kw)
py.set_geometry(geom)
py.psd_integrator = py_psd.PSDIntegrator()
py.psd_integrator.num_points = 8
py.psd_integrator.D_max = 4.0
py.psd = py_psd.GammaPSD(D0=1.0, Nw=1e3, mu=4)
py.psd_integrator.init_scatter_table(py, angular_integration=True)
rs = RsScatterer(**kw)
rs.set_geometry(geom)
rs.psd_integrator = rs_psd.PSDIntegrator()
rs.psd_integrator.num_points = 8
rs.psd_integrator.D_max = 4.0
rs.psd = rs_psd.GammaPSD(D0=1.0, Nw=1e3, mu=4)
rs.psd_integrator.init_scatter_table(rs, angular_integration=True)
for key in ("sca_xsect", "ext_xsect", "asym"):
for pol in ("h_pol", "v_pol"):
p = py.psd_integrator._angular_table[key][pol][geom]
r = rs.psd_integrator._angular_table[key][pol][geom]
np.testing.assert_allclose(r, p, rtol=1e-3, atol=1e-6)
def test_psd_integration_orient_adaptive_parity(PyScatterer):
from pytmatrix import orientation as py_orient, psd as py_psd
from rustmatrix import orientation as rs_orient, psd as rs_psd
geom = (90.0, 90.0, 0.0, 180.0, 0.0, 0.0)
m = complex(1.5, 0.01)
kw = dict(wavelength=6.283185307, m=m, axis_ratio=1.5,
ddelt=1e-4, ndgs=2)
py = PyScatterer(**kw)
py.set_geometry(geom)
py.or_pdf = py_orient.gaussian_pdf(std=20.0, mean=90.0)
py.orient = py_orient.orient_averaged_adaptive
py.psd_integrator = py_psd.PSDIntegrator()
py.psd_integrator.num_points = 4
py.psd_integrator.D_max = 3.0
py.psd = py_psd.GammaPSD(D0=1.0, Nw=1e3, mu=4)
py.psd_integrator.init_scatter_table(py)
rs = RsScatterer(**kw)
rs.set_geometry(geom)
rs.or_pdf = rs_orient.gaussian_pdf(std=20.0, mean=90.0)
rs.orient = rs_orient.orient_averaged_adaptive
rs.psd_integrator = rs_psd.PSDIntegrator()
rs.psd_integrator.num_points = 4
rs.psd_integrator.D_max = 3.0
rs.psd = rs_psd.GammaPSD(D0=1.0, Nw=1e3, mu=4)
rs.psd_integrator.init_scatter_table(rs)
_compare(py, rs, s_tol=5e-3, z_tol=5e-3)