hyperlattice 0.6.0

A small Rust linear algebra library with hyperreal-backed scalar, vector, and matrix types.
Documentation
fn bench_complex_operations_for<F>(
    group: &mut BenchmarkGroup<'_, criterion::measurement::WallTime>,
    label: &str,
    make_scalar: F,
) where
    F: Copy + Fn(f64) -> Real,
{
    let lhs_cases = [
        Complex::new(make_scalar(3.0), make_scalar(4.0)),
        Complex::new(make_scalar(1.0e-9), make_scalar(-1.0e-9)),
        Complex::new(make_scalar(1.0e9), make_scalar(-1.0)),
        Complex::new(
            make_scalar(std::f64::consts::PI),
            make_scalar(-std::f64::consts::E),
        ),
    ];
    let rhs_cases = [
        Complex::new(make_scalar(1.5), make_scalar(-2.0)),
        Complex::new(make_scalar(-1.0e-9), make_scalar(2.0e-9)),
        Complex::new(make_scalar(-1.0e9), make_scalar(2.0)),
        Complex::new(
            make_scalar(std::f64::consts::SQRT_2),
            make_scalar(std::f64::consts::FRAC_1_PI),
        ),
    ];
    let real_cases = [
        make_scalar(2.0),
        make_scalar(1.0e-9),
        make_scalar(-1.0e9),
        make_scalar(std::f64::consts::PI),
    ];

    trace_dispatch_cases(format!("complex_ops/{label}/powi"), &lhs_cases, |value| {
        let _ = black_box(value.clone().powi(5).unwrap());
    });
    trace_dispatch_cases(
        format!("complex_ops/{label}/powi_negative_one"),
        &lhs_cases,
        |value| {
            let _ = black_box(value.clone().powi(-1).unwrap());
        },
    );
    trace_dispatch_cases(
        format!("complex_ops/{label}/powi_checked"),
        &lhs_cases,
        |value| {
            let _ = black_box(value.clone().powi_checked(5).unwrap());
        },
    );
    trace_dispatch_cases(
        format!("complex_ops/{label}/powi_checked_negative_one"),
        &lhs_cases,
        |value| {
            let _ = black_box(value.clone().powi_checked(-1).unwrap());
        },
    );

    group.bench_function(format!("{label}/zero"), |b| {
        b.iter(|| black_box(Complex::zero()))
    });
    group.bench_function(format!("{label}/one"), |b| {
        b.iter(|| black_box(Complex::one()))
    });
    group.bench_function(format!("{label}/i"), |b| {
        b.iter(|| black_box(Complex::i()))
    });
    group.bench_function(format!("{label}/free_i"), |b| {
        b.iter(|| black_box(Complex::i()))
    });
    group.bench_function(format!("{label}/conjugate"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| black_box(black_box(next_case(&lhs_cases, &cursor).clone()).conjugate()))
    });
    group.bench_function(format!("{label}/norm_squared"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| black_box(black_box(next_case(&lhs_cases, &cursor)).norm_squared()))
    });
    group.bench_function(format!("{label}/reciprocal"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| {
            black_box(
                black_box(next_case(&lhs_cases, &cursor).clone())
                    .reciprocal()
                    .unwrap(),
            )
        })
    });
    group.bench_function(format!("{label}/reciprocal_checked"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| {
            black_box(
                black_box(next_case(&lhs_cases, &cursor).clone())
                    .reciprocal_checked()
                    .unwrap(),
            )
        })
    });
    group.bench_function(format!("{label}/powi"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| {
            black_box(
                black_box(next_case(&lhs_cases, &cursor).clone())
                    .powi(5)
                    .unwrap(),
            )
        })
    });
    group.bench_function(format!("{label}/powi_negative_one"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| {
            black_box(
                black_box(next_case(&lhs_cases, &cursor).clone())
                    .powi(-1)
                    .unwrap(),
            )
        })
    });
    group.bench_function(format!("{label}/powi_checked"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| {
            black_box(
                black_box(next_case(&lhs_cases, &cursor).clone())
                    .powi_checked(5)
                    .unwrap(),
            )
        })
    });
    group.bench_function(format!("{label}/powi_checked_negative_one"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| {
            black_box(
                black_box(next_case(&lhs_cases, &cursor).clone())
                    .powi_checked(-1)
                    .unwrap(),
            )
        })
    });
    group.bench_function(format!("{label}/div_checked"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| {
            let index = cursor.get();
            cursor.set((index + 1) % lhs_cases.len());
            black_box(
                black_box(lhs_cases[index].clone())
                    .div_checked(black_box(rhs_cases[index].clone()))
                    .unwrap(),
            )
        })
    });
    group.bench_function(format!("{label}/div_real_checked"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| {
            let index = cursor.get();
            cursor.set((index + 1) % lhs_cases.len());
            black_box(
                black_box(lhs_cases[index].clone())
                    .div_real_checked(black_box(real_cases[index].clone()))
                    .unwrap(),
            )
        })
    });
    group.bench_function(format!("{label}/from_scalar"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| {
            black_box(Complex::from(black_box(
                next_case(&real_cases, &cursor).clone(),
            )))
        })
    });
    group.bench_function(format!("{label}/add"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| {
            let index = cursor.get();
            cursor.set((index + 1) % lhs_cases.len());
            black_box(black_box(lhs_cases[index].clone()) + black_box(rhs_cases[index].clone()))
        })
    });
    group.bench_function(format!("{label}/sub"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| {
            let index = cursor.get();
            cursor.set((index + 1) % lhs_cases.len());
            black_box(black_box(lhs_cases[index].clone()) - black_box(rhs_cases[index].clone()))
        })
    });
    group.bench_function(format!("{label}/neg"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| black_box(-black_box(next_case(&lhs_cases, &cursor).clone())))
    });
    group.bench_function(format!("{label}/mul"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| {
            let index = cursor.get();
            cursor.set((index + 1) % lhs_cases.len());
            black_box(black_box(lhs_cases[index].clone()) * black_box(rhs_cases[index].clone()))
        })
    });
    group.bench_function(format!("{label}/div"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| {
            let index = cursor.get();
            cursor.set((index + 1) % lhs_cases.len());
            black_box(
                (black_box(lhs_cases[index].clone()) / black_box(rhs_cases[index].clone()))
                    .unwrap(),
            )
        })
    });
    group.bench_function(format!("{label}/div_real"), |b| {
        let cursor = Cell::new(0);
        b.iter(|| {
            let index = cursor.get();
            cursor.set((index + 1) % lhs_cases.len());
            black_box(
                (black_box(lhs_cases[index].clone()) / black_box(real_cases[index].clone()))
                    .unwrap(),
            )
        })
    });
}

fn bench_complex_operations(c: &mut Criterion) {
    let mut group = c.benchmark_group("complex_ops");
    bench_complex_operations_for::<_>(
        &mut group,
        "hyperreal",
        s,
    );
    bench_complex_operations_for::<_>(&mut group, "hyperreal-rational", qr);
    bench_numerica_complex_operations(&mut group, "numerica128");
    bench_symbolica_complex_operations(&mut group, "symbolica");
    group.finish();
}

macro_rules! bench_external_complex_operations {
    ($engine:ident, $group:expr, $label:expr) => {{
        let ctx = $engine::Ctx::new(128);
        let lhs_cases = [
            $engine::Complex::new(&ctx, 3.0, 4.0),
            $engine::Complex::new(&ctx, 1.0e-9, -1.0e-9),
            $engine::Complex::new(&ctx, 1.0e9, -1.0),
            $engine::Complex::new(&ctx, std::f64::consts::PI, -std::f64::consts::E),
        ];
        let rhs_cases = [
            $engine::Complex::new(&ctx, 1.5, -2.0),
            $engine::Complex::new(&ctx, -1.0e-9, 2.0e-9),
            $engine::Complex::new(&ctx, -1.0e9, 2.0),
            $engine::Complex::new(&ctx, std::f64::consts::SQRT_2, std::f64::consts::FRAC_1_PI),
        ];
        let real_cases = [2.0, 1.0e-9, -1.0e9, std::f64::consts::PI].map(|value| ctx.f(value));

        $group.bench_function(format!("{}/zero", $label), |b| {
            b.iter(|| black_box($engine::Complex::zero(&ctx)))
        });
        $group.bench_function(format!("{}/one", $label), |b| {
            b.iter(|| black_box($engine::Complex::one(&ctx)))
        });
        $group.bench_function(format!("{}/i", $label), |b| {
            b.iter(|| black_box($engine::Complex::i(&ctx)))
        });
        $group.bench_function(format!("{}/free_i", $label), |b| {
            b.iter(|| black_box($engine::Complex::i(&ctx)))
        });
        $group.bench_function(format!("{}/conjugate", $label), |b| {
            let cursor = Cell::new(0);
            b.iter(|| black_box(next_case(&lhs_cases, &cursor).conjugate(&ctx)))
        });
        $group.bench_function(format!("{}/norm_squared", $label), |b| {
            let cursor = Cell::new(0);
            b.iter(|| black_box(next_case(&lhs_cases, &cursor).norm_squared(&ctx)))
        });
        for name in ["reciprocal", "reciprocal_checked"] {
            $group.bench_function(format!("{}/{}", $label, name), |b| {
                let cursor = Cell::new(0);
                b.iter(|| black_box(next_case(&lhs_cases, &cursor).reciprocal(&ctx)))
            });
        }
        for name in ["powi", "powi_checked"] {
            $group.bench_function(format!("{}/{}", $label, name), |b| {
                let cursor = Cell::new(0);
                b.iter(|| black_box(next_case(&lhs_cases, &cursor).powi(5, &ctx)))
            });
        }
        for name in ["powi_negative_one", "powi_checked_negative_one"] {
            $group.bench_function(format!("{}/{}", $label, name), |b| {
                let cursor = Cell::new(0);
                b.iter(|| black_box(next_case(&lhs_cases, &cursor).powi(1, &ctx).reciprocal(&ctx)))
            });
        }
        $group.bench_function(format!("{}/div_checked", $label), |b| {
            let cursor = Cell::new(0);
            b.iter(|| {
                let index = cursor.get();
                cursor.set((index + 1) % lhs_cases.len());
                black_box(lhs_cases[index].div(&rhs_cases[index], &ctx))
            })
        });
        $group.bench_function(format!("{}/div_real_checked", $label), |b| {
            let cursor = Cell::new(0);
            b.iter(|| {
                let index = cursor.get();
                cursor.set((index + 1) % lhs_cases.len());
                black_box(lhs_cases[index].div_real(&real_cases[index], &ctx))
            })
        });
        $group.bench_function(format!("{}/from_scalar", $label), |b| {
            let cursor = Cell::new(0);
            b.iter(|| {
                black_box($engine::Complex::from_scalar(
                    black_box(next_case(&real_cases, &cursor)),
                    &ctx,
                ))
            })
        });
        for name in ["add", "sub", "mul", "div"] {
            $group.bench_function(format!("{}/{}", $label, name), |b| {
                let cursor = Cell::new(0);
                b.iter(|| {
                    let index = cursor.get();
                    cursor.set((index + 1) % lhs_cases.len());
                    black_box(match name {
                        "add" => lhs_cases[index].add(&rhs_cases[index], &ctx),
                        "sub" => lhs_cases[index].sub(&rhs_cases[index], &ctx),
                        "mul" => lhs_cases[index].mul(&rhs_cases[index], &ctx),
                        _ => lhs_cases[index].div(&rhs_cases[index], &ctx),
                    })
                })
            });
        }
        $group.bench_function(format!("{}/neg", $label), |b| {
            let cursor = Cell::new(0);
            b.iter(|| black_box(next_case(&lhs_cases, &cursor).neg(&ctx)))
        });
        $group.bench_function(format!("{}/div_real", $label), |b| {
            let cursor = Cell::new(0);
            b.iter(|| {
                let index = cursor.get();
                cursor.set((index + 1) % lhs_cases.len());
                black_box(lhs_cases[index].div_real(&real_cases[index], &ctx))
            })
        });
    }};
}

fn bench_numerica_complex_operations(
    group: &mut BenchmarkGroup<'_, criterion::measurement::WallTime>,
    label: &str,
) {
    bench_external_complex_operations!(numerica_engine, group, label);
}

fn bench_symbolica_complex_operations(
    group: &mut BenchmarkGroup<'_, criterion::measurement::WallTime>,
    label: &str,
) {
    bench_external_complex_operations!(symbolica_engine, group, label);
}