use std::sync::{Arc, Mutex, PoisonError};
use super::cyl_pml::CylLayer;
use crate::axis::Axis;
use crate::constants::{EPS0, MU0, Z0};
use crate::engine::Coefficients;
use crate::engine::Engine;
use crate::engine::grid::Grid;
use crate::engine::kernel::flush;
use crate::engine::operator::{Hooks, Operator};
use crate::engine::slab::{SlabContext, SlabHooks, SlabMut, SlabPartition, SlabRef};
use crate::fdtd::{Cpml, Face};
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CpmlCoefficients {
pub rho: f64,
pub sigma: f64,
pub kappa: f64,
pub alpha: f64,
pub b: f64,
pub a: f64,
}
impl CpmlCoefficients {
pub fn new(p: &Cpml, rho: f64, delta: f64, dt: f64) -> Self {
Self::in_medium(p, rho, delta, dt, 1.0)
}
pub fn in_medium(p: &Cpml, rho: f64, delta: f64, dt: f64, n: f64) -> Self {
let g = rho.powf(p.grading_order);
let sigma = p.sigma_max(n * Z0, delta) * g;
let kappa = 1.0 + (p.kappa_max - 1.0) * g;
let alpha = p.alpha_max * (1.0 - rho);
let b = (-(sigma / kappa + alpha) * dt / EPS0).exp();
let den = sigma * kappa + kappa * kappa * alpha;
let a = if sigma > 0.0 && den > 0.0 {
sigma * (b - 1.0) / den
} else {
0.0
};
Self {
rho,
sigma,
kappa,
alpha,
b,
a,
}
}
}
#[derive(Debug, Clone)]
struct Slab {
comp: Axis,
other: Axis,
normal: Axis,
sign: f32,
lo: [usize; 3],
hi: [usize; 3],
b: Vec<f32>,
a: Vec<f32>,
k: Vec<f32>,
psi: Vec<f32>,
}
impl Slab {
fn new(
comp: Axis,
normal: Axis,
lo: [usize; 3],
hi: [usize; 3],
co: &[CpmlCoefficients],
) -> Self {
let (sign, other) = if normal == comp.next() {
(1.0, comp.prev())
} else {
(-1.0, comp.next())
};
Self {
comp,
other,
normal,
sign,
lo,
hi,
b: co.iter().map(|c| c.b as f32).collect(),
a: co.iter().map(|c| c.a as f32).collect(),
k: co.iter().map(|c| (1.0 / c.kappa - 1.0) as f32).collect(),
psi: Vec::new(),
}
}
fn clip(&self, x0: usize, x1: usize) -> Option<Self> {
let (lo0, hi0) = (self.lo[0].max(x0), self.hi[0].min(x1));
if lo0 >= hi0 || (0..3).any(|d| self.lo[d] >= self.hi[d]) {
return None;
}
let mut s = Self {
psi: Vec::new(),
..self.clone()
};
if self.normal == Axis::X {
let r = lo0 - self.lo[0]..hi0 - self.lo[0];
s.b = self.b[r.clone()].to_vec();
s.a = self.a[r.clone()].to_vec();
s.k = self.k[r].to_vec();
}
(s.lo[0], s.hi[0]) = (lo0, hi0);
let len = (0..3).map(|d| s.hi[d] - s.lo[d]).product();
s.psi = vec![0.0; len];
Some(s)
}
fn apply(
&mut self,
tgt: &mut SlabMut<'_>,
src: &SlabRef<'_>,
coef: &[f32],
zero: &[f32],
e_side: bool,
) {
let n = src.nodes();
let nz = n[2];
let pl = n[1] * nz;
let scale = if e_side { 1.0 } else { -1.0 };
let (c, sign, normal) = (self.other, self.sign, self.normal);
let (lo, hi) = (self.lo, self.hi);
let len = hi[2] - lo[2];
let mut q = 0;
for i in lo[0]..hi[0] {
let cur = src.plane(c, i);
let other: &[f32] = match (normal, e_side) {
(Axis::X, true) if i == 0 => zero, (Axis::X, true) => src.plane(c, i - 1),
(Axis::X, false) => src.plane(c, i + 1),
_ => cur,
};
let cf = &coef[i * pl..(i + 1) * pl];
let t = tgt.plane_mut(self.comp, i);
for j in lo[1]..hi[1] {
let o = j * nz + lo[2];
let psi = &mut self.psi[q..q + len];
q += len;
let (t, cf) = (&mut t[o..o + len], &cf[o..o + len]);
match normal {
Axis::X | Axis::Y => {
let z = if normal == Axis::X { i } else { j } - lo[normal.index()];
let (h, l) = match (normal, e_side) {
(Axis::X, true) => (&cur[o..o + len], &other[o..o + len]),
(Axis::X, false) => (&other[o..o + len], &cur[o..o + len]),
(_, true) if j == 0 => (&cur[o..o + len], &zero[..len]),
(_, true) => (&cur[o..o + len], &cur[o - nz..o - nz + len]),
(_, false) => (&cur[o + nz..o + nz + len], &cur[o..o + len]),
};
let co = [self.b[z], self.a[z], self.k[z]];
row_uniform(t, psi, cf, h, l, co, sign, scale);
}
Axis::Z => {
let (b, a, k) = (&self.b[..], &self.a[..], &self.k[..]);
if !e_side {
let (h, l) = (&cur[o + 1..o + 1 + len], &cur[o..o + len]);
row_graded(t, psi, cf, h, l, [b, a, k], sign, scale);
} else if lo[2] > 0 {
let (h, l) = (&cur[o..o + len], &cur[o - 1..o - 1 + len]);
row_graded(t, psi, cf, h, l, [b, a, k], sign, scale);
} else if len > 0 {
let (t0, t1) = t.split_at_mut(1);
let (p0, p1) = psi.split_at_mut(1);
let co0 = [&b[..1], &a[..1], &k[..1]];
row_graded(
t0,
p0,
&cf[..1],
&cur[o..o + 1],
&zero[..1],
co0,
sign,
scale,
);
let co1 = [&b[1..], &a[1..], &k[1..]];
let (h, l) = (&cur[o + 1..o + len], &cur[o..o + len - 1]);
row_graded(t1, p1, &cf[1..], h, l, co1, sign, scale);
}
}
}
}
}
}
}
#[inline]
#[allow(clippy::too_many_arguments, clippy::needless_range_loop)]
fn row_uniform(
t: &mut [f32],
psi: &mut [f32],
cf: &[f32],
h: &[f32],
l: &[f32],
co: [f32; 3],
sign: f32,
scale: f32,
) {
let [b, a, k] = co;
let n = t.len();
let (psi, cf, h, l) = (&mut psi[..n], &cf[..n], &h[..n], &l[..n]);
for q in 0..n {
let s = sign * (h[q] - l[q]);
psi[q] = flush(b * psi[q] + a * s);
t[q] = flush(t[q] + scale * cf[q] * (k * s + psi[q]));
}
}
#[inline]
#[allow(clippy::too_many_arguments, clippy::needless_range_loop)]
fn row_graded(
t: &mut [f32],
psi: &mut [f32],
cf: &[f32],
h: &[f32],
l: &[f32],
co: [&[f32]; 3],
sign: f32,
scale: f32,
) {
let [b, a, k] = co;
let n = t.len();
let (psi, cf, h, l) = (&mut psi[..n], &cf[..n], &h[..n], &l[..n]);
let (b, a, k) = (&b[..n], &a[..n], &k[..n]);
for q in 0..n {
let s = sign * (h[q] - l[q]);
psi[q] = flush(b[q] * psi[q] + a[q] * s);
t[q] = flush(t[q] + scale * cf[q] * (k[q] * s + psi[q]));
}
}
#[derive(Debug, Clone)]
struct Layer {
face: Face,
params: Cpml,
n: f64,
e: [Slab; 2],
h: [Slab; 2],
e_coeffs: Vec<CpmlCoefficients>,
h_coeffs: Vec<CpmlCoefficients>,
}
#[derive(Debug, Clone)]
pub struct CpmlBoundary {
nodes: [usize; 3],
dt: f64,
layers: Vec<Layer>,
cyl: Option<(Grid, usize)>,
parts: Option<Arc<CpmlParts>>,
}
#[derive(Debug)]
struct CpmlParts {
slabs: Vec<Mutex<Pieces>>,
zero: Vec<f32>,
}
#[derive(Debug, Default)]
struct Pieces {
e: Vec<Slab>,
h: Vec<Slab>,
cyl: Option<CylLayer>,
}
impl CpmlParts {
fn pieces(&self, slab: usize) -> std::sync::MutexGuard<'_, Pieces> {
self.slabs[slab]
.lock()
.unwrap_or_else(PoisonError::into_inner)
}
}
impl SlabHooks for CpmlParts {
fn post_h_slab(&self, i: &mut SlabMut<'_>, u: &SlabRef<'_>, ctx: &SlabContext<'_>) {
let mut p = self.pieces(ctx.slab);
for s in &mut p.h {
s.apply(i, u, ctx.coefficients.db(s.comp), &self.zero, false);
}
if let Some(c) = &mut p.cyl {
c.post_h(i, u, |a| ctx.coefficients.db(a), &self.zero);
}
}
fn post_e_slab(&self, u: &mut SlabMut<'_>, i: &SlabRef<'_>, ctx: &SlabContext<'_>) {
let mut p = self.pieces(ctx.slab);
for s in &mut p.e {
s.apply(u, i, ctx.coefficients.cb(s.comp), &self.zero, true);
}
if let Some(c) = &mut p.cyl {
c.post_e(u, i, |a| ctx.coefficients.cb(a), &self.zero);
}
}
}
impl CpmlBoundary {
pub fn new(engine: &Engine, layers: impl IntoIterator<Item = (Face, Cpml)>) -> Result<Self> {
let grid = engine.grid();
let co = engine.coefficients();
let dt = engine.dt();
Self::build(grid, dt, layers, |face, cells| {
layer_index(grid, &co, dt, face, cells)
})
}
pub fn with_grid(
grid: &Grid,
dt: f64,
layers: impl IntoIterator<Item = (Face, Cpml)>,
) -> Result<Self> {
Self::build(grid, dt, layers, |_, _| 1.0)
}
fn build(
grid: &Grid,
dt: f64,
layers: impl IntoIterator<Item = (Face, Cpml)>,
mut index: impl FnMut(Face, usize) -> f64,
) -> Result<Self> {
if !(dt.is_finite() && dt > 0.0) {
return Err(Error::Invalid(format!("CPML: invalid time step {dt}")));
}
let n = grid.nodes();
let mut cells = [0usize; 6];
let mut out = Vec::new();
let mut cyl = None;
for (face, params) in layers {
params.validate()?;
if cells[face.index()] != 0 {
return Err(Error::Invalid(format!("CPML: face {face} given twice")));
}
let p = face.axis();
let total = n[p.index()] - 1;
if params.cells > total {
return Err(Error::Invalid(format!(
"CPML on {face}: {} cells but the mesh has only {total} along {p}",
params.cells
)));
}
cells[face.index()] = params.cells;
if grid.is_cylindrical() && face == Face::XMax {
let inner = total - params.cells;
if grid.lines(Axis::X)[inner] <= 0.0 {
return Err(Error::Invalid(format!(
"PML on {face}: the layer of {} cells reaches the axis",
params.cells
)));
}
cyl = Some((grid.clone(), inner));
}
let n_face = index(face, params.cells);
out.push(Layer::new(grid, dt, face, params, n_face));
}
for p in Axis::ALL {
let (lo, hi) = (cells[2 * p.index()], cells[2 * p.index() + 1]);
if lo + hi > n[p.index()] - 1 {
return Err(Error::Invalid(format!(
"CPML: opposite layers along {p} ({lo} + {hi} cells) overlap"
)));
}
}
Ok(Self {
nodes: n,
dt,
layers: out,
cyl,
parts: None,
})
}
pub fn layers(&self) -> impl Iterator<Item = (Face, &Cpml)> + '_ {
self.layers.iter().map(|l| (l.face, &l.params))
}
pub fn refractive_index(&self, face: Face) -> Option<f64> {
self.layer(face).map(|l| l.n)
}
pub fn e_profile(&self, face: Face) -> Option<&[CpmlCoefficients]> {
self.layer(face).map(|l| &l.e_coeffs[..])
}
pub fn h_profile(&self, face: Face) -> Option<&[CpmlCoefficients]> {
self.layer(face).map(|l| &l.h_coeffs[..])
}
pub fn is_cylindrical_rho_max(&self) -> bool {
self.cyl.is_some()
}
pub fn plane_rho_max(mut self) -> Self {
self.cyl = None;
self
}
fn transverse(&self, e_side: bool, comp: Axis, normal: Axis) -> Option<[Vec<f32>; 3]> {
let len = self.nodes[normal.index()];
let mut out: [Vec<f32>; 3] = core::array::from_fn(|_| vec![0.0; len]);
let mut any = false;
for l in self.layers.iter().filter(|l| l.face.axis() == normal) {
let slabs = if e_side { &l.e } else { &l.h };
for s in slabs.iter().filter(|s| s.comp == comp) {
let r = s.lo[normal.index()]..s.hi[normal.index()];
out[0][r.clone()].copy_from_slice(&s.b);
out[1][r.clone()].copy_from_slice(&s.a);
out[2][r].copy_from_slice(&s.k);
any = true;
}
}
any.then_some(out)
}
fn layer(&self, face: Face) -> Option<&Layer> {
self.layers.iter().find(|l| l.face == face)
}
}
fn layer_index(grid: &Grid, co: &Coefficients, dt: f64, face: Face, cells: usize) -> f64 {
let p = face.axis();
let n = grid.nodes();
let np = n[p.index()];
let inner = if face.is_max() { np - 1 - cells } else { cells };
let lines = if face.is_max() {
inner..np
} else {
0..inner + 1
};
let cell_r = if face.is_max() {
inner..np - 1
} else {
0..inner
};
let (mut we, mut se, mut wh, mut sh) = (0.0, 0.0, 0.0, 0.0);
let (t1, t2) = p.others();
for comp in [t1, t2] {
let (ab, ac) = comp.others();
let (a, b, c) = (comp.index(), ab.index(), ac.index());
let (ca, cb) = (co.ca(comp), co.cb(comp));
let (da, db) = (co.da(comp), co.db(comp));
for i in 0..n[0] {
for j in 0..n[1] {
for k in 0..n[2] {
let node = [i, j, k];
let idx = grid.index(node);
let along = node[p.index()];
if lines.contains(&along) && node[a] + 1 < n[a] && cb[idx] != 0.0 {
let area = grid.dual_face_area(comp, node);
let l = grid.edge_length(comp, node);
let cap = dt * (1.0 + ca[idx] as f64) / (2.0 * cb[idx] as f64);
let vol = area * l;
we += vol;
se += vol * cap * l / (EPS0 * area);
}
if cell_r.contains(&along)
&& node[b] + 1 < n[b]
&& node[c] + 1 < n[c]
&& db[idx] != 0.0
{
let area = grid.face_area(comp, node);
let l = grid.dual_edge_length(comp, node);
let ind = dt * (1.0 + da[idx] as f64) / (2.0 * db[idx] as f64);
let vol = area * l;
wh += vol;
sh += vol * ind * l / (MU0 * area);
}
}
}
}
}
let eps = if we > 0.0 { se / we } else { 1.0 };
let mu = if wh > 0.0 { sh / wh } else { 1.0 };
let n_r = (eps * mu).sqrt();
if (n_r - 1.0).abs() < 1e-6 || !n_r.is_finite() || n_r <= 0.0 {
1.0
} else {
n_r
}
}
impl Layer {
fn new(grid: &Grid, dt: f64, face: Face, params: Cpml, n_face: f64) -> Self {
let p = face.axis();
let n = grid.nodes();
let np = n[p.index()];
let c = params.cells;
let inner = if face.is_max() { np - 1 - c } else { c };
let rho = |x: f64| ((x - inner as f64).abs() / c as f64).clamp(0.0, 1.0);
let e_idx: Vec<usize> = if face.is_max() {
(inner + 1..np).collect()
} else {
(0..inner).collect()
};
let h_idx: Vec<usize> = if face.is_max() {
(inner..np - 1).collect()
} else {
(0..inner).collect()
};
let node_delta = |k: usize| {
let lo = (k > 0).then(|| grid.delta(p, k - 1));
let hi = (k + 1 < np).then(|| grid.delta(p, k));
match (lo, hi) {
(Some(a), Some(b)) => 0.5 * (a + b),
(Some(a), None) | (None, Some(a)) => a,
(None, None) => unreachable!("grids have two lines per axis"),
}
};
let e_coeffs: Vec<_> = e_idx
.iter()
.map(|&k| {
CpmlCoefficients::in_medium(¶ms, rho(k as f64), node_delta(k), dt, n_face)
})
.collect();
let h_coeffs: Vec<_> = h_idx
.iter()
.map(|&k| {
let d = grid.delta(p, k);
CpmlCoefficients::in_medium(¶ms, rho(k as f64 + 0.5), d, dt, n_face)
})
.collect();
let range = |idx: &[usize]| {
(
idx.first().copied().unwrap_or(0),
idx.last().map_or(0, |&l| l + 1),
)
};
let (a1, a2) = p.others();
let e_slab = |comp: Axis| {
let (mut lo, mut hi) = ([0; 3], n);
hi[comp.index()] = n[comp.index()] - 1; (lo[p.index()], hi[p.index()]) = range(&e_idx);
Slab::new(comp, p, lo, hi, &e_coeffs)
};
let h_slab = |comp: Axis| {
let (mut lo, mut hi) = ([0; 3], n);
let o = if comp == a1 { a2 } else { a1 };
hi[o.index()] = n[o.index()] - 1; (lo[p.index()], hi[p.index()]) = range(&h_idx);
Slab::new(comp, p, lo, hi, &h_coeffs)
};
Self {
face,
params,
n: n_face,
e: [e_slab(a1), e_slab(a2)],
h: [h_slab(a1), h_slab(a2)],
e_coeffs,
h_coeffs,
}
}
}
impl Operator for CpmlBoundary {
fn hooks(&self) -> Hooks {
Hooks::NONE
}
fn slab_hooks(&mut self, part: &SlabPartition<'_>) -> Option<Arc<dyn SlabHooks>> {
assert_eq!(
part.grid().nodes(),
self.nodes,
"CPML built for another grid"
);
assert_eq!(part.dt(), self.dt, "CPML built for another time step");
let cyl = self.cyl.as_ref().map(|(grid, inner)| {
let l = self.layer(Face::XMax).expect("ρ_max layer");
let layer = CylLayer::new(grid, self.dt, &l.params, l.n, |e, c, d| {
self.transverse(e, c, d)
});
(layer, *inner)
});
let x_end = cyl.as_ref().map_or(self.nodes[0], |c| c.1);
let slabs = (0..part.slabs())
.map(|q| {
let r = part.planes(q);
let mut p = Pieces::default();
for l in &self.layers {
if cyl.is_some() && l.face == Face::XMax {
continue;
}
let end = r.end.min(x_end);
p.e.extend(l.e.iter().filter_map(|s| s.clip(r.start, end)));
p.h.extend(l.h.iter().filter_map(|s| s.clip(r.start, end)));
}
p.cyl = cyl.as_ref().map(|c| c.0.clip(r.start, r.end));
Mutex::new(p)
})
.collect();
let parts = Arc::new(CpmlParts {
slabs,
zero: vec![0.0; self.nodes[1] * self.nodes[2]],
});
self.parts = Some(Arc::clone(&parts));
Some(parts)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grading_positions() {
let g = Grid::uniform([20, 4, 4], [1e-3; 3]).unwrap();
let dt = 1e-12;
let p = Cpml::cells(4);
let b = CpmlBoundary::with_grid(&g, dt, [(Face::XMin, p), (Face::XMax, p)]).unwrap();
for face in [Face::XMin, Face::XMax] {
let e = b.e_profile(face).unwrap();
let h = b.h_profile(face).unwrap();
assert_eq!((e.len(), h.len()), (4, 4));
let er: Vec<f64> = e.iter().map(|c| c.rho).collect();
let hr: Vec<f64> = h.iter().map(|c| c.rho).collect();
let (mut ew, mut hw) = (vec![1.0, 0.75, 0.5, 0.25], vec![0.875, 0.625, 0.375, 0.125]);
if face.is_max() {
ew.reverse();
hw.reverse();
}
for (a, b) in er.iter().zip(&ew).chain(hr.iter().zip(&hw)) {
assert!((a - b).abs() < 1e-12, "{er:?} {hr:?}");
}
}
let c = b.e_profile(Face::XMin).unwrap()[0];
let smax = p.sigma_max(Z0, 1e-3);
assert!((c.sigma / smax - 1.0).abs() < 1e-12);
assert!((c.b - (-smax * dt / EPS0).exp()).abs() < 1e-15);
assert!((c.a - (c.b - 1.0)).abs() < 1e-15); let q = Cpml::cells(4).kappa_max(5.0).alpha_max(0.05);
let c = CpmlCoefficients::new(&q, 0.0, 1e-3, dt);
assert_eq!((c.sigma, c.kappa, c.a), (0.0, 1.0, 0.0));
assert!((c.alpha - 0.05).abs() < 1e-15);
let c = CpmlCoefficients::new(&q, 1.0, 1e-3, dt);
assert_eq!((c.kappa, c.alpha), (5.0, 0.0));
}
#[test]
fn graded_local_delta() {
let y = vec![0.0, 1e-3, 2e-3, 3e-3, 5e-3, 8e-3];
let g = Grid::new([vec![0.0, 1e-3], y, vec![0.0, 1e-3]]).unwrap();
let p = Cpml::cells(3);
let b = CpmlBoundary::with_grid(&g, 1e-12, [(Face::YMax, p)]).unwrap();
let h = b.h_profile(Face::YMax).unwrap();
assert!((h[2].rho - 2.5 / 3.0).abs() < 1e-12);
let want = p.sigma_max(Z0, 3e-3) * (2.5f64 / 3.0).powi(3);
assert!((h[2].sigma / want - 1.0).abs() < 1e-12);
let e = b.e_profile(Face::YMax).unwrap();
let want = p.sigma_max(Z0, 2.5e-3) * (2.0f64 / 3.0).powi(3);
assert!((e[1].sigma / want - 1.0).abs() < 1e-12);
assert!((e[2].sigma / p.sigma_max(Z0, 3e-3) - 1.0).abs() < 1e-12);
}
#[test]
fn layer_refractive_index() {
use crate::engine::{CellMaterials, Material};
let g = Grid::uniform([12, 6, 6], [1e-3; 3]).unwrap();
let build = |m: Material| {
let mats = CellMaterials::new(&g, m);
let e = Engine::builder(g.clone())
.materials(mats)
.threads(1)
.build()
.unwrap();
let p = Cpml::cells(4);
CpmlBoundary::new(&e, [(Face::XMin, p), (Face::ZMax, p)]).unwrap()
};
let b = build(Material::VACUUM);
assert_eq!(b.refractive_index(Face::XMin), Some(1.0));
assert_eq!(b.refractive_index(Face::YMin), None);
let vac = CpmlBoundary::with_grid(&g, 1e-12, [(Face::XMin, Cpml::cells(4))]).unwrap();
assert_eq!(vac.refractive_index(Face::XMin), Some(1.0));
let b = build(Material::dielectric(4.0));
let n = b.refractive_index(Face::ZMax).unwrap();
assert!((n - 2.0).abs() < 1e-6, "{n}");
let e = b.e_profile(Face::ZMax).unwrap().last().unwrap();
let want = Cpml::cells(4).sigma_max(Z0, 1e-3) / n;
assert!((e.sigma / want - 1.0).abs() < 1e-12);
let m = Material {
eps_r: [2.0; 3],
mu_r: [3.0; 3],
..Material::VACUUM
};
let n = build(m).refractive_index(Face::XMin).unwrap();
assert!((n - 6f64.sqrt()).abs() < 1e-6, "{n}");
}
#[test]
fn validation() {
let g = Grid::uniform([10, 4, 4], [1e-3; 3]).unwrap();
let ok = |l: Vec<(Face, Cpml)>| CpmlBoundary::with_grid(&g, 1e-12, l).is_ok();
assert!(ok(vec![
(Face::XMin, Cpml::cells(5)),
(Face::XMax, Cpml::cells(5))
]));
assert!(!ok(vec![
(Face::XMin, Cpml::cells(6)),
(Face::XMax, Cpml::cells(5))
]));
assert!(!ok(vec![(Face::YMin, Cpml::cells(5))]));
assert!(!ok(vec![
(Face::XMin, Cpml::cells(2)),
(Face::XMin, Cpml::cells(2))
]));
assert!(!ok(vec![(Face::XMin, Cpml::cells(0))]));
assert!(CpmlBoundary::with_grid(&g, 0.0, []).is_err());
}
}