Numerical namespaces¶
These modules keep the function and argument names of treams, so a call such
as treams.sw.translate(...) runs as
treams_rs.sw.translate(...) with the same arguments. They take and return
NumPy arrays, and most broadcast like NumPy ufuncs. Differentiable functions
also accept Advect, JAX, PyTorch and HIPS Autograd values directly, selecting
their native derivative adapter without another treams-rs import. The
differences page lists every case where
a result differs from treams; the
Python reference lists every signature.
The functions take the polarization convention as poltype="helicity" or
"parity" and the radial kind as singular=True (radiating waves) or
singular=False (regular waves), as in treams.
special¶
Mirrors treams.special: Bessel and Hankel functions, Legendre functions,
Wigner symbols, vector spherical, cylindrical and plane waves, translation
coefficients and coordinate transforms.
import numpy as np
from treams_rs import special
x = np.array([0.5, 2.0, 7.5])
np.testing.assert_allclose(
special.spherical_jn(1, x), np.sin(x) / x**2 - np.cos(x) / x, rtol=1e-13
)
np.testing.assert_allclose(
special.spherical_hankel1(0, x), -1j * np.exp(1j * x) / x, rtol=1e-13
)
On the NumPy path, Bessel, Hankel and wave functions return complex128, also
for real inputs. Framework results follow their adapter's precision. An
evaluation that treams returns as NaN raises ValueError; see
deliberate differences.
For example, HIPS Autograd differentiates a Bessel function through the same namespace. The integer order stays fixed:
import autograd.numpy as anp
from autograd import grad
from treams_rs import special
derivative = grad(lambda x: anp.real(special.jv(0, x)))(0.8)
assert anp.allclose(derivative, -special.jv(1, 0.8).real)
NumPy calls retain their ufunc options. Differentiated calls do not accept
mutable out or masked where outputs; use the framework's array operations
after the call. See framework support.
sw¶
Mirrors treams.sw: coefficients of spherical waves. In
sw.translate(lambda_, mu, pol, l, m, qol, kr, theta, phi), the first three
labels belong to the destination mode, the next three to the source mode, and
kr, theta, phi give the translation vector in spherical coordinates, its
length multiplied by the wavenumber.
import numpy as np
from treams_rs import sw
# A rotation by phi about z multiplies a mode of order m by exp(-i m phi).
np.testing.assert_allclose(sw.rotate(1, 1, 1, 1, 1, 1, 0.3), np.exp(-0.3j))
# A regular translation along z (theta = 0) keeps the order m.
assert sw.translate(1, 1, 1, 1, 0, 1, 2.0, 0.0, 0.0, singular=False) == 0
np.testing.assert_allclose(
sw.translate(1, 0, 1, 1, 0, 1, 0.0, 0.0, 0.0, singular=False), 1
)
cw¶
Mirrors treams.cw: coefficients of cylindrical waves, labelled by the axial
wavenumber kz, the order m and pol.
import numpy as np
from treams_rs import cw
kz, z = 0.5, 2.0
# A shift along the axis multiplies a cylindrical wave by exp(i kz z).
shifted = cw.translate(kz, 1, 1, kz, 1, 1, 0.0, 0.0, z, singular=False)
np.testing.assert_allclose(shifted, np.exp(1j * kz * z))
pw¶
Mirrors treams.pw: plane-wave phases, expansions into spherical and
cylindrical waves and coordinate permutations.
import numpy as np
from treams_rs import pw
k, r = np.array([1.0, 0.0, 2.0]), np.array([0.3, 0.0, 0.5])
np.testing.assert_allclose(pw.translate(*k, *r), np.exp(1j * k @ r))
lattice¶
Mirrors treams.lattice: lattice sums of spherical and cylindrical waves in one,
two and three dimensions, and lattice geometry. The lsum* functions evaluate
a sum with the Ewald method, which splits it into a real-space part
(realsum*) and a reciprocal-space part (recsum*); eta sets the split and
eta=0 picks it automatically. The arguments kpar (Bloch wavevector), a
(lattice vectors as rows) and r (shift) follow treams.
import numpy as np
from treams_rs import lattice
a = 1.7 * np.eye(2)
args = (2, 0, 1.3, [0.1, 0.0], a, [0.0, 0.0]) # l, m, k, kpar, a, r
split = lattice.realsumsw2d(*args, 0.6) + lattice.recsumsw2d(*args, 0.6)
np.testing.assert_allclose(lattice.lsumsw2d(*args, eta=0.6), split, rtol=1e-14)
np.testing.assert_allclose(lattice.lsumsw2d(*args), split, rtol=1e-12)
np.testing.assert_allclose(lattice.reciprocal(a), 2 * np.pi / 1.7 * np.eye(2))
coeffs¶
Mirrors treams.coeffs: Mie coefficients of layered spheres and cylinders and
Fresnel coefficients. mie(l, x, epsilon, mu, kappa) takes the size parameters
x = k0 r and the materials from the inside out, followed by the surrounding
medium. It takes one degree l per call, where treams broadcasts over degrees.
import numpy as np
from treams_rs import coeffs
t = coeffs.mie(1, [1.3 * 0.5], [4, 1], [1, 1], [0, 0]) # 2x2 helicity block
s = np.eye(2) + 2 * t
# A lossless sphere conserves energy: S = 1 + 2 T is unitary.
np.testing.assert_allclose(s.conj().T @ s, np.eye(2), atol=1e-14)
misc¶
Mirrors treams.misc: refractive indices, normal wavenumbers, mode selection
and reduction to the first Brillouin zone.
import numpy as np
from treams_rs import misc
# The two helicities see n - kappa and n + kappa.
np.testing.assert_allclose(misc.refractive_index(4, 1, 0.1), [1.9, 2.1])
np.testing.assert_allclose(misc.wave_vec_z(3, 0, 5), 4)
np.testing.assert_allclose(misc.wave_vec_z(0, 2, 1), 1j * np.sqrt(3)) # evanescent
ebcm¶
Mirrors treams.ebcm: surface integrals of the extended boundary condition
method for particles with rotational symmetry about z. qmat(r, dr, ks, zs,
out, in_) takes the radius r(theta), its derivative, the wavenumbers and
impedances (inside, then outside) and the destination and source modes.
import numpy as np
import treams_rs as tr
basis = tr.SphericalBasis.default(2)
media = [tr.Material(4), tr.Material(1)]
ks = [m.ks(1.3) for m in media]
zs = [m.impedance for m in media]
q = tr.ebcm.qmat(lambda _: 0.3, lambda _: 0, ks, zs, basis)
q_regular = tr.ebcm.qmat(lambda _: 0.3, lambda _: 0, ks, zs, basis, singular=False)
tmatrix = -np.linalg.solve(q, q_regular)
# For a sphere, EBCM reproduces the Mie T-matrix.
sphere = tr.sphere_tmatrix(k0=1.3, lmax=2, radius=0.3, material=4)
np.testing.assert_allclose(tmatrix, sphere.array, atol=1e-12)
qmat includes the radial factor of the surface element;
radial_area_factor=False reproduces treams, which omits it. order sets the
number of Gauss-Legendre nodes, where treams integrates adaptively. See the
differences page.
operators and PhysicsArray¶
Mirrors the operators of treams (treams.efield, treams.Rotate, ...) and
treams.PhysicsArray. efield and the other field operators, rotate,
translate, expand, expandlattice, changepoltype and permute return
explicit matrices. PhysicsArray holds an array with its basis, k0, material
and conventions, and provides these operations as methods:
import numpy as np
import treams_rs as tr
from treams_rs import operators
basis = tr.SphericalBasis.default(1)
array = operators.PhysicsArray(np.eye(6), basis=basis, k0=1.3)
rotation = array.rotate.eval(0.3, 0, 0) # Euler angles phi, theta, psi
np.testing.assert_allclose(np.diag(rotation), np.exp(-0.3j * basis.m))
larger = tr.SphericalBasis.default(2)
expansion = array.expand.eval(basis=larger)
np.testing.assert_allclose(expansion, operators.expand((larger, basis), k0=1.3))
assert isinstance(array * 2, np.ndarray) # arithmetic returns plain arrays
Arithmetic with a PhysicsArray returns plain arrays without metadata.
operators.expand and operators.expandlattice between spherical and cylindrical waves add the terms
of every pair of positions, where treams pairs only equal particle indices;
see spheres and cylinders at several positions.
Use the methods of TMatrix, Wave and SMatrix to retain physical metadata.