Fri, 15 May 2026 14:46:30 -0500
Merge dev into default
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.gitignore Fri May 15 14:46:30 2026 -0500 @@ -0,0 +1,1 @@ +.hgignore \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Fri May 15 14:46:30 2026 -0500 @@ -0,0 +1,4 @@ +syntax: glob +target/ +Cargo.lock +**/*.orig
--- a/Cargo.lock Sun Apr 27 20:29:43 2025 -0500 +++ b/Cargo.lock Fri May 15 14:46:30 2026 -0500 @@ -4,7 +4,7 @@ [[package]] name = "alg_tools" -version = "0.3.2" +version = "0.4.1-dev" dependencies = [ "anyhow", "colored", @@ -15,6 +15,7 @@ "num", "num-traits", "numeric_literals", + "pyo3", "rayon", "rustc_version", "serde", @@ -122,6 +123,114 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] +name = "glam" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da" + +[[package]] +name = "glam" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3abb554f8ee44336b72d522e0a7fe86a29e09f839a36022fa869a7dfe941a54b" + +[[package]] +name = "glam" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16" + +[[package]] +name = "glam" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01732b97afd8508eee3333a541b9f7610f454bb818669e66e90f5f57c93a776" + +[[package]] +name = "glam" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525a3e490ba77b8e326fb67d4b44b4bd2f920f44d4cc73ccec50adc68e3bee34" + +[[package]] +name = "glam" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca" + +[[package]] +name = "glam" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f" + +[[package]] +name = "glam" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" + +[[package]] +name = "glam" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774" + +[[package]] +name = "glam" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c" + +[[package]] +name = "glam" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" + +[[package]] +name = "glam" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" + +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" + +[[package]] +name = "glam" +version = "0.30.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd47b05dddf0005d850e5644cae7f2b14ac3df487979dbfff3b56f20b1a6ae46" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + +[[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -165,12 +274,37 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] name = "nalgebra" -version = "0.33.2" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" +checksum = "c4d5b3eff5cd580f93da45e64715e8c20a3996342f1e466599cf7a267a0c2f5f" dependencies = [ "approx", + "glam 0.14.0", + "glam 0.15.2", + "glam 0.16.0", + "glam 0.17.3", + "glam 0.18.0", + "glam 0.19.0", + "glam 0.20.5", + "glam 0.21.3", + "glam 0.22.0", + "glam 0.23.0", + "glam 0.24.2", + "glam 0.25.0", + "glam 0.27.0", + "glam 0.28.0", + "glam 0.29.3", + "glam 0.30.9", "matrixmultiply", "nalgebra-macros", "num-complex", @@ -182,9 +316,9 @@ [[package]] name = "nalgebra-macros" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" +checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889" dependencies = [ "proc-macro2", "quote", @@ -275,12 +409,24 @@ ] [[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] name = "proc-macro2" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -290,6 +436,67 @@ ] [[package]] +name = "pyo3" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a6df7eab65fc7bee654a421404947e10a0f7085b6951bf2ea395f4659fb0cf" +dependencies = [ + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f77d387774f6f6eec64a004eac0ed525aab7fa1966d94b42f743797b3e395afb" +dependencies = [ + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd13844a4242793e02df3e2ec093f540d948299a6a77ea9ce7afd8623f542be" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf8f9f1108270b90d3676b8679586385430e5c0bb78bb5f043f95499c821a71" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a3b2274450ba5288bc9b8c1b69ff569d1d61189d4bff38f8d22e03d17f932b" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn 2.0.90", +] + +[[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -422,6 +629,12 @@ ] [[package]] +name = "target-lexicon" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" + +[[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -434,6 +647,12 @@ checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + +[[package]] name = "wide" version = "0.7.30" source = "registry+https://github.com/rust-lang/crates.io-index"
--- a/Cargo.toml Sun Apr 27 20:29:43 2025 -0500 +++ b/Cargo.toml Fri May 15 14:46:30 2026 -0500 @@ -1,6 +1,6 @@ [package] name = "alg_tools" -version = "0.3.2" +version = "0.4.1-dev" edition = "2021" rust-version = "1.85" authors = ["Tuomo Valkonen <tuomov@iki.fi>"] @@ -20,7 +20,7 @@ [dependencies] serde = { version = "1.0", features = ["derive"] } csv = "~1.3.1" -nalgebra = "~0.33.0" +nalgebra = "~0.34.0" num-traits = { version = "~0.2.14", features = ["std"] } colored = "~2.1.0" num = "~0.4.0" @@ -31,11 +31,11 @@ rayon = "1.5.3" simba = "0.9.0" anyhow = "1.0.95" +pyo3 = { version = "~0.27.0", optional = true } [package.metadata.docs.rs] rustdoc-args = ["--html-in-header", "katex-header.html"] - [profile.release] debug = true @@ -45,6 +45,7 @@ # The nightly feature enables some additional features. # Nightly-based optimisations are decided automatically by build.rs. nightly = [] +pyo3 = ["dep:pyo3"] [build-dependencies] rustc_version = "0.4"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rustfmt.toml Fri May 15 14:46:30 2026 -0500 @@ -0,0 +1,3 @@ +overflow_delimited_expr = true +struct_lit_width = 80 +
--- a/src/bisection_tree.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/bisection_tree.rs Fri May 15 14:46:30 2026 -0500 @@ -14,8 +14,10 @@ value of the sum, each $f_k$ also needs to implement [`Mapping`][crate::mapping::Mapping]. Moreover, the sum needs to be represented by a [`SupportGenerator`] that associates to a low-storage-requirement identifier (typically `usize`) an object of the type that represents -$f_k$. [`BTFN`]s support basic vector space operations, and [minimisation][BTFN::minimise] and -[maximisation][BTFN::maximise] via a [branch-and-bound strategy][BTSearch::search_and_refine]. +$f_k$. [`BTFN`]s support basic vector space operations, and +[minimisation][crate::bounds::MinMaxMapping::minimise] and +[maximisation][crate::bounds::MinMaxMapping::maximise] +via a [branch-and-bound strategy][BTSearch::search_and_refine]. The nodes of a bisection tree also store aggregate information about the objects stored in the tree via an [`Aggregator`]. This way, rough upper and lower [bound][Bounds] estimates on @@ -42,10 +44,12 @@ a [`SupportGenerator`]. They can be summed and multipliced by a schalar using standard arithmetic operations. The types of the objects in two summed `BTFN`s do not need to be the same. To find an approximate minimum of a `BTFN` using a branch-and-bound strategy, -use [`BTFN::minimise`]. [`Bounded::bounds`] provides a shortcut to [`GlobalAnalysis`] with the +use [`crate::bounds::MinMaxMapping::minimise`]. +[`crate::bounds::Bounded::bounds`] provides a shortcut to [`GlobalAnalysis`] with the [`Bounds`] aggregator. If the rough bounds so obtained do not indicate that the `BTFN` is in some given bounds, instead of doing a full minimisation and maximisation for higher quality bounds, -it is more efficient to use [`BTFN::has_upper_bound`] and [`BTFN::has_lower_bound`]. +it is more efficient to use [`crate::bounds::MinMaxMapping::has_upper_bound`] and +[`crate::bounds::MinMaxMapping::has_lower_bound`]. */ mod supportid;
--- a/src/bisection_tree/aggregator.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/bisection_tree/aggregator.rs Fri May 15 14:46:30 2026 -0500 @@ -2,9 +2,8 @@ Aggregation / summarisation of information in branches of bisection trees. */ +pub use crate::bounds::Bounds; use crate::types::*; -use crate::sets::Set; -use crate::instance::Instance; /// Trait for aggregating information about a branch of a [bisection tree][super::BT]. /// @@ -19,180 +18,60 @@ /// of a function on a greater domain from bounds on subdomains /// (in practise [`Cube`][crate::sets::Cube]s). /// -pub trait Aggregator : Clone + Sync + Send + 'static + std::fmt::Debug { +pub trait Aggregator: Clone + Sync + Send + 'static + std::fmt::Debug { /// Aggregate a new data to current state. - fn aggregate<I>(&mut self, aggregates : I) - where I : Iterator<Item=Self>; + fn aggregate<I>(&mut self, aggregates: I) + where + I: Iterator<Item = Self>; /// Summarise several other aggregators, resetting current state. - fn summarise<'a, I>(&'a mut self, aggregates : I) - where I : Iterator<Item=&'a Self>; + fn summarise<'a, I>(&'a mut self, aggregates: I) + where + I: Iterator<Item = &'a Self>; /// Create a new “empty” aggregate data. fn new() -> Self; } /// An [`Aggregator`] that doesn't aggregate anything. -#[derive(Clone,Debug)] +#[derive(Clone, Debug)] pub struct NullAggregator; impl Aggregator for NullAggregator { - fn aggregate<I>(&mut self, _aggregates : I) - where I : Iterator<Item=Self> {} - - fn summarise<'a, I>(&'a mut self, _aggregates : I) - where I : Iterator<Item=&'a Self> {} - - fn new() -> Self { NullAggregator } -} - -/// Upper and lower bounds on an `F`-valued function. -#[derive(Copy,Clone,Debug)] -pub struct Bounds<F>( - /// Lower bound - pub F, - /// Upper bound - pub F -); - -impl<F : Copy> Bounds<F> { - /// Returns the lower bound - #[inline] - pub fn lower(&self) -> F { self.0 } - - /// Returns the upper bound - #[inline] - pub fn upper(&self) -> F { self.1 } -} - -impl<F : Float> Bounds<F> { - /// Returns a uniform bound. - /// - /// This is maximum over the absolute values of the upper and lower bound. - #[inline] - pub fn uniform(&self) -> F { - let &Bounds(lower, upper) = self; - lower.abs().max(upper.abs()) + fn aggregate<I>(&mut self, _aggregates: I) + where + I: Iterator<Item = Self>, + { } - /// Construct a bounds, making sure `lower` bound is less than `upper` - #[inline] - pub fn corrected(lower : F, upper : F) -> Self { - if lower <= upper { - Bounds(lower, upper) - } else { - Bounds(upper, lower) - } + fn summarise<'a, I>(&'a mut self, _aggregates: I) + where + I: Iterator<Item = &'a Self>, + { } - /// Refine the lower bound - #[inline] - pub fn refine_lower(&self, lower : F) -> Self { - let &Bounds(l, u) = self; - debug_assert!(l <= u); - Bounds(l.max(lower), u.max(lower)) - } - - /// Refine the lower bound - #[inline] - pub fn refine_upper(&self, upper : F) -> Self { - let &Bounds(l, u) = self; - debug_assert!(l <= u); - Bounds(l.min(upper), u.min(upper)) + fn new() -> Self { + NullAggregator } } -impl<'a, F : Float> std::ops::Add<Self> for Bounds<F> { - type Output = Self; - #[inline] - fn add(self, Bounds(l2, u2) : Self) -> Self::Output { - let Bounds(l1, u1) = self; - debug_assert!(l1 <= u1 && l2 <= u2); - Bounds(l1 + l2, u1 + u2) - } -} - -impl<'a, F : Float> std::ops::Mul<Self> for Bounds<F> { - type Output = Self; - #[inline] - fn mul(self, Bounds(l2, u2) : Self) -> Self::Output { - let Bounds(l1, u1) = self; - debug_assert!(l1 <= u1 && l2 <= u2); - let a = l1 * l2; - let b = u1 * u2; - // The order may flip when negative numbers are involved, so need min/max - Bounds(a.min(b), a.max(b)) - } -} - -impl<F : Float> std::iter::Product for Bounds<F> { +impl<F: Float> Aggregator for Bounds<F> { #[inline] - fn product<I>(mut iter: I) -> Self - where I: Iterator<Item = Self> { - match iter.next() { - None => Bounds(F::ZERO, F::ZERO), - Some(init) => iter.fold(init, |a, b| a*b) - } - } -} - -impl<F : Float> Set<F> for Bounds<F> { - fn contains<I : Instance<F>>(&self, item : I) -> bool { - let v = item.own(); - let &Bounds(l, u) = self; - debug_assert!(l <= u); - l <= v && v <= u - } -} - -impl<F : Float> Bounds<F> { - /// Calculate a common bound (glb, lub) for two bounds. - #[inline] - pub fn common(&self, &Bounds(l2, u2) : &Self) -> Self { - let &Bounds(l1, u1) = self; - debug_assert!(l1 <= u1 && l2 <= u2); - Bounds(l1.min(l2), u1.max(u2)) - } - - /// Indicates whether `Self` is a superset of the argument bound. - #[inline] - pub fn superset(&self, &Bounds(l2, u2) : &Self) -> bool { - let &Bounds(l1, u1) = self; - debug_assert!(l1 <= u1 && l2 <= u2); - l1 <= l2 && u2 <= u1 - } - - /// Returns the greatest bound contained by both argument bounds, if one exists. - #[inline] - pub fn glb(&self, &Bounds(l2, u2) : &Self) -> Option<Self> { - let &Bounds(l1, u1) = self; - debug_assert!(l1 <= u1 && l2 <= u2); - let l = l1.max(l2); - let u = u1.min(u2); - debug_assert!(l <= u); - if l < u { - Some(Bounds(l, u)) - } else { - None - } - } -} - -impl<F : Float> Aggregator for Bounds<F> { - #[inline] - fn aggregate<I>(&mut self, aggregates : I) - where I : Iterator<Item=Self> { + fn aggregate<I>(&mut self, aggregates: I) + where + I: Iterator<Item = Self>, + { *self = aggregates.fold(*self, |a, b| a + b); } #[inline] - fn summarise<'a, I>(&'a mut self, mut aggregates : I) - where I : Iterator<Item=&'a Self> { + fn summarise<'a, I>(&'a mut self, mut aggregates: I) + where + I: Iterator<Item = &'a Self>, + { *self = match aggregates.next() { None => Bounds(F::ZERO, F::ZERO), // No parts in this cube; the function is zero - Some(&bounds) => { - aggregates.fold(bounds, |a, b| a.common(b)) - } + Some(&bounds) => aggregates.fold(bounds, |a, b| a.common(b)), } }
--- a/src/bisection_tree/bt.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/bisection_tree/bt.rs Fri May 15 14:46:30 2026 -0500 @@ -1,34 +1,28 @@ - /*! Bisection tree basics, [`BT`] type and the [`BTImpl`] trait. */ -use std::slice::IterMut; -use std::iter::once; -use std::sync::Arc; -use serde::{Serialize, Deserialize}; +use itertools::izip; pub(super) use nalgebra::Const; -use itertools::izip; +use serde::{Deserialize, Serialize}; +use std::iter::once; +use std::slice::IterMut; +use std::sync::Arc; -use crate::types::{Float, Num}; -use crate::parallelism::{with_task_budget, TaskBudget}; +use super::aggregator::*; +use super::support::*; use crate::coefficients::pow; -use crate::maputil::{ - array_init, - map2, - map2_indexed, - collect_into_array_unchecked -}; +use crate::loc::Loc; +use crate::maputil::{array_init, collect_into_array_unchecked, map2, map2_indexed}; +use crate::parallelism::{with_task_budget, TaskBudget}; use crate::sets::Cube; -use crate::loc::Loc; -use super::support::*; -use super::aggregator::*; +use crate::types::{Float, Num}; /// An enum that indicates whether a [`Node`] of a [`BT`] is uninitialised, leaf, or branch. /// /// For the type and const parametere, see the [module level documentation][super]. -#[derive(Clone,Debug)] -pub(super) enum NodeOption<F : Num, D, A : Aggregator, const N : usize, const P : usize> { +#[derive(Clone, Debug)] +pub(super) enum NodeOption<F: Num, D, A: Aggregator, const N: usize, const P: usize> { /// Indicates an uninitilised node; may become a branch or a leaf. // TODO: Could optimise Uninitialised away by simply treat Leaf with an empty Vec as // something that can be still replaced with Branches. @@ -44,39 +38,41 @@ /// /// For the type and const parameteres, see the [module level documentation][super]. #[derive(Clone, Debug)] -pub struct Node<F : Num, D, A : Aggregator, const N : usize, const P : usize> { +pub struct Node<F: Num, D, A: Aggregator, const N: usize, const P: usize> { /// The data or branches under the node. - pub(super) data : NodeOption<F, D, A, N, P>, + pub(super) data: NodeOption<F, D, A, N, P>, /// Aggregator for `data`. - pub(super) aggregator : A, + pub(super) aggregator: A, } /// Branching information of a [`Node`] of a [`BT`] bisection tree into `P` subnodes. /// /// For the type and const parameters, see the [module level documentation][super]. #[derive(Clone, Debug)] -pub(super) struct Branches<F : Num, D, A : Aggregator, const N : usize, const P : usize> { +pub(super) struct Branches<F: Num, D, A: Aggregator, const N: usize, const P: usize> { /// Point for subdivision of the (unstored) [`Cube`] corresponding to the node. - pub(super) branch_at : Loc<F, N>, + pub(super) branch_at: Loc<N, F>, /// Subnodes - pub(super) nodes : [Node<F, D, A, N, P>; P], + pub(super) nodes: [Node<F, D, A, N, P>; P], } /// Dirty workaround to broken Rust drop, see [https://github.com/rust-lang/rust/issues/58068](). -impl<F : Num, D, A : Aggregator, const N : usize, const P : usize> -Drop for Node<F, D, A, N, P> { +impl<F: Num, D, A: Aggregator, const N: usize, const P: usize> Drop for Node<F, D, A, N, P> { fn drop(&mut self) { use NodeOption as NO; - let process = |brc : Arc<Branches<F, D, A, N, P>>, - to_drop : &mut Vec<Arc<Branches<F, D, A, N, P>>>| { + let process = |brc: Arc<Branches<F, D, A, N, P>>, + to_drop: &mut Vec<Arc<Branches<F, D, A, N, P>>>| { // We only drop Branches if we have the only strong reference. // FIXME: update the RwLocks on Nodes. - Arc::try_unwrap(brc).ok().map(|branches| branches.nodes.map(|mut node| { - if let NO::Branches(brc2) = std::mem::replace(&mut node.data, NO::Uninitialised) { - to_drop.push(brc2) - } - })); + Arc::try_unwrap(brc).ok().map(|branches| { + branches.nodes.map(|mut node| { + if let NO::Branches(brc2) = std::mem::replace(&mut node.data, NO::Uninitialised) + { + to_drop.push(brc2) + } + }) + }); }; // We mark Self as NodeOption::Uninitialised, extracting the real contents. @@ -98,9 +94,9 @@ /// Trait for the depth of a [`BT`]. /// /// This will generally be either a runtime [`DynamicDepth`] or compile-time [`Const`] depth. -pub trait Depth : 'static + Copy + Send + Sync + std::fmt::Debug { +pub trait Depth: 'static + Copy + Send + Sync + std::fmt::Debug { /// Lower depth type. - type Lower : Depth; + type Lower: Depth; /// Returns a lower depth, if there still is one. fn lower(&self) -> Option<Self::Lower>; @@ -113,26 +109,26 @@ } /// Dynamic (runtime) [`Depth`] for a [`BT`]. -#[derive(Copy,Clone,Debug,Serialize,Deserialize)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct DynamicDepth( /// The depth - pub u8 + pub u8, ); impl Depth for DynamicDepth { type Lower = Self; #[inline] fn lower(&self) -> Option<Self> { - if self.0>0 { - Some(DynamicDepth(self.0-1)) - } else { + if self.0 > 0 { + Some(DynamicDepth(self.0 - 1)) + } else { None } } #[inline] fn lower_or(&self) -> Self { - DynamicDepth(if self.0>0 { self.0 - 1 } else { 0 }) + DynamicDepth(if self.0 > 0 { self.0 - 1 } else { 0 }) } #[inline] @@ -143,9 +139,15 @@ impl Depth for Const<0> { type Lower = Self; - fn lower(&self) -> Option<Self::Lower> { None } - fn lower_or(&self) -> Self::Lower { Const } - fn value(&self) -> u32 { 0 } + fn lower(&self) -> Option<Self::Lower> { + None + } + fn lower_or(&self) -> Self::Lower { + Const + } + fn value(&self) -> u32 { + 0 + } } macro_rules! impl_constdepth { @@ -165,7 +167,7 @@ /// The const parameter `P` from the [module level documentation][super] is required to satisfy /// `Const<P> : Branchcount<N>`. /// This trait is implemented for `P=pow(2, N)` for small `N`. -pub trait BranchCount<const N : usize> {} +pub trait BranchCount<const N: usize> {} macro_rules! impl_branchcount { ($($n:literal)*) => { $( impl BranchCount<$n> for Const<{pow(2, $n)}>{} @@ -173,18 +175,19 @@ } impl_branchcount!(1 2 3 4 5 6 7 8); -impl<F : Float, D, A, const N : usize, const P : usize> Branches<F,D,A,N,P> -where Const<P> : BranchCount<N>, - A : Aggregator +impl<F: Float, D, A, const N: usize, const P: usize> Branches<F, D, A, N, P> +where + Const<P>: BranchCount<N>, + A: Aggregator, { /// Returns the index in {0, …, `P`-1} for the branch to which the point `x` corresponds. /// /// This only takes the branch subdivision point $d$ into account, so is always succesfull. /// Thus, for this point, each branch corresponds to a quadrant of $ℝ^N$ relative to $d$. - fn get_node_index(&self, x : &Loc<F, N>) -> usize { - izip!(0..P, x.iter(), self.branch_at.iter()).map(|(i, x_i, branch_i)| - if x_i > branch_i { 1<<i } else { 0 } - ).sum() + fn get_node_index(&self, x: &Loc<N, F>) -> usize { + izip!(0..P, x.iter(), self.branch_at.iter()) + .map(|(i, x_i, branch_i)| if x_i > branch_i { 1 << i } else { 0 }) + .sum() } /// Returns the node within `Self` containing the point `x`. @@ -192,37 +195,37 @@ /// This only takes the branch subdivision point $d$ into account, so is always succesfull. /// Thus, for this point, each branch corresponds to a quadrant of $ℝ^N$ relative to $d$. #[inline] - fn get_node(&self, x : &Loc<F,N>) -> &Node<F,D,A,N,P> { - &self.nodes[self.get_node_index(x)] + fn get_node(&self, x: &Loc<N, F>) -> &Node<F, D, A, N, P> { + &self.nodes[self.get_node_index(x)] } } /// An iterator over the $P=2^N$ subcubes of a [`Cube`] subdivided at a point `d`. -pub(super) struct SubcubeIter<'b, F : Float, const N : usize, const P : usize> { - domain : &'b Cube<F, N>, - branch_at : Loc<F, N>, - index : usize, +pub(super) struct SubcubeIter<'b, F: Float, const N: usize, const P: usize> { + domain: &'b Cube<N, F>, + branch_at: Loc<N, F>, + index: usize, } /// Returns the `i`:th subcube of `domain` subdivided at `branch_at`. #[inline] -fn get_subcube<F : Float, const N : usize>( - branch_at : &Loc<F, N>, - domain : &Cube<F, N>, - i : usize -) -> Cube<F, N> { +fn get_subcube<F: Float, const N: usize>( + branch_at: &Loc<N, F>, + domain: &Cube<N, F>, + i: usize, +) -> Cube<N, F> { map2_indexed(branch_at, domain, move |j, &branch, &[start, end]| { if i & (1 << j) != 0 { [branch, end] } else { [start, branch] } - }).into() + }) + .into() } -impl<'a, 'b, F : Float, const N : usize, const P : usize> Iterator -for SubcubeIter<'b, F, N, P> { - type Item = Cube<F, N>; +impl<'a, 'b, F: Float, const N: usize, const P: usize> Iterator for SubcubeIter<'b, F, N, P> { + type Item = Cube<N, F>; #[inline] fn next(&mut self) -> Option<Self::Item> { if self.index < P { @@ -235,30 +238,33 @@ } } -impl<F : Float, D, A, const N : usize, const P : usize> -Branches<F,D,A,N,P> -where Const<P> : BranchCount<N>, - A : Aggregator, - D : 'static + Copy + Send + Sync { - +impl<F: Float, D, A, const N: usize, const P: usize> Branches<F, D, A, N, P> +where + Const<P>: BranchCount<N>, + A: Aggregator, + D: 'static + Copy + Send + Sync, +{ /// Creates a new node branching structure, subdividing `domain` based on the /// [hint][Support::support_hint] of `support`. - pub(super) fn new_with<S : LocalAnalysis <F, A, N>>( - domain : &Cube<F,N>, - support : &S + pub(super) fn new_with<S: LocalAnalysis<F, A, N> + Support<N, F>>( + domain: &Cube<N, F>, + support: &S, ) -> Self { let hint = support.bisection_hint(domain); let branch_at = map2(&hint, domain, |h, r| { - h.unwrap_or_else(|| (r[0]+r[1])/F::TWO).max(r[0]).min(r[1]) - }).into(); - Branches{ - branch_at : branch_at, - nodes : array_init(|| Node::new()), + h.unwrap_or_else(|| (r[0] + r[1]) / F::TWO) + .max(r[0]) + .min(r[1]) + }) + .into(); + Branches { + branch_at: branch_at, + nodes: array_init(|| Node::new()), } } /// Summarises the aggregators of these branches into `agg` - pub(super) fn summarise_into(&self, agg : &mut A) { + pub(super) fn summarise_into(&self, agg: &mut A) { // We need to create an array of the aggregators clones due to the RwLock. agg.summarise(self.nodes.iter().map(Node::get_aggregator)); } @@ -266,19 +272,18 @@ /// Returns an iterator over the subcubes of `domain` subdivided at the branching point /// of `self`. #[inline] - pub(super) fn iter_subcubes<'b>(&self, domain : &'b Cube<F, N>) - -> SubcubeIter<'b, F, N, P> { + pub(super) fn iter_subcubes<'b>(&self, domain: &'b Cube<N, F>) -> SubcubeIter<'b, F, N, P> { SubcubeIter { - domain : domain, - branch_at : self.branch_at, - index : 0, + domain: domain, + branch_at: self.branch_at, + index: 0, } } /* /// Returns an iterator over all nodes and corresponding subcubes of `self`. #[inline] - pub(super) fn nodes_and_cubes<'a, 'b>(&'a self, domain : &'b Cube<F, N>) + pub(super) fn nodes_and_cubes<'a, 'b>(&'a self, domain : &'b Cube<N, F>) -> std::iter::Zip<Iter<'a, Node<F,D,A,N,P>>, SubcubeIter<'b, F, N, P>> { self.nodes.iter().zip(self.iter_subcubes(domain)) } @@ -286,8 +291,10 @@ /// Mutably iterate over all nodes and corresponding subcubes of `self`. #[inline] - pub(super) fn nodes_and_cubes_mut<'a, 'b>(&'a mut self, domain : &'b Cube<F, N>) - -> std::iter::Zip<IterMut<'a, Node<F,D,A,N,P>>, SubcubeIter<'b, F, N, P>> { + pub(super) fn nodes_and_cubes_mut<'a, 'b>( + &'a mut self, + domain: &'b Cube<N, F>, + ) -> std::iter::Zip<IterMut<'a, Node<F, D, A, N, P>>, SubcubeIter<'b, F, N, P>> { let subcube_iter = self.iter_subcubes(domain); self.nodes.iter_mut().zip(subcube_iter) } @@ -296,12 +303,16 @@ #[inline] fn recurse<'scope, 'smaller, 'refs>( &'smaller mut self, - domain : &'smaller Cube<F, N>, - task_budget : TaskBudget<'scope, 'refs>, - guard : impl Fn(&Node<F,D,A,N,P>, &Cube<F, N>) -> bool + Send + 'smaller, - mut f : impl for<'a> FnMut(&mut Node<F,D,A,N,P>, &Cube<F, N>, TaskBudget<'smaller, 'a>) - + Send + Copy + 'smaller - ) where 'scope : 'smaller { + domain: &'smaller Cube<N, F>, + task_budget: TaskBudget<'scope, 'refs>, + guard: impl Fn(&Node<F, D, A, N, P>, &Cube<N, F>) -> bool + Send + 'smaller, + mut f: impl for<'a> FnMut(&mut Node<F, D, A, N, P>, &Cube<N, F>, TaskBudget<'smaller, 'a>) + + Send + + Copy + + 'smaller, + ) where + 'scope: 'smaller, + { let subs = self.nodes_and_cubes_mut(domain); task_budget.zoom(move |s| { for (node, subcube) in subs { @@ -321,19 +332,23 @@ /// * `support` is the [`Support`] that is used determine with which subcubes of `domain` /// (at subdivision depth `new_leaf_depth`) the data `d` is to be associated with. /// - pub(super) fn insert<'refs, 'scope, M : Depth, S : LocalAnalysis<F, A, N>>( + pub(super) fn insert<'refs, 'scope, M: Depth, S: LocalAnalysis<F, A, N> + Support<N, F>>( &mut self, - domain : &Cube<F,N>, - d : D, - new_leaf_depth : M, - support : &S, - task_budget : TaskBudget<'scope, 'refs>, + domain: &Cube<N, F>, + d: D, + new_leaf_depth: M, + support: &S, + task_budget: TaskBudget<'scope, 'refs>, ) { let support_hint = support.support_hint(); - self.recurse(domain, task_budget, - |_, subcube| support_hint.intersects(&subcube), - move |node, subcube, new_budget| node.insert(subcube, d, new_leaf_depth, support, - new_budget)); + self.recurse( + domain, + task_budget, + |_, subcube| support_hint.intersects(&subcube), + move |node, subcube, new_budget| { + node.insert(subcube, d, new_leaf_depth, support, new_budget) + }, + ); } /// Construct a new instance of the branch for a different aggregator. @@ -344,20 +359,24 @@ /// generator's `SupportType`. pub(super) fn convert_aggregator<ANew, G>( self, - generator : &G, - domain : &Cube<F, N> - ) -> Branches<F,D,ANew,N,P> - where ANew : Aggregator, - G : SupportGenerator<F, N, Id=D>, - G::SupportType : LocalAnalysis<F, ANew, N> { + generator: &G, + domain: &Cube<N, F>, + ) -> Branches<F, D, ANew, N, P> + where + ANew: Aggregator, + G: SupportGenerator<N, F, Id = D>, + G::SupportType: LocalAnalysis<F, ANew, N>, + { let branch_at = self.branch_at; let subcube_iter = self.iter_subcubes(domain); - let new_nodes = self.nodes.into_iter().zip(subcube_iter).map(|(node, subcube)| { - Node::convert_aggregator(node, generator, &subcube) - }); + let new_nodes = self + .nodes + .into_iter() + .zip(subcube_iter) + .map(|(node, subcube)| Node::convert_aggregator(node, generator, &subcube)); Branches { - branch_at : branch_at, - nodes : collect_into_array_unchecked(new_nodes), + branch_at: branch_at, + nodes: collect_into_array_unchecked(new_nodes), } } @@ -367,37 +386,43 @@ /// [`Support`]s. The `domain` is the cube corresponding to `self`. pub(super) fn refresh_aggregator<'refs, 'scope, G>( &mut self, - generator : &G, - domain : &Cube<F, N>, - task_budget : TaskBudget<'scope, 'refs>, - ) where G : SupportGenerator<F, N, Id=D>, - G::SupportType : LocalAnalysis<F, A, N> { - self.recurse(domain, task_budget, - |_, _| true, - move |node, subcube, new_budget| node.refresh_aggregator(generator, subcube, - new_budget)); + generator: &G, + domain: &Cube<N, F>, + task_budget: TaskBudget<'scope, 'refs>, + ) where + G: SupportGenerator<N, F, Id = D>, + G::SupportType: LocalAnalysis<F, A, N>, + { + self.recurse( + domain, + task_budget, + |_, _| true, + move |node, subcube, new_budget| { + node.refresh_aggregator(generator, subcube, new_budget) + }, + ); } } -impl<F : Float, D, A, const N : usize, const P : usize> -Node<F,D,A,N,P> -where Const<P> : BranchCount<N>, - A : Aggregator, - D : 'static + Copy + Send + Sync { - +impl<F: Float, D, A, const N: usize, const P: usize> Node<F, D, A, N, P> +where + Const<P>: BranchCount<N>, + A: Aggregator, + D: 'static + Copy + Send + Sync, +{ /// Create a new node #[inline] pub(super) fn new() -> Self { Node { - data : NodeOption::Uninitialised, - aggregator : A::new(), + data: NodeOption::Uninitialised, + aggregator: A::new(), } } /* /// Get leaf data #[inline] - pub(super) fn get_leaf_data(&self, x : &Loc<F, N>) -> Option<&Vec<D>> { + pub(super) fn get_leaf_data(&self, x : &Loc<N, F>) -> Option<&Vec<D>> { match self.data { NodeOption::Uninitialised => None, NodeOption::Leaf(ref data) => Some(data), @@ -407,7 +432,7 @@ /// Get leaf data iterator #[inline] - pub(super) fn get_leaf_data_iter(&self, x : &Loc<F, N>) -> Option<std::slice::Iter<'_, D>> { + pub(super) fn get_leaf_data_iter(&self, x: &Loc<N, F>) -> Option<std::slice::Iter<'_, D>> { match self.data { NodeOption::Uninitialised => None, NodeOption::Leaf(ref data) => Some(data.iter()), @@ -434,13 +459,13 @@ /// If `self` is a [`NodeOption::Branches`], the data is passed to branches whose subcubes /// `support` intersects. If an [`NodeOption::Uninitialised`] node is encountered, a new leaf is /// created at a minimum depth of `new_leaf_depth`. - pub(super) fn insert<'refs, 'scope, M : Depth, S : LocalAnalysis <F, A, N>>( + pub(super) fn insert<'refs, 'scope, M: Depth, S: LocalAnalysis<F, A, N> + Support<N, F>>( &mut self, - domain : &Cube<F,N>, - d : D, - new_leaf_depth : M, - support : &S, - task_budget : TaskBudget<'scope, 'refs>, + domain: &Cube<N, F>, + d: D, + new_leaf_depth: M, + support: &S, + task_budget: TaskBudget<'scope, 'refs>, ) { match &mut self.data { NodeOption::Uninitialised => { @@ -451,10 +476,10 @@ self.aggregator.aggregate(once(a)); // TODO: this is currently a dirty hard-coded heuristic; // should add capacity as a parameter - let mut vec = Vec::with_capacity(2*P+1); + let mut vec = Vec::with_capacity(2 * P + 1); vec.push(d); NodeOption::Leaf(vec) - }, + } Some(lower) => { let b = Arc::new({ let mut b0 = Branches::new_with(domain, support); @@ -465,19 +490,19 @@ NodeOption::Branches(b) } } - }, + } NodeOption::Leaf(leaf) => { leaf.push(d); let a = support.local_analysis(&domain); self.aggregator.aggregate(once(a)); - }, + } NodeOption::Branches(b) => { // FIXME: recursion that may cause stack overflow if the tree becomes // very deep, e.g. due to [`BTSearch::search_and_refine`]. let bm = Arc::make_mut(b); bm.insert(domain, d, new_leaf_depth.lower_or(), support, task_budget); bm.summarise_into(&mut self.aggregator); - }, + } } } @@ -489,18 +514,19 @@ /// generator's `SupportType`. pub(super) fn convert_aggregator<ANew, G>( mut self, - generator : &G, - domain : &Cube<F, N> - ) -> Node<F,D,ANew,N,P> - where ANew : Aggregator, - G : SupportGenerator<F, N, Id=D>, - G::SupportType : LocalAnalysis<F, ANew, N> { - + generator: &G, + domain: &Cube<N, F>, + ) -> Node<F, D, ANew, N, P> + where + ANew: Aggregator, + G: SupportGenerator<N, F, Id = D>, + G::SupportType: LocalAnalysis<F, ANew, N>, + { // The mem::replace is needed due to the [`Drop`] implementation to extract self.data. match std::mem::replace(&mut self.data, NodeOption::Uninitialised) { NodeOption::Uninitialised => Node { - data : NodeOption::Uninitialised, - aggregator : ANew::new(), + data: NodeOption::Uninitialised, + aggregator: ANew::new(), }, NodeOption::Leaf(v) => { let mut anew = ANew::new(); @@ -510,10 +536,10 @@ })); Node { - data : NodeOption::Leaf(v), - aggregator : anew, + data: NodeOption::Leaf(v), + aggregator: anew, } - }, + } NodeOption::Branches(b) => { // FIXME: recursion that may cause stack overflow if the tree becomes // very deep, e.g. due to [`BTSearch::search_and_refine`]. @@ -521,8 +547,8 @@ let mut anew = ANew::new(); bnew.summarise_into(&mut anew); Node { - data : NodeOption::Branches(Arc::new(bnew)), - aggregator : anew, + data: NodeOption::Branches(Arc::new(bnew)), + aggregator: anew, } } } @@ -534,20 +560,22 @@ /// [`Support`]s. The `domain` is the cube corresponding to `self`. pub(super) fn refresh_aggregator<'refs, 'scope, G>( &mut self, - generator : &G, - domain : &Cube<F, N>, - task_budget : TaskBudget<'scope, 'refs>, - ) where G : SupportGenerator<F, N, Id=D>, - G::SupportType : LocalAnalysis<F, A, N> { + generator: &G, + domain: &Cube<N, F>, + task_budget: TaskBudget<'scope, 'refs>, + ) where + G: SupportGenerator<N, F, Id = D>, + G::SupportType: LocalAnalysis<F, A, N>, + { match &mut self.data { - NodeOption::Uninitialised => { }, + NodeOption::Uninitialised => {} NodeOption::Leaf(v) => { self.aggregator = A::new(); - self.aggregator.aggregate(v.iter().map(|d| { - generator.support_for(*d) - .local_analysis(&domain) - })); - }, + self.aggregator.aggregate( + v.iter() + .map(|d| generator.support_for(*d).local_analysis(&domain)), + ); + } NodeOption::Branches(ref mut b) => { // FIXME: recursion that may cause stack overflow if the tree becomes // very deep, e.g. due to [`BTSearch::search_and_refine`]. @@ -563,11 +591,13 @@ /// /// This can be removed and the methods implemented directly on [`BT`] once Rust's const generics /// are flexible enough to allow fixing `P=pow(2, N)`. -pub trait BTNode<F, D, A, const N : usize> -where F : Float, - D : 'static + Copy, - A : Aggregator { - type Node : Clone + std::fmt::Debug; +pub trait BTNode<F, D, A, const N: usize> +where + F: Float, + D: 'static + Copy, + A: Aggregator, +{ + type Node: Clone + std::fmt::Debug; } /// Helper structure for looking up a [`Node`] without the knowledge of `P`. @@ -580,56 +610,60 @@ /// Basic interface to a [`BT`] bisection tree. /// /// Further routines are provided by the [`BTSearch`][super::refine::BTSearch] trait. -pub trait BTImpl<F : Float, const N : usize> : std::fmt::Debug + Clone + GlobalAnalysis<F, Self::Agg> { +pub trait BTImpl<const N: usize, F: Float = f64>: + std::fmt::Debug + Clone + GlobalAnalysis<F, Self::Agg> +{ /// The data type stored in the tree - type Data : 'static + Copy + Send + Sync; + type Data: 'static + Copy + Send + Sync; /// The depth type of the tree - type Depth : Depth; + type Depth: Depth; /// The type for the [aggregate information][Aggregator] about the `Data` stored in each node /// of the tree. - type Agg : Aggregator; + type Agg: Aggregator; /// The type of the tree with the aggregator converted to `ANew`. - type Converted<ANew> : BTImpl<F, N, Data=Self::Data, Agg=ANew> where ANew : Aggregator; + type Converted<ANew>: BTImpl<N, F, Data = Self::Data, Agg = ANew> + where + ANew: Aggregator; /// Insert the data `d` into the tree for `support`. /// /// Every leaf node of the tree that intersects the `support` will contain a copy of /// `d`. - fn insert<S : LocalAnalysis<F, Self::Agg, N>>( + fn insert<S: LocalAnalysis<F, Self::Agg, N> + Support<N, F>>( &mut self, - d : Self::Data, - support : &S + d: Self::Data, + support: &S, ); /// Construct a new instance of the tree for a different aggregator /// /// The `generator` is used to convert the data of type [`Self::Data`] contained in the tree /// into corresponding [`Support`]s. - fn convert_aggregator<ANew, G>(self, generator : &G) - -> Self::Converted<ANew> - where ANew : Aggregator, - G : SupportGenerator<F, N, Id=Self::Data>, - G::SupportType : LocalAnalysis<F, ANew, N>; - + fn convert_aggregator<ANew, G>(self, generator: &G) -> Self::Converted<ANew> + where + ANew: Aggregator, + G: SupportGenerator<N, F, Id = Self::Data>, + G::SupportType: LocalAnalysis<F, ANew, N>; /// Refreshes the aggregator of the three after possible changes to the support generator. /// /// The `generator` is used to convert the data of type [`Self::Data`] contained in the tree /// into corresponding [`Support`]s. - fn refresh_aggregator<G>(&mut self, generator : &G) - where G : SupportGenerator<F, N, Id=Self::Data>, - G::SupportType : LocalAnalysis<F, Self::Agg, N>; + fn refresh_aggregator<G>(&mut self, generator: &G) + where + G: SupportGenerator<N, F, Id = Self::Data>, + G::SupportType: LocalAnalysis<F, Self::Agg, N>; /// Returns an iterator over all [`Self::Data`] items at the point `x` of the domain. - fn iter_at(&self, x : &Loc<F,N>) -> std::slice::Iter<'_, Self::Data>; + fn iter_at(&self, x: &Loc<N, F>) -> std::slice::Iter<'_, Self::Data>; /* /// Returns all [`Self::Data`] items at the point `x` of the domain. - fn data_at(&self, x : &Loc<F,N>) -> Arc<Vec<Self::Data>>; + fn data_at(&self, x : &Loc<N, F>) -> Arc<Vec<Self::Data>>; */ /// Create a new tree on `domain` of indicated `depth`. - fn new(domain : Cube<F, N>, depth : Self::Depth) -> Self; + fn new(domain: Cube<N, F>, depth: Self::Depth) -> Self; } /// The main bisection tree structure. @@ -637,20 +671,17 @@ /// It should be accessed via the [`BTImpl`] trait to hide the `const P : usize` parameter until /// const generics are flexible enough to fix `P=pow(2, N)` and thus also get rid of /// the `BTNodeLookup : BTNode<F, D, A, N>` trait bound. -#[derive(Clone,Debug)] -pub struct BT< - M : Depth, - F : Float, - D : 'static + Copy, - A : Aggregator, - const N : usize, -> where BTNodeLookup : BTNode<F, D, A, N> { +#[derive(Clone, Debug)] +pub struct BT<M: Depth, F: Float, D: 'static + Copy, A: Aggregator, const N: usize> +where + BTNodeLookup: BTNode<F, D, A, N>, +{ /// The depth of the tree (initial, before refinement) - pub(super) depth : M, + pub(super) depth: M, /// The domain of the toplevel node - pub(super) domain : Cube<F, N>, + pub(super) domain: Cube<N, F>, /// The toplevel node of the tree - pub(super) topnode : <BTNodeLookup as BTNode<F, D, A, N>>::Node, + pub(super) topnode: <BTNodeLookup as BTNode<F, D, A, N>>::Node, } macro_rules! impl_bt { @@ -662,7 +693,7 @@ type Node = Node<F,D,A,$n,{pow(2, $n)}>; } - impl<M,F,D,A> BTImpl<F,$n> for BT<M,F,D,A,$n> + impl<M,F,D,A> BTImpl<$n, F> for BT<M,F,D,A,$n> where M : Depth, F : Float, D : 'static + Copy + Send + Sync + std::fmt::Debug, @@ -672,7 +703,7 @@ type Agg = A; type Converted<ANew> = BT<M,F,D,ANew,$n> where ANew : Aggregator; - fn insert<S: LocalAnalysis<F, A, $n>>( + fn insert<S: LocalAnalysis<F, A, $n> + Support< $n, F>>( &mut self, d : D, support : &S @@ -690,7 +721,7 @@ fn convert_aggregator<ANew, G>(self, generator : &G) -> Self::Converted<ANew> where ANew : Aggregator, - G : SupportGenerator<F, $n, Id=D>, + G : SupportGenerator< $n, F, Id=D>, G::SupportType : LocalAnalysis<F, ANew, $n> { let topnode = self.topnode.convert_aggregator(generator, &self.domain); @@ -702,22 +733,22 @@ } fn refresh_aggregator<G>(&mut self, generator : &G) - where G : SupportGenerator<F, $n, Id=Self::Data>, + where G : SupportGenerator< $n, F, Id=Self::Data>, G::SupportType : LocalAnalysis<F, Self::Agg, $n> { with_task_budget(|task_budget| self.topnode.refresh_aggregator(generator, &self.domain, task_budget) ) } - /*fn data_at(&self, x : &Loc<F,$n>) -> Arc<Vec<D>> { + /*fn data_at(&self, x : &Loc<$n, F>) -> Arc<Vec<D>> { self.topnode.get_leaf_data(x).unwrap_or_else(|| Arc::new(Vec::new())) }*/ - fn iter_at(&self, x : &Loc<F,$n>) -> std::slice::Iter<'_, D> { + fn iter_at(&self, x : &Loc<$n, F>) -> std::slice::Iter<'_, D> { self.topnode.get_leaf_data_iter(x).unwrap_or_else(|| [].iter()) } - fn new(domain : Cube<F, $n>, depth : M) -> Self { + fn new(domain : Cube<$n, F>, depth : M) -> Self { BT { depth : depth, domain : domain, @@ -739,4 +770,3 @@ } impl_bt!(1 2 3 4); -
--- a/src/bisection_tree/btfn.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/bisection_tree/btfn.rs Fri May 15 14:46:30 2026 -0500 @@ -1,65 +1,88 @@ - +use crate::instance::{ClosedSpace, Instance, MyCow, Ownable, Space}; +use crate::mapping::{BasicDecomposition, DifferentiableImpl, DifferentiableMapping, Mapping}; +use crate::types::Float; use numeric_literals::replace_float_literals; use std::iter::Sum; use std::marker::PhantomData; use std::sync::Arc; -use crate::types::Float; -use crate::mapping::{ - Instance, Mapping, DifferentiableImpl, DifferentiableMapping, Space, - BasicDecomposition, -}; //use crate::linops::{Apply, Linear}; -use crate::sets::Set; -use crate::sets::Cube; -use crate::loc::Loc; +use super::aggregator::*; +use super::bt::*; +use super::either::*; +use super::refine::*; use super::support::*; -use super::bt::*; -use super::refine::*; -use super::aggregator::*; -use super::either::*; +use crate::bounds::MinMaxMapping; use crate::fe_model::base::RealLocalModel; use crate::fe_model::p2_local_model::*; +use crate::loc::Loc; +use crate::sets::Cube; +use crate::sets::Set; -/// Presentation for (mathematical) functions constructed as a sum of components functions with +/// Presentation for (mathematical) functions constructed as a sum of components functions with /// typically small support. /// -/// The domain of the function is [`Loc`]`<F, N>`, where `F` is the type of floating point numbers, +/// The domain of the function is [`Loc`]`<N, F>`, where `F` is the type of floating point numbers, /// and `N` the dimension. /// /// The `generator` lists the component functions that have to implement [`Support`]. /// Identifiers of the components ([`SupportGenerator::Id`], usually `usize`) are stored stored /// in a [bisection tree][BTImpl], when one is provided as `bt`. However `bt` may also be `()` /// for a [`PreBTFN`] that is only useful for vector space operations with a full [`BTFN`]. -#[derive(Clone,Debug)] -pub struct BTFN< - F : Float, - G : SupportGenerator<F, N>, - BT /*: BTImpl<F, N>*/, - const N : usize -> /*where G::SupportType : LocalAnalysis<F, A, N>*/ { - bt : BT, - generator : Arc<G>, - _phantoms : PhantomData<F>, +#[derive(Clone, Debug)] +pub struct BTFN<F: Float, G: SupportGenerator<N, F>, BT /*: BTImpl< N, F>*/, const N: usize> /*where G::SupportType : LocalAnalysis<F, A, N>*/ +{ + bt: BT, + generator: Arc<G>, + _phantoms: PhantomData<F>, } -impl<F : Float, G, BT, const N : usize> -Space for BTFN<F, G, BT, N> +impl<F: Float, G, BT, const N: usize> Ownable for BTFN<F, G, BT, N> where - G : SupportGenerator<F, N, Id=BT::Data>, - G::SupportType : LocalAnalysis<F, BT::Agg, N>, - BT : BTImpl<F, N> + G: SupportGenerator<N, F, Id = BT::Data>, + G::SupportType: LocalAnalysis<F, BT::Agg, N>, + BT: BTImpl<N, F>, { + type OwnedVariant = Self; + + fn into_owned(self) -> Self::OwnedVariant { + self + } + + fn clone_owned(&self) -> Self::OwnedVariant { + self.clone() + } + + fn cow_owned<'b>(self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + MyCow::Owned(self) + } + + fn ref_cow_owned<'b>(&'b self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + MyCow::Borrowed(self) + } +} + +impl<F: Float, G, BT, const N: usize> Space for BTFN<F, G, BT, N> +where + G: SupportGenerator<N, F, Id = BT::Data>, + G::SupportType: LocalAnalysis<F, BT::Agg, N>, + BT: BTImpl<N, F>, +{ + type Principal = Self; type Decomp = BasicDecomposition; } -impl<F : Float, G, BT, const N : usize> -BTFN<F, G, BT, N> +impl<F: Float, G, BT, const N: usize> BTFN<F, G, BT, N> where - G : SupportGenerator<F, N, Id=BT::Data>, - G::SupportType : LocalAnalysis<F, BT::Agg, N>, - BT : BTImpl<F, N> + G: SupportGenerator<N, F, Id = BT::Data>, + G::SupportType: LocalAnalysis<F, BT::Agg, N>, + BT: BTImpl<N, F>, { - /// Create a new BTFN from a support generator and a pre-initialised bisection tree. /// /// The bisection tree `bt` should be pre-initialised to correspond to the `generator`. @@ -67,16 +90,12 @@ /// when the aggregators of the tree may need updates. /// /// See the documentation for [`BTFN`] on the role of the `generator`. - pub fn new(bt : BT, generator : G) -> Self { + pub fn new(bt: BT, generator: G) -> Self { Self::new_arc(bt, Arc::new(generator)) } - fn new_arc(bt : BT, generator : Arc<G>) -> Self { - BTFN { - bt : bt, - generator : generator, - _phantoms : std::marker::PhantomData, - } + fn new_arc(bt: BT, generator: Arc<G>) -> Self { + BTFN { bt, generator, _phantoms: std::marker::PhantomData } } /// Create a new BTFN support generator and a pre-initialised bisection tree, @@ -86,7 +105,7 @@ /// the aggregator may be out of date. /// /// See the documentation for [`BTFN`] on the role of the `generator`. - pub fn new_refresh(bt : &BT, generator : G) -> Self { + pub fn new_refresh(bt: &BT, generator: G) -> Self { // clone().refresh_aggregator(…) as opposed to convert_aggregator // ensures that type is maintained. Due to Rc-pointer copy-on-write, // the effort is not significantly different. @@ -100,11 +119,11 @@ /// The top node of the created [`BT`] will have the given `domain`. /// /// See the documentation for [`BTFN`] on the role of the `generator`. - pub fn construct(domain : Cube<F, N>, depth : BT::Depth, generator : G) -> Self { + pub fn construct(domain: Cube<N, F>, depth: BT::Depth, generator: G) -> Self { Self::construct_arc(domain, depth, Arc::new(generator)) } - fn construct_arc(domain : Cube<F, N>, depth : BT::Depth, generator : Arc<G>) -> Self { + fn construct_arc(domain: Cube<N, F>, depth: BT::Depth, generator: Arc<G>) -> Self { let mut bt = BT::new(domain, depth); for (d, support) in generator.all_data() { bt.insert(d, &support); @@ -117,14 +136,16 @@ /// This will construct a [`BTFN`] with the same components and generator as the (consumed) /// `self`, but a new `BT` with [`Aggregator`]s of type `ANew`. pub fn convert_aggregator<ANew>(self) -> BTFN<F, G, BT::Converted<ANew>, N> - where ANew : Aggregator, - G : SupportGenerator<F, N, Id=BT::Data>, - G::SupportType : LocalAnalysis<F, ANew, N> { + where + ANew: Aggregator, + G: SupportGenerator<N, F, Id = BT::Data>, + G::SupportType: LocalAnalysis<F, ANew, N>, + { BTFN::new_arc(self.bt.convert_aggregator(&*self.generator), self.generator) } /// Change the generator (after, e.g., a scaling of the latter). - fn new_generator(&self, generator : G) -> Self { + fn new_generator(&self, generator: G) -> Self { BTFN::new_refresh(&self.bt, generator) } @@ -132,20 +153,24 @@ fn refresh_aggregator(&mut self) { self.bt.refresh_aggregator(&*self.generator); } - } -impl<F : Float, G, BT, const N : usize> -BTFN<F, G, BT, N> -where G : SupportGenerator<F, N> { +impl<F: Float, G, BT, const N: usize> BTFN<F, G, BT, N> +where + G: SupportGenerator<N, F>, +{ /// Change the [bisection tree][BTImpl] of the [`BTFN`] to a different one. /// /// This can be used to convert a [`PreBTFN`] to a full [`BTFN`], or the change - /// the aggreagator; see also [`self.convert_aggregator`]. - pub fn instantiate< - BTNew : BTImpl<F, N, Data=G::Id>, - > (self, domain : Cube<F, N>, depth : BTNew::Depth) -> BTFN<F, G, BTNew, N> - where G::SupportType : LocalAnalysis<F, BTNew::Agg, N> { + /// the aggreagator; see also [`Self::convert_aggregator`]. + pub fn instantiate<BTNew: BTImpl<N, F, Data = G::Id>>( + self, + domain: Cube<N, F>, + depth: BTNew::Depth, + ) -> BTFN<F, G, BTNew, N> + where + G::SupportType: LocalAnalysis<F, BTNew::Agg, N>, + { BTFN::construct_arc(domain, depth, self.generator) } } @@ -155,31 +180,30 @@ /// Most BTFN methods are not available, but if a BTFN is going to be summed with another /// before other use, it will be more efficient to not construct an unnecessary bisection tree /// that would be shortly dropped. -pub type PreBTFN<F, G, const N : usize> = BTFN<F, G, (), N>; +pub type PreBTFN<F, G, const N: usize> = BTFN<F, G, (), N>; -impl<F : Float, G, const N : usize> PreBTFN<F, G, N> where G : SupportGenerator<F, N> { - +impl<F: Float, G, const N: usize> PreBTFN<F, G, N> +where + G: SupportGenerator<N, F>, +{ /// Create a new [`PreBTFN`] with no bisection tree. - pub fn new_pre(generator : G) -> Self { - BTFN { - bt : (), - generator : Arc::new(generator), - _phantoms : std::marker::PhantomData, - } + pub fn new_pre(generator: G) -> Self { + BTFN { bt: (), generator: Arc::new(generator), _phantoms: std::marker::PhantomData } } } -impl<F : Float, G, BT, const N : usize> -BTFN<F, G, BT, N> -where G : SupportGenerator<F, N, Id=usize>, - G::SupportType : LocalAnalysis<F, BT::Agg, N>, - BT : BTImpl<F, N, Data=usize> { - +impl<F: Float, G, BT, const N: usize> BTFN<F, G, BT, N> +where + G: SupportGenerator<N, F, Id = usize>, + G::SupportType: LocalAnalysis<F, BT::Agg, N>, + BT: BTImpl<N, F, Data = usize>, +{ /// Helper function for implementing [`std::ops::Add`]. - fn add_another<G2>(&self, g2 : Arc<G2>) -> BTFN<F, BothGenerators<G, G2>, BT, N> - where G2 : SupportGenerator<F, N, Id=usize>, - G2::SupportType : LocalAnalysis<F, BT::Agg, N> { - + fn add_another<G2>(&self, g2: Arc<G2>) -> BTFN<F, BothGenerators<G, G2>, BT, N> + where + G2: SupportGenerator<N, F, Id = usize>, + G2::SupportType: LocalAnalysis<F, BT::Agg, N>, + { let mut bt = self.bt.clone(); let both = BothGenerators(Arc::clone(&self.generator), g2); @@ -187,11 +211,7 @@ bt.insert(d, &support); } - BTFN { - bt : bt, - generator : Arc::new(both), - _phantoms : std::marker::PhantomData, - } + BTFN { bt: bt, generator: Arc::new(both), _phantoms: std::marker::PhantomData } } } @@ -200,9 +220,9 @@ impl<'a, F : Float, G1, G2, BT1, BT2, const N : usize> std::ops::Add<BTFN<F, G2, BT2, N>> for $lhs - where BT1 : BTImpl<F, N, Data=usize>, - G1 : SupportGenerator<F, N, Id=usize> + $($extra_trait)?, - G2 : SupportGenerator<F, N, Id=usize>, + where BT1 : BTImpl< N, F, Data=usize>, + G1 : SupportGenerator< N, F, Id=usize> + $($extra_trait)?, + G2 : SupportGenerator< N, F, Id=usize>, G1::SupportType : LocalAnalysis<F, BT1::Agg, N>, G2::SupportType : LocalAnalysis<F, BT1::Agg, N> { type Output = BTFN<F, BothGenerators<G1, G2>, BT1, N>; @@ -215,9 +235,9 @@ impl<'a, 'b, F : Float, G1, G2, BT1, BT2, const N : usize> std::ops::Add<&'b BTFN<F, G2, BT2, N>> for $lhs - where BT1 : BTImpl<F, N, Data=usize>, - G1 : SupportGenerator<F, N, Id=usize> + $($extra_trait)?, - G2 : SupportGenerator<F, N, Id=usize>, + where BT1 : BTImpl< N, F, Data=usize>, + G1 : SupportGenerator< N, F, Id=usize> + $($extra_trait)?, + G2 : SupportGenerator< N, F, Id=usize>, G1::SupportType : LocalAnalysis<F, BT1::Agg, N>, G2::SupportType : LocalAnalysis<F, BT1::Agg, N> { @@ -231,16 +251,16 @@ } make_btfn_add!(BTFN<F, G1, BT1, N>, std::convert::identity, ); -make_btfn_add!(&'a BTFN<F, G1, BT1, N>, Clone::clone, ); +make_btfn_add!(&'a BTFN<F, G1, BT1, N>, Clone::clone,); macro_rules! make_btfn_sub { ($lhs:ty, $preprocess:path, $($extra_trait:ident)?) => { impl<'a, F : Float, G1, G2, BT1, BT2, const N : usize> std::ops::Sub<BTFN<F, G2, BT2, N>> for $lhs - where BT1 : BTImpl<F, N, Data=usize>, - G1 : SupportGenerator<F, N, Id=usize> + $($extra_trait)?, - G2 : SupportGenerator<F, N, Id=usize>, + where BT1 : BTImpl< N, F, Data=usize>, + G1 : SupportGenerator< N, F, Id=usize> + $($extra_trait)?, + G2 : SupportGenerator< N, F, Id=usize>, G1::SupportType : LocalAnalysis<F, BT1::Agg, N>, G2::SupportType : LocalAnalysis<F, BT1::Agg, N> { type Output = BTFN<F, BothGenerators<G1, G2>, BT1, N>; @@ -257,9 +277,9 @@ impl<'a, 'b, F : Float, G1, G2, BT1, BT2, const N : usize> std::ops::Sub<&'b BTFN<F, G2, BT2, N>> for $lhs - where BT1 : BTImpl<F, N, Data=usize>, - G1 : SupportGenerator<F, N, Id=usize> + $($extra_trait)?, - G2 : SupportGenerator<F, N, Id=usize> + Clone, + where BT1 : BTImpl< N, F, Data=usize>, + G1 : SupportGenerator< N, F, Id=usize> + $($extra_trait)?, + G2 : SupportGenerator< N, F, Id=usize> + Clone, G1::SupportType : LocalAnalysis<F, BT1::Agg, N>, G2::SupportType : LocalAnalysis<F, BT1::Agg, N>, &'b G2 : std::ops::Neg<Output=G2> { @@ -274,52 +294,52 @@ } make_btfn_sub!(BTFN<F, G1, BT1, N>, std::convert::identity, ); -make_btfn_sub!(&'a BTFN<F, G1, BT1, N>, std::convert::identity, ); +make_btfn_sub!(&'a BTFN<F, G1, BT1, N>, std::convert::identity,); macro_rules! make_btfn_scalarop_rhs { ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { - impl<F : Float, G, BT, const N : usize> - std::ops::$trait_assign<F> - for BTFN<F, G, BT, N> - where BT : BTImpl<F, N>, - G : SupportGenerator<F, N, Id=BT::Data>, - G::SupportType : LocalAnalysis<F, BT::Agg, N> { + impl<F: Float, G, BT, const N: usize> std::ops::$trait_assign<F> for BTFN<F, G, BT, N> + where + BT: BTImpl<N, F>, + G: SupportGenerator<N, F, Id = BT::Data>, + G::SupportType: LocalAnalysis<F, BT::Agg, N>, + { #[inline] - fn $fn_assign(&mut self, t : F) { + fn $fn_assign(&mut self, t: F) { Arc::make_mut(&mut self.generator).$fn_assign(t); self.refresh_aggregator(); } } - impl<F : Float, G, BT, const N : usize> - std::ops::$trait<F> - for BTFN<F, G, BT, N> - where BT : BTImpl<F, N>, - G : SupportGenerator<F, N, Id=BT::Data>, - G::SupportType : LocalAnalysis<F, BT::Agg, N> { + impl<F: Float, G, BT, const N: usize> std::ops::$trait<F> for BTFN<F, G, BT, N> + where + BT: BTImpl<N, F>, + G: SupportGenerator<N, F, Id = BT::Data>, + G::SupportType: LocalAnalysis<F, BT::Agg, N>, + { type Output = Self; #[inline] - fn $fn(mut self, t : F) -> Self::Output { + fn $fn(mut self, t: F) -> Self::Output { Arc::make_mut(&mut self.generator).$fn_assign(t); self.refresh_aggregator(); self } } - impl<'a, F : Float, G, BT, const N : usize> - std::ops::$trait<F> - for &'a BTFN<F, G, BT, N> - where BT : BTImpl<F, N>, - G : SupportGenerator<F, N, Id=BT::Data>, - G::SupportType : LocalAnalysis<F, BT::Agg, N>, - &'a G : std::ops::$trait<F,Output=G> { + impl<'a, F: Float, G, BT, const N: usize> std::ops::$trait<F> for &'a BTFN<F, G, BT, N> + where + BT: BTImpl<N, F>, + G: SupportGenerator<N, F, Id = BT::Data>, + G::SupportType: LocalAnalysis<F, BT::Agg, N>, + &'a G: std::ops::$trait<F, Output = G>, + { type Output = BTFN<F, G, BT, N>; #[inline] - fn $fn(self, t : F) -> Self::Output { + fn $fn(self, t: F) -> Self::Output { self.new_generator(self.generator.$fn(t)) } } - } + }; } make_btfn_scalarop_rhs!(Mul, mul, MulAssign, mul_assign); @@ -330,8 +350,8 @@ impl<G, BT, const N : usize> std::ops::$trait<BTFN<$f, G, BT, N>> for $f - where BT : BTImpl<$f, N>, - G : SupportGenerator<$f, N, Id=BT::Data>, + where BT : BTImpl< N, $f>, + G : SupportGenerator< N, $f, Id=BT::Data>, G::SupportType : LocalAnalysis<$f, BT::Agg, N> { type Output = BTFN<$f, G, BT, N>; #[inline] @@ -345,8 +365,8 @@ impl<'a, G, BT, const N : usize> std::ops::$trait<&'a BTFN<$f, G, BT, N>> for $f - where BT : BTImpl<$f, N>, - G : SupportGenerator<$f, N, Id=BT::Data> + Clone, + where BT : BTImpl< N, $f>, + G : SupportGenerator< N, $f, Id=BT::Data> + Clone, G::SupportType : LocalAnalysis<$f, BT::Agg, N>, // FIXME: This causes compiler overflow /*&'a G : std::ops::$trait<$f,Output=G>*/ { @@ -368,12 +388,12 @@ macro_rules! make_btfn_unaryop { ($trait:ident, $fn:ident) => { - impl<F : Float, G, BT, const N : usize> - std::ops::$trait - for BTFN<F, G, BT, N> - where BT : BTImpl<F, N>, - G : SupportGenerator<F, N, Id=BT::Data>, - G::SupportType : LocalAnalysis<F, BT::Agg, N> { + impl<F: Float, G, BT, const N: usize> std::ops::$trait for BTFN<F, G, BT, N> + where + BT: BTImpl<N, F>, + G: SupportGenerator<N, F, Id = BT::Data>, + G::SupportType: LocalAnalysis<F, BT::Agg, N>, + { type Output = Self; #[inline] fn $fn(mut self) -> Self::Output { @@ -386,8 +406,8 @@ /*impl<'a, F : Float, G, BT, const N : usize> std::ops::$trait for &'a BTFN<F, G, BT, N> - where BT : BTImpl<F, N>, - G : SupportGenerator<F, N, Id=BT::Data>, + where BT : BTImpl< N, F>, + G : SupportGenerator< N, F, Id=BT::Data>, G::SupportType : LocalAnalysis<F, BT::Agg, N>, &'a G : std::ops::$trait<Output=G> { type Output = BTFN<F, G, BT, N>; @@ -396,7 +416,7 @@ self.new_generator(std::ops::$trait::$fn(&self.generator)) } }*/ - } + }; } make_btfn_unaryop!(Neg, neg); @@ -405,39 +425,38 @@ // Apply, Mapping, Differentiate // -impl<F : Float, G, BT, V, const N : usize> Mapping<Loc<F, N>> -for BTFN<F, G, BT, N> +impl<F: Float, G, BT, V, const N: usize> Mapping<Loc<N, F>> for BTFN<F, G, BT, N> where - BT : BTImpl<F, N>, - G : SupportGenerator<F, N, Id=BT::Data>, - G::SupportType : LocalAnalysis<F, BT::Agg, N> + Mapping<Loc<F, N>, Codomain = V>, - V : Sum + Space, + BT: BTImpl<N, F>, + G: SupportGenerator<N, F, Id = BT::Data>, + G::SupportType: LocalAnalysis<F, BT::Agg, N> + Mapping<Loc<N, F>, Codomain = V>, + V: Sum + ClosedSpace, { - type Codomain = V; - fn apply<I : Instance<Loc<F,N>>>(&self, x : I) -> Self::Codomain { - let xc = x.cow(); - self.bt.iter_at(&*xc) - .map(|&d| self.generator.support_for(d).apply(&*xc)).sum() + fn apply<I: Instance<Loc<N, F>>>(&self, x: I) -> Self::Codomain { + let xc = x.decompose(); + self.bt + .iter_at(&*xc) + .map(|&d| self.generator.support_for(d).apply(&*xc)) + .sum() } } -impl<F : Float, G, BT, V, const N : usize> DifferentiableImpl<Loc<F, N>> -for BTFN<F, G, BT, N> +impl<F: Float, G, BT, V, const N: usize> DifferentiableImpl<Loc<N, F>> for BTFN<F, G, BT, N> where - BT : BTImpl<F, N>, - G : SupportGenerator<F, N, Id=BT::Data>, - G::SupportType : LocalAnalysis<F, BT::Agg, N> - + DifferentiableMapping<Loc<F, N>, DerivativeDomain = V>, - V : Sum + Space, + BT: BTImpl<N, F>, + G: SupportGenerator<N, F, Id = BT::Data>, + G::SupportType: + LocalAnalysis<F, BT::Agg, N> + DifferentiableMapping<Loc<N, F>, DerivativeDomain = V>, + V: Sum + ClosedSpace, { - type Derivative = V; - fn differential_impl<I : Instance<Loc<F, N>>>(&self, x :I) -> Self::Derivative { - let xc = x.cow(); - self.bt.iter_at(&*xc) + fn differential_impl<I: Instance<Loc<N, F>>>(&self, x: I) -> Self::Derivative { + let xc = x.decompose(); + self.bt + .iter_at(&*xc) .map(|&d| self.generator.support_for(d).differential(&*xc)) .sum() } @@ -447,12 +466,12 @@ // GlobalAnalysis // -impl<F : Float, G, BT, const N : usize> GlobalAnalysis<F, BT::Agg> -for BTFN<F, G, BT, N> -where BT : BTImpl<F, N>, - G : SupportGenerator<F, N, Id=BT::Data>, - G::SupportType : LocalAnalysis<F, BT::Agg, N> { - +impl<F: Float, G, BT, const N: usize> GlobalAnalysis<F, BT::Agg> for BTFN<F, G, BT, N> +where + BT: BTImpl<N, F>, + G: SupportGenerator<N, F, Id = BT::Data>, + G::SupportType: LocalAnalysis<F, BT::Agg, N>, +{ #[inline] fn global_analysis(&self) -> BT::Agg { self.bt.global_analysis() @@ -467,8 +486,8 @@ /* impl<'b, X, F : Float, G, BT, const N : usize> Apply<&'b X, F> for BTFN<F, G, BT, N> -where BT : BTImpl<F, N>, - G : SupportGenerator<F, N, Id=BT::Data>, +where BT : BTImpl< N, F>, + G : SupportGenerator< N, F, Id=BT::Data>, G::SupportType : LocalAnalysis<F, BT::Agg, N>, X : for<'a> Apply<&'a BTFN<F, G, BT, N>, F> { @@ -480,8 +499,8 @@ impl<X, F : Float, G, BT, const N : usize> Apply<X, F> for BTFN<F, G, BT, N> -where BT : BTImpl<F, N>, - G : SupportGenerator<F, N, Id=BT::Data>, +where BT : BTImpl< N, F>, + G : SupportGenerator< N, F, Id=BT::Data>, G::SupportType : LocalAnalysis<F, BT::Agg, N>, X : for<'a> Apply<&'a BTFN<F, G, BT, N>, F> { @@ -493,8 +512,8 @@ impl<X, F : Float, G, BT, const N : usize> Linear<X> for BTFN<F, G, BT, N> -where BT : BTImpl<F, N>, - G : SupportGenerator<F, N, Id=BT::Data>, +where BT : BTImpl< N, F>, + G : SupportGenerator< N, F, Id=BT::Data>, G::SupportType : LocalAnalysis<F, BT::Agg, N>, X : for<'a> Apply<&'a BTFN<F, G, BT, N>, F> { type Codomain = F; @@ -505,24 +524,23 @@ /// /// `U` is the domain, generally [`Loc`]`<F, N>`, and `F` the type of floating point numbers. /// `Self` is generally a set of `U`, for example, [`Cube`]`<F, N>`. -pub trait P2Minimise<U : Space, F : Float> : Set<U> { +pub trait P2Minimise<U: Space, F: Float>: Set<U> { /// Minimise `g` over the set presented by `Self`. /// /// The function returns `(x, v)` where `x` is the minimiser `v` an approximation of `g(x)`. - fn p2_minimise<G : Fn(&U) -> F>(&self, g : G) -> (U, F); - + fn p2_minimise<G: Fn(&U) -> F>(&self, g: G) -> (U, F); } -impl<F : Float> P2Minimise<Loc<F, 1>, F> for Cube<F, 1> { - fn p2_minimise<G : Fn(&Loc<F, 1>) -> F>(&self, g : G) -> (Loc<F, 1>, F) { +impl<F: Float> P2Minimise<Loc<1, F>, F> for Cube<1, F> { + fn p2_minimise<G: Fn(&Loc<1, F>) -> F>(&self, g: G) -> (Loc<1, F>, F) { let interval = Simplex(self.corners()); interval.p2_model(&g).minimise(&interval) } } #[replace_float_literals(F::cast_from(literal))] -impl<F : Float> P2Minimise<Loc<F, 2>, F> for Cube<F, 2> { - fn p2_minimise<G : Fn(&Loc<F, 2>) -> F>(&self, g : G) -> (Loc<F, 2>, F) { +impl<F: Float> P2Minimise<Loc<2, F>, F> for Cube<2, F> { + fn p2_minimise<G: Fn(&Loc<2, F>) -> F>(&self, g: G) -> (Loc<2, F>, F) { if false { // Split into two triangle (simplex) with separate P2 model in each. // The six nodes of each triangle are the corners and the edges. @@ -537,49 +555,46 @@ let [vab, vbc, vca, vcd, vda] = [g(&ab), g(&bc), g(&ca), g(&cd), g(&da)]; let s1 = Simplex([a, b, c]); - let m1 = P2LocalModel::<F, 2, 3>::new( - &[a, b, c, ab, bc, ca], - &[va, vb, vc, vab, vbc, vca] - ); + let m1 = + P2LocalModel::<F, 2, 3>::new(&[a, b, c, ab, bc, ca], &[va, vb, vc, vab, vbc, vca]); - let r1@(_, v1) = m1.minimise(&s1); + let r1 @ (_, v1) = m1.minimise(&s1); let s2 = Simplex([c, d, a]); - let m2 = P2LocalModel::<F, 2, 3>::new( - &[c, d, a, cd, da, ca], - &[vc, vd, va, vcd, vda, vca] - ); + let m2 = + P2LocalModel::<F, 2, 3>::new(&[c, d, a, cd, da, ca], &[vc, vd, va, vcd, vda, vca]); + + let r2 @ (_, v2) = m2.minimise(&s2); - let r2@(_, v2) = m2.minimise(&s2); - - if v1 < v2 { r1 } else { r2 } + if v1 < v2 { + r1 + } else { + r2 + } } else { // Single P2 model for the entire cube. let [a, b, c, d] = self.corners(); let [va, vb, vc, vd] = [g(&a), g(&b), g(&c), g(&d)]; let [e, f] = match 'r' { - 'm' => [(&a + &b + &c) / 3.0, (&c + &d + &a) / 3.0], - 'c' => [midpoint(&a, &b), midpoint(&a, &d)], - 'w' => [(&a + &b * 2.0) / 3.0, (&a + &d * 2.0) / 3.0], - 'r' => { + 'm' => [(&a + &b + &c) / 3.0, (&c + &d + &a) / 3.0], + 'c' => [midpoint(&a, &b), midpoint(&a, &d)], + 'w' => [(&a + &b * 2.0) / 3.0, (&a + &d * 2.0) / 3.0], + 'r' => { // Pseudo-randomise edge midpoints let Loc([x, y]) = a; - let tmp : f64 = (x+y).as_(); + let tmp: f64 = (x + y).as_(); match tmp.to_bits() % 4 { - 0 => [midpoint(&a, &b), midpoint(&a, &d)], - 1 => [midpoint(&c, &d), midpoint(&a, &d)], - 2 => [midpoint(&a, &b), midpoint(&b, &c)], - _ => [midpoint(&c, &d), midpoint(&b, &c)], + 0 => [midpoint(&a, &b), midpoint(&a, &d)], + 1 => [midpoint(&c, &d), midpoint(&a, &d)], + 2 => [midpoint(&a, &b), midpoint(&b, &c)], + _ => [midpoint(&c, &d), midpoint(&b, &c)], } - }, - _ => [self.center(), (&a + &b) / 2.0], + } + _ => [self.center(), (&a + &b) / 2.0], }; let [ve, vf] = [g(&e), g(&f)]; - let m1 = P2LocalModel::<F, 2, 3>::new( - &[a, b, c, d, e, f], - &[va, vb, vc, vd, ve, vf], - ); + let m1 = P2LocalModel::<F, 2, 3>::new(&[a, b, c, d, e, f], &[va, vb, vc, vd, ve, vf]); m1.minimise(self) } @@ -595,44 +610,46 @@ /// A bisection tree [`Refiner`] for maximising or minimising a [`BTFN`]. /// /// The type parameter `T` should be either [`RefineMax`] or [`RefineMin`]. -struct P2Refiner<F : Float, T> { +struct P2Refiner<F: Float, T> { /// The maximum / minimum should be above / below this threshold. /// If the threshold cannot be satisfied, the refiner will return `None`. - bound : Option<F>, + bound: Option<F>, /// Tolerance for function value estimation. - tolerance : F, + tolerance: F, /// Maximum number of steps to execute the refiner for - max_steps : usize, + max_steps: usize, /// Either [`RefineMax`] or [`RefineMin`]. Used only for type system purposes. #[allow(dead_code)] // `how` is just for type system purposes. - how : T, + how: T, } -impl<F : Float, G, const N : usize> Refiner<F, Bounds<F>, G, N> -for P2Refiner<F, RefineMax> -where Cube<F, N> : P2Minimise<Loc<F, N>, F>, - G : SupportGenerator<F, N>, - G::SupportType : Mapping<Loc<F, N>, Codomain=F> - + LocalAnalysis<F, Bounds<F>, N> { - type Result = Option<(Loc<F, N>, F)>; +impl<F: Float, G, const N: usize> Refiner<F, Bounds<F>, G, N> for P2Refiner<F, RefineMax> +where + Cube<N, F>: P2Minimise<Loc<N, F>, F>, + G: SupportGenerator<N, F>, + G::SupportType: Mapping<Loc<N, F>, Codomain = F> + LocalAnalysis<F, Bounds<F>, N>, +{ + type Result = Option<(Loc<N, F>, F)>; type Sorting = UpperBoundSorting<F>; fn refine( &self, - aggregator : &Bounds<F>, - cube : &Cube<F, N>, - data : &[G::Id], - generator : &G, - step : usize + aggregator: &Bounds<F>, + cube: &Cube<N, F>, + data: &[G::Id], + generator: &G, + step: usize, ) -> RefinerResult<Bounds<F>, Self::Result> { - - if self.bound.map_or(false, |b| aggregator.upper() <= b + self.tolerance) { + if self + .bound + .map_or(false, |b| aggregator.upper() <= b + self.tolerance) + { // The upper bound is below the maximisation threshold. Don't bother with this cube. - return RefinerResult::Uncertain(*aggregator, None) + return RefinerResult::Uncertain(*aggregator, None); } // g gives the negative of the value of the function presented by `data` and `generator`. - let g = move |x : &Loc<F, N>| { + let g = move |x: &Loc<N, F>| { let f = move |&d| generator.support_for(d).apply(x); -data.iter().map(f).sum::<F>() }; @@ -641,8 +658,9 @@ //let v = -neg_v; let v = -g(&x); - if step < self.max_steps && (aggregator.upper() > v + self.tolerance - /*|| aggregator.lower() > v - self.tolerance*/) { + if step < self.max_steps + && (aggregator.upper() > v + self.tolerance/*|| aggregator.lower() > v - self.tolerance*/) + { // The function isn't refined enough in `cube`, so return None // to indicate that further subdivision is required. RefinerResult::NeedRefinement @@ -655,41 +673,46 @@ } } - fn fuse_results(r1 : &mut Self::Result, r2 : Self::Result) { + fn fuse_results(r1: &mut Self::Result, r2: Self::Result) { match (*r1, r2) { - (Some((_, v1)), Some((_, v2))) => if v1 < v2 { *r1 = r2 } + (Some((_, v1)), Some((_, v2))) => { + if v1 < v2 { + *r1 = r2 + } + } (None, Some(_)) => *r1 = r2, - (_, _) => {}, + (_, _) => {} } } } - -impl<F : Float, G, const N : usize> Refiner<F, Bounds<F>, G, N> -for P2Refiner<F, RefineMin> -where Cube<F, N> : P2Minimise<Loc<F, N>, F>, - G : SupportGenerator<F, N>, - G::SupportType : Mapping<Loc<F, N>, Codomain=F> - + LocalAnalysis<F, Bounds<F>, N> { - type Result = Option<(Loc<F, N>, F)>; +impl<F: Float, G, const N: usize> Refiner<F, Bounds<F>, G, N> for P2Refiner<F, RefineMin> +where + Cube<N, F>: P2Minimise<Loc<N, F>, F>, + G: SupportGenerator<N, F>, + G::SupportType: Mapping<Loc<N, F>, Codomain = F> + LocalAnalysis<F, Bounds<F>, N>, +{ + type Result = Option<(Loc<N, F>, F)>; type Sorting = LowerBoundSorting<F>; fn refine( &self, - aggregator : &Bounds<F>, - cube : &Cube<F, N>, - data : &[G::Id], - generator : &G, - step : usize + aggregator: &Bounds<F>, + cube: &Cube<N, F>, + data: &[G::Id], + generator: &G, + step: usize, ) -> RefinerResult<Bounds<F>, Self::Result> { - - if self.bound.map_or(false, |b| aggregator.lower() >= b - self.tolerance) { + if self + .bound + .map_or(false, |b| aggregator.lower() >= b - self.tolerance) + { // The lower bound is above the minimisation threshold. Don't bother with this cube. - return RefinerResult::Uncertain(*aggregator, None) + return RefinerResult::Uncertain(*aggregator, None); } // g gives the value of the function presented by `data` and `generator`. - let g = move |x : &Loc<F, N>| { + let g = move |x: &Loc<N, F>| { let f = move |&d| generator.support_for(d).apply(x); data.iter().map(f).sum::<F>() }; @@ -697,8 +720,9 @@ let (x, _v) = cube.p2_minimise(g); let v = g(&x); - if step < self.max_steps && (aggregator.lower() < v - self.tolerance - /*|| aggregator.upper() < v + self.tolerance*/) { + if step < self.max_steps + && (aggregator.lower() < v - self.tolerance/*|| aggregator.upper() < v + self.tolerance*/) + { // The function isn't refined enough in `cube`, so return None // to indicate that further subdivision is required. RefinerResult::NeedRefinement @@ -717,47 +741,51 @@ } } - fn fuse_results(r1 : &mut Self::Result, r2 : Self::Result) { + fn fuse_results(r1: &mut Self::Result, r2: Self::Result) { match (*r1, r2) { - (Some((_, v1)), Some((_, v2))) => if v1 > v2 { *r1 = r2 } + (Some((_, v1)), Some((_, v2))) => { + if v1 > v2 { + *r1 = r2 + } + } (_, Some(_)) => *r1 = r2, - (_, _) => {}, + (_, _) => {} } } } - /// A bisection tree [`Refiner`] for checking that a [`BTFN`] is within a stated //// upper or lower bound. /// /// The type parameter `T` should be either [`RefineMax`] for upper bound or [`RefineMin`] /// for lower bound. -struct BoundRefiner<F : Float, T> { +struct BoundRefiner<F: Float, T> { /// The upper/lower bound to check for - bound : F, + bound: F, /// Tolerance for function value estimation. - tolerance : F, + tolerance: F, /// Maximum number of steps to execute the refiner for - max_steps : usize, + max_steps: usize, #[allow(dead_code)] // `how` is just for type system purposes. /// Either [`RefineMax`] or [`RefineMin`]. Used only for type system purposes. - how : T, + how: T, } -impl<F : Float, G, const N : usize> Refiner<F, Bounds<F>, G, N> -for BoundRefiner<F, RefineMax> -where G : SupportGenerator<F, N> { +impl<F: Float, G, const N: usize> Refiner<F, Bounds<F>, G, N> for BoundRefiner<F, RefineMax> +where + G: SupportGenerator<N, F>, +{ type Result = bool; type Sorting = UpperBoundSorting<F>; fn refine( &self, - aggregator : &Bounds<F>, - _cube : &Cube<F, N>, - _data : &[G::Id], - _generator : &G, - step : usize + aggregator: &Bounds<F>, + _cube: &Cube<N, F>, + _data: &[G::Id], + _generator: &G, + step: usize, ) -> RefinerResult<Bounds<F>, Self::Result> { if aggregator.upper() <= self.bound + self.tolerance { // Below upper bound within tolerances. Indicate uncertain success. @@ -774,24 +802,25 @@ } } - fn fuse_results(r1 : &mut Self::Result, r2 : Self::Result) { + fn fuse_results(r1: &mut Self::Result, r2: Self::Result) { *r1 = *r1 && r2; } } -impl<F : Float, G, const N : usize> Refiner<F, Bounds<F>, G, N> -for BoundRefiner<F, RefineMin> -where G : SupportGenerator<F, N> { +impl<F: Float, G, const N: usize> Refiner<F, Bounds<F>, G, N> for BoundRefiner<F, RefineMin> +where + G: SupportGenerator<N, F>, +{ type Result = bool; type Sorting = UpperBoundSorting<F>; fn refine( &self, - aggregator : &Bounds<F>, - _cube : &Cube<F, N>, - _data : &[G::Id], - _generator : &G, - step : usize + aggregator: &Bounds<F>, + _cube: &Cube<N, F>, + _data: &[G::Id], + _generator: &G, + step: usize, ) -> RefinerResult<Bounds<F>, Self::Result> { if aggregator.lower() >= self.bound - self.tolerance { // Above lower bound within tolerances. Indicate uncertain success. @@ -808,7 +837,7 @@ } } - fn fuse_results(r1 : &mut Self::Result, r2 : Self::Result) { + fn fuse_results(r1: &mut Self::Result, r2: Self::Result) { *r1 = *r1 && r2; } } @@ -828,66 +857,64 @@ // there should be a result, or new nodes above the `glb` inserted into the queue. Then the waiting // threads can also continue processing. If, however, numerical inaccuracy destroyes the `glb`, // the queue may run out, and we get “Refiner failure”. -impl<F : Float, G, BT, const N : usize> BTFN<F, G, BT, N> -where BT : BTSearch<F, N, Agg=Bounds<F>>, - G : SupportGenerator<F, N, Id=BT::Data>, - G::SupportType : Mapping<Loc<F, N>, Codomain=F> - + LocalAnalysis<F, Bounds<F>, N>, - Cube<F, N> : P2Minimise<Loc<F, N>, F> { - - /// Maximise the `BTFN` within stated value `tolerance`. - /// - /// At most `max_steps` refinement steps are taken. - /// Returns the approximate maximiser and the corresponding function value. - pub fn maximise(&mut self, tolerance : F, max_steps : usize) -> (Loc<F, N>, F) { - let refiner = P2Refiner{ tolerance, max_steps, how : RefineMax, bound : None }; - self.bt.search_and_refine(refiner, &self.generator).expect("Refiner failure.").unwrap() +impl<F: Float, G, BT, const N: usize> MinMaxMapping<Loc<N, F>, F> for BTFN<F, G, BT, N> +where + BT: BTSearch<N, F, Agg = Bounds<F>>, + G: SupportGenerator<N, F, Id = BT::Data>, + G::SupportType: Mapping<Loc<N, F>, Codomain = F> + LocalAnalysis<F, Bounds<F>, N>, + Cube<N, F>: P2Minimise<Loc<N, F>, F>, +{ + fn maximise(&mut self, tolerance: F, max_steps: usize) -> (Loc<N, F>, F) { + let refiner = P2Refiner { tolerance, max_steps, how: RefineMax, bound: None }; + self.bt + .search_and_refine(refiner, &self.generator) + .expect("Refiner failure.") + .unwrap() } - /// Maximise the `BTFN` within stated value `tolerance` subject to a lower bound. - /// - /// At most `max_steps` refinement steps are taken. - /// Returns the approximate maximiser and the corresponding function value when one is found - /// above the `bound` threshold, otherwise `None`. - pub fn maximise_above(&mut self, bound : F, tolerance : F, max_steps : usize) - -> Option<(Loc<F, N>, F)> { - let refiner = P2Refiner{ tolerance, max_steps, how : RefineMax, bound : Some(bound) }; - self.bt.search_and_refine(refiner, &self.generator).expect("Refiner failure.") + fn maximise_above( + &mut self, + bound: F, + tolerance: F, + max_steps: usize, + ) -> Option<(Loc<N, F>, F)> { + let refiner = P2Refiner { tolerance, max_steps, how: RefineMax, bound: Some(bound) }; + self.bt + .search_and_refine(refiner, &self.generator) + .expect("Refiner failure.") } - /// Minimise the `BTFN` within stated value `tolerance`. - /// - /// At most `max_steps` refinement steps are taken. - /// Returns the approximate minimiser and the corresponding function value. - pub fn minimise(&mut self, tolerance : F, max_steps : usize) -> (Loc<F, N>, F) { - let refiner = P2Refiner{ tolerance, max_steps, how : RefineMin, bound : None }; - self.bt.search_and_refine(refiner, &self.generator).expect("Refiner failure.").unwrap() + fn minimise(&mut self, tolerance: F, max_steps: usize) -> (Loc<N, F>, F) { + let refiner = P2Refiner { tolerance, max_steps, how: RefineMin, bound: None }; + self.bt + .search_and_refine(refiner, &self.generator) + .expect("Refiner failure.") + .unwrap() } - /// Minimise the `BTFN` within stated value `tolerance` subject to a lower bound. - /// - /// At most `max_steps` refinement steps are taken. - /// Returns the approximate minimiser and the corresponding function value when one is found - /// above the `bound` threshold, otherwise `None`. - pub fn minimise_below(&mut self, bound : F, tolerance : F, max_steps : usize) - -> Option<(Loc<F, N>, F)> { - let refiner = P2Refiner{ tolerance, max_steps, how : RefineMin, bound : Some(bound) }; - self.bt.search_and_refine(refiner, &self.generator).expect("Refiner failure.") + fn minimise_below( + &mut self, + bound: F, + tolerance: F, + max_steps: usize, + ) -> Option<(Loc<N, F>, F)> { + let refiner = P2Refiner { tolerance, max_steps, how: RefineMin, bound: Some(bound) }; + self.bt + .search_and_refine(refiner, &self.generator) + .expect("Refiner failure.") } - /// Verify that the `BTFN` has a given upper `bound` within indicated `tolerance`. - /// - /// At most `max_steps` refinement steps are taken. - pub fn has_upper_bound(&mut self, bound : F, tolerance : F, max_steps : usize) -> bool { - let refiner = BoundRefiner{ bound, tolerance, max_steps, how : RefineMax }; - self.bt.search_and_refine(refiner, &self.generator).expect("Refiner failure.") + fn has_upper_bound(&mut self, bound: F, tolerance: F, max_steps: usize) -> bool { + let refiner = BoundRefiner { bound, tolerance, max_steps, how: RefineMax }; + self.bt + .search_and_refine(refiner, &self.generator) + .expect("Refiner failure.") } - /// Verify that the `BTFN` has a given lower `bound` within indicated `tolerance`. - /// - /// At most `max_steps` refinement steps are taken. - pub fn has_lower_bound(&mut self, bound : F, tolerance : F, max_steps : usize) -> bool { - let refiner = BoundRefiner{ bound, tolerance, max_steps, how : RefineMin }; - self.bt.search_and_refine(refiner, &self.generator).expect("Refiner failure.") + fn has_lower_bound(&mut self, bound: F, tolerance: F, max_steps: usize) -> bool { + let refiner = BoundRefiner { bound, tolerance, max_steps, how: RefineMin }; + self.bt + .search_and_refine(refiner, &self.generator) + .expect("Refiner failure.") } }
--- a/src/bisection_tree/either.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/bisection_tree/either.rs Fri May 15 14:46:30 2026 -0500 @@ -1,36 +1,28 @@ - use std::iter::Chain; use std::sync::Arc; -use crate::types::*; +use crate::iter::{MapF, MapZ, Mappable}; +use crate::loc::Loc; use crate::mapping::{ - Instance, - Mapping, - DifferentiableImpl, - DifferentiableMapping, - Space, + ClosedSpace, DifferentiableImpl, DifferentiableMapping, Instance, Mapping, Space, }; -use crate::iter::{Mappable, MapF, MapZ}; use crate::sets::Cube; -use crate::loc::Loc; +use crate::types::*; +use super::aggregator::*; use super::support::*; -use super::aggregator::*; /// A structure for storing two [`SupportGenerator`]s summed/chain together. /// /// This is needed to work with sums of different types of [`Support`]s. -#[derive(Debug,Clone)] -pub struct BothGenerators<A, B>( - pub(super) Arc<A>, - pub(super) Arc<B>, -); +#[derive(Debug, Clone)] +pub struct BothGenerators<A, B>(pub(super) Arc<A>, pub(super) Arc<B>); /// A structure for a [`Support`] that can be either `A` or `B`. /// /// This is needed to work with sums of different types of [`Support`]s. -#[derive(Debug,Clone)] -pub enum EitherSupport<A, B> { +#[derive(Debug, Clone)] +pub enum EitherSupport<B, A> { Left(A), Right(B), } @@ -38,46 +30,55 @@ // We need type alias bounds to access associate types. #[allow(type_alias_bounds)] type BothAllDataIter< - 'a, F, - G1 : SupportGenerator<F, N>, - G2 : SupportGenerator<F, N>, - const N : usize + 'a, + F, + G1: SupportGenerator<N, F>, + G2: SupportGenerator<N, F>, + const N: usize, > = Chain< - MapF<G1::AllDataIter<'a>, (usize, EitherSupport<G1::SupportType, G2::SupportType>)>, - MapZ<G2::AllDataIter<'a>, usize, (usize, EitherSupport<G1::SupportType, G2::SupportType>)>, + MapF<G1::AllDataIter<'a>, (usize, EitherSupport<G2::SupportType, G1::SupportType>)>, + MapZ<G2::AllDataIter<'a>, usize, (usize, EitherSupport<G2::SupportType, G1::SupportType>)>, >; impl<G1, G2> BothGenerators<G1, G2> { /// Helper for [`all_left_data`]. #[inline] - fn map_left<F : Float, const N : usize>((d, support) : (G1::Id, G1::SupportType)) - -> (usize, EitherSupport<G1::SupportType, G2::SupportType>) - where G1 : SupportGenerator<F, N, Id=usize>, - G2 : SupportGenerator<F, N, Id=usize> { - - let id : usize = d.into(); + fn map_left<F: Float, const N: usize>( + (d, support): (G1::Id, G1::SupportType), + ) -> (usize, EitherSupport<G2::SupportType, G1::SupportType>) + where + G1: SupportGenerator<N, F, Id = usize>, + G2: SupportGenerator<N, F, Id = usize>, + { + let id: usize = d.into(); (id.into(), EitherSupport::Left(support)) } /// Helper for [`all_right_data`]. #[inline] - fn map_right<F : Float, const N : usize>(n0 : &usize, (d, support) : (G2::Id, G2::SupportType)) - -> (usize, EitherSupport<G1::SupportType, G2::SupportType>) - where G1 : SupportGenerator<F, N, Id=usize>, - G2 : SupportGenerator<F, N, Id=usize> { - - let id : usize = d.into(); - ((n0+id).into(), EitherSupport::Right(support)) + fn map_right<F: Float, const N: usize>( + n0: &usize, + (d, support): (G2::Id, G2::SupportType), + ) -> (usize, EitherSupport<G2::SupportType, G1::SupportType>) + where + G1: SupportGenerator<N, F, Id = usize>, + G2: SupportGenerator<N, F, Id = usize>, + { + let id: usize = d.into(); + ((n0 + id).into(), EitherSupport::Right(support)) } /// Calls [`SupportGenerator::all_data`] on the “left” support generator. /// /// Converts both the id and the [`Support`] into a form that corresponds to `BothGenerators`. #[inline] - pub(super) fn all_left_data<F : Float, const N : usize>(&self) - -> MapF<G1::AllDataIter<'_>, (usize, EitherSupport<G1::SupportType, G2::SupportType>)> - where G1 : SupportGenerator<F, N, Id=usize>, - G2 : SupportGenerator<F, N, Id=usize> { + pub(super) fn all_left_data<F: Float, const N: usize>( + &self, + ) -> MapF<G1::AllDataIter<'_>, (usize, EitherSupport<G2::SupportType, G1::SupportType>)> + where + G1: SupportGenerator<N, F, Id = usize>, + G2: SupportGenerator<N, F, Id = usize>, + { self.0.all_data().mapF(Self::map_left) } @@ -85,33 +86,38 @@ /// /// Converts both the id and the [`Support`] into a form that corresponds to `BothGenerators`. #[inline] - pub(super) fn all_right_data<F : Float, const N : usize>(&self) - -> MapZ<G2::AllDataIter<'_>, usize, (usize, EitherSupport<G1::SupportType, G2::SupportType>)> - where G1 : SupportGenerator<F, N, Id=usize>, - G2 : SupportGenerator<F, N, Id=usize> { + pub(super) fn all_right_data<F: Float, const N: usize>( + &self, + ) -> MapZ<G2::AllDataIter<'_>, usize, (usize, EitherSupport<G2::SupportType, G1::SupportType>)> + where + G1: SupportGenerator<N, F, Id = usize>, + G2: SupportGenerator<N, F, Id = usize>, + { let n0 = self.0.support_count(); self.1.all_data().mapZ(n0, Self::map_right) } } -impl<F : Float, G1, G2, const N : usize> -SupportGenerator<F, N> -for BothGenerators<G1, G2> -where G1 : SupportGenerator<F, N, Id=usize>, - G2 : SupportGenerator<F, N, Id=usize> { - +impl<F: Float, G1, G2, const N: usize> SupportGenerator<N, F> for BothGenerators<G1, G2> +where + G1: SupportGenerator<N, F, Id = usize>, + G2: SupportGenerator<N, F, Id = usize>, +{ type Id = usize; - type SupportType = EitherSupport<G1::SupportType, G2::SupportType>; - type AllDataIter<'a> = BothAllDataIter<'a, F, G1, G2, N> where G1 : 'a, G2 : 'a; + type SupportType = EitherSupport<G2::SupportType, G1::SupportType>; + type AllDataIter<'a> + = BothAllDataIter<'a, F, G1, G2, N> + where + G1: 'a, + G2: 'a; #[inline] - fn support_for(&self, id : Self::Id) - -> Self::SupportType { + fn support_for(&self, id: Self::Id) -> Self::SupportType { let n0 = self.0.support_count(); if id < n0 { EitherSupport::Left(self.0.support_for(id.into())) } else { - EitherSupport::Right(self.1.support_for((id-n0).into())) + EitherSupport::Right(self.1.support_for((id - n0).into())) } } @@ -126,12 +132,13 @@ } } -impl<F: Float, S1, S2, const N : usize> Support<F, N> for EitherSupport<S1, S2> -where S1 : Support<F, N>, - S2 : Support<F, N> { - +impl<F: Float, S1, S2, const N: usize> Support<N, F> for EitherSupport<S2, S1> +where + S1: Support<N, F>, + S2: Support<N, F>, +{ #[inline] - fn support_hint(&self) -> Cube<F,N> { + fn support_hint(&self) -> Cube<N, F> { match self { EitherSupport::Left(ref a) => a.support_hint(), EitherSupport::Right(ref b) => b.support_hint(), @@ -139,7 +146,7 @@ } #[inline] - fn in_support(&self, x : &Loc<F,N>) -> bool { + fn in_support(&self, x: &Loc<N, F>) -> bool { match self { EitherSupport::Left(ref a) => a.in_support(x), EitherSupport::Right(ref b) => b.in_support(x), @@ -147,7 +154,7 @@ } #[inline] - fn bisection_hint(&self, cube : &Cube<F, N>) -> [Option<F>; N] { + fn bisection_hint(&self, cube: &Cube<N, F>) -> [Option<F>; N] { match self { EitherSupport::Left(ref a) => a.bisection_hint(cube), EitherSupport::Right(ref b) => b.bisection_hint(cube), @@ -155,13 +162,14 @@ } } -impl<F : Float, A, S1, S2, const N : usize> LocalAnalysis<F, A, N> for EitherSupport<S1, S2> -where A : Aggregator, - S1 : LocalAnalysis<F, A, N>, - S2 : LocalAnalysis<F, A, N>, { - +impl<F: Float, A, S1, S2, const N: usize> LocalAnalysis<F, A, N> for EitherSupport<S2, S1> +where + A: Aggregator, + S1: LocalAnalysis<F, A, N>, + S2: LocalAnalysis<F, A, N>, +{ #[inline] - fn local_analysis(&self, cube : &Cube<F, N>) -> A { + fn local_analysis(&self, cube: &Cube<N, F>) -> A { match self { EitherSupport::Left(ref a) => a.local_analysis(cube), EitherSupport::Right(ref b) => b.local_analysis(cube), @@ -169,11 +177,12 @@ } } -impl<F : Float, A, S1, S2> GlobalAnalysis<F, A> for EitherSupport<S1, S2> -where A : Aggregator, - S1 : GlobalAnalysis<F, A>, - S2 : GlobalAnalysis<F, A>, { - +impl<F: Float, A, S1, S2> GlobalAnalysis<F, A> for EitherSupport<S2, S1> +where + A: Aggregator, + S1: GlobalAnalysis<F, A>, + S2: GlobalAnalysis<F, A>, +{ #[inline] fn global_analysis(&self) -> A { match self { @@ -183,17 +192,17 @@ } } -impl<F, S1, S2, X> Mapping<X> for EitherSupport<S1, S2> +impl<F, S1, S2, X> Mapping<X> for EitherSupport<S2, S1> where - F : Space, - X : Space, - S1 : Mapping<X, Codomain=F>, - S2 : Mapping<X, Codomain=F>, + F: ClosedSpace, + X: Space, + S1: Mapping<X, Codomain = F>, + S2: Mapping<X, Codomain = F>, { type Codomain = F; #[inline] - fn apply<I : Instance<X>>(&self, x : I) -> F { + fn apply<I: Instance<X>>(&self, x: I) -> F { match self { EitherSupport::Left(ref a) => a.apply(x), EitherSupport::Right(ref b) => b.apply(x), @@ -201,17 +210,17 @@ } } -impl<X, S1, S2, O> DifferentiableImpl<X> for EitherSupport<S1, S2> +impl<X, S1, S2, O> DifferentiableImpl<X> for EitherSupport<S2, S1> where - O : Space, - X : Space, - S1 : DifferentiableMapping<X, DerivativeDomain=O>, - S2 : DifferentiableMapping<X, DerivativeDomain=O>, + O: ClosedSpace, + X: ClosedSpace, + S1: DifferentiableMapping<X, DerivativeDomain = O>, + S2: DifferentiableMapping<X, DerivativeDomain = O>, { type Derivative = O; #[inline] - fn differential_impl<I : Instance<X>>(&self, x : I) -> O { + fn differential_impl<I: Instance<X>>(&self, x: I) -> O { match self { EitherSupport::Left(ref a) => a.differential(x), EitherSupport::Right(ref b) => b.differential(x), @@ -221,44 +230,47 @@ macro_rules! make_either_scalarop_rhs { ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { - impl<F : Float, G1, G2> - std::ops::$trait_assign<F> - for BothGenerators<G1, G2> - where G1 : std::ops::$trait_assign<F> + Clone, - G2 : std::ops::$trait_assign<F> + Clone, { + impl<F: Float, G1, G2> std::ops::$trait_assign<F> for BothGenerators<G1, G2> + where + G1: std::ops::$trait_assign<F> + Clone, + G2: std::ops::$trait_assign<F> + Clone, + { #[inline] - fn $fn_assign(&mut self, t : F) { + fn $fn_assign(&mut self, t: F) { Arc::make_mut(&mut self.0).$fn_assign(t); Arc::make_mut(&mut self.1).$fn_assign(t); } } - impl<'a, F : Float, G1, G2> - std::ops::$trait<F> - for &'a BothGenerators<G1, G2> - where &'a G1 : std::ops::$trait<F,Output=G1>, - &'a G2 : std::ops::$trait<F,Output=G2> { + impl<'a, F: Float, G1, G2> std::ops::$trait<F> for &'a BothGenerators<G1, G2> + where + &'a G1: std::ops::$trait<F, Output = G1>, + &'a G2: std::ops::$trait<F, Output = G2>, + { type Output = BothGenerators<G1, G2>; #[inline] - fn $fn(self, t : F) -> BothGenerators<G1, G2> { - BothGenerators(Arc::new(self.0.$fn(t)), - Arc::new(self.1.$fn(t))) + fn $fn(self, t: F) -> BothGenerators<G1, G2> { + BothGenerators(Arc::new(self.0.$fn(t)), Arc::new(self.1.$fn(t))) } } - } + }; } make_either_scalarop_rhs!(Mul, mul, MulAssign, mul_assign); make_either_scalarop_rhs!(Div, div, DivAssign, div_assign); impl<G1, G2> std::ops::Neg for BothGenerators<G1, G2> -where G1 : std::ops::Neg + Clone, - G2 : std::ops::Neg + Clone, { +where + G1: std::ops::Neg + Clone, + G2: std::ops::Neg + Clone, +{ type Output = BothGenerators<G1::Output, G2::Output>; #[inline] fn neg(self) -> Self::Output { - BothGenerators(Arc::new(Arc::unwrap_or_clone(self.0).neg()), - Arc::new(Arc::unwrap_or_clone(self.1).neg())) + BothGenerators( + Arc::new(Arc::unwrap_or_clone(self.0).neg()), + Arc::new(Arc::unwrap_or_clone(self.1).neg()), + ) } } /* @@ -270,4 +282,4 @@ BothGenerators(self.0.neg(), self.1.neg()) } } -*/ \ No newline at end of file +*/
--- a/src/bisection_tree/refine.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/bisection_tree/refine.rs Fri May 15 14:46:30 2026 -0500 @@ -1,32 +1,31 @@ - -use std::collections::BinaryHeap; -use std::cmp::{PartialOrd, Ord, Ordering, Ordering::*, max}; -use std::marker::PhantomData; -use std::sync::{Arc, Mutex, MutexGuard, Condvar}; -use crate::types::*; +use super::aggregator::*; +use super::bt::*; +use super::support::*; use crate::nanleast::NaNLeast; +use crate::parallelism::TaskBudget; +use crate::parallelism::{thread_pool, thread_pool_size}; use crate::sets::Cube; -use crate::parallelism::{thread_pool_size, thread_pool}; -use super::support::*; -use super::bt::*; -use super::aggregator::*; -use crate::parallelism::TaskBudget; +use crate::types::*; +use std::cmp::{max, Ord, Ordering, Ordering::*, PartialOrd}; +use std::collections::BinaryHeap; +use std::marker::PhantomData; +use std::sync::{Arc, Condvar, Mutex, MutexGuard}; /// Trait for sorting [`Aggregator`]s for [`BT`] refinement. /// /// The sorting involves two sorting keys, the “upper” and the “lower” key. Any [`BT`] nodes /// with upper key less the lower key of another are discarded from the refinement process. /// Nodes with the highest upper sorting key are picked for refinement. -pub trait AggregatorSorting : Sync + Send + 'static { +pub trait AggregatorSorting: Sync + Send + 'static { // Priority - type Agg : Aggregator; - type Sort : Ord + Copy + std::fmt::Debug + Sync + Send; + type Agg: Aggregator; + type Sort: Ord + Copy + std::fmt::Debug + Sync + Send; /// Returns lower sorting key - fn sort_lower(aggregator : &Self::Agg) -> Self::Sort; + fn sort_lower(aggregator: &Self::Agg) -> Self::Sort; /// Returns upper sorting key - fn sort_upper(aggregator : &Self::Agg) -> Self::Sort; + fn sort_upper(aggregator: &Self::Agg) -> Self::Sort; /// Returns a sorting key that is less than any other sorting key. fn bottom() -> Self::Sort; @@ -35,53 +34,64 @@ /// An [`AggregatorSorting`] for [`Bounds`], using the upper/lower bound as the upper/lower key. /// /// See [`LowerBoundSorting`] for the opposite ordering. -pub struct UpperBoundSorting<F : Float>(PhantomData<F>); +pub struct UpperBoundSorting<F: Float>(PhantomData<F>); /// An [`AggregatorSorting`] for [`Bounds`], using the upper/lower bound as the lower/upper key. /// /// See [`UpperBoundSorting`] for the opposite ordering. -pub struct LowerBoundSorting<F : Float>(PhantomData<F>); +pub struct LowerBoundSorting<F: Float>(PhantomData<F>); -impl<F : Float> AggregatorSorting for UpperBoundSorting<F> { +impl<F: Float> AggregatorSorting for UpperBoundSorting<F> { type Agg = Bounds<F>; type Sort = NaNLeast<F>; #[inline] - fn sort_lower(aggregator : &Bounds<F>) -> Self::Sort { NaNLeast(aggregator.lower()) } - - #[inline] - fn sort_upper(aggregator : &Bounds<F>) -> Self::Sort { NaNLeast(aggregator.upper()) } + fn sort_lower(aggregator: &Bounds<F>) -> Self::Sort { + NaNLeast(aggregator.lower()) + } #[inline] - fn bottom() -> Self::Sort { NaNLeast(F::NEG_INFINITY) } + fn sort_upper(aggregator: &Bounds<F>) -> Self::Sort { + NaNLeast(aggregator.upper()) + } + + #[inline] + fn bottom() -> Self::Sort { + NaNLeast(F::NEG_INFINITY) + } } - -impl<F : Float> AggregatorSorting for LowerBoundSorting<F> { +impl<F: Float> AggregatorSorting for LowerBoundSorting<F> { type Agg = Bounds<F>; type Sort = NaNLeast<F>; #[inline] - fn sort_upper(aggregator : &Bounds<F>) -> Self::Sort { NaNLeast(-aggregator.lower()) } + fn sort_upper(aggregator: &Bounds<F>) -> Self::Sort { + NaNLeast(-aggregator.lower()) + } #[inline] - fn sort_lower(aggregator : &Bounds<F>) -> Self::Sort { NaNLeast(-aggregator.upper()) } + fn sort_lower(aggregator: &Bounds<F>) -> Self::Sort { + NaNLeast(-aggregator.upper()) + } #[inline] - fn bottom() -> Self::Sort { NaNLeast(F::NEG_INFINITY) } + fn bottom() -> Self::Sort { + NaNLeast(F::NEG_INFINITY) + } } /// Return type of [`Refiner::refine`]. /// /// The parameter `R` is the result type of the refiner acting on an [`Aggregator`] of type `A`. -pub enum RefinerResult<A : Aggregator, R> { +pub enum RefinerResult<A: Aggregator, R> { /// Indicates an insufficiently refined state: the [`BT`] needs to be further refined. NeedRefinement, /// Indicates a certain result `R`, stop refinement immediately. Certain(R), /// Indicates an uncertain result: continue refinement until candidates have been exhausted /// or a certain result found. - Uncertain(A, R) + Uncertain(A, R), } use RefinerResult::*; @@ -92,16 +102,17 @@ /// The `Refiner` is used to determine whether an [`Aggregator`] `A` stored in the [`BT`] is /// sufficiently refined within a [`Cube`], and in such a case, produce a desired result (e.g. /// a maximum value of a function). -pub trait Refiner<F : Float, A, G, const N : usize> : Sync + Send + 'static -where F : Num, - A : Aggregator, - G : SupportGenerator<F, N> { - +pub trait Refiner<F: Float, A, G, const N: usize>: Sync + Send + 'static +where + F: Num, + A: Aggregator, + G: SupportGenerator<N, F>, +{ /// The result type of the refiner - type Result : std::fmt::Debug + Sync + Send + 'static; + type Result: std::fmt::Debug + Sync + Send + 'static; /// The sorting to be employed by [`BTSearch::search_and_refine`] on node aggregators /// to detemrine node priority. - type Sorting : AggregatorSorting<Agg = A>; + type Sorting: AggregatorSorting<Agg = A>; /// Determines whether `aggregator` is sufficiently refined within `domain`. /// @@ -124,42 +135,45 @@ /// number of steps is reached. fn refine( &self, - aggregator : &A, - domain : &Cube<F, N>, - data : &[G::Id], - generator : &G, - step : usize, + aggregator: &A, + domain: &Cube<N, F>, + data: &[G::Id], + generator: &G, + step: usize, ) -> RefinerResult<A, Self::Result>; /// Fuse two [`Self::Result`]s (needed in threaded refinement). - fn fuse_results(r1 : &mut Self::Result, r2 : Self::Result); + fn fuse_results(r1: &mut Self::Result, r2: Self::Result); } /// Structure for tracking the refinement process in a [`BinaryHeap`]. -struct RefinementInfo<'a, F, D, A, S, RResult, const N : usize, const P : usize> -where F : Float, - D : 'static, - A : Aggregator, - S : AggregatorSorting<Agg = A> { +struct RefinementInfo<'a, F, D, A, S, RResult, const N: usize, const P: usize> +where + F: Float, + D: 'static, + A: Aggregator, + S: AggregatorSorting<Agg = A>, +{ /// Domain of `node` - cube : Cube<F, N>, + cube: Cube<N, F>, /// Node to be refined - node : &'a mut Node<F, D, A, N, P>, + node: &'a mut Node<F, D, A, N, P>, /// Result and improve aggregator for the [`Refiner`] - refiner_info : Option<(A, RResult)>, + refiner_info: Option<(A, RResult)>, /// For [`AggregatorSorting`] being used for the type system - sorting : PhantomData<S>, + sorting: PhantomData<S>, } -impl<'a, F, D, A, S, RResult, const N : usize, const P : usize> -RefinementInfo<'a, F, D, A, S, RResult, N, P> -where F : Float, - D : 'static, - A : Aggregator, - S : AggregatorSorting<Agg = A> { - +impl<'a, F, D, A, S, RResult, const N: usize, const P: usize> + RefinementInfo<'a, F, D, A, S, RResult, N, P> +where + F: Float, + D: 'static, + A: Aggregator, + S: AggregatorSorting<Agg = A>, +{ #[inline] - fn with_aggregator<U>(&self, f : impl FnOnce(&A) -> U) -> U { + fn with_aggregator<U>(&self, f: impl FnOnce(&A) -> U) -> U { match self.refiner_info { Some((ref agg, _)) => f(agg), None => f(&self.node.aggregator), @@ -177,93 +191,105 @@ } } -impl<'a, F, D, A, S, RResult, const N : usize, const P : usize> PartialEq -for RefinementInfo<'a, F, D, A, S, RResult, N, P> -where F : Float, - D : 'static, - A : Aggregator, - S : AggregatorSorting<Agg = A> { - +impl<'a, F, D, A, S, RResult, const N: usize, const P: usize> PartialEq + for RefinementInfo<'a, F, D, A, S, RResult, N, P> +where + F: Float, + D: 'static, + A: Aggregator, + S: AggregatorSorting<Agg = A>, +{ #[inline] - fn eq(&self, other : &Self) -> bool { self.cmp(other) == Equal } -} - -impl<'a, F, D, A, S, RResult, const N : usize, const P : usize> PartialOrd -for RefinementInfo<'a, F, D, A, S, RResult, N, P> -where F : Float, - D : 'static, - A : Aggregator, - S : AggregatorSorting<Agg = A> { - - #[inline] - fn partial_cmp(&self, other : &Self) -> Option<Ordering> { Some(self.cmp(other)) } + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == Equal + } } -impl<'a, F, D, A, S, RResult, const N : usize, const P : usize> Eq -for RefinementInfo<'a, F, D, A, S, RResult, N, P> -where F : Float, - D : 'static, - A : Aggregator, - S : AggregatorSorting<Agg = A> { +impl<'a, F, D, A, S, RResult, const N: usize, const P: usize> PartialOrd + for RefinementInfo<'a, F, D, A, S, RResult, N, P> +where + F: Float, + D: 'static, + A: Aggregator, + S: AggregatorSorting<Agg = A>, +{ + #[inline] + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + Some(self.cmp(other)) + } } -impl<'a, F, D, A, S, RResult, const N : usize, const P : usize> Ord -for RefinementInfo<'a, F, D, A, S, RResult, N, P> -where F : Float, - D : 'static, - A : Aggregator, - S : AggregatorSorting<Agg = A> { +impl<'a, F, D, A, S, RResult, const N: usize, const P: usize> Eq + for RefinementInfo<'a, F, D, A, S, RResult, N, P> +where + F: Float, + D: 'static, + A: Aggregator, + S: AggregatorSorting<Agg = A>, +{ +} +impl<'a, F, D, A, S, RResult, const N: usize, const P: usize> Ord + for RefinementInfo<'a, F, D, A, S, RResult, N, P> +where + F: Float, + D: 'static, + A: Aggregator, + S: AggregatorSorting<Agg = A>, +{ #[inline] - fn cmp(&self, other : &Self) -> Ordering { - self.with_aggregator(|agg1| other.with_aggregator(|agg2| { - match S::sort_upper(agg1).cmp(&S::sort_upper(agg2)) { + fn cmp(&self, other: &Self) -> Ordering { + self.with_aggregator(|agg1| { + other.with_aggregator(|agg2| match S::sort_upper(agg1).cmp(&S::sort_upper(agg2)) { Equal => S::sort_lower(agg1).cmp(&S::sort_lower(agg2)), order => order, - } - })) + }) + }) } } /// This is a container for a [`BinaryHeap`] of [`RefinementInfo`]s together with tracking of /// the greatest lower bound of the [`Aggregator`]s of the [`Node`]s therein accroding to /// chosen [`AggregatorSorting`]. -struct HeapContainer<'a, F, D, A, S, RResult, const N : usize, const P : usize> -where F : Float, - D : 'static + Copy, - Const<P> : BranchCount<N>, - A : Aggregator, - S : AggregatorSorting<Agg = A> { +struct HeapContainer<'a, F, D, A, S, RResult, const N: usize, const P: usize> +where + F: Float, + D: 'static + Copy, + Const<P>: BranchCount<N>, + A: Aggregator, + S: AggregatorSorting<Agg = A>, +{ /// Priority queue of nodes to be refined - heap : BinaryHeap<RefinementInfo<'a, F, D, A, S, RResult, N, P>>, + heap: BinaryHeap<RefinementInfo<'a, F, D, A, S, RResult, N, P>>, /// Maximum of node sorting lower bounds seen in the heap - glb : S::Sort, + glb: S::Sort, /// Number of insertions in the heap since previous prune - insert_counter : usize, + insert_counter: usize, /// If a result has been found by some refinment threat, it is stored here - result : Option<RResult>, + result: Option<RResult>, /// Refinement step counter - step : usize, + step: usize, /// Number of threads currently processing (not sleeping) - n_processing : usize, + n_processing: usize, /// Threshold for heap pruning - heap_prune_threshold : usize, + heap_prune_threshold: usize, } -impl<'a, F, D, A, S, RResult, const N : usize, const P : usize> -HeapContainer<'a, F, D, A, S, RResult, N, P> -where F : Float, - D : 'static + Copy, - Const<P> : BranchCount<N>, - A : Aggregator, - S : AggregatorSorting<Agg = A> { - +impl<'a, F, D, A, S, RResult, const N: usize, const P: usize> + HeapContainer<'a, F, D, A, S, RResult, N, P> +where + F: Float, + D: 'static + Copy, + Const<P>: BranchCount<N>, + A: Aggregator, + S: AggregatorSorting<Agg = A>, +{ /// Push `ri` into the [`BinaryHeap`]. Do greatest lower bound maintenance. /// /// Returns a boolean indicating whether the push was actually performed due to glb /// filtering or not. #[inline] - fn push(&mut self, ri : RefinementInfo<'a, F, D, A, S, RResult, N, P>) -> bool { + fn push(&mut self, ri: RefinementInfo<'a, F, D, A, S, RResult, N, P>) -> bool { if ri.sort_upper() >= self.glb { let l = ri.sort_lower(); self.heap.push(ri); @@ -276,37 +302,38 @@ } } -impl<F : Float, D, A, const N : usize, const P : usize> -Branches<F,D,A,N,P> -where Const<P> : BranchCount<N>, - A : Aggregator, - D : 'static + Copy + Send + Sync { - +impl<F: Float, D, A, const N: usize, const P: usize> Branches<F, D, A, N, P> +where + Const<P>: BranchCount<N>, + A: Aggregator, + D: 'static + Copy + Send + Sync, +{ /// Stage all subnodes of `self` into the refinement queue `container`. fn stage_refine<'a, S, RResult>( &'a mut self, - domain : Cube<F,N>, - container : &mut HeapContainer<'a, F, D, A, S, RResult, N, P>, - ) where S : AggregatorSorting<Agg = A> { + domain: Cube<N, F>, + container: &mut HeapContainer<'a, F, D, A, S, RResult, N, P>, + ) where + S: AggregatorSorting<Agg = A>, + { // Insert all subnodes into the refinement heap. for (node, cube) in self.nodes_and_cubes_mut(&domain) { container.push(RefinementInfo { cube, node, - refiner_info : None, - sorting : PhantomData, + refiner_info: None, + sorting: PhantomData, }); } } } - -impl<F : Float, D, A, const N : usize, const P : usize> -Node<F,D,A,N,P> -where Const<P> : BranchCount<N>, - A : Aggregator, - D : 'static + Copy + Send + Sync { - +impl<F: Float, D, A, const N: usize, const P: usize> Node<F, D, A, N, P> +where + Const<P>: BranchCount<N>, + A: Aggregator, + D: 'static + Copy + Send + Sync, +{ /// If `self` is a leaf node, uses the `refiner` to determine whether further subdivision /// is required to get a sufficiently refined solution for the problem the refiner is used /// to solve. If the refiner returns [`RefinerResult::Certain`] result, it is returned. @@ -316,17 +343,18 @@ /// /// `domain`, as usual, indicates the spatial area corresponding to `self`. fn search_and_refine<'a, 'b, 'c, R, G>( - self : &'a mut Self, - domain : Cube<F,N>, - refiner : &R, - generator : &G, - container_arc : &'c Arc<Mutex<HeapContainer<'a, F, D, A, R::Sorting, R::Result, N, P>>>, - step : usize + self: &'a mut Self, + domain: Cube<N, F>, + refiner: &R, + generator: &G, + container_arc: &'c Arc<Mutex<HeapContainer<'a, F, D, A, R::Sorting, R::Result, N, P>>>, + step: usize, ) -> Result<R::Result, MutexGuard<'c, HeapContainer<'a, F, D, A, R::Sorting, R::Result, N, P>>> - where R : Refiner<F, A, G, N>, - G : SupportGenerator<F, N, Id=D>, - G::SupportType : LocalAnalysis<F, A, N> { - + where + R: Refiner<F, A, G, N>, + G: SupportGenerator<N, F, Id = D>, + G::SupportType: LocalAnalysis<F, A, N>, + { //drop(container); // Refine a leaf. @@ -368,26 +396,27 @@ unsafe { Arc::get_mut_unchecked(arc_b) } .stage_refine(domain, &mut *container); #[cfg(not(nightly))] - Arc::get_mut(arc_b).unwrap() + Arc::get_mut(arc_b) + .unwrap() .stage_refine(domain, &mut *container); - - return Err(container) - }, + + return Err(container); + } _ => unreachable!("This cannot happen"), } } } res - }, + } NodeOption::Branches(ref mut b) => { // Insert branches into refinement priority queue. let mut container = container_arc.lock().unwrap(); Arc::make_mut(b).stage_refine(domain, &mut *container); - return Err(container) - }, + return Err(container); + } NodeOption::Uninitialised => { refiner.refine(&self.aggregator, &domain, &[], generator, step) - }, + } }; match res { @@ -399,17 +428,17 @@ // aggregator. let mut container = container_arc.lock().unwrap(); container.push(RefinementInfo { - cube : domain, - node : self, - refiner_info : Some((agg, val)), - sorting : PhantomData, + cube: domain, + node: self, + refiner_info: Some((agg, val)), + sorting: PhantomData, }); Err(container) - }, + } Certain(val) => { // The refiner gave a certain result so return it to allow early termination Ok(val) - }, + } NeedRefinement => { // This should only happen when we run into NodeOption::Uninitialised above. // There's really nothing to do. @@ -423,9 +452,10 @@ /// /// This can be removed and the methods implemented directly on [`BT`] once Rust's const generics /// are flexible enough to allow fixing `P=pow(2, N)`. -pub trait BTSearch<F, const N : usize> : BTImpl<F, N> -where F : Float { - +pub trait BTSearch<const N: usize, F = f64>: BTImpl<N, F> +where + F: Float, +{ /// Perform a search on on `Self`, as determined by `refiner`. /// /// Nodes are inserted in a priority queue and processed in the order determined by the @@ -437,26 +467,28 @@ /// The `generator` converts [`BTImpl::Data`] stored in the bisection tree into a [`Support`]. fn search_and_refine<'b, R, G>( &'b mut self, - refiner : R, - generator : &Arc<G>, + refiner: R, + generator: &Arc<G>, ) -> Option<R::Result> - where R : Refiner<F, Self::Agg, G, N> + Sync + Send + 'static, - G : SupportGenerator<F, N, Id=Self::Data> + Sync + Send + 'static, - G::SupportType : LocalAnalysis<F, Self::Agg, N>; + where + R: Refiner<F, Self::Agg, G, N> + Sync + Send + 'static, + G: SupportGenerator<N, F, Id = Self::Data> + Sync + Send + 'static, + G::SupportType: LocalAnalysis<F, Self::Agg, N>; } -fn refinement_loop<F : Float, D, A, R, G, const N : usize, const P : usize> ( - wakeup : Option<Arc<Condvar>>, - refiner : &R, - generator_arc : &Arc<G>, - container_arc : &Arc<Mutex<HeapContainer<F, D, A, R::Sorting, R::Result, N, P>>>, -) where A : Aggregator, - R : Refiner<F, A, G, N>, - G : SupportGenerator<F, N, Id=D>, - G::SupportType : LocalAnalysis<F, A, N>, - Const<P> : BranchCount<N>, - D : 'static + Copy + Sync + Send + std::fmt::Debug { - +fn refinement_loop<F: Float, D, A, R, G, const N: usize, const P: usize>( + wakeup: Option<Arc<Condvar>>, + refiner: &R, + generator_arc: &Arc<G>, + container_arc: &Arc<Mutex<HeapContainer<F, D, A, R::Sorting, R::Result, N, P>>>, +) where + A: Aggregator, + R: Refiner<F, A, G, N>, + G: SupportGenerator<N, F, Id = D>, + G::SupportType: LocalAnalysis<F, A, N>, + Const<P>: BranchCount<N>, + D: 'static + Copy + Sync + Send + std::fmt::Debug, +{ let mut did_park = true; let mut container = container_arc.lock().unwrap(); @@ -471,7 +503,7 @@ // Some refinement task/thread has found a result, return if container.result.is_some() { container.n_processing -= 1; - break 'main + break 'main; } match container.heap.pop() { @@ -489,7 +521,7 @@ container = c.wait(container).unwrap(); continue 'get_next; } else { - break 'main + break 'main; } } }; @@ -502,7 +534,7 @@ // Terminate based on a “best possible” result. container.result = Some(result); container.n_processing -= 1; - break 'main + break 'main; } // Do priority queue maintenance @@ -513,10 +545,8 @@ container.glb = glb; // Prune container.heap.retain(|ri| ri.sort_upper() >= glb); - }, - None => { - container.glb = R::Sorting::bottom() } + None => container.glb = R::Sorting::bottom(), } container.insert_counter = 0; } @@ -525,8 +555,14 @@ drop(container); // … and process the node. We may get returned an already unlocked mutex. - match Node::search_and_refine(ri.node, ri.cube, refiner, &**generator_arc, - &container_arc, step) { + match Node::search_and_refine( + ri.node, + ri.cube, + refiner, + &**generator_arc, + &container_arc, + step, + ) { Ok(r) => { let mut container = container_arc.lock().unwrap(); // Terminate based on a certain result from the refiner @@ -534,8 +570,8 @@ Some(ref mut r_prev) => R::fuse_results(r_prev, r), None => container.result = Some(r), } - break 'main - }, + break 'main; + } Err(cnt) => { container = cnt; // Wake up another thread if one is sleeping; there should be now work in the @@ -545,7 +581,6 @@ } } } - } // Make sure no task is sleeping @@ -558,9 +593,9 @@ macro_rules! impl_btsearch { ($($n:literal)*) => { $( impl<'a, M, F, D, A> - BTSearch<F, $n> + BTSearch<$n, F> for BT<M,F,D,A,$n> - where //Self : BTImpl<F,$n,Data=D,Agg=A, Depth=M>, // <== automatically deduced + where //Self : BTImpl<$n, F, Data=D,Agg=A, Depth=M>, // <== automatically deduced M : Depth, F : Float + Send, A : Aggregator, @@ -571,7 +606,7 @@ generator : &Arc<G>, ) -> Option<R::Result> where R : Refiner<F, A, G, $n>, - G : SupportGenerator<F, $n, Id=D>, + G : SupportGenerator< $n, F, Id=D>, G::SupportType : LocalAnalysis<F, A, $n> { let mut init_container = HeapContainer { heap : BinaryHeap::new(), @@ -620,4 +655,3 @@ } impl_btsearch!(1 2 3 4); -
--- a/src/bisection_tree/support.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/bisection_tree/support.rs Fri May 15 14:46:30 2026 -0500 @@ -1,31 +1,29 @@ - /*! Traits for representing the support of a [`Mapping`], and analysing the mapping on a [`Cube`]. */ -use serde::Serialize; -use std::ops::{MulAssign,DivAssign,Neg}; -use crate::types::{Float, Num}; +use super::aggregator::Bounds; +pub use crate::bounds::{GlobalAnalysis, LocalAnalysis}; +use crate::loc::Loc; +use crate::mapping::{ClosedSpace, DifferentiableImpl, DifferentiableMapping, Instance, Mapping}; use crate::maputil::map2; -use crate::mapping::{ - Instance, Mapping, DifferentiableImpl, DifferentiableMapping, Space -}; +use crate::norms::{Linfinity, Norm, L1, L2}; +pub use crate::operator_arithmetic::{Constant, Weighted}; use crate::sets::Cube; -use crate::loc::Loc; -use super::aggregator::Bounds; -use crate::norms::{Norm, L1, L2, Linfinity}; -pub use crate::operator_arithmetic::{Weighted, Constant}; +use crate::types::{Float, Num}; +use serde::Serialize; +use std::ops::{DivAssign, MulAssign, Neg}; /// A trait for working with the supports of [`Mapping`]s. /// /// `Mapping` is not a super-trait to allow more general use. -pub trait Support<F : Num, const N : usize> : Sized + Sync + Send + 'static { +pub trait Support<const N: usize, F: Num>: Sized + Sync + Send + 'static { /// Return a cube containing the support of the function represented by `self`. /// /// The hint may be larger than the actual support, but must contain it. - fn support_hint(&self) -> Cube<F,N>; + fn support_hint(&self) -> Cube<N, F>; /// Indicate whether `x` is in the support of the function represented by `self`. - fn in_support(&self, x : &Loc<F,N>) -> bool; + fn in_support(&self, x: &Loc<N, F>) -> bool; // Indicate whether `cube` is fully in the support of the function represented by `self`. //fn fully_in_support(&self, cube : &Cube<F,N>) -> bool; @@ -41,139 +39,99 @@ /// The default implementation returns `[None; N]`. #[inline] #[allow(unused_variables)] - fn bisection_hint(&self, cube : &Cube<F, N>) -> [Option<F>; N] { + fn bisection_hint(&self, cube: &Cube<N, F>) -> [Option<F>; N] { [None; N] } /// Translate `self` by `x`. #[inline] - fn shift(self, x : Loc<F, N>) -> Shift<Self, F, N> { - Shift { shift : x, base_fn : self } + fn shift(self, x: Loc<N, F>) -> Shift<Self, N, F> { + Shift { shift: x, base_fn: self } } } -/// Trait for globally analysing a property `A` of a [`Mapping`]. -/// -/// Typically `A` is an [`Aggregator`][super::aggregator::Aggregator] such as -/// [`Bounds`][super::aggregator::Bounds]. -pub trait GlobalAnalysis<F : Num, A> { - /// Perform global analysis of the property `A` of `Self`. - /// - /// As an example, in the case of `A` being [`Bounds`][super::aggregator::Bounds], - /// this function will return global upper and lower bounds for the mapping - /// represented by `self`. - fn global_analysis(&self) -> A; +/// Shift of [`Support`] and [`Mapping`]; output of [`Support::shift`]. +#[derive(Copy, Clone, Debug, Serialize)] // Serialize! but not implemented by Loc. +pub struct Shift<T, const N: usize, F = f64> { + shift: Loc<N, F>, + base_fn: T, } -// default impl<F, A, N, L> GlobalAnalysis<F, A, N> for L -// where L : LocalAnalysis<F, A, N> { -// #[inline] -// fn global_analysis(&self) -> Bounds<F> { -// self.local_analysis(&self.support_hint()) -// } -// } - -/// Trait for locally analysing a property `A` of a [`Mapping`] (implementing [`Support`]) -/// within a [`Cube`]. -/// -/// Typically `A` is an [`Aggregator`][super::aggregator::Aggregator] such as -/// [`Bounds`][super::aggregator::Bounds]. -pub trait LocalAnalysis<F : Num, A, const N : usize> : GlobalAnalysis<F, A> + Support<F, N> { - /// Perform local analysis of the property `A` of `Self`. - /// - /// As an example, in the case of `A` being [`Bounds`][super::aggregator::Bounds], - /// this function will return upper and lower bounds within `cube` for the mapping - /// represented by `self`. - fn local_analysis(&self, cube : &Cube<F, N>) -> A; -} - -/// Trait for determining the upper and lower bounds of an float-valued [`Mapping`]. -/// -/// This is a blanket-implemented alias for [`GlobalAnalysis`]`<F, Bounds<F>>` -/// [`Mapping`] is not a supertrait to allow flexibility in the implementation of either -/// reference or non-reference arguments. -pub trait Bounded<F : Float> : GlobalAnalysis<F, Bounds<F>> { - /// Return lower and upper bounds for the values of of `self`. - #[inline] - fn bounds(&self) -> Bounds<F> { - self.global_analysis() - } -} - -impl<F : Float, T : GlobalAnalysis<F, Bounds<F>>> Bounded<F> for T { } - -/// Shift of [`Support`] and [`Mapping`]; output of [`Support::shift`]. -#[derive(Copy,Clone,Debug,Serialize)] // Serialize! but not implemented by Loc. -pub struct Shift<T, F, const N : usize> { - shift : Loc<F, N>, - base_fn : T, -} - -impl<'a, T, V : Space, F : Float, const N : usize> Mapping<Loc<F, N>> for Shift<T,F,N> -where T : Mapping<Loc<F, N>, Codomain=V> { +impl<'a, T, V: ClosedSpace, F: Float, const N: usize> Mapping<Loc<N, F>> for Shift<T, N, F> +where + T: Mapping<Loc<N, F>, Codomain = V>, +{ type Codomain = V; #[inline] - fn apply<I : Instance<Loc<F, N>>>(&self, x : I) -> Self::Codomain { + fn apply<I: Instance<Loc<N, F>>>(&self, x: I) -> Self::Codomain { self.base_fn.apply(x.own() - &self.shift) } } -impl<'a, T, V : Space, F : Float, const N : usize> DifferentiableImpl<Loc<F, N>> for Shift<T,F,N> -where T : DifferentiableMapping<Loc<F, N>, DerivativeDomain=V> { +impl<'a, T, V: ClosedSpace, F: Float, const N: usize> DifferentiableImpl<Loc<N, F>> + for Shift<T, N, F> +where + T: DifferentiableMapping<Loc<N, F>, DerivativeDomain = V>, +{ type Derivative = V; #[inline] - fn differential_impl<I : Instance<Loc<F, N>>>(&self, x : I) -> Self::Derivative { + fn differential_impl<I: Instance<Loc<N, F>>>(&self, x: I) -> Self::Derivative { self.base_fn.differential(x.own() - &self.shift) } } -impl<'a, T, F : Float, const N : usize> Support<F,N> for Shift<T,F,N> -where T : Support<F, N> { +impl<'a, T, F: Float, const N: usize> Support<N, F> for Shift<T, N, F> +where + T: Support<N, F>, +{ #[inline] - fn support_hint(&self) -> Cube<F,N> { + fn support_hint(&self) -> Cube<N, F> { self.base_fn.support_hint().shift(&self.shift) } #[inline] - fn in_support(&self, x : &Loc<F,N>) -> bool { + fn in_support(&self, x: &Loc<N, F>) -> bool { self.base_fn.in_support(&(x - &self.shift)) } - + // fn fully_in_support(&self, _cube : &Cube<F,N>) -> bool { // //self.base_fn.fully_in_support(cube.shift(&vectorneg(self.shift))) // todo!("Not implemented, but not used at the moment") // } #[inline] - fn bisection_hint(&self, cube : &Cube<F,N>) -> [Option<F>; N] { + fn bisection_hint(&self, cube: &Cube<N, F>) -> [Option<F>; N] { let base_hint = self.base_fn.bisection_hint(cube); map2(base_hint, &self.shift, |h, s| h.map(|z| z + *s)) } - } -impl<'a, T, F : Float, const N : usize> GlobalAnalysis<F, Bounds<F>> for Shift<T,F,N> -where T : LocalAnalysis<F, Bounds<F>, N> { +impl<'a, T, F: Float, const N: usize> GlobalAnalysis<F, Bounds<F>> for Shift<T, N, F> +where + T: LocalAnalysis<F, Bounds<F>, N>, +{ #[inline] fn global_analysis(&self) -> Bounds<F> { self.base_fn.global_analysis() } } -impl<'a, T, F : Float, const N : usize> LocalAnalysis<F, Bounds<F>, N> for Shift<T,F,N> -where T : LocalAnalysis<F, Bounds<F>, N> { +impl<'a, T, F: Float, const N: usize> LocalAnalysis<F, Bounds<F>, N> for Shift<T, N, F> +where + T: LocalAnalysis<F, Bounds<F>, N>, +{ #[inline] - fn local_analysis(&self, cube : &Cube<F, N>) -> Bounds<F> { + fn local_analysis(&self, cube: &Cube<N, F>) -> Bounds<F> { self.base_fn.local_analysis(&cube.shift(&(-self.shift))) } } macro_rules! impl_shift_norm { ($($norm:ident)*) => { $( - impl<'a, T, F : Float, const N : usize> Norm<F, $norm> for Shift<T,F,N> - where T : Norm<F, $norm> { + impl<'a, T, F : Float, const N : usize> Norm<$norm, F> for Shift<T, N, F> + where T : Norm<$norm, F> { #[inline] fn norm(&self, n : $norm) -> F { self.base_fn.norm(n) @@ -184,33 +142,36 @@ impl_shift_norm!(L1 L2 Linfinity); -impl<'a, T, F : Float, C, const N : usize> Support<F,N> for Weighted<T, C> -where T : Support<F, N>, - C : Constant<Type=F> { - +impl<'a, T, F: Float, C, const N: usize> Support<N, F> for Weighted<T, C> +where + T: Support<N, F>, + C: Constant<Type = F>, +{ #[inline] - fn support_hint(&self) -> Cube<F,N> { + fn support_hint(&self) -> Cube<N, F> { self.base_fn.support_hint() } #[inline] - fn in_support(&self, x : &Loc<F,N>) -> bool { + fn in_support(&self, x: &Loc<N, F>) -> bool { self.base_fn.in_support(x) } - + // fn fully_in_support(&self, cube : &Cube<F,N>) -> bool { // self.base_fn.fully_in_support(cube) // } #[inline] - fn bisection_hint(&self, cube : &Cube<F,N>) -> [Option<F>; N] { + fn bisection_hint(&self, cube: &Cube<N, F>) -> [Option<F>; N] { self.base_fn.bisection_hint(cube) } } -impl<'a, T, F : Float, C> GlobalAnalysis<F, Bounds<F>> for Weighted<T, C> -where T : GlobalAnalysis<F, Bounds<F>>, - C : Constant<Type=F> { +impl<'a, T, F: Float, C> GlobalAnalysis<F, Bounds<F>> for Weighted<T, C> +where + T: GlobalAnalysis<F, Bounds<F>>, + C: Constant<Type = F>, +{ #[inline] fn global_analysis(&self) -> Bounds<F> { let Bounds(lower, upper) = self.base_fn.global_analysis(); @@ -222,11 +183,13 @@ } } -impl<'a, T, F : Float, C, const N : usize> LocalAnalysis<F, Bounds<F>, N> for Weighted<T, C> -where T : LocalAnalysis<F, Bounds<F>, N>, - C : Constant<Type=F> { +impl<'a, T, F: Float, C, const N: usize> LocalAnalysis<F, Bounds<F>, N> for Weighted<T, C> +where + T: LocalAnalysis<F, Bounds<F>, N>, + C: Constant<Type = F>, +{ #[inline] - fn local_analysis(&self, cube : &Cube<F, N>) -> Bounds<F> { + fn local_analysis(&self, cube: &Cube<N, F>) -> Bounds<F> { let Bounds(lower, upper) = self.base_fn.local_analysis(cube); debug_assert!(lower <= upper); match self.weight.value() { @@ -238,31 +201,33 @@ macro_rules! make_weighted_scalarop_rhs { ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { - impl<F : Float, T> std::ops::$trait_assign<F> for Weighted<T, F> { + impl<F: Float, T> std::ops::$trait_assign<F> for Weighted<T, F> { #[inline] - fn $fn_assign(&mut self, t : F) { + fn $fn_assign(&mut self, t: F) { self.weight.$fn_assign(t); } } - impl<'a, F : Float, T> std::ops::$trait<F> for Weighted<T, F> { + impl<'a, F: Float, T> std::ops::$trait<F> for Weighted<T, F> { type Output = Self; #[inline] - fn $fn(mut self, t : F) -> Self { + fn $fn(mut self, t: F) -> Self { self.weight.$fn_assign(t); self } } - impl<'a, F : Float, T> std::ops::$trait<F> for &'a Weighted<T, F> - where T : Clone { + impl<'a, F: Float, T> std::ops::$trait<F> for &'a Weighted<T, F> + where + T: Clone, + { type Output = Weighted<T, F>; #[inline] - fn $fn(self, t : F) -> Self::Output { - Weighted { weight : self.weight.$fn(t), base_fn : self.base_fn.clone() } + fn $fn(self, t: F) -> Self::Output { + Weighted { weight: self.weight.$fn(t), base_fn: self.base_fn.clone() } } } - } + }; } make_weighted_scalarop_rhs!(Mul, mul, MulAssign, mul_assign); @@ -270,8 +235,8 @@ macro_rules! impl_weighted_norm { ($($norm:ident)*) => { $( - impl<'a, T, F : Float> Norm<F, $norm> for Weighted<T,F> - where T : Norm<F, $norm> { + impl<'a, T, F : Float> Norm<$norm, F> for Weighted<T,F> + where T : Norm<$norm, F> { #[inline] fn norm(&self, n : $norm) -> F { self.base_fn.norm(n) * self.weight.abs() @@ -282,52 +247,60 @@ impl_weighted_norm!(L1 L2 Linfinity); - /// Normalisation of [`Support`] and [`Mapping`] to L¹ norm 1. /// /// Currently only scalar-valued functions are supported. #[derive(Copy, Clone, Debug, Serialize, PartialEq)] pub struct Normalised<T>( /// The base [`Support`] or [`Mapping`]. - pub T + pub T, ); -impl<'a, T, F : Float, const N : usize> Mapping<Loc<F, N>> for Normalised<T> -where T : Norm<F, L1> + Mapping<Loc<F,N>, Codomain=F> { +impl<'a, T, F: Float, const N: usize> Mapping<Loc<N, F>> for Normalised<T> +where + T: Norm<L1, F> + Mapping<Loc<N, F>, Codomain = F>, +{ type Codomain = F; #[inline] - fn apply<I : Instance<Loc<F, N>>>(&self, x : I) -> Self::Codomain { + fn apply<I: Instance<Loc<N, F>>>(&self, x: I) -> Self::Codomain { let w = self.0.norm(L1); - if w == F::ZERO { F::ZERO } else { self.0.apply(x) / w } + if w == F::ZERO { + F::ZERO + } else { + self.0.apply(x) / w + } } } -impl<'a, T, F : Float, const N : usize> Support<F,N> for Normalised<T> -where T : Norm<F, L1> + Support<F, N> { - +impl<'a, T, F: Float, const N: usize> Support<N, F> for Normalised<T> +where + T: Norm<L1, F> + Support<N, F>, +{ #[inline] - fn support_hint(&self) -> Cube<F,N> { + fn support_hint(&self) -> Cube<N, F> { self.0.support_hint() } #[inline] - fn in_support(&self, x : &Loc<F,N>) -> bool { + fn in_support(&self, x: &Loc<N, F>) -> bool { self.0.in_support(x) } - + // fn fully_in_support(&self, cube : &Cube<F,N>) -> bool { // self.0.fully_in_support(cube) // } #[inline] - fn bisection_hint(&self, cube : &Cube<F,N>) -> [Option<F>; N] { + fn bisection_hint(&self, cube: &Cube<N, F>) -> [Option<F>; N] { self.0.bisection_hint(cube) } } -impl<'a, T, F : Float> GlobalAnalysis<F, Bounds<F>> for Normalised<T> -where T : Norm<F, L1> + GlobalAnalysis<F, Bounds<F>> { +impl<'a, T, F: Float> GlobalAnalysis<F, Bounds<F>> for Normalised<T> +where + T: Norm<L1, F> + GlobalAnalysis<F, Bounds<F>>, +{ #[inline] fn global_analysis(&self) -> Bounds<F> { let Bounds(lower, upper) = self.0.global_analysis(); @@ -338,10 +311,12 @@ } } -impl<'a, T, F : Float, const N : usize> LocalAnalysis<F, Bounds<F>, N> for Normalised<T> -where T : Norm<F, L1> + LocalAnalysis<F, Bounds<F>, N> { +impl<'a, T, F: Float, const N: usize> LocalAnalysis<F, Bounds<F>, N> for Normalised<T> +where + T: Norm<L1, F> + LocalAnalysis<F, Bounds<F>, N>, +{ #[inline] - fn local_analysis(&self, cube : &Cube<F, N>) -> Bounds<F> { + fn local_analysis(&self, cube: &Cube<N, F>) -> Bounds<F> { let Bounds(lower, upper) = self.0.local_analysis(cube); debug_assert!(lower <= upper); let w = self.0.norm(L1); @@ -350,19 +325,25 @@ } } -impl<'a, T, F : Float> Norm<F, L1> for Normalised<T> -where T : Norm<F, L1> { +impl<'a, T, F: Float> Norm<L1, F> for Normalised<T> +where + T: Norm<L1, F>, +{ #[inline] - fn norm(&self, _ : L1) -> F { + fn norm(&self, _: L1) -> F { let w = self.0.norm(L1); - if w == F::ZERO { F::ZERO } else { F::ONE } + if w == F::ZERO { + F::ZERO + } else { + F::ONE + } } } macro_rules! impl_normalised_norm { ($($norm:ident)*) => { $( - impl<'a, T, F : Float> Norm<F, $norm> for Normalised<T> - where T : Norm<F, $norm> + Norm<F, L1> { + impl<'a, T, F : Float> Norm<$norm, F> for Normalised<T> + where T : Norm<$norm, F> + Norm<L1, F> { #[inline] fn norm(&self, n : $norm) -> F { let w = self.0.norm(L1); @@ -375,37 +356,39 @@ impl_normalised_norm!(L2 Linfinity); /* -impl<F : Num, S : Support<F, N>, const N : usize> LocalAnalysis<F, NullAggregator, N> for S { - fn local_analysis(&self, _cube : &Cube<F, N>) -> NullAggregator { NullAggregator } +impl<F : Num, S : Support< N, F>, const N : usize> LocalAnalysis<F, NullAggregator, N> for S { + fn local_analysis(&self, _cube : &Cube<N, F>) -> NullAggregator { NullAggregator } } impl<F : Float, S : Bounded<F>, const N : usize> LocalAnalysis<F, Bounds<F>, N> for S { #[inline] - fn local_analysis(&self, cube : &Cube<F, N>) -> Bounds<F> { + fn local_analysis(&self, cube : &Cube<N, F>) -> Bounds<F> { self.bounds(cube) } }*/ /// Generator of [`Support`]-implementing component functions based on low storage requirement /// [ids][`Self::Id`]. -pub trait SupportGenerator<F : Float, const N : usize> -: MulAssign<F> + DivAssign<F> + Neg<Output=Self> + Clone + Sync + Send + 'static { +pub trait SupportGenerator<const N: usize, F: Float = f64>: + MulAssign<F> + DivAssign<F> + Neg<Output = Self> + Clone + Sync + Send + 'static +{ /// The identification type - type Id : 'static + Copy; + type Id: 'static + Copy; /// The type of the [`Support`] (often also a [`Mapping`]). - type SupportType : 'static + Support<F, N>; + type SupportType: 'static + Support<N, F>; /// An iterator over all the [`Support`]s of the generator. - type AllDataIter<'a> : Iterator<Item=(Self::Id, Self::SupportType)> where Self : 'a; + type AllDataIter<'a>: Iterator<Item = (Self::Id, Self::SupportType)> + where + Self: 'a; /// Returns the component identified by `id`. /// /// Panics if `id` is an invalid identifier. - fn support_for(&self, id : Self::Id) -> Self::SupportType; - + fn support_for(&self, id: Self::Id) -> Self::SupportType; + /// Returns the number of different components in this generator. fn support_count(&self) -> usize; /// Returns an iterator over all pairs of `(id, support)`. fn all_data(&self) -> Self::AllDataIter<'_>; } -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/bounds.rs Fri May 15 14:46:30 2026 -0500 @@ -0,0 +1,252 @@ +/*! +Bounded and minimizable/maximizable mappings. +*/ + +use crate::instance::{Instance, Space}; +use crate::mapping::Mapping; +use crate::sets::{Cube, Set}; +use crate::types::{Float, Num}; + +/// Trait for globally analysing a property `A` of a [`Mapping`]. +/// +/// Typically `A` is an [`Aggregator`][super::bisection_tree::Aggregator] +/// such as [`Bounds`]. +pub trait GlobalAnalysis<F: Num, A> { + /// Perform global analysis of the property `A` of `Self`. + /// + /// As an example, in the case of `A` being [`Bounds`], + /// this function will return global upper and lower bounds for the mapping + /// represented by `self`. + fn global_analysis(&self) -> A; +} + +// default impl<F, A, N, L> GlobalAnalysis<F, A, N> for L +// where L : LocalAnalysis<F, A, N> { +// #[inline] +// fn global_analysis(&self) -> Bounds<F> { +// self.local_analysis(&self.support_hint()) +// } +// } + +/// Trait for locally analysing a property `A` of a [`Mapping`] (implementing [`super::bisection_tree::Support`]) +/// within a [`Cube`]. +/// +/// Typically `A` is an [`Aggregator`][super::bisection_tree::Aggregator] such as [`Bounds`]. +pub trait LocalAnalysis<F: Num, A, const N: usize>: GlobalAnalysis<F, A> { + /// Perform local analysis of the property `A` of `Self`. + /// + /// As an example, in the case of `A` being [`Bounds`], + /// this function will return upper and lower bounds within `cube` for the mapping + /// represented by `self`. + fn local_analysis(&self, cube: &Cube<N, F>) -> A; +} + +/// Trait for determining the upper and lower bounds of an float-valued [`Mapping`]. +/// +/// This is a blanket-implemented alias for [`GlobalAnalysis`]`<F, Bounds<F>>` +/// [`Mapping`] is not a supertrait to allow flexibility in the implementation of either +/// reference or non-reference arguments. +pub trait Bounded<F: Float>: GlobalAnalysis<F, Bounds<F>> { + /// Return lower and upper bounds for the values of of `self`. + #[inline] + fn bounds(&self) -> Bounds<F> { + self.global_analysis() + } +} + +impl<F: Float, T: GlobalAnalysis<F, Bounds<F>>> Bounded<F> for T {} + +/// A real-valued [`Mapping`] that provides rough bounds as well as minimisation and maximisation. +pub trait MinMaxMapping<Domain: Space, F: Float = f64>: + Mapping<Domain, Codomain = F> + Bounded<F> +{ + /// Maximise the mapping within stated value `tolerance`. + /// + /// At most `max_steps` refinement steps are taken. + /// Returns the approximate maximiser and the corresponding function value. + fn maximise(&mut self, tolerance: F, max_steps: usize) -> (Domain, F); + + /// Maximise the mapping within stated value `tolerance` subject to a lower bound. + /// + /// At most `max_steps` refinement steps are taken. + /// Returns the approximate maximiser and the corresponding function value when one is found + /// above the `bound` threshold, otherwise `None`. + fn maximise_above(&mut self, bound: F, tolerance: F, max_steps: usize) -> Option<(Domain, F)> { + let res @ (_, v) = self.maximise(tolerance, max_steps); + (v > bound).then_some(res) + } + + /// Minimise the mapping within stated value `tolerance`. + /// + /// At most `max_steps` refinement steps are taken. + /// Returns the approximate minimiser and the corresponding function value. + fn minimise(&mut self, tolerance: F, max_steps: usize) -> (Domain, F); + + /// Minimise the mapping within stated value `tolerance` subject to a lower bound. + /// + /// At most `max_steps` refinement steps are taken. + /// Returns the approximate minimiser and the corresponding function value when one is found + /// below the `bound` threshold, otherwise `None`. + fn minimise_below(&mut self, bound: F, tolerance: F, max_steps: usize) -> Option<(Domain, F)> { + let res @ (_, v) = self.minimise(tolerance, max_steps); + (v < bound).then_some(res) + } + + /// Verify that the mapping has a given upper `bound` within indicated `tolerance`. + /// + /// At most `max_steps` refinement steps are taken. + fn has_upper_bound(&mut self, bound: F, tolerance: F, max_steps: usize) -> bool { + match self.maximise_above(bound, tolerance, max_steps) { + None => true, + Some((_, v)) => v <= bound, + } + } + + /// Verify that the mapping has a given lower `bound` within indicated `tolerance`. + /// + /// At most `max_steps` refinement steps are taken. + fn has_lower_bound(&mut self, bound: F, tolerance: F, max_steps: usize) -> bool { + match self.minimise_below(bound, tolerance, max_steps) { + None => true, + Some((_, v)) => v >= bound, + } + } +} + +/// Upper and lower bounds on an `F`-valued function. +#[derive(Copy, Clone, Debug)] +pub struct Bounds<F>( + /// Lower bound + pub F, + /// Upper bound + pub F, +); + +impl<F: Copy> Bounds<F> { + /// Returns the lower bound + #[inline] + pub fn lower(&self) -> F { + self.0 + } + + /// Returns the upper bound + #[inline] + pub fn upper(&self) -> F { + self.1 + } +} + +impl<F: Float> Bounds<F> { + /// Returns a uniform bound. + /// + /// This is maximum over the absolute values of the upper and lower bound. + #[inline] + pub fn uniform(&self) -> F { + let &Bounds(lower, upper) = self; + lower.abs().max(upper.abs()) + } + + /// Construct a bounds, making sure `lower` bound is less than `upper` + #[inline] + pub fn corrected(lower: F, upper: F) -> Self { + if lower <= upper { + Bounds(lower, upper) + } else { + Bounds(upper, lower) + } + } + + /// Refine the lower bound + #[inline] + pub fn refine_lower(&self, lower: F) -> Self { + let &Bounds(l, u) = self; + debug_assert!(l <= u); + Bounds(l.max(lower), u.max(lower)) + } + + /// Refine the lower bound + #[inline] + pub fn refine_upper(&self, upper: F) -> Self { + let &Bounds(l, u) = self; + debug_assert!(l <= u); + Bounds(l.min(upper), u.min(upper)) + } +} + +impl<'a, F: Float> std::ops::Add<Self> for Bounds<F> { + type Output = Self; + #[inline] + fn add(self, Bounds(l2, u2): Self) -> Self::Output { + let Bounds(l1, u1) = self; + debug_assert!(l1 <= u1 && l2 <= u2); + Bounds(l1 + l2, u1 + u2) + } +} + +impl<'a, F: Float> std::ops::Mul<Self> for Bounds<F> { + type Output = Self; + #[inline] + fn mul(self, Bounds(l2, u2): Self) -> Self::Output { + let Bounds(l1, u1) = self; + debug_assert!(l1 <= u1 && l2 <= u2); + let a = l1 * l2; + let b = u1 * u2; + // The order may flip when negative numbers are involved, so need min/max + Bounds(a.min(b), a.max(b)) + } +} + +impl<F: Float> std::iter::Product for Bounds<F> { + #[inline] + fn product<I>(mut iter: I) -> Self + where + I: Iterator<Item = Self>, + { + match iter.next() { + None => Bounds(F::ZERO, F::ZERO), + Some(init) => iter.fold(init, |a, b| a * b), + } + } +} + +impl<F: Float> Set<F> for Bounds<F> { + fn contains<I: Instance<F>>(&self, item: I) -> bool { + let v = item.own(); + let &Bounds(l, u) = self; + debug_assert!(l <= u); + l <= v && v <= u + } +} + +impl<F: Float> Bounds<F> { + /// Calculate a common bound (glb, lub) for two bounds. + #[inline] + pub fn common(&self, &Bounds(l2, u2): &Self) -> Self { + let &Bounds(l1, u1) = self; + debug_assert!(l1 <= u1 && l2 <= u2); + Bounds(l1.min(l2), u1.max(u2)) + } + + /// Indicates whether `Self` is a superset of the argument bound. + #[inline] + pub fn superset(&self, &Bounds(l2, u2): &Self) -> bool { + let &Bounds(l1, u1) = self; + debug_assert!(l1 <= u1 && l2 <= u2); + l1 <= l2 && u2 <= u1 + } + + /// Returns the greatest bound contained by both argument bounds, if one exists. + #[inline] + pub fn glb(&self, &Bounds(l2, u2): &Self) -> Option<Self> { + let &Bounds(l1, u1) = self; + debug_assert!(l1 <= u1 && l2 <= u2); + let l = l1.max(l2); + let u = u1.min(u2); + debug_assert!(l <= u); + if l < u { + Some(Bounds(l, u)) + } else { + None + } + } +}
--- a/src/collection.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/collection.rs Fri May 15 14:46:30 2026 -0500 @@ -5,20 +5,24 @@ use crate::loc::Loc; /// An abstract collection of elements. -pub trait Collection : IntoIterator<Item = Self::Element> { +pub trait Collection: IntoIterator<Item = Self::Element> { /// Type of elements of the collection type Element; /// Iterator over references to elements of the collection - type RefsIter<'a> : Iterator<Item=&'a Self::Element> where Self : 'a; + type RefsIter<'a>: Iterator<Item = &'a Self::Element> + where + Self: 'a; /// Returns an iterator over references to elements of the collection. fn iter_refs(&self) -> Self::RefsIter<'_>; } /// An abstract collection of mutable elements. -pub trait CollectionMut : Collection { +pub trait CollectionMut: Collection { /// Iterator over references to elements of the collection - type RefsIterMut<'a> : Iterator<Item=&'a mut Self::Element> where Self : 'a; + type RefsIterMut<'a>: Iterator<Item = &'a mut Self::Element> + where + Self: 'a; /// Returns an iterator over references to elements of the collection. fn iter_refs_mut(&mut self) -> Self::RefsIterMut<'_>; @@ -51,4 +55,4 @@ slice_like_collection!(Vec<E> where E); slice_like_collection!([E; N] where E, const N : usize); -slice_like_collection!(Loc<E, N> where E, const N : usize); +slice_like_collection!(Loc<N, E> where E, const N : usize);
--- a/src/convex.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/convex.rs Fri May 15 14:46:30 2026 -0500 @@ -2,27 +2,36 @@ Some convex analysis basics */ -use std::marker::PhantomData; +use crate::error::DynResult; +use crate::euclidean::Euclidean; +use crate::instance::{ClosedSpace, DecompositionMut, Instance}; +use crate::linops::{IdOp, Scaled, SimpleZeroOp, AXPY}; +use crate::mapping::{DifferentiableImpl, LipschitzDifferentiableImpl, Mapping, Space}; +use crate::norms::*; +use crate::operator_arithmetic::{Constant, Weighted}; use crate::types::*; -use crate::mapping::{Mapping, Space}; -use crate::linops::IdOp; -use crate::instance::{Instance, InstanceMut, DecompositionMut}; -use crate::operator_arithmetic::{Constant, Weighted}; -use crate::norms::*; +use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; /// Trait for convex mappings. Has no features, just serves as a constraint /// /// TODO: should constrain `Mapping::Codomain` to implement a partial order, /// but this makes everything complicated with little benefit. -pub trait ConvexMapping<Domain : Space, F : Num = f64> : Mapping<Domain, Codomain = F> -{} +pub trait ConvexMapping<Domain: Space, F: Num = f64>: Mapping<Domain, Codomain = F> { + /// Returns (a lower estimate of) the factor of strong convexity in the norm of `Domain`. + fn factor_of_strong_convexity(&self) -> F { + F::ZERO + } +} /// Trait for mappings with a Fenchel conjugate /// /// The conjugate type has to implement [`ConvexMapping`], but a `Conjugable` mapping need /// not be convex. -pub trait Conjugable<Domain : HasDual<F>, F : Num = f64> : Mapping<Domain> { - type Conjugate<'a> : ConvexMapping<Domain::DualSpace, F> where Self : 'a; +pub trait Conjugable<Domain: HasDual<F>, F: Num = f64>: Mapping<Domain, Codomain = F> { + type Conjugate<'a>: ConvexMapping<Domain::DualSpace, F> + where + Self: 'a; fn conjugate(&self) -> Self::Conjugate<'_>; } @@ -31,12 +40,14 @@ /// /// In contrast to [`Conjugable`], the preconjugate need not implement [`ConvexMapping`], /// but a `Preconjugable` mapping has to be convex. -pub trait Preconjugable<Domain, Predual, F : Num = f64> : ConvexMapping<Domain, F> +pub trait Preconjugable<Domain, Predual, F: Num = f64>: ConvexMapping<Domain, F> where - Domain : Space, - Predual : HasDual<F> + Domain: Space, + Predual: HasDual<F>, { - type Preconjugate<'a> : Mapping<Predual> where Self : 'a; + type Preconjugate<'a>: Mapping<Predual, Codomain = F> + where + Self: 'a; fn preconjugate(&self) -> Self::Preconjugate<'_>; } @@ -45,53 +56,55 @@ /// /// The conjugate type has to implement [`ConvexMapping`], but a `Conjugable` mapping need /// not be convex. -pub trait Prox<Domain : Space> : Mapping<Domain> { - type Prox<'a> : Mapping<Domain, Codomain=Domain> where Self : 'a; +pub trait Prox<Domain: Space>: Mapping<Domain> { + type Prox<'a>: Mapping<Domain, Codomain = Domain::Principal> + where + Self: 'a; /// Returns a proximal mapping with weight τ - fn prox_mapping(&self, τ : Self::Codomain) -> Self::Prox<'_>; + fn prox_mapping(&self, τ: Self::Codomain) -> Self::Prox<'_>; /// Calculate the proximal mapping with weight τ - fn prox<I : Instance<Domain>>(&self, τ : Self::Codomain, z : I) -> Domain { + fn prox<I: Instance<Domain>>(&self, τ: Self::Codomain, z: I) -> Domain::Principal { self.prox_mapping(τ).apply(z) } /// Calculate the proximal mapping with weight τ in-place - fn prox_mut<'b>(&self, τ : Self::Codomain, y : &'b mut Domain) + fn prox_mut<'b>(&self, τ: Self::Codomain, y: &'b mut Domain::Principal) where - &'b mut Domain : InstanceMut<Domain>, - Domain:: Decomp : DecompositionMut<Domain>, - for<'a> &'a Domain : Instance<Domain>, + Domain::Decomp: DecompositionMut<Domain>, + for<'a> &'a Domain::Principal: Instance<Domain>, { *y = self.prox(τ, &*y); } } - /// Constraint to the unit ball of the norm described by `E`. -pub struct NormConstraint<F : Float, E : NormExponent> { - radius : F, - norm : NormMapping<F, E>, +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct NormConstraint<F: Float, E: NormExponent> { + radius: F, + norm: NormMapping<F, E>, } impl<Domain, E, F> ConvexMapping<Domain, F> for NormMapping<F, E> where - Domain : Space, - E : NormExponent, - F : Float, - Self : Mapping<Domain, Codomain=F> -{} - + Domain: Space, + E: NormExponent, + F: Float, + Self: Mapping<Domain, Codomain = F>, +{ +} impl<F, E, Domain> Mapping<Domain> for NormConstraint<F, E> where - Domain : Space + Norm<F, E>, - F : Float, - E : NormExponent, + Domain: Space, + Domain::Principal: Norm<E, F>, + F: Float, + E: NormExponent, { type Codomain = F; - fn apply<I : Instance<Domain>>(&self, d : I) -> F { + fn apply<I: Instance<Domain>>(&self, d: I) -> F { if d.eval(|x| x.norm(self.norm.exponent)) <= self.radius { F::ZERO } else { @@ -102,68 +115,78 @@ impl<Domain, E, F> ConvexMapping<Domain, F> for NormConstraint<F, E> where - Domain : Space, - E : NormExponent, - F : Float, - Self : Mapping<Domain, Codomain=F> -{} - + Domain: Space, + E: NormExponent, + F: Float, + Self: Mapping<Domain, Codomain = F>, +{ +} impl<E, F, Domain> Conjugable<Domain, F> for NormMapping<F, E> where - E : HasDualExponent, - F : Float, - Domain : HasDual<F> + Norm<F, E> + Space, - <Domain as HasDual<F>>::DualSpace : Norm<F, E::DualExp> + E: HasDualExponent, + F: Float, + Domain: HasDual<F>, + Domain::Principal: Norm<E, F>, + <Domain as HasDual<F>>::DualSpace: Norm<E::DualExp, F>, { - type Conjugate<'a> = NormConstraint<F, E::DualExp> where Self : 'a; + type Conjugate<'a> + = NormConstraint<F, E::DualExp> + where + Self: 'a; fn conjugate(&self) -> Self::Conjugate<'_> { - NormConstraint { - radius : F::ONE, - norm : self.exponent.dual_exponent().as_mapping() - } + NormConstraint { radius: F::ONE, norm: self.exponent.dual_exponent().as_mapping() } } } impl<C, E, F, Domain> Conjugable<Domain, F> for Weighted<NormMapping<F, E>, C> where - C : Constant<Type = F>, - E : HasDualExponent, - F : Float, - Domain : HasDual<F> + Norm<F, E> + Space, - <Domain as HasDual<F>>::DualSpace : Norm<F, E::DualExp> + C: Constant<Type = F>, + E: HasDualExponent, + F: Float, + Domain: HasDual<F>, + Domain::Principal: Norm<E, F>, + <Domain as HasDual<F>>::DualSpace: Norm<E::DualExp, F>, { - type Conjugate<'a> = NormConstraint<F, E::DualExp> where Self : 'a; + type Conjugate<'a> + = NormConstraint<F, E::DualExp> + where + Self: 'a; fn conjugate(&self) -> Self::Conjugate<'_> { NormConstraint { - radius : self.weight.value(), - norm : self.base_fn.exponent.dual_exponent().as_mapping() + radius: self.weight.value(), + norm: self.base_fn.exponent.dual_exponent().as_mapping(), } } } impl<Domain, E, F> Prox<Domain> for NormConstraint<F, E> where - Domain : Space + Norm<F, E>, - E : NormExponent, - F : Float, - NormProjection<F, E> : Mapping<Domain, Codomain=Domain>, + Domain: Space, + Domain::Principal: Norm<E, F>, + E: NormExponent, + F: Float, + NormProjection<F, E>: Mapping<Domain, Codomain = Domain::Principal>, { - type Prox<'a> = NormProjection<F, E> where Self : 'a; + type Prox<'a> + = NormProjection<F, E> + where + Self: 'a; #[inline] - fn prox_mapping(&self, _τ : Self::Codomain) -> Self::Prox<'_> { + fn prox_mapping(&self, _τ: Self::Codomain) -> Self::Prox<'_> { assert!(self.radius >= F::ZERO); - NormProjection{ radius : self.radius, exponent : self.norm.exponent } + NormProjection { radius: self.radius, exponent: self.norm.exponent } } } /// Projection to the unit ball of the norm described by `E`. -pub struct NormProjection<F : Float, E : NormExponent> { - radius : F, - exponent : E, +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct NormProjection<F: Float, E: NormExponent> { + radius: F, + exponent: E, } /* @@ -182,41 +205,44 @@ impl<F, E, Domain> Mapping<Domain> for NormProjection<F, E> where - Domain : Space + Projection<F, E>, - F : Float, - E : NormExponent, + Domain: Space, + Domain::Principal: ClosedSpace + Projection<F, E>, + F: Float, + E: NormExponent, { - type Codomain = Domain; + type Codomain = Domain::Principal; - fn apply<I : Instance<Domain>>(&self, d : I) -> Domain { + fn apply<I: Instance<Domain>>(&self, d: I) -> Self::Codomain { d.own().proj_ball(self.radius, self.exponent) } } +/// The zero mapping +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct Zero<Domain: Space, F: Num>(PhantomData<(Domain, F)>); -/// The zero mapping -pub struct Zero<Domain : Space, F : Num>(PhantomData<(Domain, F)>); - -impl<Domain : Space, F : Num> Zero<Domain, F> { +impl<Domain: Space, F: Num> Zero<Domain, F> { pub fn new() -> Self { Zero(PhantomData) } } -impl<Domain : Space, F : Num> Mapping<Domain> for Zero<Domain, F> { +impl<Domain: Space, F: Num> Mapping<Domain> for Zero<Domain, F> { type Codomain = F; /// Compute the value of `self` at `x`. - fn apply<I : Instance<Domain>>(&self, _x : I) -> Self::Codomain { + fn apply<I: Instance<Domain>>(&self, _x: I) -> Self::Codomain { F::ZERO } } -impl<Domain : Space, F : Num> ConvexMapping<Domain, F> for Zero<Domain, F> { } - +impl<Domain: Space, F: Float> ConvexMapping<Domain, F> for Zero<Domain, F> {} -impl<Domain : HasDual<F>, F : Float> Conjugable<Domain, F> for Zero<Domain, F> { - type Conjugate<'a> = ZeroIndicator<Domain::DualSpace, F> where Self : 'a; +impl<Domain: HasDual<F>, F: Float> Conjugable<Domain, F> for Zero<Domain, F> { + type Conjugate<'a> + = ZeroIndicator<Domain::DualSpace, F> + where + Self: 'a; #[inline] fn conjugate(&self) -> Self::Conjugate<'_> { @@ -224,12 +250,15 @@ } } -impl<Domain, Predual, F : Float> Preconjugable<Domain, Predual, F> for Zero<Domain, F> +impl<Domain, Predual, F: Float> Preconjugable<Domain, Predual, F> for Zero<Domain, F> where - Domain : Space, - Predual : HasDual<F> + Domain: Normed<F>, + Predual: HasDual<F, PrincipalV = Predual>, { - type Preconjugate<'a> = ZeroIndicator<Predual, F> where Self : 'a; + type Preconjugate<'a> + = ZeroIndicator<Predual, F> + where + Self: 'a; #[inline] fn preconjugate(&self) -> Self::Preconjugate<'_> { @@ -237,38 +266,61 @@ } } -impl<Domain : Space + Clone, F : Num> Prox<Domain> for Zero<Domain, F> { - type Prox<'a> = IdOp<Domain> where Self : 'a; +impl<Domain: Space, F: Num> Prox<Domain> for Zero<Domain, F> { + type Prox<'a> + = IdOp<Domain> + where + Self: 'a; #[inline] - fn prox_mapping(&self, _τ : Self::Codomain) -> Self::Prox<'_> { + fn prox_mapping(&self, _τ: Self::Codomain) -> Self::Prox<'_> { IdOp::new() } } +/// The zero indicator +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct ZeroIndicator<Domain: Space, F: Num>(PhantomData<(Domain, F)>); -/// The zero indicator -pub struct ZeroIndicator<Domain : Space, F : Num>(PhantomData<(Domain, F)>); - -impl<Domain : Space, F : Num> ZeroIndicator<Domain, F> { +impl<Domain: Space, F: Num> ZeroIndicator<Domain, F> { pub fn new() -> Self { ZeroIndicator(PhantomData) } } -impl<Domain : Normed<F>, F : Float> Mapping<Domain> for ZeroIndicator<Domain, F> { +impl<Domain, F> Mapping<Domain> for ZeroIndicator<Domain, F> +where + F: Float, + Domain: Space, + Domain::Principal: Normed<F>, +{ type Codomain = F; /// Compute the value of `self` at `x`. - fn apply<I : Instance<Domain>>(&self, x : I) -> Self::Codomain { + fn apply<I: Instance<Domain>>(&self, x: I) -> Self::Codomain { x.eval(|x̃| if x̃.is_zero() { F::ZERO } else { F::INFINITY }) } } -impl<Domain : Normed<F>, F : Float> ConvexMapping<Domain, F> for ZeroIndicator<Domain, F> { } +impl<Domain, F: Float> ConvexMapping<Domain, F> for ZeroIndicator<Domain, F> +where + Domain: Space, + Domain::Principal: Normed<F>, +{ + fn factor_of_strong_convexity(&self) -> F { + F::INFINITY + } +} -impl<Domain : HasDual<F>, F : Float> Conjugable<Domain, F> for ZeroIndicator<Domain, F> { - type Conjugate<'a> = Zero<Domain::DualSpace, F> where Self : 'a; +impl<Domain, F: Float> Conjugable<Domain, F> for ZeroIndicator<Domain, F> +where + Domain: HasDual<F>, + Domain::PrincipalV: Normed<F>, +{ + type Conjugate<'a> + = Zero<Domain::DualSpace, F> + where + Self: 'a; #[inline] fn conjugate(&self) -> Self::Conjugate<'_> { @@ -276,15 +328,123 @@ } } -impl<Domain, Predual, F : Float> Preconjugable<Domain, Predual, F> for ZeroIndicator<Domain, F> +impl<Domain, Predual, F: Float> Preconjugable<Domain, Predual, F> for ZeroIndicator<Domain, F> where - Domain : Normed<F>, - Predual : HasDual<F> + Domain: Space, + Domain::Principal: Normed<F>, + Predual: HasDual<F>, { - type Preconjugate<'a> = Zero<Predual, F> where Self : 'a; + type Preconjugate<'a> + = Zero<Predual, F> + where + Self: 'a; #[inline] fn preconjugate(&self) -> Self::Preconjugate<'_> { Zero::new() } } + +impl<Domain, F> Prox<Domain> for ZeroIndicator<Domain, F> +where + Domain: AXPY<Field = F, PrincipalV = Domain> + Normed<F>, + F: Float, +{ + type Prox<'a> + = SimpleZeroOp + where + Self: 'a; + + /// Returns a proximal mapping with weight τ + fn prox_mapping(&self, _τ: F) -> Self::Prox<'_> { + return SimpleZeroOp; + } +} + +/// The squared Euclidean norm divided by two +#[derive(Copy, Clone, Serialize, Deserialize)] +pub struct Norm222<F: Float>(PhantomData<F>); + +impl</*Domain: Euclidean<F>,*/ F: Float> Norm222<F> { + pub fn new() -> Self { + Norm222(PhantomData) + } +} + +impl<X: Euclidean<F>, F: Float> Mapping<X> for Norm222<F> { + type Codomain = F; + + /// Compute the value of `self` at `x`. + fn apply<I: Instance<X>>(&self, x: I) -> Self::Codomain { + x.eval(|z| z.norm2_squared() / F::TWO) + } +} + +impl<X: Euclidean<F>, F: Float> ConvexMapping<X, F> for Norm222<F> { + fn factor_of_strong_convexity(&self) -> F { + F::ONE + } +} + +impl<X: Euclidean<F>, F: Float> Conjugable<X, F> for Norm222<F> { + type Conjugate<'a> + = Self + where + Self: 'a; + + #[inline] + fn conjugate(&self) -> Self::Conjugate<'_> { + Self::new() + } +} + +impl<X: Euclidean<F>, F: Float> Preconjugable<X, X, F> for Norm222<F> { + type Preconjugate<'a> + = Self + where + Self: 'a; + + #[inline] + fn preconjugate(&self) -> Self::Preconjugate<'_> { + Self::new() + } +} + +impl<X, F> Prox<X> for Norm222<F> +where + F: Float, + X: Euclidean<F>, +{ + type Prox<'a> + = Scaled<F> + where + Self: 'a; + + fn prox_mapping(&self, τ: F) -> Self::Prox<'_> { + Scaled(F::ONE / (F::ONE + τ)) + } +} + +impl<X, F> DifferentiableImpl<X> for Norm222<F> +where + F: Float, + X: Euclidean<F>, +{ + type Derivative = X::PrincipalV; + + fn differential_impl<I: Instance<X>>(&self, x: I) -> Self::Derivative { + x.own() + } +} + +impl<X, F> LipschitzDifferentiableImpl<X, L2> for Norm222<F> +where + F: Float, + X: Euclidean<F>, +{ + type FloatType = F; + + fn diff_lipschitz_factor(&self, _: L2) -> DynResult<Self::FloatType> { + Ok(F::ONE) + } +}
--- a/src/direct_product.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/direct_product.rs Fri May 15 14:46:30 2026 -0500 @@ -6,11 +6,11 @@ */ use crate::euclidean::Euclidean; -use crate::instance::{Decomposition, DecompositionMut, Instance, InstanceMut, MyCow}; -use crate::linops::AXPY; +use crate::instance::{Decomposition, DecompositionMut, Instance, InstanceMut, MyCow, Ownable}; +use crate::linops::{VectorSpace, AXPY}; use crate::loc::Loc; use crate::mapping::Space; -use crate::norms::{HasDual, Norm, NormExponent, Normed, PairNorm, L2}; +use crate::norms::{Dist, HasDual, Norm, NormExponent, Normed, PairNorm, L2}; use crate::types::{Float, Num}; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use serde::{Deserialize, Serialize}; @@ -39,239 +39,290 @@ } } -macro_rules! impl_binop { - (($a : ty, $b : ty), $trait : ident, $fn : ident, $refl:ident, $refr:ident) => { - impl_binop!(@doit: $a, $b, $trait, $fn; - maybe_lifetime!($refl, &'l Pair<$a,$b>), - (maybe_lifetime!($refl, &'l $a), - maybe_lifetime!($refl, &'l $b)); - maybe_lifetime!($refr, &'r Pair<Ai,Bi>), - (maybe_lifetime!($refr, &'r Ai), - maybe_lifetime!($refr, &'r Bi)); - $refl, $refr); - }; - - (@doit: $a:ty, $b:ty, - $trait:ident, $fn:ident; - $self:ty, ($aself:ty, $bself:ty); - $in:ty, ($ain:ty, $bin:ty); - $refl:ident, $refr:ident) => { - impl<'l, 'r, Ai, Bi> $trait<$in> - for $self - where $aself: $trait<$ain>, - $bself: $trait<$bin> { - type Output = Pair<<$aself as $trait<$ain>>::Output, - <$bself as $trait<$bin>>::Output>; - - #[inline] - fn $fn(self, y : $in) -> Self::Output { - Pair(maybe_ref!($refl, self.0).$fn(maybe_ref!($refr, y.0)), - maybe_ref!($refl, self.1).$fn(maybe_ref!($refr, y.1))) +macro_rules! impl_unary { + ($trait:ident, $fn:ident) => { + impl<A, B> $trait for Pair<A, B> + where + A: $trait, + B: $trait, + { + type Output = Pair<A::Output, B::Output>; + fn $fn(self) -> Self::Output { + let Pair(a, b) = self; + Pair(a.$fn(), b.$fn()) } } + + // Compiler overflow + // impl<'a, A, B> $trait for &'a Pair<A, B> + // where + // &'a A: $trait, + // &'a B: $trait, + // { + // type Output = Pair<<&'a A as $trait>::Output, <&'a B as $trait>::Output>; + // fn $fn(self) -> Self::Output { + // let Pair(ref a, ref b) = self; + // Pair(a.$fn(), b.$fn()) + // } + // } }; } -macro_rules! impl_assignop { - (($a : ty, $b : ty), $trait : ident, $fn : ident, $refr:ident) => { - impl_assignop!(@doit: $a, $b, - $trait, $fn; - maybe_lifetime!($refr, &'r Pair<Ai,Bi>), - (maybe_lifetime!($refr, &'r Ai), - maybe_lifetime!($refr, &'r Bi)); - $refr); - }; - (@doit: $a : ty, $b : ty, - $trait:ident, $fn:ident; - $in:ty, ($ain:ty, $bin:ty); - $refr:ident) => { - impl<'r, Ai, Bi> $trait<$in> - for Pair<$a,$b> - where $a: $trait<$ain>, - $b: $trait<$bin> { - #[inline] - fn $fn(&mut self, y : $in) -> () { - self.0.$fn(maybe_ref!($refr, y.0)); - self.1.$fn(maybe_ref!($refr, y.1)); +impl_unary!(Neg, neg); + +macro_rules! impl_binary { + ($trait:ident, $fn:ident) => { + impl<A, B, C, D> $trait<Pair<C, D>> for Pair<A, B> + where + A: $trait<C>, + B: $trait<D>, + { + type Output = Pair<A::Output, B::Output>; + fn $fn(self, Pair(c, d): Pair<C, D>) -> Self::Output { + let Pair(a, b) = self; + Pair(a.$fn(c), b.$fn(d)) } } - } -} -macro_rules! impl_scalarop { - (($a : ty, $b : ty), $field : ty, $trait : ident, $fn : ident, $refl:ident) => { - impl_scalarop!(@doit: $field, - $trait, $fn; - maybe_lifetime!($refl, &'l Pair<$a,$b>), - (maybe_lifetime!($refl, &'l $a), - maybe_lifetime!($refl, &'l $b)); - $refl); - }; - (@doit: $field : ty, - $trait:ident, $fn:ident; - $self:ty, ($aself:ty, $bself:ty); - $refl:ident) => { - // Scalar as Rhs - impl<'l> $trait<$field> - for $self - where $aself: $trait<$field>, - $bself: $trait<$field> { - type Output = Pair<<$aself as $trait<$field>>::Output, - <$bself as $trait<$field>>::Output>; - #[inline] - fn $fn(self, a : $field) -> Self::Output { - Pair(maybe_ref!($refl, self.0).$fn(a), - maybe_ref!($refl, self.1).$fn(a)) + impl<'a, A, B, C, D> $trait<Pair<C, D>> for &'a Pair<A, B> + where + &'a A: $trait<C>, + &'a B: $trait<D>, + { + type Output = Pair<<&'a A as $trait<C>>::Output, <&'a B as $trait<D>>::Output>; + fn $fn(self, Pair(c, d): Pair<C, D>) -> Self::Output { + let Pair(ref a, ref b) = self; + Pair(a.$fn(c), b.$fn(d)) } } - } -} -// Not used due to compiler overflow -#[allow(unused_macros)] -macro_rules! impl_scalarlhs_op { - (($a : ty, $b : ty), $field : ty, $trait:ident, $fn:ident, $refr:ident) => { - impl_scalarlhs_op!(@doit: $trait, $fn, - maybe_lifetime!($refr, &'r Pair<$a,$b>), - (maybe_lifetime!($refr, &'r $a), - maybe_lifetime!($refr, &'r $b)); - $refr, $field); - }; - (@doit: $trait:ident, $fn:ident, - $in:ty, ($ain:ty, $bin:ty); - $refr:ident, $field:ty) => { - impl<'r> $trait<$in> - for $field - where $field : $trait<$ain> - + $trait<$bin> { - type Output = Pair<<$field as $trait<$ain>>::Output, - <$field as $trait<$bin>>::Output>; - #[inline] - fn $fn(self, x : $in) -> Self::Output { - Pair(self.$fn(maybe_ref!($refr, x.0)), - self.$fn(maybe_ref!($refr, x.1))) + impl<'a, 'b, A, B, C, D> $trait<&'b Pair<C, D>> for &'a Pair<A, B> + where + &'a A: $trait<&'b C>, + &'a B: $trait<&'b D>, + { + type Output = Pair<<&'a A as $trait<&'b C>>::Output, <&'a B as $trait<&'b D>>::Output>; + fn $fn(self, Pair(ref c, ref d): &'b Pair<C, D>) -> Self::Output { + let Pair(ref a, ref b) = self; + Pair(a.$fn(c), b.$fn(d)) + } + } + + impl<'b, A, B, C, D> $trait<&'b Pair<C, D>> for Pair<A, B> + where + A: $trait<&'b C>, + B: $trait<&'b D>, + { + type Output = Pair<<A as $trait<&'b C>>::Output, <B as $trait<&'b D>>::Output>; + fn $fn(self, Pair(ref c, ref d): &'b Pair<C, D>) -> Self::Output { + let Pair(a, b) = self; + Pair(a.$fn(c), b.$fn(d)) } } }; } -macro_rules! impl_scalar_assignop { - (($a : ty, $b : ty), $field : ty, $trait : ident, $fn : ident) => { - impl<'r> $trait<$field> for Pair<$a, $b> +impl_binary!(Add, add); +impl_binary!(Sub, sub); + +macro_rules! impl_scalar { + ($trait:ident, $fn:ident) => { + impl<A, B, F: Num> $trait<F> for Pair<A, B> + where + A: $trait<F>, + B: $trait<F>, + { + type Output = Pair<A::Output, B::Output>; + fn $fn(self, t: F) -> Self::Output { + let Pair(a, b) = self; + Pair(a.$fn(t), b.$fn(t)) + } + } + + impl<'a, A, B, F: Num> $trait<F> for &'a Pair<A, B> where - $a: $trait<$field>, - $b: $trait<$field>, + &'a A: $trait<F>, + &'a B: $trait<F>, + { + type Output = Pair<<&'a A as $trait<F>>::Output, <&'a B as $trait<F>>::Output>; + fn $fn(self, t: F) -> Self::Output { + let Pair(ref a, ref b) = self; + Pair(a.$fn(t), b.$fn(t)) + } + } + + // impl<'a, 'b, A, B> $trait<&'b $F> for &'a Pair<A, B> + // where + // &'a A: $trait<&'b $F>, + // &'a B: $trait<&'b $F>, + // { + // type Output = + // Pair<<&'a A as $trait<&'b $F>>::Output, <&'a B as $trait<&'b $F>>::Output>; + // fn $fn(self, t: &'b $F) -> Self::Output { + // let Pair(ref a, ref b) = self; + // Pair(a.$fn(t), b.$fn(t)) + // } + // } + + // impl<'b, A, B> $trait<&'b $F> for Pair<A, B> + // where + // A: $trait<&'b $F>, + // B: $trait<&'b $F>, + // { + // type Output = Pair<<A as $trait<&'b $F>>::Output, <B as $trait<&'b $F>>::Output>; + // fn $fn(self, t: &'b $F) -> Self::Output { + // let Pair(a, b) = self; + // Pair(a.$fn(t), b.$fn(t)) + // } + // } + }; +} + +impl_scalar!(Mul, mul); +impl_scalar!(Div, div); + +macro_rules! impl_scalar_lhs { + ($trait:ident, $fn:ident, $F:ty) => { + impl<A, B> $trait<Pair<A, B>> for $F + where + $F: $trait<A> + $trait<B>, { - #[inline] - fn $fn(&mut self, a: $field) -> () { - self.0.$fn(a); - self.1.$fn(a); + type Output = Pair<<$F as $trait<A>>::Output, <$F as $trait<B>>::Output>; + fn $fn(self, Pair(a, b): Pair<A, B>) -> Self::Output { + Pair(self.$fn(a), self.$fn(b)) + } + } + + // Compiler overflow: + // + // impl<'a, A, B> $trait<&'a Pair<A, B>> for $F + // where + // $F: $trait<&'a A> + $trait<&'a B>, + // { + // type Output = Pair<<$F as $trait<&'a A>>::Output, <$F as $trait<&'a B>>::Output>; + // fn $fn(self, Pair(a, b): &'a Pair<A, B>) -> Self::Output { + // Pair(self.$fn(a), self.$fn(b)) + // } + // } + }; +} + +impl_scalar_lhs!(Mul, mul, f32); +impl_scalar_lhs!(Mul, mul, f64); +impl_scalar_lhs!(Div, div, f32); +impl_scalar_lhs!(Div, div, f64); + +macro_rules! impl_binary_mut { + ($trait:ident, $fn:ident) => { + impl<'a, A, B, C, D> $trait<Pair<C, D>> for Pair<A, B> + where + A: $trait<C>, + B: $trait<D>, + { + fn $fn(&mut self, Pair(c, d): Pair<C, D>) { + let Pair(ref mut a, ref mut b) = self; + a.$fn(c); + b.$fn(d); + } + } + + impl<'a, 'b, A, B, C, D> $trait<&'b Pair<C, D>> for Pair<A, B> + where + A: $trait<&'b C>, + B: $trait<&'b D>, + { + fn $fn(&mut self, Pair(ref c, ref d): &'b Pair<C, D>) { + let Pair(ref mut a, ref mut b) = self; + a.$fn(c); + b.$fn(d); } } }; } -macro_rules! impl_unaryop { - (($a : ty, $b : ty), $trait:ident, $fn:ident, $refl:ident) => { - impl_unaryop!(@doit: $trait, $fn; - maybe_lifetime!($refl, &'l Pair<$a,$b>), - (maybe_lifetime!($refl, &'l $a), - maybe_lifetime!($refl, &'l $b)); - $refl); - }; - (@doit: $trait:ident, $fn:ident; - $self:ty, ($aself:ty, $bself:ty); - $refl : ident) => { - impl<'l> $trait - for $self - where $aself: $trait, - $bself: $trait { - type Output = Pair<<$aself as $trait>::Output, - <$bself as $trait>::Output>; - #[inline] - fn $fn(self) -> Self::Output { - Pair(maybe_ref!($refl, self.0).$fn(), - maybe_ref!($refl, self.1).$fn()) +impl_binary_mut!(AddAssign, add_assign); +impl_binary_mut!(SubAssign, sub_assign); + +macro_rules! impl_scalar_mut { + ($trait:ident, $fn:ident) => { + impl<'a, A, B, F: Num> $trait<F> for Pair<A, B> + where + A: $trait<F>, + B: $trait<F>, + { + fn $fn(&mut self, t: F) { + let Pair(ref mut a, ref mut b) = self; + a.$fn(t); + b.$fn(t); } } + }; +} + +impl_scalar_mut!(MulAssign, mul_assign); +impl_scalar_mut!(DivAssign, div_assign); + +/// Trait for ownable-by-consumption objects +impl<A, B> Ownable for Pair<A, B> +where + A: Ownable, + B: Ownable, +{ + type OwnedVariant = Pair<A::OwnedVariant, B::OwnedVariant>; + + #[inline] + fn into_owned(self) -> Self::OwnedVariant { + Pair(self.0.into_owned(), self.1.into_owned()) + } + + #[inline] + fn clone_owned(&self) -> Self::OwnedVariant { + Pair(self.0.clone_owned(), self.1.clone_owned()) + } + + #[inline] + fn cow_owned<'b>(self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + MyCow::Owned(self.into_owned()) + } + + #[inline] + fn ref_cow_owned<'b>(&'b self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + MyCow::Owned(self.clone_owned()) } } -#[macro_export] -macro_rules! impl_pair_vectorspace_ops { - (($a:ty, $b:ty), $field:ty) => { - impl_pair_vectorspace_ops!(@binary, ($a, $b), Add, add); - impl_pair_vectorspace_ops!(@binary, ($a, $b), Sub, sub); - impl_pair_vectorspace_ops!(@assign, ($a, $b), AddAssign, add_assign); - impl_pair_vectorspace_ops!(@assign, ($a, $b), SubAssign, sub_assign); - impl_pair_vectorspace_ops!(@scalar, ($a, $b), $field, Mul, mul); - impl_pair_vectorspace_ops!(@scalar, ($a, $b), $field, Div, div); - // Compiler overflow - // $( - // impl_pair_vectorspace_ops!(@scalar_lhs, ($a, $b), $field, $impl_scalarlhs_op, Mul, mul); - // )* - impl_pair_vectorspace_ops!(@scalar_assign, ($a, $b), $field, MulAssign, mul_assign); - impl_pair_vectorspace_ops!(@scalar_assign, ($a, $b), $field, DivAssign, div_assign); - impl_pair_vectorspace_ops!(@unary, ($a, $b), Neg, neg); - }; - (@binary, ($a : ty, $b : ty), $trait : ident, $fn : ident) => { - impl_binop!(($a, $b), $trait, $fn, ref, ref); - impl_binop!(($a, $b), $trait, $fn, ref, noref); - impl_binop!(($a, $b), $trait, $fn, noref, ref); - impl_binop!(($a, $b), $trait, $fn, noref, noref); - }; - (@assign, ($a : ty, $b : ty), $trait : ident, $fn :ident) => { - impl_assignop!(($a, $b), $trait, $fn, ref); - impl_assignop!(($a, $b), $trait, $fn, noref); - }; - (@scalar, ($a : ty, $b : ty), $field : ty, $trait : ident, $fn :ident) => { - impl_scalarop!(($a, $b), $field, $trait, $fn, ref); - impl_scalarop!(($a, $b), $field, $trait, $fn, noref); - }; - (@scalar_lhs, ($a : ty, $b : ty), $field : ty, $trait : ident, $fn : ident) => { - impl_scalarlhs_op!(($a, $b), $field, $trait, $fn, ref); - impl_scalarlhs_op!(($a, $b), $field, $trait, $fn, noref); - }; - (@scalar_assign, ($a : ty, $b : ty), $field : ty, $trait : ident, $fn : ident) => { - impl_scalar_assignop!(($a, $b), $field, $trait, $fn); - }; - (@unary, ($a : ty, $b : ty), $trait : ident, $fn : ident) => { - impl_unaryop!(($a, $b), $trait, $fn, ref); - impl_unaryop!(($a, $b), $trait, $fn, noref); - }; -} - -impl_pair_vectorspace_ops!((f32, f32), f32); -impl_pair_vectorspace_ops!((f64, f64), f64); - -type PairOutput<F, A, B> = Pair<<A as Euclidean<F>>::Output, <B as Euclidean<F>>::Output>; - -impl<A, B, F> Euclidean<F> for Pair<A, B> +/// We only support 'closed' `Euclidean` `Pair`s, as more general ones cause +/// compiler overflows. +impl<A, B, F: Float> Euclidean<F> for Pair<A, B> where A: Euclidean<F>, B: Euclidean<F>, - F: Float, - PairOutput<F, A, B>: Euclidean<F>, - Self: Sized - + Mul<F, Output = PairOutput<F, A, B>> - + MulAssign<F> - + Div<F, Output = PairOutput<F, A, B>> - + DivAssign<F> - + Add<Self, Output = PairOutput<F, A, B>> - + Sub<Self, Output = PairOutput<F, A, B>> - + for<'b> Add<&'b Self, Output = PairOutput<F, A, B>> - + for<'b> Sub<&'b Self, Output = PairOutput<F, A, B>> - + AddAssign<Self> - + for<'b> AddAssign<&'b Self> - + SubAssign<Self> - + for<'b> SubAssign<&'b Self> - + Neg<Output = PairOutput<F, A, B>>, + // //Pair<A, B>: Euclidean<F>, + // Self: Sized + // + Mul<F, Output = Self::OwnedEuclidean> + // + MulAssign<F> + // + Div<F, Output = Self::OwnedEuclidean> + // + DivAssign<F> + // + Add<Self, Output = Self::OwnedEuclidean> + // + Sub<Self, Output = Self::OwnedEuclidean> + // + for<'b> Add<&'b Self, Output = Self::OwnedEuclidean> + // + for<'b> Sub<&'b Self, Output = Self::OwnedEuclidean> + // + AddAssign<Self> + // + for<'b> AddAssign<&'b Self> + // + SubAssign<Self> + // + for<'b> SubAssign<&'b Self> + // + Neg<Output = Self::OwnedEuclidean>, { - type Output = PairOutput<F, A, B>; + type PrincipalE = Pair<A::PrincipalE, B::PrincipalE>; fn dot<I: Instance<Self>>(&self, other: I) -> F { - let Pair(u, v) = other.decompose(); - self.0.dot(u) + self.1.dot(v) + other.eval_decompose(|Pair(u, v)| self.0.dot(u) + self.1.dot(v)) } fn norm2_squared(&self) -> F { @@ -279,45 +330,59 @@ } fn dist2_squared<I: Instance<Self>>(&self, other: I) -> F { - let Pair(u, v) = other.decompose(); - self.0.dist2_squared(u) + self.1.dist2_squared(v) + other.eval_decompose(|Pair(u, v)| self.0.dist2_squared(u) + self.1.dist2_squared(v)) } } -impl<F, A, B, U, V> AXPY<F, Pair<U, V>> for Pair<A, B> +impl<F, A, B> VectorSpace for Pair<A, B> +where + A: VectorSpace<Field = F>, + B: VectorSpace<Field = F>, + F: Num, +{ + type Field = F; + type PrincipalV = Pair<A::PrincipalV, B::PrincipalV>; + + /// Return a similar zero as `self`. + fn similar_origin(&self) -> Self::PrincipalV { + Pair(self.0.similar_origin(), self.1.similar_origin()) + } + + // #[inline] + // fn into_owned(self) -> Self::Owned { + // Pair(self.0.into_owned(), self.1.into_owned()) + // } +} + +impl<F, A, B, U, V> AXPY<Pair<U, V>> for Pair<A, B> where U: Space, V: Space, - A: AXPY<F, U>, - B: AXPY<F, V>, + A: AXPY<U, Field = F>, + B: AXPY<V, Field = F>, F: Num, - Self: MulAssign<F>, - Pair<A, B>: MulAssign<F>, - Pair<A::Owned, B::Owned>: AXPY<F, Pair<U, V>>, + // Self: MulAssign<F> + DivAssign<F>, + // Pair<A, B>: MulAssign<F> + DivAssign<F>, { - type Owned = Pair<A::Owned, B::Owned>; - fn axpy<I: Instance<Pair<U, V>>>(&mut self, α: F, x: I, β: F) { - let Pair(u, v) = x.decompose(); - self.0.axpy(α, u, β); - self.1.axpy(α, v, β); + x.eval_decompose(|Pair(u, v)| { + self.0.axpy(α, u, β); + self.1.axpy(α, v, β); + }) } fn copy_from<I: Instance<Pair<U, V>>>(&mut self, x: I) { - let Pair(u, v) = x.decompose(); - self.0.copy_from(u); - self.1.copy_from(v); + x.eval_decompose(|Pair(u, v)| { + self.0.copy_from(u); + self.1.copy_from(v); + }) } fn scale_from<I: Instance<Pair<U, V>>>(&mut self, α: F, x: I) { - let Pair(u, v) = x.decompose(); - self.0.scale_from(α, u); - self.1.scale_from(α, v); - } - - /// Return a similar zero as `self`. - fn similar_origin(&self) -> Self::Owned { - Pair(self.0.similar_origin(), self.1.similar_origin()) + x.eval_decompose(|Pair(u, v)| { + self.0.scale_from(α, u); + self.1.scale_from(α, v); + }) } /// Set self to zero. @@ -332,6 +397,7 @@ pub struct PairDecomposition<D, Q>(D, Q); impl<A: Space, B: Space> Space for Pair<A, B> { + type Principal = Pair<A::Principal, B::Principal>; type Decomp = PairDecomposition<A::Decomp, B::Decomp>; } @@ -367,25 +433,16 @@ V: Instance<B, Q>, { #[inline] - fn decompose<'b>( - self, - ) -> <PairDecomposition<D, Q> as Decomposition<Pair<A, B>>>::Decomposition<'b> + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(Pair<D::Reference<'b>, Q::Reference<'b>>) -> R) -> R where + Pair<A, B>: 'b, Self: 'b, - Pair<A, B>: 'b, { - Pair(self.0.decompose(), self.1.decompose()) + self.0.eval_ref(|a| self.1.eval_ref(|b| f(Pair(a, b)))) } #[inline] - fn ref_instance( - &self, - ) -> <PairDecomposition<D, Q> as Decomposition<Pair<A, B>>>::Reference<'_> { - Pair(self.0.ref_instance(), self.1.ref_instance()) - } - - #[inline] - fn cow<'b>(self) -> MyCow<'b, Pair<A, B>> + fn cow<'b>(self) -> MyCow<'b, Pair<A::Principal, B::Principal>> where Self: 'b, { @@ -393,9 +450,17 @@ } #[inline] - fn own(self) -> Pair<A, B> { + fn own(self) -> Pair<A::Principal, B::Principal> { Pair(self.0.own(), self.1.own()) } + + #[inline] + fn decompose<'b>(self) -> Pair<D::Decomposition<'b>, Q::Decomposition<'b>> + where + Self: 'b, + { + Pair(self.0.decompose(), self.1.decompose()) + } } impl<'a, A, B, U, V, D, Q> Instance<Pair<A, B>, PairDecomposition<D, Q>> for &'a Pair<U, V> @@ -409,29 +474,16 @@ &'a U: Instance<A, D>, &'a V: Instance<B, Q>, { - #[inline] - fn decompose<'b>( - self, - ) -> <PairDecomposition<D, Q> as Decomposition<Pair<A, B>>>::Decomposition<'b> + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(Pair<D::Reference<'b>, Q::Reference<'b>>) -> R) -> R where + Pair<A, B>: 'b, Self: 'b, - Pair<A, B>: 'b, { - Pair( - D::lift(self.0.ref_instance()), - Q::lift(self.1.ref_instance()), - ) + self.0.eval_ref(|a| self.1.eval_ref(|b| f(Pair(a, b)))) } #[inline] - fn ref_instance( - &self, - ) -> <PairDecomposition<D, Q> as Decomposition<Pair<A, B>>>::Reference<'_> { - Pair(self.0.ref_instance(), self.1.ref_instance()) - } - - #[inline] - fn cow<'b>(self) -> MyCow<'b, Pair<A, B>> + fn cow<'b>(self) -> MyCow<'b, Pair<A::Principal, B::Principal>> where Self: 'b, { @@ -439,10 +491,19 @@ } #[inline] - fn own(self) -> Pair<A, B> { + fn own(self) -> Pair<A::Principal, B::Principal> { let Pair(ref u, ref v) = self; Pair(u.own(), v.own()) } + + #[inline] + fn decompose<'b>(self) -> Pair<D::Decomposition<'b>, Q::Decomposition<'b>> + where + Self: 'b, + { + let Pair(u, v) = self; + Pair(u.decompose(), v.decompose()) + } } impl<A, B, D, Q> DecompositionMut<Pair<A, B>> for PairDecomposition<D, Q> @@ -492,18 +553,63 @@ } } -impl<F, A, B, ExpA, ExpB, ExpJ> Norm<F, PairNorm<ExpA, ExpB, ExpJ>> for Pair<A, B> +impl<F, A, B, ExpA, ExpB, ExpJ> Norm<PairNorm<ExpA, ExpB, ExpJ>, F> for Pair<A, B> +where + F: Num, + ExpA: NormExponent, + ExpB: NormExponent, + ExpJ: NormExponent, + A: Norm<ExpA, F>, + B: Norm<ExpB, F>, + Loc<2, F>: Norm<ExpJ, F>, +{ + fn norm(&self, PairNorm(expa, expb, expj): PairNorm<ExpA, ExpB, ExpJ>) -> F { + Loc([self.0.norm(expa), self.1.norm(expb)]).norm(expj) + } +} + +impl<F, A, B, ExpA, ExpB, ExpJ> Dist<PairNorm<ExpA, ExpB, ExpJ>, F> for Pair<A, B> where F: Num, ExpA: NormExponent, ExpB: NormExponent, ExpJ: NormExponent, - A: Norm<F, ExpA>, - B: Norm<F, ExpB>, - Loc<F, 2>: Norm<F, ExpJ>, + A: Dist<ExpA, F>, + B: Dist<ExpB, F>, + Loc<2, F>: Norm<ExpJ, F>, { - fn norm(&self, PairNorm(expa, expb, expj): PairNorm<ExpA, ExpB, ExpJ>) -> F { - Loc([self.0.norm(expa), self.1.norm(expb)]).norm(expj) + fn dist<I: Instance<Self>>( + &self, + x: I, + PairNorm(expa, expb, expj): PairNorm<ExpA, ExpB, ExpJ>, + ) -> F { + x.eval_decompose(|Pair(x1, x2)| { + Loc([self.0.dist(x1, expa), self.1.dist(x2, expb)]).norm(expj) + }) + } +} + +impl<F, A, B> Norm<L2, F> for Pair<A, B> +where + F: Num, + A: Norm<L2, F>, + B: Norm<L2, F>, + Loc<2, F>: Norm<L2, F>, +{ + fn norm(&self, _: L2) -> F { + Loc([self.0.norm(L2), self.1.norm(L2)]).norm(L2) + } +} + +impl<F, A, B> Dist<L2, F> for Pair<A, B> +where + F: Num, + A: Dist<L2, F>, + B: Dist<L2, F>, + Loc<2, F>: Norm<L2, F>, +{ + fn dist<I: Instance<Self>>(&self, x: I, _: L2) -> F { + x.eval_decompose(|Pair(x1, x2)| Loc([self.0.dist(x1, L2), self.1.dist(x2, L2)]).norm(L2)) } } @@ -531,4 +637,72 @@ B: HasDual<F>, { type DualSpace = Pair<A::DualSpace, B::DualSpace>; + + fn dual_origin(&self) -> <Self::DualSpace as VectorSpace>::PrincipalV { + Pair(self.0.dual_origin(), self.1.dual_origin()) + } } + +#[cfg(feature = "pyo3")] +mod python { + use super::Pair; + use pyo3::conversion::FromPyObject; + use pyo3::types::{PyAny, PyTuple}; + use pyo3::{Borrowed, Bound, IntoPyObject, PyErr, Python}; + + impl<'py, A, B> IntoPyObject<'py> for Pair<A, B> + where + A: IntoPyObject<'py>, + B: IntoPyObject<'py>, + { + type Target = PyTuple; + type Error = PyErr; + type Output = Bound<'py, Self::Target>; + + fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> { + (self.0, self.1).into_pyobject(py) + } + } + + /* + impl<'a, 'py, A, B> IntoPyObject<'py> for &'a mut Pair<A, B> + where + &'a mut A: IntoPyObject<'py>, + &'a mut B: IntoPyObject<'py>, + { + type Target = PyTuple; + type Error = PyErr; + type Output = Bound<'py, Self::Target>; + + fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> { + (&mut self.0, &mut self.1).into_pyobject(py) + } + } + + impl<'a, 'py, A, B> IntoPyObject<'py> for &'a Pair<A, B> + where + &'a A: IntoPyObject<'py>, + &'a B: IntoPyObject<'py>, + { + type Target = PyTuple; + type Error = PyErr; + type Output = Bound<'py, Self::Target>; + + fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> { + (&self.0, &self.1).into_pyobject(py) + } + } + */ + + impl<'a, 'py, A, B> FromPyObject<'a, 'py> for Pair<A, B> + where + A: Clone + FromPyObject<'a, 'py>, + B: Clone + FromPyObject<'a, 'py>, + { + type Error = PyErr; + + fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, PyErr> { + FromPyObject::extract(ob).map(|(a, b)| Pair(a, b)) + } + } +}
--- a/src/discrete_gradient.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/discrete_gradient.rs Fri May 15 14:46:30 2026 -0500 @@ -1,14 +1,13 @@ /*! Simple disrete gradient operators */ +use crate::error::DynResult; +use crate::instance::Instance; +use crate::linops::{Adjointable, BoundedLinear, Linear, Mapping, SimplyAdjointable, GEMV}; +use crate::norms::{Norm, L2}; +use crate::types::Float; +use nalgebra::{DVector, Dyn, Matrix, Storage, StorageMut, U1}; use numeric_literals::replace_float_literals; -use nalgebra::{ - DVector, Matrix, U1, Storage, StorageMut, Dyn -}; -use crate::types::Float; -use crate::instance::Instance; -use crate::linops::{Mapping, Linear, BoundedLinear, Adjointable, GEMV}; -use crate::norms::{Norm, L2}; #[derive(Copy, Clone, Debug)] /// Forward differences with Neumann boundary conditions @@ -27,26 +26,17 @@ pub struct BackwardNeumann; /// Finite differences gradient -pub struct Grad< - F : Float + nalgebra::RealField, - B : Discretisation<F>, - const N : usize -> { - dims : [usize; N], - h : F, // may be negative to implement adjoints! - discretisation : B, +pub struct Grad<F: Float + nalgebra::RealField, B: Discretisation<F>, const N: usize> { + dims: [usize; N], + h: F, // may be negative to implement adjoints! + discretisation: B, } - /// Finite differences divergence -pub struct Div< - F : Float + nalgebra::RealField, - B : Discretisation<F>, - const N : usize -> { - dims : [usize; N], - h : F, // may be negative to implement adjoints! - discretisation : B, +pub struct Div<F: Float + nalgebra::RealField, B: Discretisation<F>, const N: usize> { + dims: [usize; N], + h: F, // may be negative to implement adjoints! + discretisation: B, } /// Internal: classification of a point in a 1D discretisation @@ -62,53 +52,53 @@ use DiscretisationOrInterior::*; /// Trait for different discretisations -pub trait Discretisation<F : Float + nalgebra::RealField> : Copy { +pub trait Discretisation<F: Float + nalgebra::RealField>: Copy { /// Opposite discretisation, appropriate for adjoints with negated cell width. - type Opposite : Discretisation<F>; + type Opposite: Discretisation<F>; /// Add to appropiate index of `v` (as determined by `b`) the appropriate difference /// of `x` with cell width `h`. fn add_diff_mut<SMut, S>( &self, - v : &mut Matrix<F, Dyn, U1, SMut>, - x : &Matrix<F, Dyn, U1, S>, - α : F, - b : DiscretisationOrInterior, + v: &mut Matrix<F, Dyn, U1, SMut>, + x: &Matrix<F, Dyn, U1, S>, + α: F, + b: DiscretisationOrInterior, ) where - SMut : StorageMut<F, Dyn, U1>, - S : Storage<F, Dyn, U1>; + SMut: StorageMut<F, Dyn, U1>, + S: Storage<F, Dyn, U1>; /// Give the opposite discretisation, appropriate for adjoints with negated `h`. fn opposite(&self) -> Self::Opposite; /// Bound for the corresponding operator norm. #[replace_float_literals(F::cast_from(literal))] - fn opnorm_bound(&self, h : F) -> F { + fn opnorm_bound(&self, h: F) -> DynResult<F> { // See: Chambolle, “An Algorithm for Total Variation Minimization and Applications”. // Ok for forward and backward differences. // // Fuck nalgebra for polluting everything with its own shit. - num_traits::Float::sqrt(8.0) / h + Ok(num_traits::Float::sqrt(8.0) / h) } } -impl<F : Float + nalgebra::RealField> Discretisation<F> for ForwardNeumann { +impl<F: Float + nalgebra::RealField> Discretisation<F> for ForwardNeumann { type Opposite = BackwardDirichlet; #[inline] fn add_diff_mut<SMut, S>( &self, - v : &mut Matrix<F, Dyn, U1, SMut>, - x : &Matrix<F, Dyn, U1, S>, - α : F, - b : DiscretisationOrInterior, + v: &mut Matrix<F, Dyn, U1, SMut>, + x: &Matrix<F, Dyn, U1, S>, + α: F, + b: DiscretisationOrInterior, ) where - SMut : StorageMut<F, Dyn, U1>, - S : Storage<F, Dyn, U1> + SMut: StorageMut<F, Dyn, U1>, + S: Storage<F, Dyn, U1>, { match b { - Interior(c, (_, f)) | LeftBoundary(c, f) => { v[c] += (x[f] - x[c]) * α }, - RightBoundary(_c, _b) => { }, + Interior(c, (_, f)) | LeftBoundary(c, f) => v[c] += (x[f] - x[c]) * α, + RightBoundary(_c, _b) => {} } } @@ -118,23 +108,23 @@ } } -impl<F : Float + nalgebra::RealField> Discretisation<F> for BackwardNeumann { +impl<F: Float + nalgebra::RealField> Discretisation<F> for BackwardNeumann { type Opposite = ForwardDirichlet; #[inline] fn add_diff_mut<SMut, S>( &self, - v : &mut Matrix<F, Dyn, U1, SMut>, - x : &Matrix<F, Dyn, U1, S>, - α : F, - b : DiscretisationOrInterior, + v: &mut Matrix<F, Dyn, U1, SMut>, + x: &Matrix<F, Dyn, U1, S>, + α: F, + b: DiscretisationOrInterior, ) where - SMut : StorageMut<F, Dyn, U1>, - S : Storage<F, Dyn, U1> + SMut: StorageMut<F, Dyn, U1>, + S: Storage<F, Dyn, U1>, { match b { - Interior(c, (b, _)) | RightBoundary(c, b) => { v[c] += (x[c] - x[b]) * α }, - LeftBoundary(_c, _f) => { }, + Interior(c, (b, _)) | RightBoundary(c, b) => v[c] += (x[c] - x[b]) * α, + LeftBoundary(_c, _f) => {} } } @@ -144,24 +134,24 @@ } } -impl<F : Float + nalgebra::RealField> Discretisation<F> for BackwardDirichlet { +impl<F: Float + nalgebra::RealField> Discretisation<F> for BackwardDirichlet { type Opposite = ForwardNeumann; #[inline] fn add_diff_mut<SMut, S>( &self, - v : &mut Matrix<F, Dyn, U1, SMut>, - x : &Matrix<F, Dyn, U1, S>, - α : F, - b : DiscretisationOrInterior, + v: &mut Matrix<F, Dyn, U1, SMut>, + x: &Matrix<F, Dyn, U1, S>, + α: F, + b: DiscretisationOrInterior, ) where - SMut : StorageMut<F, Dyn, U1>, - S : Storage<F, Dyn, U1> + SMut: StorageMut<F, Dyn, U1>, + S: Storage<F, Dyn, U1>, { match b { - Interior(c, (b, _f)) => { v[c] += (x[c] - x[b]) * α }, - LeftBoundary(c, _f) => { v[c] += x[c] * α }, - RightBoundary(c, b) => { v[c] -= x[b] * α }, + Interior(c, (b, _f)) => v[c] += (x[c] - x[b]) * α, + LeftBoundary(c, _f) => v[c] += x[c] * α, + RightBoundary(c, b) => v[c] -= x[b] * α, } } @@ -171,24 +161,24 @@ } } -impl<F : Float + nalgebra::RealField> Discretisation<F> for ForwardDirichlet { +impl<F: Float + nalgebra::RealField> Discretisation<F> for ForwardDirichlet { type Opposite = BackwardNeumann; #[inline] fn add_diff_mut<SMut, S>( &self, - v : &mut Matrix<F, Dyn, U1, SMut>, - x : &Matrix<F, Dyn, U1, S>, - α : F, - b : DiscretisationOrInterior, + v: &mut Matrix<F, Dyn, U1, SMut>, + x: &Matrix<F, Dyn, U1, S>, + α: F, + b: DiscretisationOrInterior, ) where - SMut : StorageMut<F, Dyn, U1>, - S : Storage<F, Dyn, U1> + SMut: StorageMut<F, Dyn, U1>, + S: Storage<F, Dyn, U1>, { match b { - Interior(c, (_b, f)) => { v[c] += (x[f] - x[c]) * α }, - LeftBoundary(c, f) => { v[c] += x[f] * α }, - RightBoundary(c, _b) => { v[c] -= x[c] * α }, + Interior(c, (_b, f)) => v[c] += (x[f] - x[c]) * α, + LeftBoundary(c, f) => v[c] += x[f] * α, + RightBoundary(c, _b) => v[c] -= x[c] * α, } } @@ -198,30 +188,30 @@ } } -struct Iter<'a, const N : usize> { +struct Iter<'a, const N: usize> { /// Dimensions - dims : &'a [usize; N], + dims: &'a [usize; N], /// Dimension along which to calculate differences - d : usize, + d: usize, /// Stride along coordinate d - d_stride : usize, + d_stride: usize, /// Cartesian indices - i : [usize; N], + i: [usize; N], /// Linear index - k : usize, + k: usize, /// Maximal linear index - len : usize + len: usize, } -impl<'a, const N : usize> Iter<'a, N> { - fn new(dims : &'a [usize; N], d : usize) -> Self { +impl<'a, const N: usize> Iter<'a, N> { + fn new(dims: &'a [usize; N], d: usize) -> Self { let d_stride = dims[0..d].iter().product::<usize>(); let len = dims.iter().product::<usize>(); - Iter{ dims, d, d_stride, i : [0; N], k : 0, len } + Iter { dims, d, d_stride, i: [0; N], k: 0, len } } } -impl<'a, const N : usize> Iterator for Iter<'a, N> { +impl<'a, const N: usize> Iterator for Iter<'a, N> { type Item = DiscretisationOrInterior; fn next(&mut self) -> Option<Self::Item> { let res = if self.k >= self.len { @@ -243,7 +233,7 @@ for j in 0..N { if self.i[j] + 1 < self.dims[j] { self.i[j] += 1; - break + break; } self.i[j] = 0 } @@ -251,14 +241,13 @@ } } -impl<F, B, const N : usize> Mapping<DVector<F>> -for Grad<F, B, N> +impl<F, B, const N: usize> Mapping<DVector<F>> for Grad<F, B, N> where - B : Discretisation<F>, - F : Float + nalgebra::RealField, + B: Discretisation<F>, + F: Float + nalgebra::RealField, { type Codomain = DVector<F>; - fn apply<I : Instance<DVector<F>>>(&self, i : I) -> DVector<F> { + fn apply<I: Instance<DVector<F>>>(&self, i: I) -> DVector<F> { let mut y = DVector::zeros(N * self.len()); self.apply_add(&mut y, i); y @@ -266,15 +255,12 @@ } #[replace_float_literals(F::cast_from(literal))] -impl<F, B, const N : usize> GEMV<F, DVector<F>> -for Grad<F, B, N> +impl<F, B, const N: usize> GEMV<F, DVector<F>> for Grad<F, B, N> where - B : Discretisation<F>, - F : Float + nalgebra::RealField, + B: Discretisation<F>, + F: Float + nalgebra::RealField, { - fn gemv<I : Instance<DVector<F>>>( - &self, y : &mut DVector<F>, α : F, i : I, β : F - ) { + fn gemv<I: Instance<DVector<F>>>(&self, y: &mut DVector<F>, α: F, i: I, β: F) { if β == 0.0 { y.as_mut_slice().iter_mut().for_each(|x| *x = 0.0); } else if β != 1.0 { @@ -286,23 +272,22 @@ i.eval(|x| { assert_eq!(x.len(), m); for d in 0..N { - let mut v = y.generic_view_mut((d*m, 0), (Dyn(m), U1)); + let mut v = y.generic_view_mut((d * m, 0), (Dyn(m), U1)); for b in Iter::new(&self.dims, d) { - self.discretisation.add_diff_mut(&mut v, x, α/h, b) + self.discretisation.add_diff_mut(&mut v, x, α / h, b) } } }) } } -impl<F, B, const N : usize> Mapping<DVector<F>> -for Div<F, B, N> +impl<F, B, const N: usize> Mapping<DVector<F>> for Div<F, B, N> where - B : Discretisation<F>, - F : Float + nalgebra::RealField, + B: Discretisation<F>, + F: Float + nalgebra::RealField, { type Codomain = DVector<F>; - fn apply<I : Instance<DVector<F>>>(&self, i : I) -> DVector<F> { + fn apply<I: Instance<DVector<F>>>(&self, i: I) -> DVector<F> { let mut y = DVector::zeros(self.len()); self.apply_add(&mut y, i); y @@ -310,15 +295,12 @@ } #[replace_float_literals(F::cast_from(literal))] -impl<F, B, const N : usize> GEMV<F, DVector<F>> -for Div<F, B, N> +impl<F, B, const N: usize> GEMV<F, DVector<F>> for Div<F, B, N> where - B : Discretisation<F>, - F : Float + nalgebra::RealField, + B: Discretisation<F>, + F: Float + nalgebra::RealField, { - fn gemv<I : Instance<DVector<F>>>( - &self, y : &mut DVector<F>, α : F, i : I, β : F - ) { + fn gemv<I: Instance<DVector<F>>>(&self, y: &mut DVector<F>, α: F, i: I, β: F) { if β == 0.0 { y.as_mut_slice().iter_mut().for_each(|x| *x = 0.0); } else if β != 1.0 { @@ -327,29 +309,46 @@ } let h = self.h; let m = self.len(); - i.eval(|x| { + i.eval_decompose(|x| { assert_eq!(x.len(), N * m); for d in 0..N { - let v = x.generic_view((d*m, 0), (Dyn(m), U1)); + let v = x.generic_view((d * m, 0), (Dyn(m), U1)); for b in Iter::new(&self.dims, d) { - self.discretisation.add_diff_mut(y, &v, α/h, b) + self.discretisation.add_diff_mut(y, &v, α / h, b) } } }) } } -impl<F, B, const N : usize> Grad<F, B, N> +impl<F, B, const N: usize> Grad<F, B, N> where - B : Discretisation<F>, - F : Float + nalgebra::RealField + B: Discretisation<F>, + F: Float + nalgebra::RealField, { /// Creates a new discrete gradient operator for the vector `u`, verifying dimensions. - pub fn new_for(u : &DVector<F>, h : F, dims : [usize; N], discretisation : B) - -> Option<Self> - { + pub fn new_for(u: &DVector<F>, h: F, dims: [usize; N], discretisation: B) -> Option<Self> { if u.len() == dims.iter().product::<usize>() { - Some(Grad { dims, h, discretisation } ) + Some(Grad { dims, h, discretisation }) + } else { + None + } + } + + fn len(&self) -> usize { + self.dims.iter().product::<usize>() + } +} + +impl<F, B, const N: usize> Div<F, B, N> +where + B: Discretisation<F>, + F: Float + nalgebra::RealField, +{ + /// Creates a new discrete gradient operator for the vector `u`, verifying dimensions. + pub fn new_for(u: &DVector<F>, h: F, dims: [usize; N], discretisation: B) -> Option<Self> { + if u.len() == dims.iter().product::<usize>() * N { + Some(Div { dims, h, discretisation }) } else { None } @@ -360,109 +359,101 @@ } } - -impl<F, B, const N : usize> Div<F, B, N> +impl<F, B, const N: usize> Linear<DVector<F>> for Grad<F, B, N> where - B : Discretisation<F>, - F : Float + nalgebra::RealField + B: Discretisation<F>, + F: Float + nalgebra::RealField, +{ +} + +impl<F, B, const N: usize> Linear<DVector<F>> for Div<F, B, N> +where + B: Discretisation<F>, + F: Float + nalgebra::RealField, { - /// Creates a new discrete gradient operator for the vector `u`, verifying dimensions. - pub fn new_for(u : &DVector<F>, h : F, dims : [usize; N], discretisation : B) - -> Option<Self> - { - if u.len() == dims.iter().product::<usize>() * N { - Some(Div { dims, h, discretisation } ) - } else { - None - } - } - - fn len(&self) -> usize { - self.dims.iter().product::<usize>() +} + +impl<F, B, const N: usize> BoundedLinear<DVector<F>, L2, L2, F> for Grad<F, B, N> +where + B: Discretisation<F>, + F: Float + nalgebra::RealField, + DVector<F>: Norm<L2, F>, +{ + fn opnorm_bound(&self, _: L2, _: L2) -> DynResult<F> { + // Fuck nalgebra. + self.discretisation + .opnorm_bound(num_traits::Float::abs(self.h)) } } -impl<F, B, const N : usize> Linear<DVector<F>> -for Grad<F, B, N> -where - B : Discretisation<F>, - F : Float + nalgebra::RealField, -{ -} - -impl<F, B, const N : usize> Linear<DVector<F>> -for Div<F, B, N> +impl<F, B, const N: usize> BoundedLinear<DVector<F>, L2, L2, F> for Div<F, B, N> where - B : Discretisation<F>, - F : Float + nalgebra::RealField, + B: Discretisation<F>, + F: Float + nalgebra::RealField, + DVector<F>: Norm<L2, F>, { -} - -impl<F, B, const N : usize> BoundedLinear<DVector<F>, L2, L2, F> -for Grad<F, B, N> -where - B : Discretisation<F>, - F : Float + nalgebra::RealField, - DVector<F> : Norm<F, L2>, -{ - fn opnorm_bound(&self, _ : L2, _ : L2) -> F { + fn opnorm_bound(&self, _: L2, _: L2) -> DynResult<F> { // Fuck nalgebra. - self.discretisation.opnorm_bound(num_traits::Float::abs(self.h)) + self.discretisation + .opnorm_bound(num_traits::Float::abs(self.h)) } } - -impl<F, B, const N : usize> BoundedLinear<DVector<F>, L2, L2, F> -for Div<F, B, N> +impl<F, B, const N: usize> Adjointable<DVector<F>, DVector<F>> for Grad<F, B, N> where - B : Discretisation<F>, - F : Float + nalgebra::RealField, - DVector<F> : Norm<F, L2>, + B: Discretisation<F>, + F: Float + nalgebra::RealField, { - fn opnorm_bound(&self, _ : L2, _ : L2) -> F { - // Fuck nalgebra. - self.discretisation.opnorm_bound(num_traits::Float::abs(self.h)) + type AdjointCodomain = DVector<F>; + type Adjoint<'a> + = Div<F, B::Opposite, N> + where + Self: 'a; + + fn adjoint(&self) -> Self::Adjoint<'_> { + Div { dims: self.dims, h: -self.h, discretisation: self.discretisation.opposite() } } } -impl<F, B, const N : usize> -Adjointable<DVector<F>, DVector<F>> -for Grad<F, B, N> +impl<F, B, const N: usize> SimplyAdjointable<DVector<F>, DVector<F>> for Grad<F, B, N> where - B : Discretisation<F>, - F : Float + nalgebra::RealField, + B: Discretisation<F>, + F: Float + nalgebra::RealField, { type AdjointCodomain = DVector<F>; - type Adjoint<'a> = Div<F, B::Opposite, N> where Self : 'a; + type SimpleAdjoint = Div<F, B::Opposite, N>; - /// Form the adjoint operator of `self`. - fn adjoint(&self) -> Self::Adjoint<'_> { - Div { - dims : self.dims, - h : -self.h, - discretisation : self.discretisation.opposite(), - } + fn adjoint(&self) -> Self::SimpleAdjoint { + Div { dims: self.dims, h: -self.h, discretisation: self.discretisation.opposite() } } } - -impl<F, B, const N : usize> -Adjointable<DVector<F>, DVector<F>> -for Div<F, B, N> +impl<F, B, const N: usize> Adjointable<DVector<F>, DVector<F>> for Div<F, B, N> where - B : Discretisation<F>, - F : Float + nalgebra::RealField, + B: Discretisation<F>, + F: Float + nalgebra::RealField, { type AdjointCodomain = DVector<F>; - type Adjoint<'a> = Grad<F, B::Opposite, N> where Self : 'a; + type Adjoint<'a> + = Grad<F, B::Opposite, N> + where + Self: 'a; - /// Form the adjoint operator of `self`. fn adjoint(&self) -> Self::Adjoint<'_> { - Grad { - dims : self.dims, - h : -self.h, - discretisation : self.discretisation.opposite(), - } + Grad { dims: self.dims, h: -self.h, discretisation: self.discretisation.opposite() } + } +} + +impl<F, B, const N: usize> SimplyAdjointable<DVector<F>, DVector<F>> for Div<F, B, N> +where + B: Discretisation<F>, + F: Float + nalgebra::RealField, +{ + type AdjointCodomain = DVector<F>; + type SimpleAdjoint = Grad<F, B::Opposite, N>; + + fn adjoint(&self) -> Self::SimpleAdjoint { + Grad { dims: self.dims, h: -self.h, discretisation: self.discretisation.opposite() } } } @@ -472,8 +463,8 @@ #[test] fn grad_adjoint() { - let im = DVector::from( (0..9).map(|t| t as f64).collect::<Vec<_>>()); - let v = DVector::from( (0..18).map(|t| t as f64).collect::<Vec<_>>()); + let im = DVector::from((0..9).map(|t| t as f64).collect::<Vec<_>>()); + let v = DVector::from((0..18).map(|t| t as f64).collect::<Vec<_>>()); let grad = Grad::new_for(&im, 1.0, [3, 3], ForwardNeumann).unwrap(); let a = grad.apply(&im).dot(&v); @@ -484,6 +475,5 @@ let a = grad.apply(&im).dot(&v); let b = grad.adjoint().apply(&v).dot(&im); assert_eq!(a, b); - } }
--- a/src/euclidean.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/euclidean.rs Fri May 15 14:46:30 2026 -0500 @@ -2,31 +2,30 @@ Euclidean spaces. */ -use std::ops::{Mul, MulAssign, Div, DivAssign, Add, Sub, AddAssign, SubAssign, Neg}; +use crate::instance::Instance; +use crate::linops::{VectorSpace, AXPY}; +use crate::norms::{HasDual, Norm, Normed, Reflexive, L2}; use crate::types::*; -use crate::instance::Instance; -use crate::norms::{HasDual, Reflexive}; + +pub mod wrap; +// TODO: Euclidean & EuclideanMut +// /// Space (type) with Euclidean and vector space structure /// /// The type should implement vector space operations (addition, subtraction, scalar /// multiplication and scalar division) along with their assignment versions, as well /// as an inner product. -pub trait Euclidean<F : Float> : HasDual<F, DualSpace=Self> + Reflexive<F> - + Mul<F, Output=<Self as Euclidean<F>>::Output> + MulAssign<F> - + Div<F, Output=<Self as Euclidean<F>>::Output> + DivAssign<F> - + Add<Self, Output=<Self as Euclidean<F>>::Output> - + Sub<Self, Output=<Self as Euclidean<F>>::Output> - + for<'b> Add<&'b Self, Output=<Self as Euclidean<F>>::Output> - + for<'b> Sub<&'b Self, Output=<Self as Euclidean<F>>::Output> - + AddAssign<Self> + for<'b> AddAssign<&'b Self> - + SubAssign<Self> + for<'b> SubAssign<&'b Self> - + Neg<Output=<Self as Euclidean<F>>::Output> +// TODO: remove F parameter, use VectorSpace::Field +pub trait Euclidean<F: Float = f64>: + VectorSpace<Field = F, PrincipalV = Self::PrincipalE> + Reflexive<F, DualSpace = Self::PrincipalE> { - type Output : Euclidean<F>; + /// Principal form of the space; always equal to [`crate::linops::Space::Principal`] and + /// [`VectorSpace::PrincipalV`], but with more traits guaranteed. + type PrincipalE: ClosedEuclidean<F>; // Inner product - fn dot<I : Instance<Self>>(&self, other : I) -> F; + fn dot<I: Instance<Self>>(&self, other: I) -> F; /// Calculate the square of the 2-norm, $\frac{1}{2}\\|x\\|_2^2$, where `self` is $x$. /// @@ -38,7 +37,7 @@ /// where `self` is $x$. #[inline] fn norm2_squared_div2(&self) -> F { - self.norm2_squared()/F::TWO + self.norm2_squared() / F::TWO } /// Calculate the 2-norm $‖x‖_2$, where `self` is $x$. @@ -48,33 +47,119 @@ } /// Calculate the 2-distance squared $\\|x-y\\|_2^2$, where `self` is $x$. - fn dist2_squared<I : Instance<Self>>(&self, y : I) -> F; + fn dist2_squared<I: Instance<Self>>(&self, y: I) -> F; /// Calculate the 2-distance $\\|x-y\\|_2$, where `self` is $x$. #[inline] - fn dist2<I : Instance<Self>>(&self, y : I) -> F { + fn dist2<I: Instance<Self>>(&self, y: I) -> F { self.dist2_squared(y).sqrt() } /// Projection to the 2-ball. #[inline] - fn proj_ball2(mut self, ρ : F) -> Self { - self.proj_ball2_mut(ρ); - self + fn proj_ball2(self, ρ: F) -> Self::PrincipalV { + let r = self.norm2(); + if r > ρ { + self * (ρ / r) + } else { + self.into_owned() + } } +} +pub trait ClosedEuclidean<F: Float = f64>: + Instance<Self> + Euclidean<F, PrincipalE = Self> +{ +} +impl<F: Float, X: Instance<X> + Euclidean<F, PrincipalE = Self>> ClosedEuclidean<F> for X {} + +// TODO: remove F parameter, use AXPY::Field +pub trait EuclideanMut<F: Float = f64>: Euclidean<F> + AXPY<Field = F> { /// In-place projection to the 2-ball. #[inline] - fn proj_ball2_mut(&mut self, ρ : F) { + fn proj_ball2_mut(&mut self, ρ: F) { let r = self.norm2(); - if r>ρ { - *self *= ρ/r + if r > ρ { + *self *= ρ / r } } } +impl<X, F: Float> EuclideanMut<F> for X where X: Euclidean<F> + AXPY<Field = F> {} + /// Trait for [`Euclidean`] spaces with dimensions known at compile time. -pub trait StaticEuclidean<F : Float> : Euclidean<F> { +pub trait StaticEuclidean<F: Float = f64>: Euclidean<F> { /// Returns the origin - fn origin() -> <Self as Euclidean<F>>::Output; + fn origin() -> <Self as Euclidean<F>>::PrincipalE; } + +macro_rules! scalar_euclidean { + ($f:ident) => { + impl VectorSpace for $f { + type Field = $f; + type PrincipalV = $f; + + #[inline] + fn similar_origin(&self) -> Self::PrincipalV { + 0.0 + } + } + impl AXPY for $f { + #[inline] + fn axpy<I: Instance<$f>>(&mut self, α: $f, x: I, β: $f) { + *self = β * *self + α * x.own() + } + + #[inline] + fn set_zero(&mut self) { + *self = 0.0 + } + } + + impl Norm<L2, $f> for $f { + fn norm(&self, _p: L2) -> $f { + self.abs() + } + } + + impl Normed<$f> for $f { + type NormExp = L2; + + fn norm_exponent(&self) -> Self::NormExp { + L2 + } + } + + impl HasDual<$f> for $f { + type DualSpace = $f; + + #[inline] + fn dual_origin(&self) -> $f { + 0.0 + } + } + + impl Euclidean<$f> for $f { + type PrincipalE = $f; + + #[inline] + fn dot<I: Instance<Self>>(&self, other: I) -> $f { + *self * other.own() + } + + #[inline] + fn norm2_squared(&self) -> $f { + *self * *self + } + + #[inline] + fn dist2_squared<I: Instance<Self>>(&self, y: I) -> $f { + let d = *self - y.own(); + d * d + } + } + }; +} + +scalar_euclidean!(f64); +scalar_euclidean!(f32);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/euclidean/wrap.rs Fri May 15 14:46:30 2026 -0500 @@ -0,0 +1,313 @@ +/*! +Wrappers for implemention [`Euclidean`] operations. +*/ + +use crate::euclidean::Euclidean; +use crate::instance::Space; +use crate::types::Float; + +pub trait WrapGuard<'a, F: Float> { + type View<'b>: Euclidean<F> + where + Self: 'b; + fn get_view(&self) -> Self::View<'_>; +} + +pub trait WrapGuardMut<'a, F: Float> { + type ViewMut<'b>: Euclidean<F> + where + Self: 'b; + fn get_view_mut(&mut self) -> Self::ViewMut<'_>; +} + +pub trait Wrapped: Space { + type WrappedField: Float; + type Guard<'a>: WrapGuard<'a, Self::WrappedField> + where + Self: 'a; + type GuardMut<'a>: WrapGuardMut<'a, Self::WrappedField> + where + Self: 'a; + type UnwrappedOutput; + type WrappedOutput; + fn get_guard(&self) -> Self::Guard<'_>; + fn get_guard_mut(&mut self) -> Self::GuardMut<'_>; + fn wrap(output: Self::UnwrappedOutput) -> Self::WrappedOutput; +} + +#[macro_export] +macro_rules! wrap { + // Rust macros are totally fucked up. $trait:path does not work, have to + // manually code paths through $($trait:ident)::+. + (impl_unary $type:ty, $($trait:ident)::+, $fn:ident where $($qual:tt)*) => { + impl<$($qual)*> $($trait)::+ for $type { + type Output = <Self as $crate::euclidean::wrap::Wrapped>::WrappedOutput; + fn $fn(self) -> Self::Output { + let a = self.get_guard(); + Self::wrap(a.get_view().$fn()) + } + } + }; + (impl_binary $type:ty, $($trait:ident)::+, $fn:ident where $($qual:tt)*) => { + impl<$($qual)*> $($trait)::+<$type> for $type { + type Output = <Self as $crate::euclidean::wrap::Wrapped>::WrappedOutput; + fn $fn(self, other: $type) -> Self::Output { + let a = self.get_guard(); + let b = other.get_guard(); + Self::wrap(a.get_view().$fn(b.get_view())) + } + } + + impl<'a, $($qual)*> $($trait)::+<$type> for &'a $type { + type Output = <$type as $crate::euclidean::wrap::Wrapped>::WrappedOutput; + fn $fn(self, other: $type) -> Self::Output { + let a = self.get_guard(); + let b = other.get_guard(); + <$type>::wrap(a.get_view().$fn(b.get_view())) + } + } + + impl<'a, 'b, $($qual)*> $($trait)::+<&'b $type> for &'a $type { + type Output = <$type as $crate::euclidean::wrap::Wrapped>::WrappedOutput; + fn $fn(self, other: &'b $type) -> Self::Output { + let a = self.get_guard(); + let b = other.get_guard(); + <$type>::wrap(a.get_view().$fn(b.get_view())) + } + } + + impl<'b, $($qual)*> $($trait)::+<&'b $type> for $type { + type Output = <Self as $crate::euclidean::wrap::Wrapped>::WrappedOutput; + fn $fn(self, other: &'b $type) -> Self::Output { + let a = self.get_guard(); + let b = other.get_guard(); + Self::wrap(a.get_view().$fn(b.get_view())) + } + } + }; + (impl_scalar $F:ty, $type:ty, $($trait:ident)::+, $fn:ident where $($qual:tt)*) => { + impl<$($qual)*> $($trait)::+<$F> for $type + // where + // $type: $crate::euclidean::wrap::Wrapped<WrappedField = F>, + // //$type::Unwrapped: $($trait)::+<F>, + { + type Output = <Self as $crate::euclidean::wrap::Wrapped>::WrappedOutput; + fn $fn(self, t: $F) -> Self::Output { + let a = self.get_guard(); + Self::wrap(a.get_view().$fn(t)) + } + } + + impl<'a, $($qual)*> $($trait)::+<$F> for &'a $type + // where + // $type: $crate::euclidean::wrap::Wrapped<WrappedField = F>, + // //$type::Unwrapped: $($trait)::+<F>, + { + type Output = <$type as $crate::euclidean::wrap::Wrapped>::WrappedOutput; + fn $fn(self, t: $F) -> Self::Output { + let a = self.get_guard(); + <$type>::wrap(a.get_view().$fn(t)) + } + } + + }; + (impl_scalar_lhs $F:ty, $type:ty, $($trait:ident)::+, $fn:ident where $($qual:tt)*) => { + impl<$($qual)*> $($trait)::+<$type> for $F + // where + // $type: $crate::euclidean::wrap::Wrapped<WrappedField = $F>, + // // where + // // $F: $($trait)::+<$type::Unwrapped>, + { + type Output = <$type as $crate::euclidean::wrap::Wrapped>::WrappedOutput; + fn $fn(self, rhs: $type) -> Self::Output { + let b = rhs.get_guard(); + <$type>::wrap(self.$fn(b.get_view())) + } + } + }; + (impl_binary_mut $type:ty, $($trait:ident)::+, $fn:ident where $($qual:tt)*) => { + impl<$($qual)*> $($trait)::+<$type> for $type { + fn $fn(&mut self, rhs: $type) { + let mut a = self.get_guard_mut(); + let b = rhs.get_guard(); + a.get_view_mut().$fn(b.get_view()) + } + } + + impl<'b, $($qual)*> $($trait)::+<&'b $type> for $type { + fn $fn(&mut self, rhs: &'b $type) { + let mut a = self.get_guard_mut(); + let b = rhs.get_guard(); + a.get_view_mut().$fn(b.get_view()) + } + } + }; + (impl_scalar_mut $F:ty, $type:ty, $($trait:ident)::+, $fn:ident where $($qual:tt)*) => { + impl<$($qual)*> $($trait)::+<$F> for $type + // where + // $type: $crate::euclidean::wrap::Wrapped<WrappedField = F>, + // // where + // // $type::UnwrappedMut: $($trait)::+<$($trait)::+<F>>, + { + fn $fn(&mut self, t: $F) { + let mut a = self.get_guard_mut(); + a.get_view_mut().$fn(t) + } + } + }; + // ($type:ty) => { + // $crate::wrap!(imp<> do $type); + // }; + ($F:ty; $type:ty where $($qual:tt)*) => { + + $crate::wrap!(impl_unary $type, std::ops::Neg, neg where $($qual)*); + $crate::wrap!(impl_binary $type, std::ops::Add, add where $($qual)*); + $crate::wrap!(impl_binary $type, std::ops::Sub, sub where $($qual)*); + $crate::wrap!(impl_scalar $F, $type, std::ops::Mul, mul where $($qual)*); + $crate::wrap!(impl_scalar $F, $type, std::ops::Div, div where $($qual)*); + $crate::wrap!(impl_scalar_lhs $F, $type, std::ops::Mul, mul where $($qual)*); + $crate::wrap!(impl_binary_mut $type, std::ops::AddAssign, add_assign where $($qual)*); + $crate::wrap!(impl_binary_mut $type, std::ops::SubAssign, sub_assign where $($qual)*); + $crate::wrap!(impl_scalar_mut $F, $type, std::ops::MulAssign, mul_assign where $($qual)*); + $crate::wrap!(impl_scalar_mut $F, $type, std::ops::DivAssign, div_assign where $($qual)*); + + $crate::self_ownable!($type where $($qual)*); + + impl<$($qual)*> $crate::norms::Norm<$crate::norms::L2, $F> for $type + { + fn norm(&self, p : $crate::norms::L2) -> $F { + let a = self.get_guard(); + $crate::norms::Norm::norm(&a.get_view(), p) + } + } + + impl<$($qual)*> $crate::norms::Dist<$crate::norms::L2, $F> for $type + { + fn dist<I: $crate::instance::Instance<Self>>(&self, other : I, p : $crate::norms::L2) -> $F { + other.eval_ref(|other| { + let a = self.get_guard(); + let b = other.get_guard(); + a.get_view().dist(b.get_view(), p) + }) + } + } + + impl<$($qual)*> $crate::norms::Normed<$F> for $type { + type NormExp = $crate::norms::L2; + + fn norm_exponent(&self) -> Self::NormExp { + $crate::norms::L2 + } + } + + impl<$($qual)*> $crate::norms::HasDual<$F> for $type { + type DualSpace = Self; + + fn dual_origin(&self) -> Self { + $crate::linops::VectorSpace::similar_origin(self) + } + } + + impl<$($qual)*> $crate::euclidean::Euclidean<$F> for $type + // where + // Self: $crate::euclidean::wrap::Wrapped<WrappedField = $F> + // + Sized + // + std::ops::Mul<F, Output = <Self as $crate::linops::AXPY>::Owned> + // + std::ops::MulAssign<F> + // + std::ops::Div<F, Output = <Self as $crate::linops::AXPY>::Owned> + // + std::ops::DivAssign<F> + // + std::ops::Add<Self, Output = <Self as $crate::linops::AXPY>::Owned> + // + std::ops::Sub<Self, Output = <Self as $crate::linops::AXPY>::Owned> + // + for<'b> std::ops::Add<&'b Self, Output = <Self as $crate::linops::AXPY>::Owned> + // + for<'b> std::ops::Sub<&'b Self, Output = <Self as $crate::linops::AXPY>::Owned> + // + std::ops::AddAssign<Self> + // + for<'b> std::ops::AddAssign<&'b Self> + // + std::ops::SubAssign<Self> + // + for<'b> std::ops::SubAssign<&'b Self> + // + std::ops::Neg<Output = <Self as $crate::linops::AXPY>::Owned>, + { + type PrincipalE = Self; + + fn dot<I: $crate::instance::Instance<Self>>(&self, other: I) -> $F { + other.eval_decompose(|other| { + let a = self.get_guard(); + let b = other.get_guard(); + a.get_view().dot(&b.get_view()) + }) + } + + fn norm2_squared(&self) -> $F { + let a = self.get_guard(); + a.get_view().norm2_squared() + } + + fn dist2_squared<I: $crate::instance::Instance<Self>>(&self, other: I) -> $F { + other.eval_decompose(|other| { + let a = self.get_guard(); + let b = other.get_guard(); + a.get_view().dist2_squared(b.get_view()) + }) + } + } + + impl<$($qual)*> $crate::linops::VectorSpace for $type + // where + // Self : $crate::euclidean::wrap::Wrapped<WrappedField = $F>, + // Self::Unwrapped : $crate::linops::AXPY<Field = F>, + // Self: std::ops::MulAssign<F> + std::ops::DivAssign<F>, + // Self::Unwrapped: std::ops::MulAssign<F> + std::ops::DivAssign<F>, + { + type Field = $F; + type PrincipalV = Self; + + /// Return a similar zero as `self`. + fn similar_origin(&self) -> Self::PrincipalV { + let a = self.get_guard(); + Self::wrap(a.get_view().similar_origin()) + } + } + + impl<$($qual)*> $crate::linops::AXPY for $type + // where + // Self : $crate::euclidean::wrap::Wrapped<WrappedField = $F>, + // Self::Unwrapped : $crate::linops::AXPY<Field = F>, + // Self: std::ops::MulAssign<F> + std::ops::DivAssign<F>, + // Self::Unwrapped: std::ops::MulAssign<F> + std::ops::DivAssign<F>, + { + fn axpy<I: $crate::instance::Instance<Self>>(&mut self, α: $F, x: I, β: $F) { + x.eval_decompose(|other| { + let mut a = self.get_guard_mut(); + let b = other.get_guard(); + $crate::linops::AXPY::axpy(&mut a.get_view_mut(), α, b.get_view(), β) + }) + } + + fn copy_from<I: $crate::instance::Instance<Self>>(&mut self, x: I) { + x.eval_decompose(|other| { + let mut a = self.get_guard_mut(); + let b = other.get_guard(); + $crate::linops::AXPY::copy_from(&mut a.get_view_mut(), b.get_view()) + }) + } + + fn scale_from<I: $crate::instance::Instance<Self>>(&mut self, α: $F, x: I) { + x.eval_decompose(|other| { + let mut a = self.get_guard_mut(); + let b = other.get_guard(); + $crate::linops::AXPY::scale_from(&mut a.get_view_mut(), α, b.get_view()) + }) + } + + /// Set self to zero. + fn set_zero(&mut self) { + let mut a = self.get_guard_mut(); + a.get_view_mut().set_zero() + } + } + + impl<$($qual)*> $crate::instance::Space for $type { + type Decomp = $crate::instance::BasicDecomposition; + type Principal = Self; + } + }; +}
--- a/src/fe_model/p2_local_model.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/fe_model/p2_local_model.rs Fri May 15 14:46:30 2026 -0500 @@ -2,21 +2,21 @@ Second order polynomical (P2) models on real intervals and planar 2D simplices. */ -use crate::types::*; -use crate::loc::Loc; -use crate::sets::{Set,NPolygon,SpannedHalfspace}; -use crate::linsolve::*; +use super::base::{LocalModel, RealLocalModel}; use crate::euclidean::Euclidean; use crate::instance::Instance; -use super::base::{LocalModel,RealLocalModel}; +use crate::linsolve::*; +use crate::loc::Loc; use crate::sets::Cube; +use crate::sets::{NPolygon, Set, SpannedHalfspace}; +use crate::types::*; use numeric_literals::replace_float_literals; /// Type for simplices of arbitrary dimension `N`. /// /// The type parameter `D` indicates the number of nodes. (Rust's const generics do not currently /// allow its automatic calculation from `N`.) -pub struct Simplex<F : Float, const N : usize, const D : usize>(pub [Loc<F, N>; D]); +pub struct Simplex<F: Float, const N: usize, const D: usize>(pub [Loc<N, F>; D]); /// A two-dimensional planar simplex pub type PlanarSimplex<F> = Simplex<F, 2, 3>; /// A real interval @@ -25,26 +25,30 @@ /// Calculates (a+b)/2 #[inline] #[replace_float_literals(F::cast_from(literal))] -pub(crate) fn midpoint<F : Float, const N : usize>(a : &Loc<F,N>, b : &Loc<F,N>) -> Loc<F, N> { - (a+b)/2.0 +pub(crate) fn midpoint<F: Float, const N: usize>(a: &Loc<N, F>, b: &Loc<N, F>) -> Loc<N, F> { + (a + b) / 2.0 } -impl<'a, F : Float> Set<Loc<F,1>> for RealInterval<F> { +impl<'a, F: Float> Set<Loc<1, F>> for RealInterval<F> { #[inline] - fn contains<I : Instance<Loc<F, 1>>>(&self, z : I) -> bool { - let &Loc([x]) = z.ref_instance(); - let &[Loc([x0]), Loc([x1])] = &self.0; - (x0 < x && x < x1) || (x1 < x && x < x0) + fn contains<I: Instance<Loc<1, F>>>(&self, z: I) -> bool { + z.eval_ref(|&Loc([x])| { + let &[Loc([x0]), Loc([x1])] = &self.0; + (x0 < x && x < x1) || (x1 < x && x < x0) + }) } } -impl<'a, F : Float> Set<Loc<F,2>> for PlanarSimplex<F> { +impl<'a, F: Float> Set<Loc<2, F>> for PlanarSimplex<F> { #[inline] - fn contains<I : Instance<Loc<F, 2>>>(&self, z : I) -> bool { + fn contains<I: Instance<Loc<2, F>>>(&self, z: I) -> bool { let &[x0, x1, x2] = &self.0; - NPolygon([[x0, x1].spanned_halfspace(), - [x1, x2].spanned_halfspace(), - [x2, x0].spanned_halfspace()]).contains(z) + NPolygon([ + [x0, x1].spanned_halfspace(), + [x1, x2].spanned_halfspace(), + [x2, x0].spanned_halfspace(), + ]) + .contains(z) } } @@ -58,121 +62,118 @@ } #[replace_float_literals(F::cast_from(literal))] -impl<F : Float> P2Powers for Loc<F, 1> { - type Output = Loc<F, 1>; - type Full = Loc<F, 3>; - type Diff = Loc<Loc<F, 1>, 1>; +impl<F: Float> P2Powers for Loc<1, F> { + type Output = Loc<1, F>; + type Full = Loc<3, F>; + type Diff = Loc<1, Loc<1, F>>; #[inline] fn p2powers(&self) -> Self::Output { let &Loc([x0]) = self; - [x0*x0].into() + [x0 * x0].into() } #[inline] fn p2powers_full(&self) -> Self::Full { let &Loc([x0]) = self; - [1.0, x0, x0*x0].into() + [1.0, x0, x0 * x0].into() } #[inline] fn p2powers_diff(&self) -> Self::Diff { let &Loc([x0]) = self; - [[x0+x0].into()].into() + [[x0 + x0].into()].into() } } #[replace_float_literals(F::cast_from(literal))] -impl<F : Float> P2Powers for Loc<F, 2> { - type Output = Loc<F, 3>; - type Full = Loc<F, 6>; - type Diff = Loc<Loc<F, 3>, 2>; +impl<F: Float> P2Powers for Loc<2, F> { + type Output = Loc<3, F>; + type Full = Loc<6, F>; + type Diff = Loc<2, Loc<3, F>>; #[inline] fn p2powers(&self) -> Self::Output { let &Loc([x0, x1]) = self; - [x0*x0, x0*x1, x1*x1].into() + [x0 * x0, x0 * x1, x1 * x1].into() } #[inline] fn p2powers_full(&self) -> Self::Full { let &Loc([x0, x1]) = self; - [1.0, x0, x1, x0*x0, x0*x1, x1*x1].into() + [1.0, x0, x1, x0 * x0, x0 * x1, x1 * x1].into() } #[inline] fn p2powers_diff(&self) -> Self::Diff { let &Loc([x0, x1]) = self; - [[x0+x0, x1, 0.0].into(), [0.0, x0, x1+x1].into()].into() + [[x0 + x0, x1, 0.0].into(), [0.0, x0, x1 + x1].into()].into() } } /// A trait for generating second order polynomial model of dimension `N` on `Self´. /// /// `Self` should present a subset aset of elements of the type [`Loc`]`<F, N>`. -pub trait P2Model<F : Num, const N : usize> { +pub trait P2Model<F: Num, const N: usize> { /// Implementation type of the second order polynomical model. /// Typically a [`P2LocalModel`]. - type Model : LocalModel<Loc<F,N>,F>; + type Model: LocalModel<Loc<N, F>, F>; /// Generates a second order polynomial model of the function `g` on `Self`. - fn p2_model<G : Fn(&Loc<F, N>) -> F>(&self, g : G) -> Self::Model; + fn p2_model<G: Fn(&Loc<N, F>) -> F>(&self, g: G) -> Self::Model; } /// A local second order polynomical model of dimension `N` with `E` edges -pub struct P2LocalModel<F : Num, const N : usize, const E : usize/*, const V : usize, const Q : usize*/> { - a0 : F, - a1 : Loc<F, N>, - a2 : Loc<F, E>, - //node_values : Loc<F, V>, - //edge_values : Loc<F, Q>, +pub struct P2LocalModel< + F: Num, + const N: usize, + const E: usize, /*, const V : usize, const Q : usize*/ +> { + a0: F, + a1: Loc<N, F>, + a2: Loc<E, F>, + //node_values : Loc<V, F>, + //edge_values : Loc<Q, F>, } // // 1D planar model construction // -impl<F : Float> RealInterval<F> { +impl<F: Float> RealInterval<F> { #[inline] - fn midpoints(&self) -> [Loc<F, 1>; 1] { + pub fn midpoints(&self) -> [Loc<1, F>; 1] { let [ref n0, ref n1] = &self.0; let n01 = midpoint(n0, n1); [n01] } } -impl<F : Float> P2LocalModel<F, 1, 1/*, 2, 0*/> { +impl<F: Float> P2LocalModel<F, 1, 1 /*, 2, 0*/> { /// Creates a new 1D second order polynomical model based on three nodal coordinates and /// corresponding function values. #[inline] - pub fn new( - &[n0, n1, n01] : &[Loc<F, 1>; 3], - &[v0, v1, v01] : &[F; 3], - ) -> Self { - let p = move |x : &Loc<F, 1>, v : F| { + pub fn new(&[n0, n1, n01]: &[Loc<1, F>; 3], &[v0, v1, v01]: &[F; 3]) -> Self { + let p = move |x: &Loc<1, F>, v: F| { let Loc([c, d, e]) = x.p2powers_full(); [c, d, e, v] }; - let [a0, a1, a11] = linsolve([ - p(&n0, v0), - p(&n1, v1), - p(&n01, v01) - ]); + let [a0, a1, a11] = linsolve([p(&n0, v0), p(&n1, v1), p(&n01, v01)]); P2LocalModel { - a0 : a0, - a1 : [a1].into(), - a2 : [a11].into(), + a0: a0, + a1: [a1].into(), + a2: [a11].into(), //node_values : [v0, v1].into(), //edge_values: [].into(), } } } -impl<F : Float> P2Model<F,1> for RealInterval<F> { - type Model = P2LocalModel<F, 1, 1/*, 2, 0*/>; +impl<F: Float> P2Model<F, 1> for RealInterval<F> { + type Model = P2LocalModel<F, 1, 1 /*, 2, 0*/>; #[inline] - fn p2_model<G : Fn(&Loc<F, 1>) -> F>(&self, g : G) -> Self::Model { - let [n01] = self.midpoints(); + fn p2_model<G: Fn(&Loc<1, F>) -> F>(&self, g: G) -> Self::Model { + let [n01] = self.midpoints(); let [n0, n1] = self.0; let vals = [g(&n0), g(&n1), g(&n01)]; let nodes = [n0, n1, n01]; @@ -184,10 +185,10 @@ // 2D planar model construction // -impl<F : Float> PlanarSimplex<F> { +impl<F: Float> PlanarSimplex<F> { #[inline] /// Returns the midpoints of all the edges of the simplex - fn midpoints(&self) -> [Loc<F, 2>; 3] { + pub fn midpoints(&self) -> [Loc<2, F>; 3] { let [ref n0, ref n1, ref n2] = &self.0; let n01 = midpoint(n0, n1); let n12 = midpoint(n1, n2); @@ -196,15 +197,15 @@ } } -impl<F : Float> P2LocalModel<F, 2, 3/*, 3, 3*/> { +impl<F: Float> P2LocalModel<F, 2, 3 /*, 3, 3*/> { /// Creates a new 2D second order polynomical model based on six nodal coordinates and /// corresponding function values. #[inline] pub fn new( - &[n0, n1, n2, n01, n12, n20] : &[Loc<F, 2>; 6], - &[v0, v1, v2, v01, v12, v20] : &[F; 6], + &[n0, n1, n2, n01, n12, n20]: &[Loc<2, F>; 6], + &[v0, v1, v2, v01, v12, v20]: &[F; 6], ) -> Self { - let p = move |x : &Loc<F,2>, v :F| { + let p = move |x: &Loc<2, F>, v: F| { let Loc([c, d, e, f, g, h]) = x.p2powers_full(); [c, d, e, f, g, h, v] }; @@ -217,20 +218,20 @@ p(&n20, v20), ]); P2LocalModel { - a0 : a0, - a1 : [a1, a2].into(), - a2 : [a11, a12, a22].into(), + a0: a0, + a1: [a1, a2].into(), + a2: [a11, a12, a22].into(), //node_values : [v0, v1, v2].into(), //edge_values: [v01, v12, v20].into(), } } } -impl<F : Float> P2Model<F,2> for PlanarSimplex<F> { - type Model = P2LocalModel<F, 2, 3/*, 3, 3*/>; +impl<F: Float> P2Model<F, 2> for PlanarSimplex<F> { + type Model = P2LocalModel<F, 2, 3 /*, 3, 3*/>; #[inline] - fn p2_model<G : Fn(&Loc<F, 2>) -> F>(&self, g : G) -> Self::Model { + fn p2_model<G: Fn(&Loc<2, F>) -> F>(&self, g: G) -> Self::Model { let midpoints = self.midpoints(); let [ref n0, ref n1, ref n2] = self.0; let [ref n01, ref n12, ref n20] = midpoints; @@ -242,125 +243,132 @@ macro_rules! impl_local_model { ($n:literal, $e:literal, $v:literal, $q:literal) => { - impl<F : Float> LocalModel<Loc<F, $n>, F> for P2LocalModel<F, $n, $e/*, $v, $q*/> { + impl<F: Float> LocalModel<Loc<$n, F>, F> for P2LocalModel<F, $n, /*, $v, $q*/ $e> { #[inline] - fn value(&self, x : &Loc<F,$n>) -> F { + fn value(&self, x: &Loc<$n, F>) -> F { self.a0 + x.dot(&self.a1) + x.p2powers().dot(&self.a2) } #[inline] - fn differential(&self, x : &Loc<F,$n>) -> Loc<F,$n> { + fn differential(&self, x: &Loc<$n, F>) -> Loc<$n, F> { self.a1 + x.p2powers_diff().map(|di| di.dot(&self.a2)) } } - } + }; } impl_local_model!(1, 1, 2, 0); impl_local_model!(2, 3, 3, 3); - // // Minimisation // #[replace_float_literals(F::cast_from(literal))] -impl<F : Float> P2LocalModel<F, 1, 1/*, 2, 0*/> { +impl<F: Float> P2LocalModel<F, 1, 1 /*, 2, 0*/> { /// Minimises the model along the edge `[x0, x1]`. #[inline] - fn minimise_edge(&self, x0 : Loc<F, 1>, x1 : Loc<F,1>) -> (Loc<F,1>, F) { - let &P2LocalModel{ - a1 : Loc([a1]), - a2 : Loc([a11]), + fn minimise_edge(&self, x0: Loc<1, F>, x1: Loc<1, F>) -> (Loc<1, F>, F) { + let &P2LocalModel { + a1: Loc([a1]), + a2: Loc([a11]), //node_values : Loc([v0, v1]), .. - } = self; + } = self; // We do this in cases, first trying for an interior solution, then edges. // For interior solution, first check determinant; no point trying if non-positive if a11 > 0.0 { // An interior solution x[1] has to satisfy // 2a₁₁*x[1] + a₁ =0 // This gives - let t = -a1/(2.0*a11); + let t = -a1 / (2.0 * a11); let (Loc([t0]), Loc([t1])) = (x0, x1); if (t0 <= t && t <= t1) || (t1 <= t && t <= t0) { let x = [t].into(); let v = self.value(&x); - return (x, v) + return (x, v); } } let v0 = self.value(&x0); let v1 = self.value(&x1); - if v0 < v1 { (x0, v0) } else { (x1, v1) } + if v0 < v1 { + (x0, v0) + } else { + (x1, v1) + } } } -impl<'a, F : Float> RealLocalModel<RealInterval<F>,Loc<F,1>,F> -for P2LocalModel<F, 1, 1/*, 2, 0*/> { +impl<'a, F: Float> RealLocalModel<RealInterval<F>, Loc<1, F>, F> + for P2LocalModel<F, 1, 1 /*, 2, 0*/> +{ #[inline] - fn minimise(&self, &Simplex([x0, x1]) : &RealInterval<F>) -> (Loc<F,1>, F) { + fn minimise(&self, &Simplex([x0, x1]): &RealInterval<F>) -> (Loc<1, F>, F) { self.minimise_edge(x0, x1) } } #[replace_float_literals(F::cast_from(literal))] -impl<F : Float> P2LocalModel<F, 2, 3/*, 3, 3*/> { +impl<F: Float> P2LocalModel<F, 2, 3 /*, 3, 3*/> { /// Minimise the 2D model along the edge `[x0, x1] = {x0 + t(x1 - x0) | t ∈ [0, 1] }`. #[inline] - fn minimise_edge(&self, x0 : &Loc<F,2>, x1 : &Loc<F,2>/*, v0 : F, v1 : F*/) -> (Loc<F,2>, F) { - let &P2LocalModel { - a0, - a1 : Loc([a1, a2]), - a2 : Loc([a11, a12, a22]), - .. - } = self; + fn minimise_edge( + &self, + x0: &Loc<2, F>, + x1: &Loc<2, F>, /*, v0 : F, v1 : F*/ + ) -> (Loc<2, F>, F) { + let &P2LocalModel { a0, a1: Loc([a1, a2]), a2: Loc([a11, a12, a22]), .. } = self; let &Loc([x00, x01]) = x0; - let d@Loc([d0, d1]) = x1 - x0; - let b0 = a0 + a1*x00 + a2*x01 + a11*x00*x00 + a12*x00*x01 + a22*x01*x01; - let b1 = a1*d0 + a2*d1 + 2.0*a11*d0*x00 + a12*(d0*x01 + d1*x00) + 2.0*a22*d1*x01; - let b11 = a11*d0*d0 + a12*d0*d1 + a22*d1*d1; + let d @ Loc([d0, d1]) = x1 - x0; + let b0 = a0 + a1 * x00 + a2 * x01 + a11 * x00 * x00 + a12 * x00 * x01 + a22 * x01 * x01; + let b1 = a1 * d0 + + a2 * d1 + + 2.0 * a11 * d0 * x00 + + a12 * (d0 * x01 + d1 * x00) + + 2.0 * a22 * d1 * x01; + let b11 = a11 * d0 * d0 + a12 * d0 * d1 + a22 * d1 * d1; let edge_1d_model = P2LocalModel { - a0 : b0, - a1 : Loc([b1]), - a2 : Loc([b11]), + a0: b0, + a1: Loc([b1]), + a2: Loc([b11]), //node_values : Loc([v0, v1]), }; let (Loc([t]), v) = edge_1d_model.minimise_edge(0.0.into(), 1.0.into()); - (x0 + d*t, v) + (x0 + d * t, v) } } #[replace_float_literals(F::cast_from(literal))] -impl<'a, F : Float> RealLocalModel<PlanarSimplex<F>,Loc<F,2>,F> -for P2LocalModel<F, 2, 3/*, 3, 3*/> { +impl<'a, F: Float> RealLocalModel<PlanarSimplex<F>, Loc<2, F>, F> + for P2LocalModel<F, 2, 3 /*, 3, 3*/> +{ #[inline] - fn minimise(&self, el : &PlanarSimplex<F>) -> (Loc<F,2>, F) { + fn minimise(&self, el: &PlanarSimplex<F>) -> (Loc<2, F>, F) { let &P2LocalModel { - a1 : Loc([a1, a2]), - a2 : Loc([a11, a12, a22]), + a1: Loc([a1, a2]), + a2: Loc([a11, a12, a22]), //node_values : Loc([v0, v1, v2]), .. - } = self; + } = self; // We do this in cases, first trying for an interior solution, then edges. // For interior solution, first check determinant; no point trying if non-positive - let r = 2.0*(a11*a22-a12*a12); + let r = 2.0 * (a11 * a22 - a12 * a12); if r > 0.0 { // An interior solution (x[1], x[2]) has to satisfy // 2a₁₁*x[1] + 2a₁₂*x[2]+a₁ =0 and 2a₂₂*x[1] + 2a₁₂*x[1]+a₂=0 // This gives - let x = [(a22*a1-a12*a2)/r, (a12*a1-a11*a2)/r].into(); + let x = [(a22 * a1 - a12 * a2) / r, (a12 * a1 - a11 * a2) / r].into(); if el.contains(&x) { - return (x, self.value(&x)) + return (x, self.value(&x)); } } let &[ref x0, ref x1, ref x2] = &el.0; let mut min_edge = self.minimise_edge(x0, x1); - let more_edge = [self.minimise_edge(x1, x2), - self.minimise_edge(x2, x0)]; - + let more_edge = [self.minimise_edge(x1, x2), self.minimise_edge(x2, x0)]; + for edge in more_edge { if edge.1 < min_edge.1 { min_edge = edge; @@ -372,35 +380,38 @@ } #[replace_float_literals(F::cast_from(literal))] -impl<'a, F : Float> RealLocalModel<Cube<F, 2>,Loc<F,2>,F> -for P2LocalModel<F, 2, 3/*, 3, 3*/> { +impl<'a, F: Float> RealLocalModel<Cube<2, F>, Loc<2, F>, F> + for P2LocalModel<F, 2, 3 /*, 3, 3*/> +{ #[inline] - fn minimise(&self, el : &Cube<F, 2>) -> (Loc<F,2>, F) { + fn minimise(&self, el: &Cube<2, F>) -> (Loc<2, F>, F) { let &P2LocalModel { - a1 : Loc([a1, a2]), - a2 : Loc([a11, a12, a22]), + a1: Loc([a1, a2]), + a2: Loc([a11, a12, a22]), //node_values : Loc([v0, v1, v2]), .. - } = self; + } = self; // We do this in cases, first trying for an interior solution, then edges. // For interior solution, first check determinant; no point trying if non-positive - let r = 2.0*(a11*a22-a12*a12); + let r = 2.0 * (a11 * a22 - a12 * a12); if r > 0.0 { // An interior solution (x[1], x[2]) has to satisfy // 2a₁₁*x[1] + 2a₁₂*x[2]+a₁ =0 and 2a₂₂*x[1] + 2a₁₂*x[1]+a₂=0 // This gives - let x = [(a22*a1-a12*a2)/r, (a12*a1-a11*a2)/r].into(); + let x = [(a22 * a1 - a12 * a2) / r, (a12 * a1 - a11 * a2) / r].into(); if el.contains(&x) { - return (x, self.value(&x)) + return (x, self.value(&x)); } } let [x0, x1, x2, x3] = el.corners(); let mut min_edge = self.minimise_edge(&x0, &x1); - let more_edge = [self.minimise_edge(&x1, &x2), - self.minimise_edge(&x2, &x3), - self.minimise_edge(&x3, &x0)]; + let more_edge = [ + self.minimise_edge(&x1, &x2), + self.minimise_edge(&x2, &x3), + self.minimise_edge(&x3, &x0), + ]; for edge in more_edge { if edge.1 < min_edge.1 { @@ -422,7 +433,7 @@ let domain = Simplex(vertices); // A simple quadratic function for which the approximation is exact on reals, // and appears exact on f64 as well. - let f = |&Loc([x]) : &Loc<f64, 1>| x*x + x + 1.0; + let f = |&Loc([x]): &Loc<1, f64>| x * x + x + 1.0; let model = domain.p2_model(f); let xs = [Loc([0.5]), Loc([0.25])]; @@ -439,7 +450,7 @@ let domain = Simplex(vertices); // A simple quadratic function for which the approximation is exact on reals, // and appears exact on f64 as well. - let f = |&Loc([x, y]) : &Loc<f64, 2>| - (x*x + x*y + x - 2.0 * y + 1.0); + let f = |&Loc([x, y]): &Loc<2, f64>| -(x * x + x * y + x - 2.0 * y + 1.0); let model = domain.p2_model(f); let xs = [Loc([0.5, 0.5]), Loc([0.25, 0.25])];
--- a/src/instance.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/instance.rs Fri May 15 14:46:30 2026 -0500 @@ -23,32 +23,252 @@ } } -impl<'b, X> MyCow<'b, X> { +/// Trait for ownable-by-consumption objects +pub trait Ownable { + type OwnedVariant: Clone; + + /// Returns an owned instance, possibly consuming the original, + /// avoiding cloning when possible. + fn into_owned(self) -> Self::OwnedVariant; + + /// Returns an owned instance of a reference. + fn clone_owned(&self) -> Self::OwnedVariant; + + /// Returns an owned instance or a reference to one. + fn cow_owned<'b>(self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b; + + /// Returns an owned instance or a reference to one. + fn ref_cow_owned<'b>(&'b self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b; +} + +impl<'a, X: Ownable> Ownable for &'a X { + type OwnedVariant = X::OwnedVariant; + + #[inline] + fn into_owned(self) -> Self::OwnedVariant { + X::clone_owned(self) + } + + #[inline] + fn clone_owned(&self) -> Self::OwnedVariant { + X::clone_owned(self) + } + #[inline] - pub fn into_owned(self) -> X where X : Clone { + fn cow_owned<'b>(self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + X::ref_cow_owned(self) + } + + fn ref_cow_owned<'b>(&self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + X::ref_cow_owned(self) + } +} + +impl<'a, X: Ownable> Ownable for &'a mut X { + type OwnedVariant = X::OwnedVariant; + + #[inline] + fn into_owned(self) -> Self::OwnedVariant { + X::clone_owned(self) + } + + #[inline] + fn clone_owned(&self) -> Self::OwnedVariant { + X::clone_owned(self) + } + + #[inline] + fn cow_owned<'b>(self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + X::ref_cow_owned(self) + } + + fn ref_cow_owned<'b>(&'b self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + X::ref_cow_owned(self) + } +} + +impl<'a, A, B> Ownable for EitherDecomp<A, B> +where + A: Ownable, + B: Ownable<OwnedVariant = A::OwnedVariant>, +{ + type OwnedVariant = A::OwnedVariant; + + #[inline] + fn into_owned(self) -> Self::OwnedVariant { match self { - EitherDecomp::Owned(x) => x, - EitherDecomp::Borrowed(x) => x.clone(), + EitherDecomp::Owned(a) => A::into_owned(a), + EitherDecomp::Borrowed(b) => B::into_owned(b), + } + } + + #[inline] + fn clone_owned(&self) -> Self::OwnedVariant { + match self { + EitherDecomp::Owned(a) => A::clone_owned(a), + EitherDecomp::Borrowed(b) => B::clone_owned(b), + } + } + + #[inline] + fn cow_owned<'b>(self) -> MyCow<'b, Self::OwnedVariant> + where + A: 'b, + B: 'b, + { + match self { + EitherDecomp::Owned(a) => A::cow_owned(a), + EitherDecomp::Borrowed(b) => B::cow_owned(b), + } + } + + #[inline] + fn ref_cow_owned<'b>(&'b self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + match self { + EitherDecomp::Owned(a) => A::ref_cow_owned(a), + EitherDecomp::Borrowed(b) => B::ref_cow_owned(b), } } } +#[macro_export] +macro_rules! self_ownable { + ($type:ty where $($qual:tt)*) => { + impl<$($qual)*> $crate::instance::Ownable for $type { + type OwnedVariant = Self; + + #[inline] + fn into_owned(self) -> Self::OwnedVariant { + self + } + + fn clone_owned(&self) -> Self::OwnedVariant { + self.clone() + } + + fn cow_owned<'b>(self) -> $crate::instance::MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + $crate::instance::MyCow::Owned(self) + } + + fn ref_cow_owned<'b>(&'b self) -> $crate::instance::MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + $crate::instance::MyCow::Borrowed(self) + } + } + }; +} + +self_ownable!(Vec<T> where T : Clone); + +impl<'a, T: Clone> Ownable for &'a [T] { + type OwnedVariant = Vec<T>; + + #[inline] + fn into_owned(self) -> Self::OwnedVariant { + Vec::from(self) + } + + #[inline] + fn clone_owned(&self) -> Self::OwnedVariant { + Vec::from(*self) + } + + #[inline] + fn cow_owned<'b>(self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + MyCow::Owned(Vec::from(self)) + } + + fn ref_cow_owned<'b>(&'b self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + MyCow::Owned(Vec::from(*self)) + } +} + /// Trait for abitrary mathematical spaces. -pub trait Space : Instance<Self, Self::Decomp> { +pub trait Space: Ownable<OwnedVariant = Self::Principal> + Sized { + /// Principal, typically owned realisation of the space. + type Principal: ClosedSpace; + /// Default decomposition for the space - type Decomp : Decomposition<Self>; + type Decomp: Decomposition<Self>; +} + +mod private { + pub trait Sealed {} } +/// Helper trait for working with own types. +pub trait Owned: Ownable<OwnedVariant = Self> + private::Sealed {} +impl<X: Ownable<OwnedVariant = X>> private::Sealed for X {} +impl<X: Ownable<OwnedVariant = X>> Owned for X {} + +/// Helper trait for working with closed spaces, operations in which should +/// return members of the same space +pub trait ClosedSpace: Space<Principal = Self> + Owned + Instance<Self> {} +impl<X: Space<Principal = Self> + Owned + Instance<Self>> ClosedSpace for X {} + #[macro_export] macro_rules! impl_basic_space { - ($($type:ty)*) => { $( - impl $crate::instance::Space for $type { + ($($type:ty)*) => { + $( $crate::impl_basic_space!($type where ); )* + }; + ($type:ty where $($where:tt)*) => { + impl<$($where)*> $crate::instance::Space for $type { + type Principal = Self; type Decomp = $crate::instance::BasicDecomposition; } - )* }; - ($type:ty where $($where:tt)*) => { - impl<$($where)*> $crate::instance::Space for $type { - type Decomp = $crate::instance::BasicDecomposition; + + impl<$($where)*> $crate::instance::Ownable for $type { + type OwnedVariant = Self; + + #[inline] + fn into_owned(self) -> Self::OwnedVariant { + self + } + + #[inline] + fn clone_owned(&self) -> Self::OwnedVariant { + *self + } + + #[inline] + fn cow_owned<'b>(self) -> MyCow<'b, Self::OwnedVariant> where Self : 'b { + MyCow::Owned(self) + } + + #[inline] + fn ref_cow_owned<'b>(&self) -> MyCow<'b, Self::OwnedVariant> where Self : 'b { + MyCow::Owned(*self) + } } }; } @@ -58,17 +278,21 @@ f32 f64); /// Marker type for decompositions to be used with [`Instance`]. -pub trait Decomposition<X : Space> : Sized { +pub trait Decomposition<X: Space>: Sized { /// Possibly owned form of the decomposition - type Decomposition<'b> : Instance<X, Self> where X : 'b; + type Decomposition<'b>: Instance<X, Self> + where + X: 'b; /// Unlikely owned form of the decomposition. /// Type for a lightweight intermediate conversion that does not own the original variable. /// Usually this is just a reference, but may also be a lightweight structure that /// contains references; see the implementation for [`crate::direct_product::Pair`]. - type Reference<'b> : Instance<X, Self> + Copy where X : 'b; + type Reference<'b>: Instance<X, Self> + Copy + where + X: 'b; - /// Left the lightweight reference type into a full decomposition type. - fn lift<'b>(r : Self::Reference<'b>) -> Self::Decomposition<'b>; + /// Lift the lightweight reference type into a full decomposition type. + fn lift<'b>(r: Self::Reference<'b>) -> Self::Decomposition<'b>; } /// Most common [`Decomposition`] (into `Either<X, &'b X>`) that allows working with owned @@ -76,12 +300,18 @@ #[derive(Copy, Clone, Debug)] pub struct BasicDecomposition; -impl<X : Space + Clone> Decomposition<X> for BasicDecomposition { - type Decomposition<'b> = MyCow<'b, X> where X : 'b; - type Reference<'b> = &'b X where X : 'b; +impl<X: Space> Decomposition<X> for BasicDecomposition { + type Decomposition<'b> + = MyCow<'b, X> + where + X: 'b; + type Reference<'b> + = &'b X + where + X: 'b; #[inline] - fn lift<'b>(r : Self::Reference<'b>) -> Self::Decomposition<'b> { + fn lift<'b>(r: Self::Reference<'b>) -> Self::Decomposition<'b> { MyCow::Borrowed(r) } } @@ -91,192 +321,225 @@ /// generalises [`std::borrow::ToOwned`], [`std::borrow::Borrow`], and [`std::borrow::Cow`]. /// /// This is used, for example, by [`crate::mapping::Mapping::apply`]. -pub trait Instance<X : Space, D = <X as Space>::Decomp> : Sized where D : Decomposition<X> { - /// Decomposes self according to `decomposer`. - fn decompose<'b>(self) -> D::Decomposition<'b> - where Self : 'b, X : 'b; - - /// Returns a lightweight instance of `self`. - fn ref_instance(&self) -> D::Reference<'_>; +pub trait Instance<X, D = <X as Space>::Decomp>: Sized +where + X: Space, + D: Decomposition<X>, +{ + /// Decomposes self according to `decomposer`, and evaluate `f` on the result. + /// Consumes self. + #[inline] + fn eval_decompose<'b, R>(self, f: impl FnOnce(D::Decomposition<'b>) -> R) -> R + where + X: 'b, + Self: 'b, + { + f(self.decompose()) + } + + /// Does a light decomposition of self `decomposer`, and evaluates `f` on the result. + /// Does not consume self. + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(D::Reference<'b>) -> R) -> R + where + X: 'b, + Self: 'b; /// Returns an owned instance of `X`, cloning or converting non-true instances when necessary. - fn own(self) -> X; + fn own(self) -> X::Principal; - // ************** automatically implemented methods below from here ************** + fn decompose<'b>(self) -> D::Decomposition<'b> + where + Self: 'b; /// Returns an owned instance or reference to `X`, converting non-true instances when necessary. /// /// Default implementation uses [`Self::own`]. Consumes the input. - fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b { - MyCow::Owned(self.own()) - } - + fn cow<'b>(self) -> MyCow<'b, X::Principal> + where + Self: 'b; + #[inline] /// Evaluates `f` on a reference to self. /// /// Default implementation uses [`Self::cow`]. Consumes the input. - fn eval<'b, R>(self, f : impl FnOnce(&X) -> R) -> R - where X : 'b, Self : 'b + fn eval<'b, R>(self, f: impl FnOnce(&X::Principal) -> R) -> R + where + X: 'b, + Self: 'b, { f(&*self.cow()) } - - #[inline] - /// Evaluates `f` or `g` depending on whether a reference or owned value is available. - /// - /// Default implementation uses [`Self::cow`]. Consumes the input. - fn either<'b, R>( - self, - f : impl FnOnce(X) -> R, - g : impl FnOnce(&X) -> R - ) -> R - where Self : 'b - { - match self.cow() { - EitherDecomp::Owned(x) => f(x), - EitherDecomp::Borrowed(x) => g(x), - } - } } - -impl<X : Space + Clone> Instance<X, BasicDecomposition> for X { +impl<X: Space> Instance<X, BasicDecomposition> for X { #[inline] - fn decompose<'b>(self) -> <BasicDecomposition as Decomposition<X>>::Decomposition<'b> - where Self : 'b, X : 'b + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(&'b X) -> R) -> R + where + X: 'b, + Self: 'b, { - MyCow::Owned(self) + f(self) } #[inline] - fn own(self) -> X { - self + fn own(self) -> X::Principal { + self.into_owned() + } + + #[inline] + fn cow<'b>(self) -> MyCow<'b, X::Principal> + where + Self: 'b, + { + self.cow_owned() } #[inline] - fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b { + fn decompose<'b>(self) -> MyCow<'b, X> + where + Self: 'b, + { MyCow::Owned(self) } +} + +impl<'a, X: Space> Instance<X, BasicDecomposition> for &'a X { + #[inline] + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(&'b X) -> R) -> R + where + X: 'b, + Self: 'b, + { + f(*self) + } #[inline] - fn ref_instance(&self) -> <BasicDecomposition as Decomposition<X>>::Reference<'_> { - self + fn own(self) -> X::Principal { + self.into_owned() } -} -impl<'a, X : Space + Clone> Instance<X, BasicDecomposition> for &'a X { #[inline] - fn decompose<'b>(self) -> <BasicDecomposition as Decomposition<X>>::Decomposition<'b> - where Self : 'b, X : 'b + fn cow<'b>(self) -> MyCow<'b, X::Principal> + where + Self: 'b, + { + self.cow_owned() + } + + #[inline] + fn decompose<'b>(self) -> MyCow<'b, X> + where + Self: 'b, { MyCow::Borrowed(self) } +} +impl<'a, X: Space> Instance<X, BasicDecomposition> for &'a mut X { #[inline] - fn own(self) -> X { - self.clone() - } - - #[inline] - fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b { - MyCow::Borrowed(self) + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(&'b X) -> R) -> R + where + X: 'b, + Self: 'b, + { + f(*self) } #[inline] - fn ref_instance(&self) -> <BasicDecomposition as Decomposition<X>>::Reference<'_> { - *self - } -} - -impl<'a, X : Space + Clone> Instance<X, BasicDecomposition> for &'a mut X { - #[inline] - fn decompose<'b>(self) -> <BasicDecomposition as Decomposition<X>>::Decomposition<'b> - where Self : 'b, X : 'b - { - EitherDecomp::Borrowed(self) + fn own(self) -> X::Principal { + self.into_owned() } #[inline] - fn own(self) -> X { - self.clone() + fn cow<'b>(self) -> MyCow<'b, X::Principal> + where + Self: 'b, + { + self.cow_owned() } #[inline] - fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b, X : Clone { - EitherDecomp::Borrowed(self) - } - - #[inline] - fn ref_instance(&self) -> <BasicDecomposition as Decomposition<X>>::Reference<'_> { - *self + fn decompose<'b>(self) -> MyCow<'b, X> + where + Self: 'b, + { + MyCow::Borrowed(self) } } -impl<'a, X : Space + Clone> Instance<X, BasicDecomposition> for MyCow<'a, X> { - +impl<'a, X: Space> Instance<X, BasicDecomposition> for MyCow<'a, X> { #[inline] - fn decompose<'b>(self) -> <BasicDecomposition as Decomposition<X>>::Decomposition<'b> - where Self : 'b, X : 'b + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(&'b X) -> R) -> R + where + X: 'b, + Self: 'b, { - self - } - - #[inline] - fn own(self) -> X { match self { - MyCow::Borrowed(a) => a.own(), - MyCow::Owned(b) => b.own() + MyCow::Borrowed(a) => f(a), + MyCow::Owned(b) => f(&b), } } #[inline] - fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b { - match self { - MyCow::Borrowed(a) => a.cow(), - MyCow::Owned(b) => b.cow() - } + fn own(self) -> X::Principal { + self.into_owned() } #[inline] - fn ref_instance(&self) -> <BasicDecomposition as Decomposition<X>>::Reference<'_> { - match self { - MyCow::Borrowed(a) => a, - MyCow::Owned(b) => &b, - } + fn cow<'b>(self) -> MyCow<'b, X::Principal> + where + Self: 'b, + { + self.cow_owned() } -} - -/// Marker type for mutable decompositions to be used with [`InstanceMut`]. -pub trait DecompositionMut<X : Space> : Sized { - type ReferenceMut<'b> : InstanceMut<X, Self> where X : 'b; -} - -/// Helper trait for functions to work with mutable references. -pub trait InstanceMut<X : Space , D = <X as Space>::Decomp> : Sized where D : DecompositionMut<X> { - /// Returns a mutable decomposition of self. - fn ref_instance_mut(&mut self) -> D::ReferenceMut<'_>; -} - -impl<X : Space> DecompositionMut<X> for BasicDecomposition { - type ReferenceMut<'b> = &'b mut X where X : 'b; -} - -/// This impl may seem pointless, but allows throwaway mutable scratch variables -impl<'a, X : Space> InstanceMut<X, BasicDecomposition> for X { #[inline] - fn ref_instance_mut(&mut self) - -> <BasicDecomposition as DecompositionMut<X>>::ReferenceMut<'_> + fn decompose<'b>(self) -> MyCow<'b, X> + where + Self: 'b, { self } } -impl<'a, X : Space> InstanceMut<X, BasicDecomposition> for &'a mut X { +/// Marker type for mutable decompositions to be used with [`InstanceMut`]. +pub trait DecompositionMut<X: Space>: Sized { + type ReferenceMut<'b>: InstanceMut<X, Self> + where + X: 'b; +} + +/// Helper trait for functions to work with mutable references. +pub trait InstanceMut<X: Space, D = <X as Space>::Decomp>: Sized +where + D: DecompositionMut<X>, +{ + /// Returns a mutable decomposition of self. + fn ref_instance_mut(&mut self) -> D::ReferenceMut<'_>; +} + +impl<X: Space> DecompositionMut<X> for BasicDecomposition { + type ReferenceMut<'b> + = &'b mut X + where + X: 'b; +} + +/// This impl may seem pointless, but allows throwaway mutable scratch variables +impl<'a, X: Space> InstanceMut<X, BasicDecomposition> for X { #[inline] - fn ref_instance_mut(&mut self) - -> <BasicDecomposition as DecompositionMut<X>>::ReferenceMut<'_> - { + fn ref_instance_mut( + &mut self, + ) -> <BasicDecomposition as DecompositionMut<X>>::ReferenceMut<'_> { self } } + +impl<'a, X: Space> InstanceMut<X, BasicDecomposition> for &'a mut X { + #[inline] + fn ref_instance_mut( + &mut self, + ) -> <BasicDecomposition as DecompositionMut<X>>::ReferenceMut<'_> { + self + } +}
--- a/src/iterate.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/iterate.rs Fri May 15 14:46:30 2026 -0500 @@ -56,40 +56,54 @@ ``` */ -use colored::{Colorize, ColoredString}; +use crate::logger::*; +use crate::types::*; +use colored::{ColoredString, Colorize}; use core::fmt::Debug; -use serde::{Serialize, Deserialize}; use cpu_time::ProcessTime; +use serde::{Deserialize, Serialize}; +use std::cell::RefCell; +use std::error::Error; use std::marker::PhantomData; +use std::rc::Rc; use std::time::Duration; -use std::error::Error; -use std::cell::RefCell; -use std::rc::Rc; -use crate::types::*; -use crate::logger::*; /// Create the displayed presentation for log items. -pub trait LogRepr : Debug { - fn logrepr(&self) -> ColoredString { format!("« {self:?} »").as_str().into() } +pub trait LogRepr: Debug { + fn logrepr(&self) -> ColoredString { + format!("« {self:?} »").as_str().into() + } } impl LogRepr for str { - fn logrepr(&self) -> ColoredString { self.into() } + fn logrepr(&self) -> ColoredString { + self.into() + } } impl LogRepr for String { - fn logrepr(&self) -> ColoredString { self.as_str().into() } + fn logrepr(&self) -> ColoredString { + self.as_str().into() + } } -impl<T> LogRepr for T where T : Num { - fn logrepr(&self) -> ColoredString { format!("J={self}").as_str().into() } +impl<T> LogRepr for T +where + T: Num, +{ + fn logrepr(&self) -> ColoredString { + format!("J={self}").as_str().into() + } } -impl<V> LogRepr for Option<V> where V : LogRepr { +impl<V> LogRepr for Option<V> +where + V: LogRepr, +{ fn logrepr(&self) -> ColoredString { match self { - None => { "===missing value===".red() } - Some(v) => { v.logrepr() } + None => "===missing value===".red(), + Some(v) => v.logrepr(), } } } @@ -97,29 +111,33 @@ /// Helper struct for returning results annotated with an additional string to /// [`if_verbose`][AlgIteratorState::if_verbose]. The [`LogRepr`] implementation will /// display that string when so decided by the specific [`AlgIterator`] in use. -#[derive(Debug,Clone)] +#[derive(Debug, Clone)] pub struct Annotated<F>(pub F, pub String); -impl<V> LogRepr for Annotated<V> where V : LogRepr { +impl<V> LogRepr for Annotated<V> +where + V: LogRepr, +{ fn logrepr(&self) -> ColoredString { - format!("{}\t| {}", self.0.logrepr(), self.1).as_str().into() + format!("{}\t| {}", self.0.logrepr(), self.1) + .as_str() + .into() } } - /// Basic log item. #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] pub struct LogItem<V> { - pub iter : usize, + pub iter: usize, // This causes [`csv`] to crash. //#[serde(flatten)] - pub data : V + pub data: V, } impl<V> LogItem<V> { /// Creates a new log item - fn new(iter : usize, data : V) -> Self { - LogItem{ iter, data } + fn new(iter: usize, data: V) -> Self { + LogItem { iter, data } } } @@ -127,7 +145,7 @@ /// /// This is the parameter obtained by the closure passed to [`AlgIterator::iterate`] or /// [`AlgIteratorFactory::iterate`]. -pub trait AlgIteratorState : Sized { +pub trait AlgIteratorState: Sized { /// Call `call_objective` if this is a verbose iteration. /// /// Verbosity depends on the [`AlgIterator`] that produced this state. @@ -135,7 +153,7 @@ /// The closure `calc_objective` should return an arbitrary value of type `V`, to be inserted /// into the log, or whatever is deemed by the [`AlgIterator`]. For usage instructions see the /// [module documentation][self]. - fn if_verbose<V, E : Error>(self, calc_objective : impl FnOnce() -> V) -> Step<V, Self, E>; + fn if_verbose<V, E: Error>(self, calc_objective: impl FnOnce() -> V) -> Step<V, Self, E>; /// Returns the current iteration count. fn iteration(&self) -> usize; @@ -146,7 +164,7 @@ /// Result of a step of an [`AlgIterator`] #[derive(Debug, Serialize)] -pub enum Step<V, S, Fail : Error = std::convert::Infallible> { +pub enum Step<V, S, Fail: Error = std::convert::Infallible> { /// Iteration should be terminated Terminated, /// Iteration should be terminated due to failure @@ -157,19 +175,19 @@ Result(V, S), } -impl<V, S, E : Error> Step<V, S, E> { +impl<V, S, E: Error> Step<V, S, E> { /// Maps the value contained within the `Step`, if any, by the closure `f`. - pub fn map<U>(self, mut f : impl FnMut(V) -> U) -> Step<U, S, E> { + pub fn map<U>(self, mut f: impl FnMut(V) -> U) -> Step<U, S, E> { match self { - Step::Result(v, s) => Step::Result(f(v), s), + Step::Result(v, s) => Step::Result(f(v), s), Step::Failure(e) => Step::Failure(e), - Step::Quiet => Step::Quiet, + Step::Quiet => Step::Quiet, Step::Terminated => Step::Terminated, } } } -impl<V, S, E : Error> Default for Step<V, S, E> { +impl<V, S, E: Error> Default for Step<V, S, E> { fn default() -> Self { Step::Quiet } @@ -179,29 +197,34 @@ /// /// Typically not accessed directly, but transparently produced by an [`AlgIteratorFactory`]. /// Every [`AlgIteratorFactory`] has to implement a corresponding `AlgIterator`. -pub trait AlgIterator : Sized { +pub trait AlgIterator: Sized { /// The state type - type State : AlgIteratorState; + type State: AlgIteratorState; /// The output type for [`Self::poststep`] and [`Self::step`]. type Output; /// The input type for [`Self::poststep`]. type Input; /// Advance the iterator, performing `step_fn` with the state - fn step<F, E>(&mut self, step_fn : &mut F) -> Step<Self::Output, Self::State, E> - where F : FnMut(Self::State) -> Step<Self::Input, Self::State, E>, - E : Error { - self.prestep().map_or(Step::Terminated, - |state| self.poststep(step_fn(state))) + fn step<F, E>(&mut self, step_fn: &mut F) -> Step<Self::Output, Self::State, E> + where + F: FnMut(Self::State) -> Step<Self::Input, Self::State, E>, + E: Error, + { + self.prestep() + .map_or(Step::Terminated, |state| self.poststep(step_fn(state))) } /// Initial stage of advancing the iterator, before the actual step fn prestep(&mut self) -> Option<Self::State>; /// Handle step result - fn poststep<E>(&mut self, result : Step<Self::Input, Self::State, E>) - -> Step<Self::Output, Self::State, E> - where E : Error; + fn poststep<E>( + &mut self, + result: Step<Self::Input, Self::State, E>, + ) -> Step<Self::Output, Self::State, E> + where + E: Error; /// Return current iteration count. fn iteration(&self) -> usize { @@ -213,16 +236,18 @@ /// Iterate the `AlgIterator` until termination, erforming `step_fn` on each step. /// - /// Returns either `()` or an error if the step closure terminated in [`Step::Failure´]. + /// Returns either `()` or an error if the step closure terminated in [`Step::Failure`]. #[inline] - fn iterate<F, E>(&mut self, mut step_fn : F) -> Result<(), E> - where F : FnMut(Self::State) -> Step<Self::Input, Self::State, E>, - E : Error { + fn iterate<F, E>(&mut self, mut step_fn: F) -> Result<(), E> + where + F: FnMut(Self::State) -> Step<Self::Input, Self::State, E>, + E: Error, + { loop { match self.step(&mut step_fn) { Step::Terminated => return Ok(()), Step::Failure(e) => return Err(e), - _ => {}, + _ => {} } } } @@ -231,12 +256,12 @@ /// A factory for producing an [`AlgIterator`]. /// /// For usage instructions see the [module documentation][self]. -pub trait AlgIteratorFactory<V> : Sized { - type Iter : AlgIterator<State = Self::State, Input = V, Output = Self::Output>; +pub trait AlgIteratorFactory<V>: Sized { + type Iter: AlgIterator<State = Self::State, Input = V, Output = Self::Output>; /// The state type of the corresponding [`AlgIterator`]. /// A reference to this is passed to the closures passed to methods such as [`Self::iterate`]. - type State : AlgIteratorState; + type State: AlgIteratorState; /// The output type of the corresponding [`AlgIterator`]. /// This is the output of the closures passed to methods such as [`Self::iterate`] after /// mappings performed by each [`AlgIterator`] implementation. @@ -254,9 +279,11 @@ /// /// This method is equivalent to [`Self::prepare`] followed by [`AlgIterator::iterate`]. #[inline] - fn iterate_fallible<F, E>(self, step : F) -> Result<(), E> - where F : FnMut(Self::State) -> Step<V, Self::State, E>, - E : Error { + fn iterate_fallible<F, E>(self, step: F) -> Result<(), E> + where + F: FnMut(Self::State) -> Step<V, Self::State, E>, + E: Error, + { self.prepare().iterate(step) } @@ -271,8 +298,10 @@ /// This method is equivalent to [`Self::prepare`] followed by [`AlgIterator::iterate`] /// with the error type `E=`[`std::convert::Infallible`]. #[inline] - fn iterate<F>(self, step : F) - where F : FnMut(Self::State) -> Step<V, Self::State> { + fn iterate<F>(self, step: F) + where + F: FnMut(Self::State) -> Step<V, Self::State>, + { self.iterate_fallible(step).unwrap_or_default() } @@ -288,13 +317,16 @@ /// /// For usage instructions see the [module documentation][self]. #[inline] - fn iterate_data_fallible<F, D, I, E>(self, mut datasource : I, mut step : F) - -> Result<(), E> - where F : FnMut(Self::State, D) -> Step<V, Self::State, E>, - I : Iterator<Item = D>, - E : Error { + fn iterate_data_fallible<F, D, I, E>(self, mut datasource: I, mut step: F) -> Result<(), E> + where + F: FnMut(Self::State, D) -> Step<V, Self::State, E>, + I: Iterator<Item = D>, + E: Error, + { self.prepare().iterate(move |state| { - datasource.next().map_or(Step::Terminated, |d| step(state, d)) + datasource + .next() + .map_or(Step::Terminated, |d| step(state, d)) }) } @@ -309,10 +341,13 @@ /// /// For usage instructions see the [module documentation][self]. #[inline] - fn iterate_data<F, D, I>(self, datasource : I, step : F) - where F : FnMut(Self::State, D) -> Step<V, Self::State>, - I : Iterator<Item = D> { - self.iterate_data_fallible(datasource, step).unwrap_or_default() + fn iterate_data<F, D, I>(self, datasource: I, step: F) + where + F: FnMut(Self::State, D) -> Step<V, Self::State>, + I: Iterator<Item = D>, + { + self.iterate_data_fallible(datasource, step) + .unwrap_or_default() } // fn make_iterate<'own>(self) @@ -345,79 +380,87 @@ /// }) /// }) /// ``` - fn into_log<'log>(self, logger : &'log mut Logger<Self::Output>) - -> LoggingIteratorFactory<'log, Self::Output, Self> - where Self : Sized { - LoggingIteratorFactory { - base_options : self, - logger, - } + fn into_log<'log>( + self, + logger: &'log mut Logger<Self::Output>, + ) -> LoggingIteratorFactory<'log, Self::Output, Self> + where + Self: Sized, + { + LoggingIteratorFactory { base_options: self, logger } } /// Map the output of the iterator produced by the factory. /// /// Returns a new factory. - fn mapped<U, G>(self, map : G) - -> MappingIteratorFactory<G, Self> - where Self : Sized, - G : Fn(usize, Self::Output) -> U { - MappingIteratorFactory { - base_options : self, - map - } + fn mapped<U, G>(self, map: G) -> MappingIteratorFactory<G, Self> + where + Self: Sized, + G: Fn(usize, Self::Output) -> U, + { + MappingIteratorFactory { base_options: self, map } } /// Adds iteration number to the output. /// /// Returns a new factory. /// Typically followed by [`Self::into_log`]. - fn with_iteration_number(self) - -> MappingIteratorFactory<fn(usize, Self::Output) -> LogItem<Self::Output>, Self> - where Self : Sized { + fn with_iteration_number( + self, + ) -> MappingIteratorFactory<fn(usize, Self::Output) -> LogItem<Self::Output>, Self> + where + Self: Sized, + { self.mapped(LogItem::new) } /// Add timing to the iterator produced by the factory. fn timed(self) -> TimingIteratorFactory<Self> - where Self : Sized { + where + Self: Sized, + { TimingIteratorFactory(self) } /// Add value stopping threshold to the iterator produce by the factory - fn stop_target(self, target : Self::Output) -> ValueIteratorFactory<Self::Output, Self> - where Self : Sized, - Self::Output : Num { - ValueIteratorFactory { base_options : self, target : target } + fn stop_target(self, target: Self::Output) -> ValueIteratorFactory<Self::Output, Self> + where + Self: Sized, + Self::Output: Num, + { + ValueIteratorFactory { base_options: self, target: target } } /// Add stall stopping to the iterator produce by the factory - fn stop_stall(self, stall : Self::Output) -> StallIteratorFactory<Self::Output, Self> - where Self : Sized, - Self::Output : Num { - StallIteratorFactory { base_options : self, stall : stall } + fn stop_stall(self, stall: Self::Output) -> StallIteratorFactory<Self::Output, Self> + where + Self: Sized, + Self::Output: Num, + { + StallIteratorFactory { base_options: self, stall: stall } } /// Is the iterator quiet, i.e., on-verbose? - fn is_quiet(&self) -> bool { false } + fn is_quiet(&self) -> bool { + false + } /// Returns an an [`std::iter::Iterator`] that can be used in a `for`-loop. fn iter(self) -> AlgIteratorIterator<Self::Iter> { - AlgIteratorIterator { - algi : Rc::new(RefCell::new(self.prepare())), - } + AlgIteratorIterator { algi: Rc::new(RefCell::new(self.prepare())) } } /// Returns an an [`std::iter::Iterator`] that can be used in a `for`-loop, /// also inputting an initial iteration status calculated by `f` if needed. - fn iter_init(self, f : impl FnOnce() -> <Self::Iter as AlgIterator>::Input) - -> AlgIteratorIterator<Self::Iter> { + fn iter_init( + self, + f: impl FnOnce() -> <Self::Iter as AlgIterator>::Input, + ) -> AlgIteratorIterator<Self::Iter> { let mut i = self.prepare(); let st = i.state(); - let step : Step<<Self::Iter as AlgIterator>::Input, Self::State> = st.if_verbose(f); + let step: Step<<Self::Iter as AlgIterator>::Input, Self::State> = st.if_verbose(f); i.poststep(step); - AlgIteratorIterator { - algi : Rc::new(RefCell::new(i)), - } + AlgIteratorIterator { algi: Rc::new(RefCell::new(i)) } } } @@ -431,12 +474,12 @@ #[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct AlgIteratorOptions { /// Maximum number of iterations - pub max_iter : usize, + pub max_iter: usize, /// Number of iterations between verbose iterations that display state. - pub verbose_iter : Verbose, + pub verbose_iter: Verbose, /// Whether verbose iterations are displayed, or just passed onwards to a containing /// `AlgIterator`. - pub quiet : bool, + pub quiet: bool, } #[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)] @@ -444,7 +487,7 @@ /// Be verbose every $n$ iterations. Every(usize), /// Be verbose every $n$ iterations and initial $m$ iterations. - EveryAndInitial{ every : usize, initial : usize }, + EveryAndInitial { every: usize, initial: usize }, /// Be verbose if iteration number $n$ divides by $b^{\text{floor}(\log_b(n))}$, where /// $b$ is indicated logarithmic base. So, with $b=10$, /// * every iteration for first 10 iterations, @@ -455,24 +498,22 @@ /// is the given `cap`. For example, with `base=10` and `cap=2`, the first ten iterations /// will be output, then every tenth iteration, and after 100 iterations, every 100th iteration, /// without further logarithmic progression. - LogarithmicCap{ base : usize, cap : u32 }, + LogarithmicCap { base: usize, cap: u32 }, } impl Verbose { /// Indicates whether given iteration number is verbose - pub fn is_verbose(&self, iter : usize) -> bool { + pub fn is_verbose(&self, iter: usize) -> bool { match self { - &Verbose::Every(every) => { - every != 0 && iter % every == 0 - }, - &Verbose::EveryAndInitial{ every, initial } => { + &Verbose::Every(every) => every != 0 && iter % every == 0, + &Verbose::EveryAndInitial { every, initial } => { iter <= initial || (every != 0 && iter % every == 0) - }, + } &Verbose::Logarithmic(base) => { let every = base.pow((iter as float).log(base as float).floor() as u32); iter % every == 0 } - &Verbose::LogarithmicCap{base, cap} => { + &Verbose::LogarithmicCap { base, cap } => { let every = base.pow(((iter as float).log(base as float).floor() as u32).min(cap)); iter % every == 0 } @@ -482,41 +523,41 @@ impl Default for AlgIteratorOptions { fn default() -> AlgIteratorOptions { - AlgIteratorOptions{ - max_iter : 1000, - verbose_iter : Verbose::EveryAndInitial { every : 100, initial : 10 }, - quiet : false + AlgIteratorOptions { + max_iter: 1000, + verbose_iter: Verbose::EveryAndInitial { every: 100, initial: 10 }, + quiet: false, } } } /// State of a `BasicAlgIterator` -#[derive(Clone,Copy,Debug,Serialize,Eq,PartialEq)] +#[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq)] pub struct BasicState { /// Current iteration - iter : usize, + iter: usize, /// Whether the iteration is verbose, i.e., results should be displayed. /// Requires `calc` to be `true`. - verbose : bool, + verbose: bool, /// Whether results should be calculated. - calc : bool, + calc: bool, /// Indicates whether the iteration is quiet - quiet : bool, + quiet: bool, } /// [`AlgIteratorFactory`] for [`BasicAlgIterator`] -#[derive(Clone,Debug)] +#[derive(Clone, Debug)] pub struct BasicAlgIteratorFactory<V> { - options : AlgIteratorOptions, - _phantoms : PhantomData<V>, + options: AlgIteratorOptions, + _phantoms: PhantomData<V>, } /// The simplest [`AlgIterator`], created by [`BasicAlgIteratorFactory`] -#[derive(Clone,Debug)] +#[derive(Clone, Debug)] pub struct BasicAlgIterator<V> { - options : AlgIteratorOptions, - iter : usize, - _phantoms : PhantomData<V>, + options: AlgIteratorOptions, + iter: usize, + _phantoms: PhantomData<V>, } impl AlgIteratorOptions { @@ -524,25 +565,20 @@ /// however, due to type inference issues, it may become convenient to instantiate /// it to a specific return type for the inner step function. This method does that. pub fn instantiate<V>(&self) -> BasicAlgIteratorFactory<V> { - BasicAlgIteratorFactory { - options : self.clone(), - _phantoms : PhantomData - } + BasicAlgIteratorFactory { options: self.clone(), _phantoms: PhantomData } } } impl<V> AlgIteratorFactory<V> for AlgIteratorOptions -where V : LogRepr { +where + V: LogRepr, +{ type State = BasicState; type Iter = BasicAlgIterator<V>; type Output = V; fn prepare(self) -> Self::Iter { - BasicAlgIterator{ - options : self, - iter : 0, - _phantoms : PhantomData, - } + BasicAlgIterator { options: self, iter: 0, _phantoms: PhantomData } } #[inline] @@ -552,17 +588,15 @@ } impl<V> AlgIteratorFactory<V> for BasicAlgIteratorFactory<V> -where V : LogRepr { +where + V: LogRepr, +{ type State = BasicState; type Iter = BasicAlgIterator<V>; type Output = V; fn prepare(self) -> Self::Iter { - BasicAlgIterator { - options : self.options, - iter : 0, - _phantoms : PhantomData - } + BasicAlgIterator { options: self.options, iter: 0, _phantoms: PhantomData } } #[inline] @@ -572,7 +606,9 @@ } impl<V> AlgIterator for BasicAlgIterator<V> -where V : LogRepr { +where + V: LogRepr, +{ type State = BasicState; type Output = V; type Input = V; @@ -587,14 +623,17 @@ } } - fn poststep<E : Error>(&mut self, res : Step<V, Self::State, E>) -> Step<V, Self::State, E> { + fn poststep<E: Error>(&mut self, res: Step<V, Self::State, E>) -> Step<V, Self::State, E> { if let Step::Result(ref val, ref state) = res { if state.verbose && !self.options.quiet { - println!("{}{}/{} {}{}", "".dimmed(), - state.iter, - self.options.max_iter, - val.logrepr(), - "".clear()); + println!( + "{}{}/{} {}{}", + "".dimmed(), + state.iter, + self.options.max_iter, + val.logrepr(), + "".clear() + ); } } res @@ -609,18 +648,13 @@ fn state(&self) -> BasicState { let iter = self.iter; let verbose = self.options.verbose_iter.is_verbose(iter); - BasicState { - iter : iter, - verbose : verbose, - calc : verbose, - quiet : self.options.quiet - } + BasicState { iter: iter, verbose: verbose, calc: verbose, quiet: self.options.quiet } } } impl AlgIteratorState for BasicState { #[inline] - fn if_verbose<V, E : Error>(self, calc_objective : impl FnOnce() -> V) -> Step<V, Self, E> { + fn if_verbose<V, E: Error>(self, calc_objective: impl FnOnce() -> V) -> Step<V, Self, E> { if self.calc { Step::Result(calc_objective(), self) } else { @@ -647,34 +681,35 @@ /// /// We define stall as $(v_{k+n}-v_k)/v_k ≤ θ$, where $n$ the distance between /// [`Step::Result`] iterations, and $θ$ is the provided `stall` parameter. -#[derive(Clone,Copy,Debug,Serialize,Eq,PartialEq)] -pub struct StallIteratorFactory<U : Num, BaseFactory> { +#[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq)] +pub struct StallIteratorFactory<U: Num, BaseFactory> { /// An [`AlgIteratorFactory`] on which to build on - pub base_options : BaseFactory, + pub base_options: BaseFactory, /// Stalling threshold $θ$. - pub stall : U, + pub stall: U, } /// Iterator produced by [`StallIteratorFactory`]. -pub struct StallIterator<U : Num, BaseIterator> { - base_iterator : BaseIterator, - stall : U, - previous_value : Option<U>, +pub struct StallIterator<U: Num, BaseIterator> { + base_iterator: BaseIterator, + stall: U, + previous_value: Option<U>, } -impl<V, U, BaseFactory> AlgIteratorFactory<V> -for StallIteratorFactory<U, BaseFactory> -where BaseFactory : AlgIteratorFactory<V, Output=U>, - U : SignedNum + PartialOrd { +impl<V, U, BaseFactory> AlgIteratorFactory<V> for StallIteratorFactory<U, BaseFactory> +where + BaseFactory: AlgIteratorFactory<V, Output = U>, + U: SignedNum + PartialOrd, +{ type Iter = StallIterator<U, BaseFactory::Iter>; type State = BaseFactory::State; type Output = BaseFactory::Output; fn prepare(self) -> Self::Iter { StallIterator { - base_iterator : self.base_options.prepare(), - stall : self.stall, - previous_value : None, + base_iterator: self.base_options.prepare(), + stall: self.stall, + previous_value: None, } } @@ -683,10 +718,11 @@ } } -impl<U, BaseIterator> AlgIterator -for StallIterator<U, BaseIterator> -where BaseIterator : AlgIterator<Output=U>, - U : SignedNum + PartialOrd { +impl<U, BaseIterator> AlgIterator for StallIterator<U, BaseIterator> +where + BaseIterator: AlgIterator<Output = U>, + U: SignedNum + PartialOrd, +{ type State = BaseIterator::State; type Output = U; type Input = BaseIterator::Input; @@ -697,8 +733,10 @@ } #[inline] - fn poststep<E>(&mut self, res : Step<Self::Input, Self::State, E>) -> Step<U, Self::State, E> - where E : Error { + fn poststep<E>(&mut self, res: Step<Self::Input, Self::State, E>) -> Step<U, Self::State, E> + where + E: Error, + { match self.base_iterator.poststep(res) { Step::Result(nv, state) => { let previous_v = self.previous_value; @@ -707,7 +745,7 @@ Some(pv) if (nv - pv).abs() <= self.stall * pv.abs() => Step::Terminated, _ => Step::Result(nv, state), } - }, + } val => val, } } @@ -725,33 +763,31 @@ /// An [`AlgIteratorFactory`] for an [`AlgIterator`] that detect whether step function /// return value is less than `target`, and terminates if it is. -#[derive(Clone,Copy,Debug,Serialize,Eq,PartialEq)] -pub struct ValueIteratorFactory<U : Num, BaseFactory> { +#[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq)] +pub struct ValueIteratorFactory<U: Num, BaseFactory> { /// An [`AlgIteratorFactory`] on which to build on - pub base_options : BaseFactory, + pub base_options: BaseFactory, /// Target value - pub target : U, + pub target: U, } /// Iterator produced by [`ValueIteratorFactory`]. -pub struct ValueIterator<U : Num, BaseIterator> { - base_iterator : BaseIterator, - target : U, +pub struct ValueIterator<U: Num, BaseIterator> { + base_iterator: BaseIterator, + target: U, } -impl<V, U, BaseFactory> AlgIteratorFactory<V> -for ValueIteratorFactory<U, BaseFactory> -where BaseFactory : AlgIteratorFactory<V, Output=U>, - U : SignedNum + PartialOrd { +impl<V, U, BaseFactory> AlgIteratorFactory<V> for ValueIteratorFactory<U, BaseFactory> +where + BaseFactory: AlgIteratorFactory<V, Output = U>, + U: SignedNum + PartialOrd, +{ type Iter = ValueIterator<U, BaseFactory::Iter>; type State = BaseFactory::State; type Output = BaseFactory::Output; fn prepare(self) -> Self::Iter { - ValueIterator { - base_iterator : self.base_options.prepare(), - target : self.target - } + ValueIterator { base_iterator: self.base_options.prepare(), target: self.target } } fn is_quiet(&self) -> bool { @@ -759,10 +795,11 @@ } } -impl<U, BaseIterator> AlgIterator -for ValueIterator<U, BaseIterator> -where BaseIterator : AlgIterator<Output=U>, - U : SignedNum + PartialOrd { +impl<U, BaseIterator> AlgIterator for ValueIterator<U, BaseIterator> +where + BaseIterator: AlgIterator<Output = U>, + U: SignedNum + PartialOrd, +{ type State = BaseIterator::State; type Output = U; type Input = BaseIterator::Input; @@ -773,15 +810,18 @@ } #[inline] - fn poststep<E>(&mut self, res : Step<Self::Input, Self::State, E>) -> Step<U, Self::State, E> where E : Error{ + fn poststep<E>(&mut self, res: Step<Self::Input, Self::State, E>) -> Step<U, Self::State, E> + where + E: Error, + { match self.base_iterator.poststep(res) { Step::Result(v, state) => { - if v <= self.target { + if v <= self.target { Step::Terminated - } else { + } else { Step::Result(v, state) - } - }, + } + } val => val, } } @@ -808,31 +848,29 @@ #[derive(Debug)] pub struct LoggingIteratorFactory<'log, U, BaseFactory> { /// Base [`AlgIteratorFactory`] on which to build - base_options : BaseFactory, + base_options: BaseFactory, /// The `Logger` to use. - logger : &'log mut Logger<U>, + logger: &'log mut Logger<U>, } /// Iterator produced by `LoggingIteratorFactory`. pub struct LoggingIterator<'log, U, BaseIterator> { - base_iterator : BaseIterator, - logger : &'log mut Logger<U>, + base_iterator: BaseIterator, + logger: &'log mut Logger<U>, } - impl<'log, V, BaseFactory> AlgIteratorFactory<V> -for LoggingIteratorFactory<'log, BaseFactory::Output, BaseFactory> -where BaseFactory : AlgIteratorFactory<V>, - BaseFactory::Output : 'log { + for LoggingIteratorFactory<'log, BaseFactory::Output, BaseFactory> +where + BaseFactory: AlgIteratorFactory<V>, + BaseFactory::Output: 'log, +{ type State = BaseFactory::State; type Iter = LoggingIterator<'log, BaseFactory::Output, BaseFactory::Iter>; type Output = (); fn prepare(self) -> Self::Iter { - LoggingIterator { - base_iterator : self.base_options.prepare(), - logger : self.logger, - } + LoggingIterator { base_iterator: self.base_options.prepare(), logger: self.logger } } #[inline] @@ -841,10 +879,11 @@ } } -impl<'log, BaseIterator> AlgIterator -for LoggingIterator<'log, BaseIterator::Output, BaseIterator> -where BaseIterator : AlgIterator, - BaseIterator::Output : 'log { +impl<'log, BaseIterator> AlgIterator for LoggingIterator<'log, BaseIterator::Output, BaseIterator> +where + BaseIterator: AlgIterator, + BaseIterator::Output: 'log, +{ type State = BaseIterator::State; type Output = (); type Input = BaseIterator::Input; @@ -855,12 +894,15 @@ } #[inline] - fn poststep<E>(&mut self, res : Step<Self::Input, Self::State, E>) -> Step<(), Self::State, E> where E : Error { + fn poststep<E>(&mut self, res: Step<Self::Input, Self::State, E>) -> Step<(), Self::State, E> + where + E: Error, + { match self.base_iterator.poststep(res) { Step::Result(v, _) => { self.logger.log(v); Step::Quiet - }, + } Step::Quiet => Step::Quiet, Step::Terminated => Step::Terminated, Step::Failure(e) => Step::Failure(e), @@ -884,32 +926,29 @@ #[derive(Debug)] pub struct MappingIteratorFactory<G, BaseFactory> { /// Base [`AlgIteratorFactory`] on which to build - base_options : BaseFactory, + base_options: BaseFactory, /// A closure `G : Fn(usize, BaseFactory::Output) -> U` that gets the current iteration /// and the output of the base factory as input, and produces a new output. - map : G, + map: G, } /// [`AlgIterator`] produced by [`MappingIteratorFactory`]. pub struct MappingIterator<G, BaseIterator> { - base_iterator : BaseIterator, - map : G, + base_iterator: BaseIterator, + map: G, } - -impl<V, U, G, BaseFactory> AlgIteratorFactory<V> -for MappingIteratorFactory<G, BaseFactory> -where BaseFactory : AlgIteratorFactory<V>, - G : Fn(usize, BaseFactory::Output) -> U { +impl<V, U, G, BaseFactory> AlgIteratorFactory<V> for MappingIteratorFactory<G, BaseFactory> +where + BaseFactory: AlgIteratorFactory<V>, + G: Fn(usize, BaseFactory::Output) -> U, +{ type State = BaseFactory::State; type Iter = MappingIterator<G, BaseFactory::Iter>; type Output = U; fn prepare(self) -> Self::Iter { - MappingIterator { - base_iterator : self.base_options.prepare(), - map : self.map - } + MappingIterator { base_iterator: self.base_options.prepare(), map: self.map } } #[inline] @@ -918,10 +957,11 @@ } } -impl<U, G, BaseIterator> AlgIterator -for MappingIterator<G, BaseIterator> -where BaseIterator : AlgIterator, - G : Fn(usize, BaseIterator::Output) -> U { +impl<U, G, BaseIterator> AlgIterator for MappingIterator<G, BaseIterator> +where + BaseIterator: AlgIterator, + G: Fn(usize, BaseIterator::Output) -> U, +{ type State = BaseIterator::State; type Output = U; type Input = BaseIterator::Input; @@ -932,7 +972,13 @@ } #[inline] - fn poststep<E>(&mut self, res : Step<Self::Input, Self::State, E>) -> Step<Self::Output, Self::State, E> where E : Error { + fn poststep<E>( + &mut self, + res: Step<Self::Input, Self::State, E>, + ) -> Step<Self::Output, Self::State, E> + where + E: Error, + { match self.base_iterator.poststep(res) { Step::Result(v, state) => Step::Result((self.map)(self.iteration(), v), state), Step::Quiet => Step::Quiet, @@ -963,41 +1009,47 @@ /// Iterator produced by [`TimingIteratorFactory`] #[derive(Debug)] pub struct TimingIterator<BaseIterator> { - base_iterator : BaseIterator, - start_time : ProcessTime, + base_iterator: BaseIterator, + start_time: ProcessTime, } /// Data `U` with production time attached #[derive(Copy, Clone, Debug, Serialize)] pub struct Timed<U> { /// CPU time taken - pub cpu_time : Duration, + pub cpu_time: Duration, /// Iteration number - pub iter : usize, + pub iter: usize, /// User data //#[serde(flatten)] - pub data : U + pub data: U, } -impl<T> LogRepr for Timed<T> where T : LogRepr { +impl<T> LogRepr for Timed<T> +where + T: LogRepr, +{ fn logrepr(&self) -> ColoredString { - format!("[{:.3}s] {}", self.cpu_time.as_secs_f64(), self.data.logrepr()).as_str().into() + format!( + "[{:.3}s] {}", + self.cpu_time.as_secs_f64(), + self.data.logrepr() + ) + .as_str() + .into() } } - -impl<V, BaseFactory> AlgIteratorFactory<V> -for TimingIteratorFactory<BaseFactory> -where BaseFactory : AlgIteratorFactory<V> { +impl<V, BaseFactory> AlgIteratorFactory<V> for TimingIteratorFactory<BaseFactory> +where + BaseFactory: AlgIteratorFactory<V>, +{ type State = BaseFactory::State; type Iter = TimingIterator<BaseFactory::Iter>; type Output = Timed<BaseFactory::Output>; fn prepare(self) -> Self::Iter { - TimingIterator { - base_iterator : self.0.prepare(), - start_time : ProcessTime::now() - } + TimingIterator { base_iterator: self.0.prepare(), start_time: ProcessTime::now() } } #[inline] @@ -1006,9 +1058,10 @@ } } -impl<BaseIterator> AlgIterator -for TimingIterator<BaseIterator> -where BaseIterator : AlgIterator { +impl<BaseIterator> AlgIterator for TimingIterator<BaseIterator> +where + BaseIterator: AlgIterator, +{ type State = BaseIterator::State; type Output = Timed<BaseIterator::Output>; type Input = BaseIterator::Input; @@ -1019,15 +1072,18 @@ } #[inline] - fn poststep<E>(&mut self, res : Step<Self::Input, Self::State, E>) -> Step<Self::Output, Self::State, E> where E : Error { + fn poststep<E>( + &mut self, + res: Step<Self::Input, Self::State, E>, + ) -> Step<Self::Output, Self::State, E> + where + E: Error, + { match self.base_iterator.poststep(res) { - Step::Result(data, state) => { - Step::Result(Timed{ - cpu_time : self.start_time.elapsed(), - iter : self.iteration(), - data - }, state) - }, + Step::Result(data, state) => Step::Result( + Timed { cpu_time: self.start_time.elapsed(), iter: self.iteration(), data }, + state, + ), Step::Quiet => Step::Quiet, Step::Terminated => Step::Terminated, Step::Failure(e) => Step::Failure(e), @@ -1049,35 +1105,34 @@ // New for-loop interface // -pub struct AlgIteratorIterator<I : AlgIterator> { - algi : Rc<RefCell<I>>, +pub struct AlgIteratorIterator<I: AlgIterator> { + algi: Rc<RefCell<I>>, } -pub struct AlgIteratorIteration<I : AlgIterator> { - state : I::State, - algi : Rc<RefCell<I>>, +pub struct AlgIteratorIteration<I: AlgIterator> { + state: I::State, + algi: Rc<RefCell<I>>, } -impl<I : AlgIterator> std::iter::Iterator for AlgIteratorIterator<I> { +impl<I: AlgIterator> std::iter::Iterator for AlgIteratorIterator<I> { type Item = AlgIteratorIteration<I>; fn next(&mut self) -> Option<Self::Item> { let algi = self.algi.clone(); - RefCell::borrow_mut(&self.algi).prestep().map(|state| AlgIteratorIteration { - state, - algi, - }) + RefCell::borrow_mut(&self.algi) + .prestep() + .map(|state| AlgIteratorIteration { state, algi }) } } /// Types of errors that may occur -#[derive(Debug,PartialEq,Eq)] +#[derive(Debug, PartialEq, Eq)] pub enum IterationError { /// [`AlgIteratorIteration::if_verbose_check`] is not called in iteration order. - ReportingOrderingError + ReportingOrderingError, } -impl<I : AlgIterator> AlgIteratorIteration<I> { +impl<I: AlgIterator> AlgIteratorIteration<I> { /// Call `call_objective` if this is a verbose iteration. /// /// Verbosity depends on the [`AlgIterator`] that produced this state. @@ -1089,22 +1144,24 @@ /// This function may panic if result reporting is not ordered correctly (an unlikely mistake /// if using this facility correctly). For a version that propagates errors, see /// [`Self::if_verbose_check`]. - pub fn if_verbose(self, calc_objective : impl FnOnce() -> I::Input) { + pub fn if_verbose(self, calc_objective: impl FnOnce() -> I::Input) { self.if_verbose_check(calc_objective).unwrap() } /// Version of [`Self::if_verbose`] that propagates errors instead of panicking. - pub fn if_verbose_check(self, calc_objective : impl FnOnce() -> I::Input) - -> Result<(), IterationError> { + pub fn if_verbose_check( + self, + calc_objective: impl FnOnce() -> I::Input, + ) -> Result<(), IterationError> { let mut algi = match RefCell::try_borrow_mut(&self.algi) { Err(_) => return Err(IterationError::ReportingOrderingError), - Ok(algi) => algi + Ok(algi) => algi, }; if self.state.iteration() != algi.iteration() { Err(IterationError::ReportingOrderingError) } else { - let res : Step<I::Input, I::State, std::convert::Infallible> - = self.state.if_verbose(calc_objective); + let res: Step<I::Input, I::State, std::convert::Infallible> = + self.state.if_verbose(calc_objective); algi.poststep(res); Ok(()) } @@ -1131,10 +1188,10 @@ use crate::logger::Logger; #[test] fn iteration() { - let options = AlgIteratorOptions{ - max_iter : 10, - verbose_iter : Verbose::Every(3), - .. Default::default() + let options = AlgIteratorOptions { + max_iter: 10, + verbose_iter: Verbose::Every(3), + ..Default::default() }; { @@ -1149,31 +1206,35 @@ { let mut start = 1 as int; let mut log = Logger::new(); - let factory = options.instantiate() - .with_iteration_number() - .into_log(&mut log); + let factory = options + .instantiate() + .with_iteration_number() + .into_log(&mut log); factory.iterate(|state| { start = start * 2; state.if_verbose(|| start) }); assert_eq!(start, (2 as int).pow(10)); - assert_eq!(log.data() - .iter() - .map(|LogItem{ data : v, iter : _ }| v.clone()) - .collect::<Vec<int>>(), - (1..10).map(|i| (2 as int).pow(i)) - .skip(2) - .step_by(3) - .collect::<Vec<int>>()) + assert_eq!( + log.data() + .iter() + .map(|LogItem { data: v, iter: _ }| v.clone()) + .collect::<Vec<int>>(), + (1..10) + .map(|i| (2 as int).pow(i)) + .skip(2) + .step_by(3) + .collect::<Vec<int>>() + ) } } #[test] fn iteration_for_loop() { - let options = AlgIteratorOptions{ - max_iter : 10, - verbose_iter : Verbose::Every(3), - .. Default::default() + let options = AlgIteratorOptions { + max_iter: 10, + verbose_iter: Verbose::Every(3), + ..Default::default() }; { @@ -1188,23 +1249,26 @@ { let mut start = 1 as int; let mut log = Logger::new(); - let factory = options.instantiate() - .with_iteration_number() - .into_log(&mut log); + let factory = options + .instantiate() + .with_iteration_number() + .into_log(&mut log); for state in factory.iter() { start = start * 2; state.if_verbose(|| start) } assert_eq!(start, (2 as int).pow(10)); - assert_eq!(log.data() - .iter() - .map(|LogItem{ data : v, iter : _ }| v.clone()) - .collect::<Vec<int>>(), - (1..10).map(|i| (2 as int).pow(i)) - .skip(2) - .step_by(3) - .collect::<Vec<int>>()) + assert_eq!( + log.data() + .iter() + .map(|LogItem { data: v, iter: _ }| v.clone()) + .collect::<Vec<int>>(), + (1..10) + .map(|i| (2 as int).pow(i)) + .skip(2) + .step_by(3) + .collect::<Vec<int>>() + ) } } - }
--- a/src/lib.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/lib.rs Fri May 15 14:46:30 2026 -0500 @@ -12,15 +12,12 @@ nightly, feature( maybe_uninit_array_assume_init, - maybe_uninit_slice, float_minimum_maximum, get_mut_unchecked, - cow_is_borrowed ) )] #[macro_use] -pub(crate) mod metaprogramming; pub mod collection; pub mod error; pub mod euclidean; @@ -34,6 +31,7 @@ #[macro_use] pub mod loc; pub mod bisection_tree; +pub mod bounds; pub mod coefficients; pub mod convex; pub mod direct_product;
--- a/src/lingrid.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/lingrid.rs Fri May 15 14:46:30 2026 -0500 @@ -15,46 +15,54 @@ iteration over the grid. Additional utility functions are in the [`Grid`] trait. */ -use crate::types::*; +use crate::iter::{RestartableIterator, StatefulIterator}; use crate::loc::Loc; +use crate::maputil::{map2, map4}; use crate::sets::Cube; -use crate::iter::{RestartableIterator, StatefulIterator}; -use crate::maputil::{map2, map4}; -use serde::{Serialize, Deserialize}; +use crate::types::*; +use serde::{Deserialize, Serialize}; // TODO: rewrite this using crate::sets::Cube. /// An abstraction of possibly multi-dimensional linear grids. /// /// `U` is typically a `F` for a `Float` `F` for one-dimensional grids created by `linspace`, -/// or [`Loc`]`<F, N>` for multi-dimensional grids created by `lingrid`. +/// or [`Loc`]`<N, F>` for multi-dimensional grids created by `lingrid`. /// In the first case `count` of nodes is `usize`, and in the second case `[usize; N]`. #[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct LinSpace<U, I> { - pub start : U, - pub end : U, - pub count : I, + pub start: U, + pub end: U, + pub count: I, } /// A `N`-dimensional interval divided into an indicated number of equally-spaced nodes along /// each dimension. #[allow(type_alias_bounds)] // Need it to access F::CompatibleSize. -pub type LinGrid<F : Float, const N : usize> = LinSpace<Loc<F, N>, [usize; N]>; +pub type LinGrid<const N: usize, F: Float = f64> = LinSpace<Loc<N, F>, [usize; N]>; /// Creates a [`LinSpace`] on the real line. -pub fn linspace<F : Float>(start : F, end : F, count : usize) -> LinSpace<F, usize> { - LinSpace{ start : start, end : end, count : count } +pub fn linspace<F: Float>(start: F, end: F, count: usize) -> LinSpace<F, usize> { + LinSpace { + start: start, + end: end, + count: count, + } } /// Creates a multi-dimensional linear grid. /// /// The first and last point in each dimension are the boundaries of the corresponding /// dimensions of `cube`, and there are `count` nodes along each dimension. -pub fn lingrid<F : Float, const N : usize>( - cube : &Cube<F, N>, - count : &[usize; N] -) -> LinSpace<Loc<F, N>, [usize; N]> { - LinSpace{ start : cube.span_start(), end : cube.span_end(), count : *count } +pub fn lingrid<F: Float, const N: usize>( + cube: &Cube<N, F>, + count: &[usize; N], +) -> LinSpace<Loc<N, F>, [usize; N]> { + LinSpace { + start: cube.span_start(), + end: cube.span_end(), + count: *count, + } } /// Create a multi-dimensional linear grid with centered nodes. @@ -63,30 +71,33 @@ /// inside `cube`. Thus, if $w\_i$ is the width of the cube along dimension $i$, and $n_i$ the number /// of nodes, the width of the subcube along this dimension is $h_i = w\_i/(n\_i+1)$, and the first /// and last nodes are at a distance $h\_i/2$ from the closest boundary. -pub fn lingrid_centered<F : Float, const N : usize>( - cube : &Cube<F, N>, - count : &[usize; N] -) -> LinSpace<Loc<F, N>, [usize; N]> { +pub fn lingrid_centered<F: Float, const N: usize>( + cube: &Cube<N, F>, + count: &[usize; N], +) -> LinSpace<Loc<N, F>, [usize; N]> { let h_div_2 = map2(cube.width(), count, |w, &n| w / F::cast_from(2 * (n + 1))); let span_start = map2(cube.span_start(), &h_div_2, |a, &t| a + t).into(); - let span_end = map2(cube.span_end(), &h_div_2, |b, &t| b - t).into(); - LinSpace{ start : span_start, end : span_end, count : *count } + let span_end = map2(cube.span_end(), &h_div_2, |b, &t| b - t).into(); + LinSpace { + start: span_start, + end: span_end, + count: *count, + } } - /// Iterator over a `LinSpace`. #[derive(Clone, Debug)] pub struct LinSpaceIterator<F, I> { - lingrid : LinSpace<F,I>, - current : Option<I>, + lingrid: LinSpace<F, I>, + current: Option<I>, } /// Abstraction of a linear grid over space `U` with multi-dimensional index set `I`. pub trait Grid<U, I> { /// Converts a linear index `i` into a grid point. - fn entry_linear_unchecked(&self, i : usize) -> U; + fn entry_linear_unchecked(&self, i: usize) -> U; // Converts a multi-dimensional index `i` into a grid point. - fn entry_unchecked(&self, i : &I) -> U; + fn entry_unchecked(&self, i: &I) -> U; // fn entry(&self, i : I) -> Option<F> } @@ -97,7 +108,7 @@ fn next_index(&mut self) -> Option<I>; } -impl<F : Float + CastFrom<I>, I : Unsigned> Grid<F, I> for LinSpace<F, I> { +impl<F: Float + CastFrom<I>, I: Unsigned> Grid<F, I> for LinSpace<F, I> { /*fn entry(&self, i : I) -> Option<F> { if i < self.count { Some(self.entry_unchecked(i)) @@ -107,37 +118,40 @@ }*/ #[inline] - fn entry_linear_unchecked(&self, i : usize) -> F { + fn entry_linear_unchecked(&self, i: usize) -> F { self.entry_unchecked(&I::cast_from(i)) } #[inline] - fn entry_unchecked(&self, i : &I) -> F { + fn entry_unchecked(&self, i: &I) -> F { let idx = F::cast_from(*i); - let scale = F::cast_from(self.count-I::ONE); - self.start + (self.end-self.start)*idx/scale + let scale = F::cast_from(self.count - I::ONE); + self.start + (self.end - self.start) * idx / scale } } -impl<F : Float + CastFrom<I>, I : Unsigned> GridIteration<F, I> -for LinSpaceIterator<F, I> { +impl<F: Float + CastFrom<I>, I: Unsigned> GridIteration<F, I> for LinSpaceIterator<F, I> { #[inline] fn next_index(&mut self) -> Option<I> { match self.current { - None if I::ZERO < self.lingrid.count - => { self.current = Some(I::ZERO); self.current } - Some(v) if v+I::ONE < self.lingrid.count - => { self.current = Some(v+I::ONE); self.current } - _ - => { None } + None if I::ZERO < self.lingrid.count => { + self.current = Some(I::ZERO); + self.current + } + Some(v) if v + I::ONE < self.lingrid.count => { + self.current = Some(v + I::ONE); + self.current + } + _ => None, } } } -impl<F : Float + CastFrom<I>, I : Unsigned, const N : usize> Grid<Loc<F,N>, [I; N]> -for LinSpace<Loc<F,N>, [I; N]> { +impl<F: Float + CastFrom<I>, I: Unsigned, const N: usize> Grid<Loc<N, F>, [I; N]> + for LinSpace<Loc<N, F>, [I; N]> +{ #[inline] - fn entry_linear_unchecked(&self, i_ : usize) -> Loc<F, N> { + fn entry_linear_unchecked(&self, i_: usize) -> Loc<N, F> { let mut i = I::cast_from(i_); let mut tmp = [I::ZERO; N]; for k in 0..N { @@ -148,58 +162,66 @@ } #[inline] - fn entry_unchecked(&self, i : &[I; N]) -> Loc<F, N> { - let LinSpace{ start, end, count } = self; + fn entry_unchecked(&self, i: &[I; N]) -> Loc<N, F> { + let LinSpace { start, end, count } = self; map4(i, start, end, count, |&ik, &sk, &ek, &ck| { let idx = F::cast_from(ik); - let scale = F::cast_from(ck-I::ONE); + let scale = F::cast_from(ck - I::ONE); sk + (ek - sk) * idx / scale - }).into() + }) + .into() } } -impl<F : Float + CastFrom<I>, I : Unsigned, const N : usize> GridIteration<Loc<F,N>, [I; N]> -for LinSpaceIterator<Loc<F,N>, [I; N]> { - +impl<F: Float + CastFrom<I>, I: Unsigned, const N: usize> GridIteration<Loc<N, F>, [I; N]> + for LinSpaceIterator<Loc<N, F>, [I; N]> +{ #[inline] fn next_index(&mut self) -> Option<[I; N]> { match self.current { - None if self.lingrid.count.iter().all(|v| I::ZERO < *v) => { + None if self.lingrid.count.iter().all(|v| I::ZERO < *v) => { self.current = Some([I::ZERO; N]); self.current - }, + } Some(ref mut v) => { for k in 0..N { let a = v[k] + I::ONE; if a < self.lingrid.count[k] { v[k] = a; - return self.current + return self.current; } else { v[k] = I::ZERO; } } None - }, - _ => None + } + _ => None, } } } -impl<F, I> IntoIterator for LinSpace<F,I> -where LinSpace<F, I> : Grid<F, I>, - LinSpaceIterator<F, I> : GridIteration<F, I> { +impl<F, I> IntoIterator for LinSpace<F, I> +where + LinSpace<F, I>: Grid<F, I>, + LinSpaceIterator<F, I>: GridIteration<F, I>, +{ type Item = F; - type IntoIter = LinSpaceIterator<F,I>; + type IntoIter = LinSpaceIterator<F, I>; #[inline] fn into_iter(self) -> Self::IntoIter { - LinSpaceIterator { lingrid : self, current : None } + LinSpaceIterator { + lingrid: self, + current: None, + } } } -impl<F, I> Iterator for LinSpaceIterator<F,I> -where LinSpace<F, I> : Grid<F, I>, - LinSpaceIterator<F, I> : GridIteration<F, I> { +impl<F, I> Iterator for LinSpaceIterator<F, I> +where + LinSpace<F, I>: Grid<F, I>, + LinSpaceIterator<F, I>: GridIteration<F, I>, +{ type Item = F; #[inline] fn next(&mut self) -> Option<F> { @@ -207,19 +229,24 @@ } } -impl<F, I> StatefulIterator for LinSpaceIterator<F,I> -where LinSpace<F, I> : Grid<F, I>, - LinSpaceIterator<F, I> : GridIteration<F, I> { +impl<F, I> StatefulIterator for LinSpaceIterator<F, I> +where + LinSpace<F, I>: Grid<F, I>, + LinSpaceIterator<F, I>: GridIteration<F, I>, +{ #[inline] fn current(&self) -> Option<F> { - self.current.as_ref().map(|c| self.lingrid.entry_unchecked(c)) + self.current + .as_ref() + .map(|c| self.lingrid.entry_unchecked(c)) } } - -impl<F, I> RestartableIterator for LinSpaceIterator<F,I> -where LinSpace<F, I> : Grid<F, I>, - LinSpaceIterator<F, I> : GridIteration<F, I> { +impl<F, I> RestartableIterator for LinSpaceIterator<F, I> +where + LinSpace<F, I>: Grid<F, I>, + LinSpaceIterator<F, I>: GridIteration<F, I>, +{ #[inline] fn restart(&mut self) -> Option<F> { self.current = None;
--- a/src/linops.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/linops.rs Fri May 15 14:46:30 2026 -0500 @@ -2,81 +2,143 @@ Abstract linear operators. */ -use numeric_literals::replace_float_literals; -use std::marker::PhantomData; -use serde::Serialize; +use crate::direct_product::Pair; +use crate::error::DynResult; +use crate::euclidean::StaticEuclidean; +use crate::instance::Instance; +pub use crate::mapping::{ClosedSpace, Composition, DifferentiableImpl, Mapping, Space}; +use crate::norms::{HasDual, Linfinity, NormExponent, PairNorm, L1, L2}; use crate::types::*; -pub use crate::mapping::{Mapping, Space, Composition}; -use crate::direct_product::Pair; -use crate::instance::Instance; -use crate::norms::{NormExponent, PairNorm, L1, L2, Linfinity, Norm}; +use numeric_literals::replace_float_literals; +use serde::Serialize; +use std::marker::PhantomData; +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; /// Trait for linear operators on `X`. -pub trait Linear<X : Space> : Mapping<X> -{ } +pub trait Linear<X: Space>: Mapping<X> {} + +// impl<X: Space, A: Linear<X>> DifferentiableImpl<X> for A { +// type Derivative = <Self as Mapping<X>>::Codomain; + +// /// Compute the differential of `self` at `x`, consuming the input. +// fn differential_impl<I: Instance<X>>(&self, x: I) -> Self::Derivative { +// self.apply(x) +// } +// } + +/// Vector spaces +#[replace_float_literals(Self::Field::cast_from(literal))] +pub trait VectorSpace: + Space<Principal = Self::PrincipalV> + + Mul<Self::Field, Output = Self::PrincipalV> + + Div<Self::Field, Output = Self::PrincipalV> + + Add<Self, Output = Self::PrincipalV> + + Add<Self::PrincipalV, Output = Self::PrincipalV> + + Sub<Self, Output = Self::PrincipalV> + + Sub<Self::PrincipalV, Output = Self::PrincipalV> + + Neg + + for<'b> Add<&'b Self, Output = <Self as VectorSpace>::PrincipalV> + + for<'b> Sub<&'b Self, Output = <Self as VectorSpace>::PrincipalV> +{ + /// Underlying scalar field + type Field: Num; + + /// Principal form of the space; always equal to [`Space::Principal`], but with + /// more traits guaranteed. + /// + /// `PrincipalV` is only assumed to be `AXPY` for itself, as [`AXPY`] + /// uses [`Instance`] to apply all other variants and avoid problems + /// of choosing multiple implementations of the trait. + type PrincipalV: ClosedSpace + + AXPY< + Self::PrincipalV, + Field = Self::Field, + PrincipalV = Self::PrincipalV, + OwnedVariant = Self::PrincipalV, + Principal = Self::PrincipalV, + >; + + /// Return a similar zero as `self`. + fn similar_origin(&self) -> Self::PrincipalV; + // { + // self.make_origin_generator().make_origin() + // } + + /// Return a similar zero as `x`. + fn similar_origin_inst<I: Instance<Self>>(x: I) -> Self::PrincipalV { + x.eval(|xr| xr.similar_origin()) + } +} /// Efficient in-place summation. -#[replace_float_literals(F::cast_from(literal))] -pub trait AXPY<F, X = Self> : Space + std::ops::MulAssign<F> +#[replace_float_literals(Self::Field::cast_from(literal))] +pub trait AXPY<X = Self>: + VectorSpace + + MulAssign<Self::Field> + + DivAssign<Self::Field> + + AddAssign<Self> + + AddAssign<Self::PrincipalV> + + SubAssign<Self> + + SubAssign<Self::PrincipalV> + + for<'b> AddAssign<&'b Self> + + for<'b> SubAssign<&'b Self> where - F : Num, - X : Space, + X: Space, { - type Owned : AXPY<F, X>; - /// Computes `y = βy + αx`, where `y` is `Self`. - fn axpy<I : Instance<X>>(&mut self, α : F, x : I, β : F); + fn axpy<I: Instance<X>>(&mut self, α: Self::Field, x: I, β: Self::Field); /// Copies `x` to `self`. - fn copy_from<I : Instance<X>>(&mut self, x : I) { + fn copy_from<I: Instance<X>>(&mut self, x: I) { self.axpy(1.0, x, 0.0) } /// Computes `y = αx`, where `y` is `Self`. - fn scale_from<I : Instance<X>>(&mut self, α : F, x : I) { + fn scale_from<I: Instance<X>>(&mut self, α: Self::Field, x: I) { self.axpy(α, x, 0.0) } - /// Return a similar zero as `self`. - fn similar_origin(&self) -> Self::Owned; - /// Set self to zero. fn set_zero(&mut self); } +pub trait ClosedVectorSpace: Instance<Self> + VectorSpace<PrincipalV = Self> {} +impl<X: Instance<X> + VectorSpace<PrincipalV = Self>> ClosedVectorSpace for X {} + /// Efficient in-place application for [`Linear`] operators. #[replace_float_literals(F::cast_from(literal))] -pub trait GEMV<F : Num, X : Space, Y = <Self as Mapping<X>>::Codomain> : Linear<X> { +pub trait GEMV<F: Num, X: Space, Y = <Self as Mapping<X>>::Codomain>: Linear<X> { /// Computes `y = αAx + βy`, where `A` is `Self`. - fn gemv<I : Instance<X>>(&self, y : &mut Y, α : F, x : I, β : F); + fn gemv<I: Instance<X>>(&self, y: &mut Y, α: F, x: I, β: F); #[inline] /// Computes `y = Ax`, where `A` is `Self` - fn apply_mut<I : Instance<X>>(&self, y : &mut Y, x : I){ + fn apply_mut<I: Instance<X>>(&self, y: &mut Y, x: I) { self.gemv(y, 1.0, x, 0.0) } #[inline] /// Computes `y += Ax`, where `A` is `Self` - fn apply_add<I : Instance<X>>(&self, y : &mut Y, x : I){ + fn apply_add<I: Instance<X>>(&self, y: &mut Y, x: I) { self.gemv(y, 1.0, x, 1.0) } } - /// Bounded linear operators -pub trait BoundedLinear<X, XExp, CodExp, F = f64> : Linear<X> +pub trait BoundedLinear<X, XExp, CodExp, F = f64>: Linear<X> where - F : Num, - X : Space + Norm<F, XExp>, - XExp : NormExponent, - CodExp : NormExponent + F: Num, + X: Space, + XExp: NormExponent, + CodExp: NormExponent, { /// A bound on the operator norm $\|A\|$ for the linear operator $A$=`self`. /// This is not expected to be the norm, just any bound on it that can be /// reasonably implemented. The [`NormExponent`] `xexp` indicates the norm /// in `X`, and `codexp` in the codomain. - fn opnorm_bound(&self, xexp : XExp, codexp : CodExp) -> F; + /// + /// This may fail with an error if the bound is for some reason incalculable. + fn opnorm_bound(&self, xexp: XExp, codexp: CodExp) -> DynResult<F>; } // Linear operator application into mutable target. The [`AsRef`] bound @@ -90,18 +152,45 @@ }*/ /// Trait for forming the adjoint operator of `Self`. -pub trait Adjointable<X, Yʹ> : Linear<X> +pub trait Adjointable<X, Yʹ>: Linear<X> where - X : Space, - Yʹ : Space, + X: Space, + Yʹ: Space, { - type AdjointCodomain : Space; - type Adjoint<'a> : Linear<Yʹ, Codomain=Self::AdjointCodomain> where Self : 'a; + /// Codomain of the adjoint operator. + type AdjointCodomain: ClosedSpace; + /// Type of the adjoint operator. + type Adjoint<'a>: Linear<Yʹ, Codomain = Self::AdjointCodomain> + where + Self: 'a; /// Form the adjoint operator of `self`. fn adjoint(&self) -> Self::Adjoint<'_>; } +/// Variant of [`Adjointable`] where the adjoint does not depend on a lifetime parameter. +/// This exists due to restrictions of Rust's type system: if `A :: Adjointable`, and we make +/// further restrictions on the adjoint operator, through, e.g. +/// ``` +/// for<'a> A::Adjoint<'a> : GEMV<F, Z, Y>, +/// ``` +/// Then `'static` lifetime is forced on `X`. Having `A::SimpleAdjoint` not depend on `'a` +/// avoids this, but makes it impossible for the adjoint to be just a light wrapper around the +/// original operator. +pub trait SimplyAdjointable<X, Yʹ>: Linear<X> +where + X: Space, + Yʹ: Space, +{ + /// Codomain of the adjoint operator. + type AdjointCodomain: ClosedSpace; + /// Type of the adjoint operator. + type SimpleAdjoint: Linear<Yʹ, Codomain = Self::AdjointCodomain>; + + /// Form the adjoint operator of `self`. + fn adjoint(&self) -> Self::SimpleAdjoint; +} + /// Trait for forming a preadjoint of an operator. /// /// For an operator $A$ this is an operator $A\_\*$ @@ -112,404 +201,667 @@ /// We do not make additional restrictions on `Self::Preadjoint` (in particular, it /// does not have to be adjointable) to allow `X` to be a subspace yet the preadjoint /// have the full space as the codomain, etc. -pub trait Preadjointable<X : Space, Ypre : Space> : Linear<X> { - type PreadjointCodomain : Space; - type Preadjoint<'a> : Linear< - Ypre, Codomain=Self::PreadjointCodomain - > where Self : 'a; +pub trait Preadjointable<X: Space, Ypre: Space = <Self as Mapping<X>>::Codomain>: + Linear<X> +{ + type PreadjointCodomain: ClosedSpace; + type Preadjoint<'a>: Linear<Ypre, Codomain = Self::PreadjointCodomain> + where + Self: 'a; /// Form the adjoint operator of `self`. fn preadjoint(&self) -> Self::Preadjoint<'_>; } -/// Adjointable operators $A: X → Y$ between reflexive spaces $X$ and $Y$. -pub trait SimplyAdjointable<X : Space> : Adjointable<X,<Self as Mapping<X>>::Codomain> {} -impl<'a,X : Space, T> SimplyAdjointable<X> for T -where T : Adjointable<X,<Self as Mapping<X>>::Codomain> {} - /// The identity operator -#[derive(Clone,Copy,Debug,Serialize,Eq,PartialEq)] -pub struct IdOp<X> (PhantomData<X>); +#[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq)] +pub struct IdOp<X>(PhantomData<X>); impl<X> IdOp<X> { - pub fn new() -> IdOp<X> { IdOp(PhantomData) } + pub fn new() -> IdOp<X> { + IdOp(PhantomData) + } } -impl<X : Clone + Space> Mapping<X> for IdOp<X> { - type Codomain = X; +impl<X: Space> Mapping<X> for IdOp<X> { + type Codomain = X::Principal; - fn apply<I : Instance<X>>(&self, x : I) -> X { + fn apply<I: Instance<X>>(&self, x: I) -> Self::Codomain { x.own() } } -impl<X : Clone + Space> Linear<X> for IdOp<X> -{ } +impl<X: Space> Linear<X> for IdOp<X> {} #[replace_float_literals(F::cast_from(literal))] -impl<F : Num, X, Y> GEMV<F, X, Y> for IdOp<X> +impl<F: Num, X, Y> GEMV<F, X, Y> for IdOp<X> where - Y : AXPY<F, X>, - X : Clone + Space + Y: AXPY<X, Field = F>, + X: Space, { // Computes `y = αAx + βy`, where `A` is `Self`. - fn gemv<I : Instance<X>>(&self, y : &mut Y, α : F, x : I, β : F) { + fn gemv<I: Instance<X>>(&self, y: &mut Y, α: F, x: I, β: F) { y.axpy(α, x, β) } - fn apply_mut<I : Instance<X>>(&self, y : &mut Y, x : I){ + fn apply_mut<I: Instance<X>>(&self, y: &mut Y, x: I) { y.copy_from(x); } } impl<F, X, E> BoundedLinear<X, E, E, F> for IdOp<X> where - X : Space + Clone + Norm<F, E>, - F : Num, - E : NormExponent + X: Space + Clone, + F: Num, + E: NormExponent, { - fn opnorm_bound(&self, _xexp : E, _codexp : E) -> F { F::ONE } -} - -impl<X : Clone + Space> Adjointable<X,X> for IdOp<X> { - type AdjointCodomain=X; - type Adjoint<'a> = IdOp<X> where X : 'a; - - fn adjoint(&self) -> Self::Adjoint<'_> { IdOp::new() } + fn opnorm_bound(&self, _xexp: E, _codexp: E) -> DynResult<F> { + Ok(F::ONE) + } } -impl<X : Clone + Space> Preadjointable<X,X> for IdOp<X> { - type PreadjointCodomain=X; - type Preadjoint<'a> = IdOp<X> where X : 'a; +impl<X: Clone + Space> Adjointable<X, X::Principal> for IdOp<X> { + type AdjointCodomain = X::Principal; + type Adjoint<'a> + = IdOp<X::Principal> + where + X: 'a; - fn preadjoint(&self) -> Self::Preadjoint<'_> { IdOp::new() } + fn adjoint(&self) -> Self::Adjoint<'_> { + IdOp::new() + } } +impl<X: Clone + Space> SimplyAdjointable<X, X::Principal> for IdOp<X> { + type AdjointCodomain = X::Principal; + type SimpleAdjoint = IdOp<X::Principal>; -/// The zero operator -#[derive(Clone,Copy,Debug,Serialize,Eq,PartialEq)] -pub struct ZeroOp<'a, X, XD, Y, F> { - zero : &'a Y, // TODO: don't pass this in `new`; maybe not even store. - dual_or_predual_zero : XD, - _phantoms : PhantomData<(X, Y, F)>, -} - -// TODO: Need to make Zero in Instance. - -impl<'a, F : Num, X : Space, XD, Y : Space + Clone> ZeroOp<'a, X, XD, Y, F> { - pub fn new(zero : &'a Y, dual_or_predual_zero : XD) -> Self { - ZeroOp{ zero, dual_or_predual_zero, _phantoms : PhantomData } + fn adjoint(&self) -> Self::SimpleAdjoint { + IdOp::new() } } -impl<'a, F : Num, X : Space, XD, Y : AXPY<F> + Clone> Mapping<X> for ZeroOp<'a, X, XD, Y, F> { - type Codomain = Y; +impl<X: Clone + Space> Preadjointable<X, X::Principal> for IdOp<X> { + type PreadjointCodomain = X::Principal; + type Preadjoint<'a> + = IdOp<X::Principal> + where + X: 'a; - fn apply<I : Instance<X>>(&self, _x : I) -> Y { - self.zero.clone() + fn preadjoint(&self) -> Self::Preadjoint<'_> { + IdOp::new() } } -impl<'a, F : Num, X : Space, XD, Y : AXPY<F> + Clone> Linear<X> for ZeroOp<'a, X, XD, Y, F> -{ } +/// The zero operator from a space to itself +#[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq)] +pub struct SimpleZeroOp; + +impl<X: VectorSpace> Mapping<X> for SimpleZeroOp { + type Codomain = X::PrincipalV; + + fn apply<I: Instance<X>>(&self, x: I) -> X::PrincipalV { + X::similar_origin_inst(x) + } +} + +impl<X: VectorSpace> Linear<X> for SimpleZeroOp {} #[replace_float_literals(F::cast_from(literal))] -impl<'a, F, X, XD, Y> GEMV<F, X, Y> for ZeroOp<'a, X, XD, Y, F> +impl<X, Y, F> GEMV<F, X, Y> for SimpleZeroOp where - F : Num, - Y : AXPY<F, Y> + Clone, - X : Space + F: Num, + Y: AXPY<Field = F>, + X: VectorSpace<Field = F> + Instance<X>, { // Computes `y = αAx + βy`, where `A` is `Self`. - fn gemv<I : Instance<X>>(&self, y : &mut Y, _α : F, _x : I, β : F) { + fn gemv<I: Instance<X>>(&self, y: &mut Y, _α: F, _x: I, β: F) { *y *= β; } - fn apply_mut<I : Instance<X>>(&self, y : &mut Y, _x : I){ + fn apply_mut<I: Instance<X>>(&self, y: &mut Y, _x: I) { y.set_zero(); } } -impl<'a, F, X, XD, Y, E1, E2> BoundedLinear<X, E1, E2, F> for ZeroOp<'a, X, XD, Y, F> +impl<X, F, E1, E2> BoundedLinear<X, E1, E2, F> for SimpleZeroOp +where + F: Num, + X: VectorSpace<Field = F>, + E1: NormExponent, + E2: NormExponent, +{ + fn opnorm_bound(&self, _xexp: E1, _codexp: E2) -> DynResult<F> { + Ok(F::ZERO) + } +} + +impl<X, F> Adjointable<X, X::DualSpace> for SimpleZeroOp where - X : Space + Norm<F, E1>, - Y : AXPY<F> + Clone + Norm<F, E2>, - F : Num, - E1 : NormExponent, - E2 : NormExponent, + F: Num, + X: VectorSpace<Field = F> + HasDual<F>, + X::DualSpace: ClosedVectorSpace, { - fn opnorm_bound(&self, _xexp : E1, _codexp : E2) -> F { F::ZERO } + type AdjointCodomain = X::DualSpace; + type Adjoint<'b> + = SimpleZeroOp + where + Self: 'b; + // () means not (pre)adjointable. + + fn adjoint(&self) -> Self::Adjoint<'_> { + SimpleZeroOp + } +} + +pub trait OriginGenerator<Y: VectorSpace> { + type Ref<'b>: OriginGenerator<Y> + where + Self: 'b; + + fn origin(&self) -> Y::PrincipalV; + fn as_ref(&self) -> Self::Ref<'_>; } -impl<'a, F : Num, X, XD, Y, Yprime : Space> Adjointable<X, Yprime> for ZeroOp<'a, X, XD, Y, F> -where - X : Space, - Y : AXPY<F> + Clone + 'static, - XD : AXPY<F> + Clone + 'static, +#[derive(Copy, Clone, Debug)] +pub struct StaticEuclideanOriginGenerator; + +impl<Y: StaticEuclidean<F, Field = F>, F: Float> OriginGenerator<Y> + for StaticEuclideanOriginGenerator { - type AdjointCodomain = XD; - type Adjoint<'b> = ZeroOp<'b, Yprime, (), XD, F> where Self : 'b; - // () means not (pre)adjointable. + type Ref<'b> + = Self + where + Self: 'b; + + #[inline] + fn origin(&self) -> Y::PrincipalV { + return Y::origin(); + } + + #[inline] + fn as_ref(&self) -> Self::Ref<'_> { + *self + } +} + +impl<Y: VectorSpace> OriginGenerator<Y> for Y { + type Ref<'b> + = &'b Y + where + Self: 'b; + + #[inline] + fn origin(&self) -> Y::PrincipalV { + return self.similar_origin(); + } + + #[inline] + fn as_ref(&self) -> Self::Ref<'_> { + self + } +} - fn adjoint(&self) -> Self::Adjoint<'_> { - ZeroOp::new(&self.dual_or_predual_zero, ()) +impl<'b, Y: VectorSpace> OriginGenerator<Y> for &'b Y { + type Ref<'c> + = Self + where + Self: 'c; + + #[inline] + fn origin(&self) -> Y::PrincipalV { + return self.similar_origin(); + } + + #[inline] + fn as_ref(&self) -> Self::Ref<'_> { + self + } +} + +/// A zero operator that can be eitherh dualised or predualised (once). +/// This is achieved by storing an oppropriate zero. +pub struct ZeroOp<X, Y: VectorSpace<Field = F>, OY: OriginGenerator<Y>, O, F: Float = f64> { + codomain_origin_generator: OY, + other_origin_generator: O, + _phantoms: PhantomData<(X, Y, F)>, +} + +impl<X, Y, OY, F> ZeroOp<X, Y, OY, (), F> +where + OY: OriginGenerator<Y>, + X: VectorSpace<Field = F>, + Y: VectorSpace<Field = F>, + F: Float, +{ + pub fn new(y_og: OY) -> Self { + ZeroOp { + codomain_origin_generator: y_og, + other_origin_generator: (), + _phantoms: PhantomData, + } } } -impl<'a, F, X, XD, Y, Ypre> Preadjointable<X, Ypre> for ZeroOp<'a, X, XD, Y, F> +impl<X, Y, OY, OXprime, Xprime, Yprime, F> ZeroOp<X, Y, OY, OXprime, F> +where + OY: OriginGenerator<Y>, + OXprime: OriginGenerator<Xprime>, + X: HasDual<F, DualSpace = Xprime>, + Y: HasDual<F, DualSpace = Yprime>, + F: Float, + Xprime: VectorSpace<Field = F, PrincipalV = Xprime>, + Xprime::PrincipalV: AXPY<Field = F>, + Yprime: Space + Instance<Yprime>, +{ + pub fn new_dualisable(y_og: OY, xprime_og: OXprime) -> Self { + ZeroOp { + codomain_origin_generator: y_og, + other_origin_generator: xprime_og, + _phantoms: PhantomData, + } + } +} + +impl<X, Y, O, OY, F> Mapping<X> for ZeroOp<X, Y, OY, O, F> +where + X: Space, + Y: VectorSpace<Field = F>, + F: Float, + OY: OriginGenerator<Y>, +{ + type Codomain = Y::PrincipalV; + + fn apply<I: Instance<X>>(&self, _x: I) -> Y::PrincipalV { + self.codomain_origin_generator.origin() + } +} + +impl<X, Y, OY, O, F> Linear<X> for ZeroOp<X, Y, OY, O, F> +where + X: Space, + Y: VectorSpace<Field = F>, + F: Float, + OY: OriginGenerator<Y>, +{ +} + +#[replace_float_literals(F::cast_from(literal))] +impl<X, Y, OY, O, F> GEMV<F, X, Y> for ZeroOp<X, Y, OY, O, F> +where + X: Space, + Y: AXPY<Field = F, PrincipalV = Y>, + F: Float, + OY: OriginGenerator<Y>, +{ + // Computes `y = αAx + βy`, where `A` is `Self`. + fn gemv<I: Instance<X>>(&self, y: &mut Y, _α: F, _x: I, β: F) { + *y *= β; + } + + fn apply_mut<I: Instance<X>>(&self, y: &mut Y, _x: I) { + y.set_zero(); + } +} + +impl<X, Y, OY, O, F, E1, E2> BoundedLinear<X, E1, E2, F> for ZeroOp<X, Y, OY, O, F> where - F : Num, - X : Space, - Y : AXPY<F> + Clone, - Ypre : Space, - XD : AXPY<F> + Clone + 'static, + X: Space + Instance<X>, + Y: VectorSpace<Field = F>, + Y::PrincipalV: Clone, + F: Float, + E1: NormExponent, + E2: NormExponent, + OY: OriginGenerator<Y>, +{ + fn opnorm_bound(&self, _xexp: E1, _codexp: E2) -> DynResult<F> { + Ok(F::ZERO) + } +} + +impl<'b, X, Y, OY, OXprime, Xprime, Yprime, F> Adjointable<X, Yprime> + for ZeroOp<X, Y, OY, OXprime, F> +where + X: HasDual<F, DualSpace = Xprime>, + Y: HasDual<F, DualSpace = Yprime>, + F: Float, + Xprime: ClosedVectorSpace<Field = F>, + //Xprime::Owned: AXPY<Field = F>, + Yprime: ClosedSpace, + OY: OriginGenerator<Y>, + OXprime: OriginGenerator<X::DualSpace>, { - type PreadjointCodomain = XD; - type Preadjoint<'b> = ZeroOp<'b, Ypre, (), XD, F> where Self : 'b; - // () means not (pre)adjointable. + type AdjointCodomain = Xprime; + type Adjoint<'c> + = ZeroOp<Yprime, Xprime, OXprime::Ref<'c>, (), F> + where + Self: 'c; + // () means not (pre)adjointable. + + fn adjoint(&self) -> Self::Adjoint<'_> { + ZeroOp { + codomain_origin_generator: self.other_origin_generator.as_ref(), + other_origin_generator: (), + _phantoms: PhantomData, + } + } +} - fn preadjoint(&self) -> Self::Preadjoint<'_> { - ZeroOp::new(&self.dual_or_predual_zero, ()) +impl<'b, X, Y, OY, OXprime, Xprime, Yprime, F> SimplyAdjointable<X, Yprime> + for ZeroOp<X, Y, OY, OXprime, F> +where + X: HasDual<F, DualSpace = Xprime>, + Y: HasDual<F, DualSpace = Yprime>, + F: Float, + Xprime: ClosedVectorSpace<Field = F>, + //Xprime::Owned: AXPY<Field = F>, + Yprime: ClosedSpace, + OY: OriginGenerator<Y>, + OXprime: OriginGenerator<X::DualSpace> + Clone, +{ + type AdjointCodomain = Xprime; + type SimpleAdjoint = ZeroOp<Yprime, Xprime, OXprime, (), F>; + // () means not (pre)adjointable. + + fn adjoint(&self) -> Self::SimpleAdjoint { + ZeroOp { + codomain_origin_generator: self.other_origin_generator.clone(), + other_origin_generator: (), + _phantoms: PhantomData, + } } } impl<S, T, E, X> Linear<X> for Composition<S, T, E> where - X : Space, - T : Linear<X>, - S : Linear<T::Codomain> -{ } + X: Space, + T: Linear<X>, + S: Linear<T::Codomain>, +{ +} impl<F, S, T, E, X, Y> GEMV<F, X, Y> for Composition<S, T, E> where - F : Num, - X : Space, - T : Linear<X>, - S : GEMV<F, T::Codomain, Y>, + F: Num, + X: Space, + T: Linear<X>, + S: GEMV<F, T::Codomain, Y>, { - fn gemv<I : Instance<X>>(&self, y : &mut Y, α : F, x : I, β : F) { + fn gemv<I: Instance<X>>(&self, y: &mut Y, α: F, x: I, β: F) { self.outer.gemv(y, α, self.inner.apply(x), β) } /// Computes `y = Ax`, where `A` is `Self` - fn apply_mut<I : Instance<X>>(&self, y : &mut Y, x : I){ + fn apply_mut<I: Instance<X>>(&self, y: &mut Y, x: I) { self.outer.apply_mut(y, self.inner.apply(x)) } /// Computes `y += Ax`, where `A` is `Self` - fn apply_add<I : Instance<X>>(&self, y : &mut Y, x : I){ + fn apply_add<I: Instance<X>>(&self, y: &mut Y, x: I) { self.outer.apply_add(y, self.inner.apply(x)) } } impl<F, S, T, X, Z, Xexp, Yexp, Zexp> BoundedLinear<X, Xexp, Yexp, F> for Composition<S, T, Zexp> where - F : Num, - X : Space + Norm<F, Xexp>, - Z : Space + Norm<F, Zexp>, - Xexp : NormExponent, - Yexp : NormExponent, - Zexp : NormExponent, - T : BoundedLinear<X, Xexp, Zexp, F, Codomain=Z>, - S : BoundedLinear<Z, Zexp, Yexp, F>, + F: Num, + X: Space, + Z: Space, + Xexp: NormExponent, + Yexp: NormExponent, + Zexp: NormExponent, + T: BoundedLinear<X, Xexp, Zexp, F, Codomain = Z>, + S: BoundedLinear<Z, Zexp, Yexp, F>, { - fn opnorm_bound(&self, xexp : Xexp, yexp : Yexp) -> F { + fn opnorm_bound(&self, xexp: Xexp, yexp: Yexp) -> DynResult<F> { let zexp = self.intermediate_norm_exponent; - self.outer.opnorm_bound(zexp, yexp) * self.inner.opnorm_bound(xexp, zexp) + Ok(self.outer.opnorm_bound(zexp, yexp)? * self.inner.opnorm_bound(xexp, zexp)?) } } /// “Row operator” $(S, T)$; $(S, T)(x, y)=Sx + Ty$. +#[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq)] pub struct RowOp<S, T>(pub S, pub T); -use std::ops::Add; - impl<A, B, S, T> Mapping<Pair<A, B>> for RowOp<S, T> where - A : Space, - B : Space, - S : Mapping<A>, - T : Mapping<B>, - S::Codomain : Add<T::Codomain>, - <S::Codomain as Add<T::Codomain>>::Output : Space, - + A: Space, + B: Space, + S: Mapping<A>, + T: Mapping<B>, + S::Codomain: Add<T::Codomain>, + <S::Codomain as Add<T::Codomain>>::Output: ClosedSpace, { type Codomain = <S::Codomain as Add<T::Codomain>>::Output; - fn apply<I : Instance<Pair<A, B>>>(&self, x : I) -> Self::Codomain { - let Pair(a, b) = x.decompose(); - self.0.apply(a) + self.1.apply(b) + fn apply<I: Instance<Pair<A, B>>>(&self, x: I) -> Self::Codomain { + x.eval_decompose(|Pair(a, b)| self.0.apply(a) + self.1.apply(b)) } } impl<A, B, S, T> Linear<Pair<A, B>> for RowOp<S, T> where - A : Space, - B : Space, - S : Linear<A>, - T : Linear<B>, - S::Codomain : Add<T::Codomain>, - <S::Codomain as Add<T::Codomain>>::Output : Space, -{ } - + A: Space, + B: Space, + S: Linear<A>, + T: Linear<B>, + S::Codomain: Add<T::Codomain>, + <S::Codomain as Add<T::Codomain>>::Output: ClosedSpace, +{ +} impl<'b, F, S, T, Y, U, V> GEMV<F, Pair<U, V>, Y> for RowOp<S, T> where - U : Space, - V : Space, - S : GEMV<F, U, Y>, - T : GEMV<F, V, Y>, - F : Num, - Self : Linear<Pair<U, V>, Codomain=Y> + U: Space, + V: Space, + S: GEMV<F, U, Y>, + T: GEMV<F, V, Y>, + F: Num, + Self: Linear<Pair<U, V>, Codomain = Y>, { - fn gemv<I : Instance<Pair<U, V>>>(&self, y : &mut Y, α : F, x : I, β : F) { - let Pair(u, v) = x.decompose(); - self.0.gemv(y, α, u, β); - self.1.gemv(y, α, v, F::ONE); + fn gemv<I: Instance<Pair<U, V>>>(&self, y: &mut Y, α: F, x: I, β: F) { + x.eval_decompose(|Pair(u, v)| { + self.0.gemv(y, α, u, β); + self.1.gemv(y, α, v, F::ONE); + }) } - fn apply_mut<I : Instance<Pair<U, V>>>(&self, y : &mut Y, x : I) { - let Pair(u, v) = x.decompose(); - self.0.apply_mut(y, u); - self.1.apply_add(y, v); + fn apply_mut<I: Instance<Pair<U, V>>>(&self, y: &mut Y, x: I) { + x.eval_decompose(|Pair(u, v)| { + self.0.apply_mut(y, u); + self.1.apply_add(y, v); + }) } /// Computes `y += Ax`, where `A` is `Self` - fn apply_add<I : Instance<Pair<U, V>>>(&self, y : &mut Y, x : I) { - let Pair(u, v) = x.decompose(); - self.0.apply_add(y, u); - self.1.apply_add(y, v); + fn apply_add<I: Instance<Pair<U, V>>>(&self, y: &mut Y, x: I) { + x.eval_decompose(|Pair(u, v)| { + self.0.apply_add(y, u); + self.1.apply_add(y, v); + }) } } /// “Column operator” $(S; T)$; $(S; T)x=(Sx, Tx)$. +#[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq)] pub struct ColOp<S, T>(pub S, pub T); impl<A, S, T> Mapping<A> for ColOp<S, T> where - A : Space, - S : Mapping<A>, - T : Mapping<A>, + A: Space, + S: Mapping<A>, + T: Mapping<A>, { type Codomain = Pair<S::Codomain, T::Codomain>; - fn apply<I : Instance<A>>(&self, a : I) -> Self::Codomain { - Pair(self.0.apply(a.ref_instance()), self.1.apply(a)) + fn apply<I: Instance<A>>(&self, a: I) -> Self::Codomain { + Pair(a.eval_ref(|r| self.0.apply(r)), self.1.apply(a)) } } impl<A, S, T> Linear<A> for ColOp<S, T> where - A : Space, - S : Mapping<A>, - T : Mapping<A>, -{ } + A: Space, + S: Mapping<A>, + T: Mapping<A>, +{ +} impl<F, S, T, A, B, X> GEMV<F, X, Pair<A, B>> for ColOp<S, T> where - X : Space, - S : GEMV<F, X, A>, - T : GEMV<F, X, B>, - F : Num, - Self : Linear<X, Codomain=Pair<A, B>> + X: Space, + S: GEMV<F, X, A>, + T: GEMV<F, X, B>, + F: Num, + Self: Linear<X, Codomain = Pair<A, B>>, { - fn gemv<I : Instance<X>>(&self, y : &mut Pair<A, B>, α : F, x : I, β : F) { - self.0.gemv(&mut y.0, α, x.ref_instance(), β); + fn gemv<I: Instance<X>>(&self, y: &mut Pair<A, B>, α: F, x: I, β: F) { + x.eval_ref(|r| self.0.gemv(&mut y.0, α, r, β)); self.1.gemv(&mut y.1, α, x, β); } - fn apply_mut<I : Instance<X>>(&self, y : &mut Pair<A, B>, x : I){ - self.0.apply_mut(&mut y.0, x.ref_instance()); + fn apply_mut<I: Instance<X>>(&self, y: &mut Pair<A, B>, x: I) { + x.eval_ref(|r| self.0.apply_mut(&mut y.0, r)); self.1.apply_mut(&mut y.1, x); } /// Computes `y += Ax`, where `A` is `Self` - fn apply_add<I : Instance<X>>(&self, y : &mut Pair<A, B>, x : I){ - self.0.apply_add(&mut y.0, x.ref_instance()); + fn apply_add<I: Instance<X>>(&self, y: &mut Pair<A, B>, x: I) { + x.eval_ref(|r| self.0.apply_add(&mut y.0, r)); self.1.apply_add(&mut y.1, x); } } - -impl<A, B, Yʹ, S, T> Adjointable<Pair<A,B>, Yʹ> for RowOp<S, T> +impl<A, B, Yʹ, S, T> Adjointable<Pair<A, B>, Yʹ> for RowOp<S, T> where - A : Space, - B : Space, - Yʹ : Space, - S : Adjointable<A, Yʹ>, - T : Adjointable<B, Yʹ>, - Self : Linear<Pair<A, B>>, + A: Space, + B: Space, + Yʹ: Space, + S: Adjointable<A, Yʹ>, + T: Adjointable<B, Yʹ>, + Self: Linear<Pair<A, B>>, // for<'a> ColOp<S::Adjoint<'a>, T::Adjoint<'a>> : Linear< // Yʹ, // Codomain=Pair<S::AdjointCodomain, T::AdjointCodomain> // >, { type AdjointCodomain = Pair<S::AdjointCodomain, T::AdjointCodomain>; - type Adjoint<'a> = ColOp<S::Adjoint<'a>, T::Adjoint<'a>> where Self : 'a; + type Adjoint<'a> + = ColOp<S::Adjoint<'a>, T::Adjoint<'a>> + where + Self: 'a; fn adjoint(&self) -> Self::Adjoint<'_> { ColOp(self.0.adjoint(), self.1.adjoint()) } } -impl<A, B, Yʹ, S, T> Preadjointable<Pair<A,B>, Yʹ> for RowOp<S, T> +impl<A, B, Yʹ, S, T> SimplyAdjointable<Pair<A, B>, Yʹ> for RowOp<S, T> where - A : Space, - B : Space, - Yʹ : Space, - S : Preadjointable<A, Yʹ>, - T : Preadjointable<B, Yʹ>, - Self : Linear<Pair<A, B>>, - for<'a> ColOp<S::Preadjoint<'a>, T::Preadjoint<'a>> : Linear< - Yʹ, Codomain=Pair<S::PreadjointCodomain, T::PreadjointCodomain>, - >, + A: Space, + B: Space, + Yʹ: Space, + S: SimplyAdjointable<A, Yʹ>, + T: SimplyAdjointable<B, Yʹ>, + Self: Linear<Pair<A, B>>, + // for<'a> ColOp<S::Adjoint<'a>, T::Adjoint<'a>> : Linear< + // Yʹ, + // Codomain=Pair<S::AdjointCodomain, T::AdjointCodomain> + // >, +{ + type AdjointCodomain = Pair<S::AdjointCodomain, T::AdjointCodomain>; + type SimpleAdjoint = ColOp<S::SimpleAdjoint, T::SimpleAdjoint>; + + fn adjoint(&self) -> Self::SimpleAdjoint { + ColOp(self.0.adjoint(), self.1.adjoint()) + } +} + +impl<A, B, Yʹ, S, T> Preadjointable<Pair<A, B>, Yʹ> for RowOp<S, T> +where + A: Space, + B: Space, + Yʹ: Space, + S: Preadjointable<A, Yʹ>, + T: Preadjointable<B, Yʹ>, + Self: Linear<Pair<A, B>>, + for<'a> ColOp<S::Preadjoint<'a>, T::Preadjoint<'a>>: + Linear<Yʹ, Codomain = Pair<S::PreadjointCodomain, T::PreadjointCodomain>>, { type PreadjointCodomain = Pair<S::PreadjointCodomain, T::PreadjointCodomain>; - type Preadjoint<'a> = ColOp<S::Preadjoint<'a>, T::Preadjoint<'a>> where Self : 'a; + type Preadjoint<'a> + = ColOp<S::Preadjoint<'a>, T::Preadjoint<'a>> + where + Self: 'a; fn preadjoint(&self) -> Self::Preadjoint<'_> { ColOp(self.0.preadjoint(), self.1.preadjoint()) } } - -impl<A, Xʹ, Yʹ, R, S, T> Adjointable<A,Pair<Xʹ,Yʹ>> for ColOp<S, T> +impl<A, Xʹ, Yʹ, R, S, T> Adjointable<A, Pair<Xʹ, Yʹ>> for ColOp<S, T> where - A : Space, - Xʹ : Space, - Yʹ : Space, - R : Space + ClosedAdd, - S : Adjointable<A, Xʹ, AdjointCodomain = R>, - T : Adjointable<A, Yʹ, AdjointCodomain = R>, - Self : Linear<A>, + A: Space, + Xʹ: Space, + Yʹ: Space, + R: ClosedSpace + ClosedAdd, + S: Adjointable<A, Xʹ, AdjointCodomain = R>, + T: Adjointable<A, Yʹ, AdjointCodomain = R>, + Self: Linear<A>, // for<'a> RowOp<S::Adjoint<'a>, T::Adjoint<'a>> : Linear< // Pair<Xʹ,Yʹ>, // Codomain=R, // >, { type AdjointCodomain = R; - type Adjoint<'a> = RowOp<S::Adjoint<'a>, T::Adjoint<'a>> where Self : 'a; + type Adjoint<'a> + = RowOp<S::Adjoint<'a>, T::Adjoint<'a>> + where + Self: 'a; fn adjoint(&self) -> Self::Adjoint<'_> { RowOp(self.0.adjoint(), self.1.adjoint()) } } -impl<A, Xʹ, Yʹ, R, S, T> Preadjointable<A,Pair<Xʹ,Yʹ>> for ColOp<S, T> +impl<A, Xʹ, Yʹ, R, S, T> SimplyAdjointable<A, Pair<Xʹ, Yʹ>> for ColOp<S, T> where - A : Space, - Xʹ : Space, - Yʹ : Space, - R : Space + ClosedAdd, - S : Preadjointable<A, Xʹ, PreadjointCodomain = R>, - T : Preadjointable<A, Yʹ, PreadjointCodomain = R>, - Self : Linear<A>, - for<'a> RowOp<S::Preadjoint<'a>, T::Preadjoint<'a>> : Linear< - Pair<Xʹ,Yʹ>, Codomain = R, - >, + A: Space, + Xʹ: Space, + Yʹ: Space, + R: ClosedSpace + ClosedAdd, + S: SimplyAdjointable<A, Xʹ, AdjointCodomain = R>, + T: SimplyAdjointable<A, Yʹ, AdjointCodomain = R>, + Self: Linear<A>, + // for<'a> RowOp<S::Adjoint<'a>, T::Adjoint<'a>> : Linear< + // Pair<Xʹ,Yʹ>, + // Codomain=R, + // >, +{ + type AdjointCodomain = R; + type SimpleAdjoint = RowOp<S::SimpleAdjoint, T::SimpleAdjoint>; + + fn adjoint(&self) -> Self::SimpleAdjoint { + RowOp(self.0.adjoint(), self.1.adjoint()) + } +} + +impl<A, Xʹ, Yʹ, R, S, T> Preadjointable<A, Pair<Xʹ, Yʹ>> for ColOp<S, T> +where + A: Space, + Xʹ: Space, + Yʹ: Space, + R: ClosedSpace + ClosedAdd, + S: Preadjointable<A, Xʹ, PreadjointCodomain = R>, + T: Preadjointable<A, Yʹ, PreadjointCodomain = R>, + Self: Linear<A>, + for<'a> RowOp<S::Preadjoint<'a>, T::Preadjoint<'a>>: Linear<Pair<Xʹ, Yʹ>, Codomain = R>, { type PreadjointCodomain = R; - type Preadjoint<'a> = RowOp<S::Preadjoint<'a>, T::Preadjoint<'a>> where Self : 'a; + type Preadjoint<'a> + = RowOp<S::Preadjoint<'a>, T::Preadjoint<'a>> + where + Self: 'a; fn preadjoint(&self) -> Self::Preadjoint<'_> { RowOp(self.0.preadjoint(), self.1.preadjoint()) @@ -517,100 +869,126 @@ } /// Diagonal operator +#[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq)] pub struct DiagOp<S, T>(pub S, pub T); impl<A, B, S, T> Mapping<Pair<A, B>> for DiagOp<S, T> where - A : Space, - B : Space, - S : Mapping<A>, - T : Mapping<B>, + A: Space, + B: Space, + S: Mapping<A>, + T: Mapping<B>, { type Codomain = Pair<S::Codomain, T::Codomain>; - fn apply<I : Instance<Pair<A, B>>>(&self, x : I) -> Self::Codomain { - let Pair(a, b) = x.decompose(); - Pair(self.0.apply(a), self.1.apply(b)) + fn apply<I: Instance<Pair<A, B>>>(&self, x: I) -> Self::Codomain { + x.eval_decompose(|Pair(a, b)| Pair(self.0.apply(a), self.1.apply(b))) } } impl<A, B, S, T> Linear<Pair<A, B>> for DiagOp<S, T> where - A : Space, - B : Space, - S : Linear<A>, - T : Linear<B>, -{ } + A: Space, + B: Space, + S: Linear<A>, + T: Linear<B>, +{ +} impl<F, S, T, A, B, U, V> GEMV<F, Pair<U, V>, Pair<A, B>> for DiagOp<S, T> where - A : Space, - B : Space, - U : Space, - V : Space, - S : GEMV<F, U, A>, - T : GEMV<F, V, B>, - F : Num, - Self : Linear<Pair<U, V>, Codomain=Pair<A, B>>, + A: Space, + B: Space, + U: Space, + V: Space, + S: GEMV<F, U, A>, + T: GEMV<F, V, B>, + F: Num, + Self: Linear<Pair<U, V>, Codomain = Pair<A, B>>, { - fn gemv<I : Instance<Pair<U, V>>>(&self, y : &mut Pair<A, B>, α : F, x : I, β : F) { - let Pair(u, v) = x.decompose(); - self.0.gemv(&mut y.0, α, u, β); - self.1.gemv(&mut y.1, α, v, β); + fn gemv<I: Instance<Pair<U, V>>>(&self, y: &mut Pair<A, B>, α: F, x: I, β: F) { + x.eval_decompose(|Pair(u, v)| { + self.0.gemv(&mut y.0, α, u, β); + self.1.gemv(&mut y.1, α, v, β); + }) } - fn apply_mut<I : Instance<Pair<U, V>>>(&self, y : &mut Pair<A, B>, x : I){ - let Pair(u, v) = x.decompose(); - self.0.apply_mut(&mut y.0, u); - self.1.apply_mut(&mut y.1, v); + fn apply_mut<I: Instance<Pair<U, V>>>(&self, y: &mut Pair<A, B>, x: I) { + x.eval_decompose(|Pair(u, v)| { + self.0.apply_mut(&mut y.0, u); + self.1.apply_mut(&mut y.1, v); + }) } /// Computes `y += Ax`, where `A` is `Self` - fn apply_add<I : Instance<Pair<U, V>>>(&self, y : &mut Pair<A, B>, x : I){ - let Pair(u, v) = x.decompose(); - self.0.apply_add(&mut y.0, u); - self.1.apply_add(&mut y.1, v); + fn apply_add<I: Instance<Pair<U, V>>>(&self, y: &mut Pair<A, B>, x: I) { + x.eval_decompose(|Pair(u, v)| { + self.0.apply_add(&mut y.0, u); + self.1.apply_add(&mut y.1, v); + }) } } -impl<A, B, Xʹ, Yʹ, R, S, T> Adjointable<Pair<A,B>, Pair<Xʹ,Yʹ>> for DiagOp<S, T> +impl<A, B, Xʹ, Yʹ, R, S, T> Adjointable<Pair<A, B>, Pair<Xʹ, Yʹ>> for DiagOp<S, T> where - A : Space, - B : Space, + A: Space, + B: Space, Xʹ: Space, - Yʹ : Space, - R : Space, - S : Adjointable<A, Xʹ>, - T : Adjointable<B, Yʹ>, - Self : Linear<Pair<A, B>>, - for<'a> DiagOp<S::Adjoint<'a>, T::Adjoint<'a>> : Linear< - Pair<Xʹ,Yʹ>, Codomain=R, - >, + Yʹ: Space, + R: ClosedSpace, + S: Adjointable<A, Xʹ>, + T: Adjointable<B, Yʹ>, + Self: Linear<Pair<A, B>>, + for<'a> DiagOp<S::Adjoint<'a>, T::Adjoint<'a>>: Linear<Pair<Xʹ, Yʹ>, Codomain = R>, { type AdjointCodomain = R; - type Adjoint<'a> = DiagOp<S::Adjoint<'a>, T::Adjoint<'a>> where Self : 'a; + type Adjoint<'a> + = DiagOp<S::Adjoint<'a>, T::Adjoint<'a>> + where + Self: 'a; fn adjoint(&self) -> Self::Adjoint<'_> { DiagOp(self.0.adjoint(), self.1.adjoint()) } } -impl<A, B, Xʹ, Yʹ, R, S, T> Preadjointable<Pair<A,B>, Pair<Xʹ,Yʹ>> for DiagOp<S, T> +impl<A, B, Xʹ, Yʹ, R, S, T> SimplyAdjointable<Pair<A, B>, Pair<Xʹ, Yʹ>> for DiagOp<S, T> where - A : Space, - B : Space, + A: Space, + B: Space, Xʹ: Space, - Yʹ : Space, - R : Space, - S : Preadjointable<A, Xʹ>, - T : Preadjointable<B, Yʹ>, - Self : Linear<Pair<A, B>>, - for<'a> DiagOp<S::Preadjoint<'a>, T::Preadjoint<'a>> : Linear< - Pair<Xʹ,Yʹ>, Codomain=R, - >, + Yʹ: Space, + R: ClosedSpace, + S: SimplyAdjointable<A, Xʹ>, + T: SimplyAdjointable<B, Yʹ>, + Self: Linear<Pair<A, B>>, + for<'a> DiagOp<S::SimpleAdjoint, T::SimpleAdjoint>: Linear<Pair<Xʹ, Yʹ>, Codomain = R>, +{ + type AdjointCodomain = R; + type SimpleAdjoint = DiagOp<S::SimpleAdjoint, T::SimpleAdjoint>; + + fn adjoint(&self) -> Self::SimpleAdjoint { + DiagOp(self.0.adjoint(), self.1.adjoint()) + } +} + +impl<A, B, Xʹ, Yʹ, R, S, T> Preadjointable<Pair<A, B>, Pair<Xʹ, Yʹ>> for DiagOp<S, T> +where + A: Space, + B: Space, + Xʹ: Space, + Yʹ: Space, + R: ClosedSpace, + S: Preadjointable<A, Xʹ>, + T: Preadjointable<B, Yʹ>, + Self: Linear<Pair<A, B>>, + for<'a> DiagOp<S::Preadjoint<'a>, T::Preadjoint<'a>>: Linear<Pair<Xʹ, Yʹ>, Codomain = R>, { type PreadjointCodomain = R; - type Preadjoint<'a> = DiagOp<S::Preadjoint<'a>, T::Preadjoint<'a>> where Self : 'a; + type Preadjoint<'a> + = DiagOp<S::Preadjoint<'a>, T::Preadjoint<'a>> + where + Self: 'a; fn preadjoint(&self) -> Self::Preadjoint<'_> { DiagOp(self.0.preadjoint(), self.1.preadjoint()) @@ -620,65 +998,90 @@ /// Block operator pub type BlockOp<S11, S12, S21, S22> = ColOp<RowOp<S11, S12>, RowOp<S21, S22>>; - macro_rules! pairnorm { ($expj:ty) => { impl<F, A, B, S, T, ExpA, ExpB, ExpR> - BoundedLinear<Pair<A, B>, PairNorm<ExpA, ExpB, $expj>, ExpR, F> - for RowOp<S, T> + BoundedLinear<Pair<A, B>, PairNorm<ExpA, ExpB, $expj>, ExpR, F> for RowOp<S, T> where - F : Float, - A : Space + Norm<F, ExpA>, - B : Space + Norm<F, ExpB>, - S : BoundedLinear<A, ExpA, ExpR, F>, - T : BoundedLinear<B, ExpB, ExpR, F>, - S::Codomain : Add<T::Codomain>, - <S::Codomain as Add<T::Codomain>>::Output : Space, - ExpA : NormExponent, - ExpB : NormExponent, - ExpR : NormExponent, + F: Float, + A: Space, + B: Space, + S: BoundedLinear<A, ExpA, ExpR, F>, + T: BoundedLinear<B, ExpB, ExpR, F>, + S::Codomain: Add<T::Codomain>, + <S::Codomain as Add<T::Codomain>>::Output: ClosedSpace, + ExpA: NormExponent, + ExpB: NormExponent, + ExpR: NormExponent, { fn opnorm_bound( &self, - PairNorm(expa, expb, _) : PairNorm<ExpA, ExpB, $expj>, - expr : ExpR - ) -> F { + PairNorm(expa, expb, _): PairNorm<ExpA, ExpB, $expj>, + expr: ExpR, + ) -> DynResult<F> { // An application of the triangle inequality bounds the norm by the maximum // of the individual norms. A simple observation shows this to be exact. - let na = self.0.opnorm_bound(expa, expr); - let nb = self.1.opnorm_bound(expb, expr); - na.max(nb) + let na = self.0.opnorm_bound(expa, expr)?; + let nb = self.1.opnorm_bound(expb, expr)?; + Ok(na.max(nb)) } } - - impl<F, A, S, T, ExpA, ExpS, ExpT> - BoundedLinear<A, ExpA, PairNorm<ExpS, ExpT, $expj>, F> - for ColOp<S, T> + + impl<F, A, S, T, ExpA, ExpS, ExpT> BoundedLinear<A, ExpA, PairNorm<ExpS, ExpT, $expj>, F> + for ColOp<S, T> where - F : Float, - A : Space + Norm<F, ExpA>, - S : BoundedLinear<A, ExpA, ExpS, F>, - T : BoundedLinear<A, ExpA, ExpT, F>, - ExpA : NormExponent, - ExpS : NormExponent, - ExpT : NormExponent, + F: Float, + A: Space, + S: BoundedLinear<A, ExpA, ExpS, F>, + T: BoundedLinear<A, ExpA, ExpT, F>, + ExpA: NormExponent, + ExpS: NormExponent, + ExpT: NormExponent, { fn opnorm_bound( &self, - expa : ExpA, - PairNorm(exps, expt, _) : PairNorm<ExpS, ExpT, $expj> - ) -> F { + expa: ExpA, + PairNorm(exps, expt, _): PairNorm<ExpS, ExpT, $expj>, + ) -> DynResult<F> { // This is based on the rule for RowOp and ‖A^*‖ = ‖A‖, hence, // for A=[S; T], ‖A‖=‖[S^*, T^*]‖ ≤ max{‖S^*‖, ‖T^*‖} = max{‖S‖, ‖T‖} - let ns = self.0.opnorm_bound(expa, exps); - let nt = self.1.opnorm_bound(expa, expt); - ns.max(nt) + let ns = self.0.opnorm_bound(expa, exps)?; + let nt = self.1.opnorm_bound(expa, expt)?; + Ok(ns.max(nt)) } } - } + }; } pairnorm!(L1); pairnorm!(L2); pairnorm!(Linfinity); +/// The simplest linear mapping, scaling by a scalar. +/// +/// TODO: redefined/replace `Weighted` by composition with [`Scaled`]. +pub struct Scaled<F: Float>(pub F); + +impl<Domain, F> Mapping<Domain> for Scaled<F> +where + F: Float, + Domain: Space, + Domain::Principal: Mul<F>, + <Domain::Principal as Mul<F>>::Output: ClosedSpace, +{ + type Codomain = <Domain::Principal as Mul<F>>::Output; + + /// Compute the value of `self` at `x`. + fn apply<I: Instance<Domain>>(&self, x: I) -> Self::Codomain { + x.own() * self.0 + } +} + +impl<Domain, F> Linear<Domain> for Scaled<F> +where + F: Float, + Domain: Space, + Domain::Principal: Mul<F>, + <Domain::Principal as Mul<F>>::Output: ClosedSpace, +{ +}
--- a/src/loc.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/loc.rs Fri May 15 14:46:30 2026 -0500 @@ -3,44 +3,48 @@ For working with small vectors in $ℝ^2$ or $ℝ^3$. */ -use std::ops::{Add,Sub,AddAssign,SubAssign,Mul,Div,MulAssign,DivAssign,Neg,Index,IndexMut}; -use std::slice::{Iter,IterMut}; +use crate::euclidean::*; +use crate::instance::{BasicDecomposition, Instance}; +use crate::linops::{Linear, Mapping, VectorSpace, AXPY}; +use crate::mapping::Space; +use crate::maputil::{map1, map1_mut, map2, map2_mut, FixedLength, FixedLengthMut}; +use crate::norms::*; +use crate::self_ownable; +use crate::types::{Float, Num, SignedNum}; +use serde::ser::{Serialize, SerializeSeq, Serializer}; use std::fmt::{Display, Formatter}; -use crate::types::{Float,Num,SignedNum}; -use crate::maputil::{FixedLength,FixedLengthMut,map1,map2,map1_mut,map2_mut}; -use crate::euclidean::*; -use crate::norms::*; -use crate::linops::{AXPY, Mapping, Linear}; -use crate::instance::{Instance, BasicDecomposition}; -use crate::mapping::Space; -use serde::ser::{Serialize, Serializer, SerializeSeq}; - +use std::ops::{ + Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, +}; +use std::slice::{Iter, IterMut}; /// A container type for (short) `N`-dimensional vectors of element type `F`. /// /// Supports basic operations of an [`Euclidean`] space, several [`Norm`]s, and /// fused [`AXPY`] operations, among others. -#[derive(Copy,Clone,Debug,PartialEq,Eq)] -pub struct Loc<F, const N : usize>( +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Loc<const N: usize, F = f64>( /// An array of the elements of the vector - pub [F; N] + pub [F; N], ); -impl<F : Display, const N : usize> Display for Loc<F, N>{ +self_ownable!(Loc<N, F> where const N: usize, F: Copy); + +impl<F: Display, const N: usize> Display for Loc<N, F> { // Required method fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "[")?; - let mut comma = ""; - for e in self.iter() { + write!(f, "[")?; + let mut comma = ""; + for e in self.iter() { write!(f, "{comma}{e}")?; comma = ", "; - } - write!(f, "]") + } + write!(f, "]") } } // Need to manually implement as [F; N] serialisation is provided only for some N. -impl<F, const N : usize> Serialize for Loc<F, N> +impl<F, const N: usize> Serialize for Loc<N, F> where F: Serialize, { @@ -56,10 +60,10 @@ } } -impl<F, const N : usize> Loc<F, N> { +impl<F, const N: usize> Loc<N, F> { /// Creates a new `Loc` vector from an array. #[inline] - pub fn new(arr : [F; N]) -> Self { + pub fn new(arr: [F; N]) -> Self { Loc(arr) } @@ -76,43 +80,44 @@ } } -impl<F : Copy, const N : usize> Loc<F, N> { +impl<F: Copy, const N: usize> Loc<N, F> { /// Maps `g` over the elements of the vector, returning a new [`Loc`] vector #[inline] - pub fn map<H>(&self, g : impl Fn(F) -> H) -> Loc<H, N> { + pub fn map<H>(&self, g: impl Fn(F) -> H) -> Loc<N, H> { Loc::new(map1(self, |u| g(*u))) } /// Maps `g` over pairs of elements of two vectors, retuning a new one. #[inline] - pub fn map2<H>(&self, other : &Self, g : impl Fn(F, F) -> H) -> Loc<H, N> { + pub fn map2<H>(&self, other: &Self, g: impl Fn(F, F) -> H) -> Loc<N, H> { Loc::new(map2(self, other, |u, v| g(*u, *v))) } /// Maps `g` over mutable references to elements of the vector. #[inline] - pub fn map_mut(&mut self, g : impl Fn(&mut F)) { + pub fn map_mut(&mut self, g: impl Fn(&mut F)) { map1_mut(self, g) } /// Maps `g` over pairs of mutable references to elements of `self, and elements /// of `other` vector. #[inline] - pub fn map2_mut(&mut self, other : &Self, g : impl Fn(&mut F, F)) { + pub fn map2_mut(&mut self, other: &Self, g: impl Fn(&mut F, F)) { map2_mut(self, other, |u, v| g(u, *v)) } /// Maps `g` over the elements of `self` and returns the product of the results. #[inline] - pub fn product_map<A : Num>(&self, g : impl Fn(F) -> A) -> A { + pub fn product_map<A: Num>(&self, g: impl Fn(F) -> A) -> A { match N { 1 => g(unsafe { *self.0.get_unchecked(0) }), - 2 => g(unsafe { *self.0.get_unchecked(0) }) * - g(unsafe { *self.0.get_unchecked(1) }), - 3 => g(unsafe { *self.0.get_unchecked(0) }) * - g(unsafe { *self.0.get_unchecked(1) }) * - g(unsafe { *self.0.get_unchecked(2) }), - _ => self.iter().fold(A::ONE, |m, &x| m * g(x)) + 2 => g(unsafe { *self.0.get_unchecked(0) }) * g(unsafe { *self.0.get_unchecked(1) }), + 3 => { + g(unsafe { *self.0.get_unchecked(0) }) + * g(unsafe { *self.0.get_unchecked(1) }) + * g(unsafe { *self.0.get_unchecked(2) }) + } + _ => self.iter().fold(A::ONE, |m, &x| m * g(x)), } } } @@ -130,29 +135,28 @@ ($($x:expr),+ $(,)?) => { Loc::new([$($x),+]) } } - -impl<F, const N : usize> From<[F; N]> for Loc<F, N> { +impl<F, const N: usize> From<[F; N]> for Loc<N, F> { #[inline] - fn from(other: [F; N]) -> Loc<F, N> { + fn from(other: [F; N]) -> Loc<N, F> { Loc(other) } } -/*impl<F : Copy, const N : usize> From<&[F; N]> for Loc<F, N> { +/*impl<F : Copy, const N : usize> From<&[F; N]> for Loc<N, F> { #[inline] - fn from(other: &[F; N]) -> Loc<F, N> { + fn from(other: &[F; N]) -> Loc<N, F> { Loc(*other) } }*/ -impl<F> From<F> for Loc<F, 1> { +impl<F> From<F> for Loc<1, F> { #[inline] - fn from(other: F) -> Loc<F, 1> { + fn from(other: F) -> Loc<1, F> { Loc([other]) } } -impl<F> Loc<F, 1> { +impl<F> Loc<1, F> { #[inline] pub fn flatten1d(self) -> F { let Loc([v]) = self; @@ -160,22 +164,21 @@ } } -impl<F, const N : usize> From<Loc<F, N>> for [F; N] { +impl<F, const N: usize> From<Loc<N, F>> for [F; N] { #[inline] - fn from(other : Loc<F, N>) -> [F; N] { + fn from(other: Loc<N, F>) -> [F; N] { other.0 } } -/*impl<F : Copy, const N : usize> From<&Loc<F, N>> for [F; N] { +/*impl<F : Copy, const N : usize> From<&Loc<N, F>> for [F; N] { #[inline] - fn from(other : &Loc<F, N>) -> [F; N] { + fn from(other : &Loc<N, F>) -> [F; N] { other.0 } }*/ - -impl<F, const N : usize> IntoIterator for Loc<F, N> { +impl<F, const N: usize> IntoIterator for Loc<N, F> { type Item = <[F; N] as IntoIterator>::Item; type IntoIter = <[F; N] as IntoIterator>::IntoIter; @@ -187,20 +190,24 @@ // Indexing -impl<F, Ix, const N : usize> Index<Ix> for Loc<F,N> -where [F; N] : Index<Ix> { +impl<F, Ix, const N: usize> Index<Ix> for Loc<N, F> +where + [F; N]: Index<Ix>, +{ type Output = <[F; N] as Index<Ix>>::Output; #[inline] - fn index(&self, ix : Ix) -> &Self::Output { + fn index(&self, ix: Ix) -> &Self::Output { self.0.index(ix) } } -impl<F, Ix, const N : usize> IndexMut<Ix> for Loc<F,N> -where [F; N] : IndexMut<Ix> { +impl<F, Ix, const N: usize> IndexMut<Ix> for Loc<N, F> +where + [F; N]: IndexMut<Ix>, +{ #[inline] - fn index_mut(&mut self, ix : Ix) -> &mut Self::Output { + fn index_mut(&mut self, ix: Ix) -> &mut Self::Output { self.0.index_mut(ix) } } @@ -209,61 +216,61 @@ macro_rules! make_binop { ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { - impl<F : Num, const N : usize> $trait<Loc<F,N>> for Loc<F, N> { - type Output = Loc<F, N>; + impl<F: Num, const N: usize> $trait<Loc<N, F>> for Loc<N, F> { + type Output = Loc<N, F>; #[inline] - fn $fn(mut self, other : Loc<F, N>) -> Self::Output { + fn $fn(mut self, other: Loc<N, F>) -> Self::Output { self.$fn_assign(other); self } } - impl<'a, F : Num, const N : usize> $trait<&'a Loc<F,N>> for Loc<F, N> { - type Output = Loc<F, N>; + impl<'a, F: Num, const N: usize> $trait<&'a Loc<N, F>> for Loc<N, F> { + type Output = Loc<N, F>; #[inline] - fn $fn(mut self, other : &'a Loc<F, N>) -> Self::Output { + fn $fn(mut self, other: &'a Loc<N, F>) -> Self::Output { self.$fn_assign(other); self } } - impl<'b, F : Num, const N : usize> $trait<Loc<F,N>> for &'b Loc<F, N> { - type Output = Loc<F, N>; + impl<'b, F: Num, const N: usize> $trait<Loc<N, F>> for &'b Loc<N, F> { + type Output = Loc<N, F>; #[inline] - fn $fn(self, other : Loc<F, N>) -> Self::Output { + fn $fn(self, other: Loc<N, F>) -> Self::Output { self.map2(&other, |a, b| a.$fn(b)) } } - impl<'a, 'b, F : Num, const N : usize> $trait<&'a Loc<F,N>> for &'b Loc<F, N> { - type Output = Loc<F, N>; + impl<'a, 'b, F: Num, const N: usize> $trait<&'a Loc<N, F>> for &'b Loc<N, F> { + type Output = Loc<N, F>; #[inline] - fn $fn(self, other : &'a Loc<F, N>) -> Self::Output { + fn $fn(self, other: &'a Loc<N, F>) -> Self::Output { self.map2(other, |a, b| a.$fn(b)) } } - impl<F : Num, const N : usize> $trait_assign<Loc<F,N>> for Loc<F, N> { + impl<F: Num, const N: usize> $trait_assign<Loc<N, F>> for Loc<N, F> { #[inline] - fn $fn_assign(&mut self, other : Loc<F, N>) { + fn $fn_assign(&mut self, other: Loc<N, F>) { self.map2_mut(&other, |a, b| a.$fn_assign(b)) } } - impl<'a, F : Num, const N : usize> $trait_assign<&'a Loc<F,N>> for Loc<F, N> { + impl<'a, F: Num, const N: usize> $trait_assign<&'a Loc<N, F>> for Loc<N, F> { #[inline] - fn $fn_assign(&mut self, other : &'a Loc<F, N>) { + fn $fn_assign(&mut self, other: &'a Loc<N, F>) { self.map2_mut(other, |a, b| a.$fn_assign(b)) } } - } + }; } make_binop!(Add, add, AddAssign, add_assign); make_binop!(Sub, sub, SubAssign, sub_assign); -impl<F : Float, const N : usize> std::iter::Sum for Loc<F, N> { - fn sum<I: Iterator<Item = Loc<F, N>>>(mut iter: I) -> Self { +impl<F: Float, const N: usize> std::iter::Sum for Loc<N, F> { + fn sum<I: Iterator<Item = Loc<N, F>>>(mut iter: I) -> Self { match iter.next() { None => Self::ORIGIN, Some(mut v) => { @@ -278,62 +285,61 @@ macro_rules! make_scalarop_rhs { ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { - impl<F : Num, const N : usize> $trait<F> for Loc<F, N> { - type Output = Loc<F, N>; + impl<F: Num, const N: usize> $trait<F> for Loc<N, F> { + type Output = Loc<N, F>; #[inline] - fn $fn(self, b : F) -> Self::Output { + fn $fn(self, b: F) -> Self::Output { self.map(|a| a.$fn(b)) } } - impl<'a, F : Num, const N : usize> $trait<&'a F> for Loc<F, N> { - type Output = Loc<F, N>; + impl<'a, F: Num, const N: usize> $trait<&'a F> for Loc<N, F> { + type Output = Loc<N, F>; #[inline] - fn $fn(self, b : &'a F) -> Self::Output { + fn $fn(self, b: &'a F) -> Self::Output { self.map(|a| a.$fn(*b)) } } - impl<'b, F : Num, const N : usize> $trait<F> for &'b Loc<F, N> { - type Output = Loc<F, N>; + impl<'b, F: Num, const N: usize> $trait<F> for &'b Loc<N, F> { + type Output = Loc<N, F>; #[inline] - fn $fn(self, b : F) -> Self::Output { + fn $fn(self, b: F) -> Self::Output { self.map(|a| a.$fn(b)) } } - impl<'a, 'b, F : Float, const N : usize> $trait<&'a F> for &'b Loc<F, N> { - type Output = Loc<F, N>; + impl<'a, 'b, F: Float, const N: usize> $trait<&'a F> for &'b Loc<N, F> { + type Output = Loc<N, F>; #[inline] - fn $fn(self, b : &'a F) -> Self::Output { + fn $fn(self, b: &'a F) -> Self::Output { self.map(|a| a.$fn(*b)) } } - impl<F : Num, const N : usize> $trait_assign<F> for Loc<F, N> { + impl<F: Num, const N: usize> $trait_assign<F> for Loc<N, F> { #[inline] - fn $fn_assign(&mut self, b : F) { + fn $fn_assign(&mut self, b: F) { self.map_mut(|a| a.$fn_assign(b)); } } - impl<'a, F : Num, const N : usize> $trait_assign<&'a F> for Loc<F, N> { + impl<'a, F: Num, const N: usize> $trait_assign<&'a F> for Loc<N, F> { #[inline] - fn $fn_assign(&mut self, b : &'a F) { + fn $fn_assign(&mut self, b: &'a F) { self.map_mut(|a| a.$fn_assign(*b)); } } - } + }; } - make_scalarop_rhs!(Mul, mul, MulAssign, mul_assign); make_scalarop_rhs!(Div, div, DivAssign, div_assign); macro_rules! make_unaryop { ($trait:ident, $fn:ident) => { - impl<F : SignedNum, const N : usize> $trait for Loc<F, N> { - type Output = Loc<F, N>; + impl<F: SignedNum, const N: usize> $trait for Loc<N, F> { + type Output = Loc<N, F>; #[inline] fn $fn(mut self) -> Self::Output { self.map_mut(|a| *a = (*a).$fn()); @@ -341,48 +347,48 @@ } } - impl<'a, F : SignedNum, const N : usize> $trait for &'a Loc<F, N> { - type Output = Loc<F, N>; + impl<'a, F: SignedNum, const N: usize> $trait for &'a Loc<N, F> { + type Output = Loc<N, F>; #[inline] fn $fn(self) -> Self::Output { self.map(|a| a.$fn()) } } - } + }; } make_unaryop!(Neg, neg); macro_rules! make_scalarop_lhs { ($trait:ident, $fn:ident; $($f:ident)+) => { $( - impl<const N : usize> $trait<Loc<$f,N>> for $f { - type Output = Loc<$f, N>; + impl<const N : usize> $trait<Loc<N, $f>> for $f { + type Output = Loc<N, $f>; #[inline] - fn $fn(self, v : Loc<$f,N>) -> Self::Output { + fn $fn(self, v : Loc<N, $f>) -> Self::Output { v.map(|b| self.$fn(b)) } } - impl<'a, const N : usize> $trait<&'a Loc<$f,N>> for $f { - type Output = Loc<$f, N>; + impl<'a, const N : usize> $trait<&'a Loc<N, $f>> for $f { + type Output = Loc<N, $f>; #[inline] - fn $fn(self, v : &'a Loc<$f,N>) -> Self::Output { + fn $fn(self, v : &'a Loc<N, $f>) -> Self::Output { v.map(|b| self.$fn(b)) } } - impl<'b, const N : usize> $trait<Loc<$f,N>> for &'b $f { - type Output = Loc<$f, N>; + impl<'b, const N : usize> $trait<Loc<N, $f>> for &'b $f { + type Output = Loc<N, $f>; #[inline] - fn $fn(self, v : Loc<$f,N>) -> Self::Output { + fn $fn(self, v : Loc<N, $f>) -> Self::Output { v.map(|b| self.$fn(b)) } } - impl<'a, 'b, const N : usize> $trait<&'a Loc<$f,N>> for &'b $f { - type Output = Loc<$f, N>; + impl<'a, 'b, const N : usize> $trait<&'a Loc<N, $f>> for &'b $f { + type Output = Loc<N, $f>; #[inline] - fn $fn(self, v : &'a Loc<$f, N>) -> Self::Output { + fn $fn(self, v : &'a Loc<N, $f>) -> Self::Output { v.map(|b| self.$fn(b)) } } @@ -396,21 +402,21 @@ macro_rules! domination { ($norm:ident, $dominates:ident) => { - impl<F : Float, const N : usize> Dominated<F, $dominates, Loc<F, N>> for $norm { + impl<F: Float, const N: usize> Dominated<F, $dominates, Loc<N, F>> for $norm { #[inline] - fn norm_factor(&self, _p : $dominates) -> F { + fn norm_factor(&self, _p: $dominates) -> F { F::ONE } #[inline] - fn from_norm(&self, p_norm : F, _p : $dominates) -> F { + fn from_norm(&self, p_norm: F, _p: $dominates) -> F { p_norm } } }; ($norm:ident, $dominates:ident, $fn:path) => { - impl<F : Float, const N : usize> Dominated<F, $dominates, Loc<F, N>> for $norm { + impl<F: Float, const N: usize> Dominated<F, $dominates, Loc<N, F>> for $norm { #[inline] - fn norm_factor(&self, _p : $dominates) -> F { + fn norm_factor(&self, _p: $dominates) -> F { $fn(F::cast_from(N)) } } @@ -429,16 +435,19 @@ domination!(Linfinity, L2); domination!(L2, L1); -impl<F : Float,const N : usize> Euclidean<F> for Loc<F, N> { - type Output = Self; +impl<F: Float, const N: usize> Euclidean<F> for Loc<N, F> { + type PrincipalE = Self; /// This implementation is not stabilised as it's meant to be used for very small vectors. /// Use [`nalgebra`] for larger vectors. #[inline] - fn dot<I : Instance<Self>>(&self, other : I) -> F { - self.0.iter() - .zip(other.ref_instance().0.iter()) - .fold(F::ZERO, |m, (&v, &w)| m + v * w) + fn dot<I: Instance<Self>>(&self, other: I) -> F { + other.eval_ref(|r| { + self.0 + .iter() + .zip(r.0.iter()) + .fold(F::ZERO, |m, (&v, &w)| m + v * w) + }) } /// This implementation is not stabilised as it's meant to be used for very small vectors. @@ -448,16 +457,19 @@ self.iter().fold(F::ZERO, |m, &v| m + v * v) } - fn dist2_squared<I : Instance<Self>>(&self, other : I) -> F { - self.iter() - .zip(other.ref_instance().iter()) - .fold(F::ZERO, |m, (&v, &w)| { let d = v - w; m + d * d }) + fn dist2_squared<I: Instance<Self>>(&self, other: I) -> F { + other.eval_ref(|r| { + self.iter().zip(r.iter()).fold(F::ZERO, |m, (&v, &w)| { + let d = v - w; + m + d * d + }) + }) } #[inline] fn norm2(&self) -> F { // Optimisation for N==1 that avoids squaring and square rooting. - if N==1 { + if N == 1 { unsafe { self.0.get_unchecked(0) }.abs() } else { self.norm2_squared().sqrt() @@ -465,39 +477,38 @@ } #[inline] - fn dist2<I : Instance<Self>>(&self, other : I) -> F { + fn dist2<I: Instance<Self>>(&self, other: I) -> F { // Optimisation for N==1 that avoids squaring and square rooting. - let otherr = other.ref_instance(); - if N==1 { - unsafe { *self.0.get_unchecked(0) - *otherr.0.get_unchecked(0) }.abs() + if N == 1 { + other.eval_ref(|r| unsafe { *self.0.get_unchecked(0) - *r.0.get_unchecked(0) }.abs()) } else { self.dist2_squared(other).sqrt() } } } -impl<F : Num, const N : usize> Loc<F, N> { +impl<F: Num, const N: usize> Loc<N, F> { /// Origin point - pub const ORIGIN : Self = Loc([F::ZERO; N]); + pub const ORIGIN: Self = Loc([F::ZERO; N]); } -impl<F : Num + std::ops::Neg<Output=F>, const N : usize> Loc<F, N> { +impl<F: Num + std::ops::Neg<Output = F>, const N: usize> Loc<N, F> { /// Reflects along the given coordinate - pub fn reflect(mut self, i : usize) -> Self { + pub fn reflect(mut self, i: usize) -> Self { self[i] = -self[i]; self } /// Reflects along the given coordinate sequences - pub fn reflect_many<I : IntoIterator<Item=usize>>(mut self, idxs : I) -> Self { + pub fn reflect_many<I: IntoIterator<Item = usize>>(mut self, idxs: I) -> Self { for i in idxs { - self[i]=-self[i] + self[i] = -self[i] } self } } -impl<F : std::ops::Neg<Output=F>> Loc<F, 2> { +impl<F: std::ops::Neg<Output = F>> Loc<2, F> { /// Reflect x coordinate pub fn reflect_x(self) -> Self { let Loc([x, y]) = self; @@ -511,18 +522,17 @@ } } -impl<F : Float> Loc<F, 2> { +impl<F: Float> Loc<2, F> { /// Rotate by angle φ - pub fn rotate(self, φ : F) -> Self { + pub fn rotate(self, φ: F) -> Self { let Loc([x, y]) = self; let sin_φ = φ.sin(); let cos_φ = φ.cos(); - [cos_φ * x - sin_φ * y, - sin_φ * x + cos_φ * y].into() + [cos_φ * x - sin_φ * y, sin_φ * x + cos_φ * y].into() } } -impl<F : std::ops::Neg<Output=F>> Loc<F, 3> { +impl<F: std::ops::Neg<Output = F>> Loc<3, F> { /// Reflect x coordinate pub fn reflect_x(self) -> Self { let Loc([x, y, z]) = self; @@ -542,39 +552,32 @@ } } -impl<F : Float> Loc<F, 3> { +impl<F: Float> Loc<3, F> { /// Rotate by angles (yaw, pitch, roll) - pub fn rotate(self, Loc([φ, ψ, θ]) : Self) -> Self { + pub fn rotate(self, Loc([φ, ψ, θ]): Self) -> Self { let Loc([mut x, mut y, mut z]) = self; let sin_φ = φ.sin(); let cos_φ = φ.cos(); - [x, y, z] = [cos_φ * x - sin_φ *y, - sin_φ * x + cos_φ * y, - z]; + [x, y, z] = [cos_φ * x - sin_φ * y, sin_φ * x + cos_φ * y, z]; let sin_ψ = ψ.sin(); let cos_ψ = ψ.cos(); - [x, y, z] = [cos_ψ * x + sin_ψ * z, - y, - -sin_ψ * x + cos_ψ * z]; + [x, y, z] = [cos_ψ * x + sin_ψ * z, y, -sin_ψ * x + cos_ψ * z]; let sin_θ = θ.sin(); let cos_θ = θ.cos(); - [x, y, z] = [x, - cos_θ * y - sin_θ * z, - sin_θ * y + cos_θ * z]; + [x, y, z] = [x, cos_θ * y - sin_θ * z, sin_θ * y + cos_θ * z]; [x, y, z].into() } } -impl<F : Float,const N : usize> StaticEuclidean<F> for Loc<F, N> { +impl<F: Float, const N: usize> StaticEuclidean<F> for Loc<N, F> { #[inline] fn origin() -> Self { Self::ORIGIN } } - /// The default norm for `Loc` is [`L2`]. -impl<F : Float, const N : usize> Normed<F> for Loc<F, N> { +impl<F: Float, const N: usize> Normed<F> for Loc<N, F> { type NormExp = L2; #[inline] @@ -593,22 +596,30 @@ } } -impl<F : Float, const N : usize> HasDual<F> for Loc<F, N> { +impl<F: Float, const N: usize> HasDual<F> for Loc<N, F> { type DualSpace = Self; + + fn dual_origin(&self) -> Self::DualSpace { + self.similar_origin() + } } -impl<F : Float, const N : usize> Norm<F, L2> for Loc<F, N> { +impl<F: Float, const N: usize> Norm<L2, F> for Loc<N, F> { #[inline] - fn norm(&self, _ : L2) -> F { self.norm2() } + fn norm(&self, _: L2) -> F { + self.norm2() + } } -impl<F : Float, const N : usize> Dist<F, L2> for Loc<F, N> { +impl<F: Float, const N: usize> Dist<L2, F> for Loc<N, F> { #[inline] - fn dist<I : Instance<Self>>(&self, other : I, _ : L2) -> F { self.dist2(other) } + fn dist<I: Instance<Self>>(&self, other: I, _: L2) -> F { + self.dist2(other) + } } /* Implemented automatically as Euclidean. -impl<F : Float, const N : usize> Projection<F, L2> for Loc<F, N> { +impl<F : Float, const N : usize> Projection<F, L2> for Loc<N, F> { #[inline] fn proj_ball_mut(&mut self, ρ : F, nrm : L2) { let n = self.norm(nrm); @@ -618,53 +629,65 @@ } }*/ -impl<F : Float, const N : usize> Norm<F, L1> for Loc<F, N> { +impl<F: Float, const N: usize> Norm<L1, F> for Loc<N, F> { /// This implementation is not stabilised as it's meant to be used for very small vectors. /// Use [`nalgebra`] for larger vectors. #[inline] - fn norm(&self, _ : L1) -> F { + fn norm(&self, _: L1) -> F { self.iter().fold(F::ZERO, |m, v| m + v.abs()) } } -impl<F : Float, const N : usize> Dist<F, L1> for Loc<F, N> { +impl<F: Float, const N: usize> Dist<L1, F> for Loc<N, F> { #[inline] - fn dist<I : Instance<Self>>(&self, other : I, _ : L1) -> F { - self.iter() - .zip(other.ref_instance().iter()) - .fold(F::ZERO, |m, (&v, &w)| m + (v-w).abs() ) + fn dist<I: Instance<Self>>(&self, other: I, _: L1) -> F { + other.eval_ref(|r| { + self.iter() + .zip(r.iter()) + .fold(F::ZERO, |m, (&v, &w)| m + (v - w).abs()) + }) } } -impl<F : Float, const N : usize> Projection<F, Linfinity> for Loc<F, N> { +impl<F: Float, const N: usize> Projection<F, Linfinity> for Loc<N, F> { #[inline] - fn proj_ball_mut(&mut self, ρ : F, _ : Linfinity) { - self.iter_mut().for_each(|v| *v = num_traits::clamp(*v, -ρ, ρ)) + fn proj_ball(mut self, ρ: F, exp: Linfinity) -> Self { + self.proj_ball_mut(ρ, exp); + self } } -impl<F : Float, const N : usize> Norm<F, Linfinity> for Loc<F, N> { +impl<F: Float, const N: usize> ProjectionMut<F, Linfinity> for Loc<N, F> { + #[inline] + fn proj_ball_mut(&mut self, ρ: F, _: Linfinity) { + self.iter_mut() + .for_each(|v| *v = num_traits::clamp(*v, -ρ, ρ)) + } +} + +impl<F: Float, const N: usize> Norm<Linfinity, F> for Loc<N, F> { /// This implementation is not stabilised as it's meant to be used for very small vectors. /// Use [`nalgebra`] for larger vectors. #[inline] - fn norm(&self, _ : Linfinity) -> F { + fn norm(&self, _: Linfinity) -> F { self.iter().fold(F::ZERO, |m, v| m.max(v.abs())) } } -impl<F : Float, const N : usize> Dist<F, Linfinity> for Loc<F, N> { +impl<F: Float, const N: usize> Dist<Linfinity, F> for Loc<N, F> { #[inline] - fn dist<I : Instance<Self>>(&self, other : I, _ : Linfinity) -> F { - self.iter() - .zip(other.ref_instance().iter()) - .fold(F::ZERO, |m, (&v, &w)| m.max((v-w).abs())) + fn dist<I: Instance<Self>>(&self, other: I, _: Linfinity) -> F { + other.eval_ref(|r| { + self.iter() + .zip(r.iter()) + .fold(F::ZERO, |m, (&v, &w)| m.max((v - w).abs())) + }) } } - // Misc. -impl<A, const N : usize> FixedLength<N> for Loc<A,N> { +impl<A, const N: usize> FixedLength<N> for Loc<N, A> { type Iter = std::array::IntoIter<A, N>; type Elem = A; #[inline] @@ -673,15 +696,18 @@ } } -impl<A, const N : usize> FixedLengthMut<N> for Loc<A,N> { - type IterMut<'a> = std::slice::IterMut<'a, A> where A : 'a; +impl<A, const N: usize> FixedLengthMut<N> for Loc<N, A> { + type IterMut<'a> + = std::slice::IterMut<'a, A> + where + A: 'a; #[inline] fn fl_iter_mut(&mut self) -> Self::IterMut<'_> { self.iter_mut() } } -impl<'a, A, const N : usize> FixedLength<N> for &'a Loc<A,N> { +impl<'a, A, const N: usize> FixedLength<N> for &'a Loc<N, A> { type Iter = std::slice::Iter<'a, A>; type Elem = &'a A; #[inline] @@ -690,43 +716,61 @@ } } - -impl<F : Num, const N : usize> Space for Loc<F, N> { +impl<F: Num, const N: usize> Space for Loc<N, F> { + type Principal = Self; type Decomp = BasicDecomposition; } -impl<F : Float, const N : usize> Mapping<Loc<F, N>> for Loc<F, N> { +impl<F: Float, const N: usize> Mapping<Loc<N, F>> for Loc<N, F> { type Codomain = F; - fn apply<I : Instance<Loc<F, N>>>(&self, x : I) -> Self::Codomain { - x.eval(|x̃| self.dot(x̃)) + fn apply<I: Instance<Loc<N, F>>>(&self, x: I) -> Self::Codomain { + x.eval_decompose(|x̃| self.dot(x̃)) } } -impl<F : Float, const N : usize> Linear<Loc<F, N>> for Loc<F, N> { } +impl<F: Float, const N: usize> Linear<Loc<N, F>> for Loc<N, F> {} + +impl<F: Float, const N: usize> VectorSpace for Loc<N, F> { + type Field = F; + type PrincipalV = Self; -impl<F : Float, const N : usize> AXPY<F, Loc<F, N>> for Loc<F, N> { - type Owned = Self; + // #[inline] + // fn make_origin_generator(&self) -> StaticEuclideanOriginGenerator { + // StaticEuclideanOriginGenerator + // } + + #[inline] + fn similar_origin(&self) -> Self::PrincipalV { + Self::ORIGIN + } #[inline] - fn axpy<I : Instance<Loc<F, N>>>(&mut self, α : F, x : I, β : F) { - x.eval(|x̃| { + fn similar_origin_inst<I: Instance<Self>>(_: I) -> Self::PrincipalV { + Self::ORIGIN + } + + // #[inline] + // fn into_owned(self) -> Self::Owned { + // self + // } +} + +impl<F: Float, const N: usize> AXPY<Loc<N, F>> for Loc<N, F> { + #[inline] + fn axpy<I: Instance<Loc<N, F>>>(&mut self, α: F, x: I, β: F) { + x.eval_ref(|x̃| { if β == F::ZERO { - map2_mut(self, x̃, |yi, xi| { *yi = α * (*xi) }) + map2_mut(self, x̃, |yi, xi| *yi = α * (*xi)) } else { - map2_mut(self, x̃, |yi, xi| { *yi = β * (*yi) + α * (*xi) }) + map2_mut(self, x̃, |yi, xi| *yi = β * (*yi) + α * (*xi)) } }) } #[inline] - fn copy_from<I : Instance<Loc<F, N>>>(&mut self, x : I) { - x.eval(|x̃| map2_mut(self, x̃, |yi, xi| *yi = *xi )) - } - - #[inline] - fn similar_origin(&self) -> Self::Owned { - Self::ORIGIN + fn copy_from<I: Instance<Loc<N, F>>>(&mut self, x: I) { + x.eval_ref(|x̃| map2_mut(self, x̃, |yi, xi| *yi = *xi)) } #[inline] @@ -734,4 +778,3 @@ *self = Self::ORIGIN; } } -
--- a/src/mapping.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/mapping.rs Fri May 15 14:46:30 2026 -0500 @@ -2,84 +2,94 @@ Traits for mathematical functions. */ -use std::marker::PhantomData; -use std::borrow::Cow; -use crate::types::{Num, Float, ClosedMul}; +use crate::error::DynResult; +use crate::instance::MyCow; +pub use crate::instance::{BasicDecomposition, ClosedSpace, Decomposition, Instance, Space}; use crate::loc::Loc; -pub use crate::instance::{Instance, Decomposition, BasicDecomposition, Space}; use crate::norms::{Norm, NormExponent}; -use crate::operator_arithmetic::{Weighted, Constant}; +use crate::operator_arithmetic::{Constant, Weighted}; +use crate::types::{ClosedMul, Float, Num}; +use std::marker::PhantomData; +use std::ops::Mul; /// A mapping from `Domain` to `Self::Codomain`. -pub trait Mapping<Domain : Space> { - type Codomain : Space; +pub trait Mapping<Domain: Space> { + type Codomain: ClosedSpace; /// Compute the value of `self` at `x`. - fn apply<I : Instance<Domain>>(&self, x : I) -> Self::Codomain; + fn apply<I: Instance<Domain>>(&self, x: I) -> Self::Codomain; #[inline] /// Form the composition `self ∘ other` - fn compose<X : Space, T : Mapping<X, Codomain=Domain>>(self, other : T) - -> Composition<Self, T> + fn compose<X: Space, T: Mapping<X, Codomain = Domain>>(self, other: T) -> Composition<Self, T> where - Self : Sized + Self: Sized, { - Composition{ outer : self, inner : other, intermediate_norm_exponent : () } + Composition { outer: self, inner: other, intermediate_norm_exponent: () } } - #[inline] /// Form the composition `self ∘ other`, assigning a norm to the inermediate space - fn compose_with_norm<F, X, T, E>( - self, other : T, norm : E - ) -> Composition<Self, T, E> + fn compose_with_norm<F, X, T, E>(self, other: T, norm: E) -> Composition<Self, T, E> where - Self : Sized, - X : Space, - T : Mapping<X, Codomain=Domain>, - E : NormExponent, - Domain : Norm<F, E>, - F : Num + Self: Sized, + X: Space, + T: Mapping<X, Codomain = Domain>, + E: NormExponent, + Domain: Norm<E, F>, + F: Num, { - Composition{ outer : self, inner : other, intermediate_norm_exponent : norm } + Composition { outer: self, inner: other, intermediate_norm_exponent: norm } } /// Multiply `self` by the scalar `a`. #[inline] - fn weigh<C>(self, a : C) -> Weighted<Self, C> + fn weigh<C>(self, a: C) -> Weighted<Self, C> where - Self : Sized, - C : Constant, - Self::Codomain : ClosedMul<C::Type>, + Self: Sized, + C: Constant, + Self::Codomain: ClosedMul<C::Type>, { - Weighted { weight : a, base_fn : self } + Weighted { weight: a, base_fn: self } } } -/// Automatically implemented shorthand for referring to [`Mapping`]s from [`Loc<F, N>`] to `F`. -pub trait RealMapping<F : Float, const N : usize> -: Mapping<Loc<F, N>, Codomain = F> {} +/// Automatically implemented shorthand for referring to [`Mapping`]s from [`Loc<N, F>`] to `F`. +pub trait RealMapping<const N: usize, F: Float = f64>: Mapping<Loc<N, F>, Codomain = F> {} -impl<F : Float, T, const N : usize> RealMapping<F, N> for T -where T : Mapping<Loc<F, N>, Codomain = F> {} +impl<F: Float, T, const N: usize> RealMapping<N, F> for T where T: Mapping<Loc<N, F>, Codomain = F> {} -/// A helper trait alias for referring to [`Mapping`]s from [`Loc<F, N>`] to [`Loc<F, M>`]. -pub trait RealVectorField<F : Float, const N : usize, const M : usize> -: Mapping<Loc<F, N>, Codomain = Loc<F, M>> {} +/// A helper trait alias for referring to [`Mapping`]s from [`Loc<N, F>`] to [`Loc<M, F>`]. +pub trait RealVectorField<const N: usize, const M: usize, F: Float = f64>: + Mapping<Loc<N, F>, Codomain = Loc<M, F>> +{ +} -impl<F : Float, T, const N : usize, const M : usize> RealVectorField<F, N, M> for T -where T : Mapping<Loc<F, N>, Codomain = Loc<F, M>> {} +impl<F: Float, T, const N: usize, const M: usize> RealVectorField<N, M, F> for T where + T: Mapping<Loc<N, F>, Codomain = Loc<M, F>> +{ +} /// A differentiable mapping from `Domain` to [`Mapping::Codomain`], with differentials /// `Differential`. /// /// This is automatically implemented when [`DifferentiableImpl`] is. -pub trait DifferentiableMapping<Domain : Space> : Mapping<Domain> { - type DerivativeDomain : Space; - type Differential<'b> : Mapping<Domain, Codomain=Self::DerivativeDomain> where Self : 'b; +pub trait DifferentiableMapping<Domain: Space>: Mapping<Domain> { + type DerivativeDomain: ClosedSpace; + type Differential<'b>: Mapping<Domain, Codomain = Self::DerivativeDomain> + where + Self: 'b; /// Calculate differential at `x` - fn differential<I : Instance<Domain>>(&self, x : I) -> Self::DerivativeDomain; + fn differential<I: Instance<Domain>>(&self, x: I) -> Self::DerivativeDomain; + + /// Calculate differential and value at `x` + fn apply_and_differential<I: Instance<Domain>>( + &self, + x: I, + ) -> (Self::Codomain, Self::DerivativeDomain) { + x.eval_ref(|xo| (self.apply(xo), self.differential(xo))) + } /// Form the differential mapping of `self`. fn diff(self) -> Self::Differential<'static>; @@ -89,51 +99,75 @@ } /// Automatically implemented shorthand for referring to differentiable [`Mapping`]s from -/// [`Loc<F, N>`] to `F`. -pub trait DifferentiableRealMapping<F : Float, const N : usize> -: DifferentiableMapping<Loc<F, N>, Codomain = F, DerivativeDomain = Loc<F, N>> {} +/// [`Loc<N, F>`] to `F`. +pub trait DifferentiableRealMapping<const N: usize, F: Float>: + DifferentiableMapping<Loc<N, F>, Codomain = F, DerivativeDomain = Loc<N, F>> +{ +} -impl<F : Float, T, const N : usize> DifferentiableRealMapping<F, N> for T -where T : DifferentiableMapping<Loc<F, N>, Codomain = F, DerivativeDomain = Loc<F, N>> {} +impl<F: Float, T, const N: usize> DifferentiableRealMapping<N, F> for T where + T: DifferentiableMapping<Loc<N, F>, Codomain = F, DerivativeDomain = Loc<N, F>> +{ +} /// Helper trait for implementing [`DifferentiableMapping`] -pub trait DifferentiableImpl<X : Space> : Sized { - type Derivative : Space; +pub trait DifferentiableImpl<X: Space>: Sized { + type Derivative: ClosedSpace; /// Compute the differential of `self` at `x`, consuming the input. - fn differential_impl<I : Instance<X>>(&self, x : I) -> Self::Derivative; + fn differential_impl<I: Instance<X>>(&self, x: I) -> Self::Derivative; + + fn apply_and_differential_impl<I: Instance<X>>( + &self, + x: I, + ) -> (Self::Codomain, Self::Derivative) + where + Self: Mapping<X>, + { + x.eval_ref(|xo| (self.apply(xo), self.differential_impl(xo))) + } } impl<T, Domain> DifferentiableMapping<Domain> for T where - Domain : Space, - T : Clone + Mapping<Domain> + DifferentiableImpl<Domain> + Domain: Space, + T: Mapping<Domain> + DifferentiableImpl<Domain>, { type DerivativeDomain = T::Derivative; - type Differential<'b> = Differential<'b, Domain, Self> where Self : 'b; - + type Differential<'b> + = Differential<'b, Domain, Self> + where + Self: 'b; + #[inline] - fn differential<I : Instance<Domain>>(&self, x : I) -> Self::DerivativeDomain { + fn differential<I: Instance<Domain>>(&self, x: I) -> Self::DerivativeDomain { self.differential_impl(x) } + #[inline] + fn apply_and_differential<I: Instance<Domain>>( + &self, + x: I, + ) -> (T::Codomain, Self::DerivativeDomain) { + self.apply_and_differential_impl(x) + } + fn diff(self) -> Differential<'static, Domain, Self> { - Differential{ g : Cow::Owned(self), _space : PhantomData } + Differential { g: MyCow::Owned(self), _space: PhantomData } } fn diff_ref(&self) -> Differential<'_, Domain, Self> { - Differential{ g : Cow::Borrowed(self), _space : PhantomData } + Differential { g: MyCow::Borrowed(self), _space: PhantomData } } } - /// Container for the differential [`Mapping`] of a [`DifferentiableMapping`]. -pub struct Differential<'a, X, G : Clone> { - g : Cow<'a, G>, - _space : PhantomData<X> +pub struct Differential<'a, X, G> { + g: MyCow<'a, G>, + _space: PhantomData<X>, } -impl<'a, X, G : Clone> Differential<'a, X, G> { +impl<'a, X, G> Differential<'a, X, G> { pub fn base_fn(&self) -> &G { &self.g } @@ -141,65 +175,66 @@ impl<'a, X, G> Mapping<X> for Differential<'a, X, G> where - X : Space, - G : Clone + DifferentiableMapping<X> + X: Space, + G: DifferentiableMapping<X>, { type Codomain = G::DerivativeDomain; #[inline] - fn apply<I : Instance<X>>(&self, x : I) -> Self::Codomain { + fn apply<I: Instance<X>>(&self, x: I) -> Self::Codomain { (*self.g).differential(x) } } /// Container for flattening [`Loc`]`<F, 1>` codomain of a [`Mapping`] to `F`. pub struct FlattenedCodomain<X, F, G> { - g : G, - _phantoms : PhantomData<(X, F)> + g: G, + _phantoms: PhantomData<(X, F)>, } -impl<F : Space, X, G> Mapping<X> for FlattenedCodomain<X, F, G> +impl<F, X, G> Mapping<X> for FlattenedCodomain<X, F, G> where - X : Space, - G: Mapping<X, Codomain=Loc<F, 1>> + F: ClosedSpace, + X: Space, + G: Mapping<X, Codomain = Loc<1, F>>, { type Codomain = F; #[inline] - fn apply<I : Instance<X>>(&self, x : I) -> Self::Codomain { + fn apply<I: Instance<X>>(&self, x: I) -> Self::Codomain { self.g.apply(x).flatten1d() } } /// An auto-trait for constructing a [`FlattenCodomain`] structure for /// flattening the codomain of a [`Mapping`] from [`Loc`]`<F, 1>` to `F`. -pub trait FlattenCodomain<X : Space, F> : Mapping<X, Codomain=Loc<F, 1>> + Sized { +pub trait FlattenCodomain<X: Space, F>: Mapping<X, Codomain = Loc<1, F>> + Sized { /// Flatten the codomain from [`Loc`]`<F, 1>` to `F`. fn flatten_codomain(self) -> FlattenedCodomain<X, F, Self> { - FlattenedCodomain{ g : self, _phantoms : PhantomData } + FlattenedCodomain { g: self, _phantoms: PhantomData } } } -impl<X : Space, F, G : Sized + Mapping<X, Codomain=Loc<F, 1>>> FlattenCodomain<X, F> for G {} +impl<X: Space, F, G: Sized + Mapping<X, Codomain = Loc<1, F>>> FlattenCodomain<X, F> for G {} -/// Container for dimensional slicing [`Loc`]`<F, N>` codomain of a [`Mapping`] to `F`. -pub struct SlicedCodomain<'a, X, F, G : Clone, const N : usize> { - g : Cow<'a, G>, - slice : usize, - _phantoms : PhantomData<(X, F)> +/// Container for dimensional slicing [`Loc`]`<N, F>` codomain of a [`Mapping`] to `F`. +pub struct SlicedCodomain<'a, X, F, G, const N: usize> { + g: MyCow<'a, G>, + slice: usize, + _phantoms: PhantomData<(X, F)>, } -impl<'a, X, F, G, const N : usize> Mapping<X> for SlicedCodomain<'a, X, F, G, N> +impl<'a, X, F, G, const N: usize> Mapping<X> for SlicedCodomain<'a, X, F, G, N> where - X : Space, - F : Copy + Space, - G : Mapping<X, Codomain=Loc<F, N>> + Clone, + X: Space, + F: Copy + ClosedSpace, + G: Mapping<X, Codomain = Loc<N, F>>, { type Codomain = F; #[inline] - fn apply<I : Instance<X>>(&self, x : I) -> Self::Codomain { - let tmp : [F; N] = (*self.g).apply(x).into(); + fn apply<I: Instance<X>>(&self, x: I) -> Self::Codomain { + let tmp: [F; N] = (*self.g).apply(x).into(); // Safety: `slice_codomain` below checks the range. unsafe { *tmp.get_unchecked(self.slice) } } @@ -207,44 +242,106 @@ /// An auto-trait for constructing a [`FlattenCodomain`] structure for /// flattening the codomain of a [`Mapping`] from [`Loc`]`<F, 1>` to `F`. -pub trait SliceCodomain<X : Space, F : Copy, const N : usize> - : Mapping<X, Codomain=Loc<F, N>> + Clone + Sized +pub trait SliceCodomain<X: Space, const N: usize, F: Copy = f64>: + Mapping<X, Codomain = Loc<N, F>> + Sized { /// Flatten the codomain from [`Loc`]`<F, 1>` to `F`. - fn slice_codomain(self, slice : usize) -> SlicedCodomain<'static, X, F, Self, N> { + fn slice_codomain(self, slice: usize) -> SlicedCodomain<'static, X, F, Self, N> { assert!(slice < N); - SlicedCodomain{ g : Cow::Owned(self), slice, _phantoms : PhantomData } + SlicedCodomain { g: MyCow::Owned(self), slice, _phantoms: PhantomData } } /// Flatten the codomain from [`Loc`]`<F, 1>` to `F`. - fn slice_codomain_ref(&self, slice : usize) -> SlicedCodomain<'_, X, F, Self, N> { + fn slice_codomain_ref(&self, slice: usize) -> SlicedCodomain<'_, X, F, Self, N> { assert!(slice < N); - SlicedCodomain{ g : Cow::Borrowed(self), slice, _phantoms : PhantomData } + SlicedCodomain { g: MyCow::Borrowed(self), slice, _phantoms: PhantomData } } } -impl<X : Space, F : Copy, G : Sized + Mapping<X, Codomain=Loc<F, N>> + Clone, const N : usize> -SliceCodomain<X, F, N> -for G {} - +impl<X: Space, F: Copy, G: Sized + Mapping<X, Codomain = Loc<N, F>>, const N: usize> + SliceCodomain<X, N, F> for G +{ +} /// The composition S ∘ T. `E` is for storing a `NormExponent` for the intermediate space. pub struct Composition<S, T, E = ()> { - pub outer : S, - pub inner : T, - pub intermediate_norm_exponent : E + pub outer: S, + pub inner: T, + pub intermediate_norm_exponent: E, } impl<S, T, X, E> Mapping<X> for Composition<S, T, E> where - X : Space, - T : Mapping<X>, - S : Mapping<T::Codomain> + X: Space, + T: Mapping<X>, + S: Mapping<T::Codomain>, { type Codomain = S::Codomain; #[inline] - fn apply<I : Instance<X>>(&self, x : I) -> Self::Codomain { + fn apply<I: Instance<X>>(&self, x: I) -> Self::Codomain { self.outer.apply(self.inner.apply(x)) } } + +/// Helper trait for implementing [`DifferentiableMapping`] +impl<S, T, X, E, Y> DifferentiableImpl<X> for Composition<S, T, E> +where + X: Space, + T: DifferentiableImpl<X> + Mapping<X>, + S: DifferentiableImpl<T::Codomain>, + E: Copy, + //Composition<S::Derivative, T::Derivative, E>: Space, + S::Derivative: Mul<T::Derivative, Output = Y>, + Y: ClosedSpace, +{ + //type Derivative = Composition<S::Derivative, T::Derivative, E>; + type Derivative = Y; + + /// Compute the differential of `self` at `x`, consuming the input. + fn differential_impl<I: Instance<X>>(&self, x: I) -> Self::Derivative { + // Composition { + // outer: self + // .outer + // .differential_impl(self.inner.apply(x.ref_instance())), + // inner: self.inner.differential_impl(x), + // intermediate_norm_exponent: self.intermediate_norm_exponent, + // } + + self.outer + .differential_impl(x.eval_ref(|r| self.inner.apply(r))) + * self.inner.differential_impl(x) + } +} + +mod dataterm; +pub use dataterm::DataTerm; + +/// Trait for indicating that `Self` is Lipschitz with respect to the (semi)norm `D`. +pub trait Lipschitz<M> { + /// The type of floats + type FloatType: Float; + + /// Returns the Lipschitz factor of `self` with respect to the (semi)norm `D`. + fn lipschitz_factor(&self, seminorm: M) -> DynResult<Self::FloatType>; +} + +/// Helper trait for implementing [`Lipschitz`] for mappings that implement [`DifferentiableImpl`]. +pub trait LipschitzDifferentiableImpl<X: Space, M>: DifferentiableImpl<X> { + type FloatType: Float; + + /// Compute the lipschitz factor of the derivative of `f`. + fn diff_lipschitz_factor(&self, seminorm: M) -> DynResult<Self::FloatType>; +} + +impl<'b, M, X, A> Lipschitz<M> for Differential<'b, X, A> +where + X: Space, + A: LipschitzDifferentiableImpl<X, M>, +{ + type FloatType = A::FloatType; + + fn lipschitz_factor(&self, seminorm: M) -> DynResult<Self::FloatType> { + (*self.g).diff_lipschitz_factor(seminorm) + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mapping/dataterm.rs Fri May 15 14:46:30 2026 -0500 @@ -0,0 +1,155 @@ +/*! +General deata terms of the form $g(Ax-b)$ for an operator $A$ +to a [`Euclidean`] space, and a function g on that space. +*/ + +#![allow(non_snake_case)] + +use super::{DifferentiableImpl, DifferentiableMapping, LipschitzDifferentiableImpl, Mapping}; +use crate::convex::ConvexMapping; +use crate::error::DynResult; +use crate::instance::{ClosedSpace, Instance, Space}; +use crate::linops::{BoundedLinear, Linear, Preadjointable}; +use crate::norms::{Normed, L2}; +use crate::types::Float; +use std::ops::Sub; + +//use serde::{Deserialize, Serialize}; + +/// Functions of the form $g(Ax-b)$ for an operator $A$, data $b$, and fidelity $g$. +pub struct DataTerm< + F: Float, + Domain: Space, + A: Mapping<Domain>, + G: Mapping<A::Codomain, Codomain = F>, +> { + // The operator A + opA: A, + // The data b + b: <A as Mapping<Domain>>::Codomain, + // The outer fidelity + g: G, +} + +// Derive has troubles with `b`. +impl<F, Domain, A, G> Clone for DataTerm<F, Domain, A, G> +where + F: Float, + Domain: Space, + A: Mapping<Domain> + Clone, + <A as Mapping<Domain>>::Codomain: Clone, + G: Mapping<A::Codomain, Codomain = F> + Clone, +{ + fn clone(&self) -> Self { + DataTerm { opA: self.opA.clone(), b: self.b.clone(), g: self.g.clone() } + } +} + +#[allow(non_snake_case)] +impl<F: Float, Domain: Space, A: Mapping<Domain>, G: Mapping<A::Codomain, Codomain = F>> + DataTerm<F, Domain, A, G> +{ + pub fn new(opA: A, b: A::Codomain, g: G) -> Self { + DataTerm { opA, b, g } + } + + pub fn operator(&self) -> &'_ A { + &self.opA + } + + pub fn data(&self) -> &'_ <A as Mapping<Domain>>::Codomain { + &self.b + } + + pub fn fidelity(&self) -> &'_ G { + &self.g + } + + /// Returns the residual $Ax-b$. + pub fn residual<'a, 'b>(&'b self, x: &'a Domain) -> <A as Mapping<Domain>>::Codomain + where + &'a Domain: Instance<Domain>, + <A as Mapping<Domain>>::Codomain: + Sub<&'b <A as Mapping<Domain>>::Codomain, Output = <A as Mapping<Domain>>::Codomain>, + { + self.opA.apply(x) - &self.b + } +} + +//+ AdjointProductBoundedBy<RNDM<N, F>, P, FloatType = F>, + +impl<F, X, A, G> Mapping<X> for DataTerm<F, X, A, G> +where + F: Float, + X: Space, + A: Mapping<X>, + G: Mapping<A::Codomain, Codomain = F>, + A::Codomain: ClosedSpace + for<'a> Sub<&'a A::Codomain, Output = A::Codomain>, +{ + type Codomain = F; + + fn apply<I: Instance<X>>(&self, x: I) -> F { + // TODO: possibly (if at all more effcient) use GEMV once generalised + // to not require preallocation. However, Rust should be pretty efficient + // at not doing preallocations or anything here, as the result of self.opA.apply() + // can be consumed, so maybe GEMV is no use. + self.g.apply(self.opA.apply(x) - &self.b) + } +} + +impl<F, X, A, G> ConvexMapping<X, F> for DataTerm<F, X, A, G> +where + F: Float, + X: Normed<F>, + A: Linear<X>, + G: ConvexMapping<A::Codomain, F>, + A::Codomain: ClosedSpace + Normed<F> + for<'a> Sub<&'a A::Codomain, Output = A::Codomain>, +{ +} + +impl<F, X, Y, A, G> DifferentiableImpl<X> for DataTerm<F, X, A, G> +where + F: Float, + X: Space, + Y: Space + Instance<Y> + for<'a> Sub<&'a Y, Output = Y>, + A: Linear<X, Codomain = Y> + Preadjointable<X, G::DerivativeDomain>, + G::DerivativeDomain: Instance<G::DerivativeDomain>, + A::PreadjointCodomain: ClosedSpace, + G: DifferentiableMapping<Y, Codomain = F>, + Self: Mapping<X, Codomain = F>, +{ + type Derivative = A::PreadjointCodomain; + + fn differential_impl<I: Instance<X>>(&self, x: I) -> Self::Derivative { + // TODO: possibly (if at all more effcient) use GEMV once generalised + // to not require preallocation. However, Rust should be pretty efficient + // at not doing preallocations or anything here, as the result of self.opA.apply() + // can be consumed, so maybe GEMV is no use. + //self.opA.preadjoint().apply(self.opA.apply(x) - self.b) + self.opA + .preadjoint() + .apply(self.g.differential(self.opA.apply(x) - &self.b)) + } + + fn apply_and_differential_impl<I: Instance<X>>(&self, x: I) -> (F, Self::Derivative) { + let j = self.opA.apply(x) - &self.b; + let (v, d) = self.g.apply_and_differential(j); + (v, self.opA.preadjoint().apply(d)) + } +} + +impl<'a, F, X, Y, A, G> LipschitzDifferentiableImpl<X, X::NormExp> for DataTerm<F, X, A, G> +where + F: Float, + X: Normed<F>, + Y: Normed<F>, + A: BoundedLinear<X, X::NormExp, L2, F, Codomain = Y>, + G: Mapping<Y, Codomain = F> + LipschitzDifferentiableImpl<Y, Y::NormExp>, + Self: DifferentiableImpl<X>, +{ + type FloatType = F; + + fn diff_lipschitz_factor(&self, seminorm: X::NormExp) -> DynResult<F> { + Ok(self.opA.opnorm_bound(seminorm, L2)?.powi(2)) + } +}
--- a/src/metaprogramming.rs Sun Apr 27 20:29:43 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/*! -Metaprogramming tools -*/ - -/// Reference `x` if so indicated by the first parameter. -/// Typically to be used from another macro. -/// -/// ```ignore -/// maybe_ref!(ref, V) // ➡ &V -/// maybe_ref!(noref, V) // ➡ V -/// ``` -macro_rules! maybe_ref { - (ref, $x:expr) => { - &$x - }; - (noref, $x:expr) => { - $x - }; - (ref, $x:ty) => { - &$x - }; - (noref, $x:ty) => { - $x - }; -} - -/// Choose `a` if first argument is the literal `ref`, otherwise `b`. -// macro_rules! ifref { -// (noref, $a:expr, $b:expr) => { -// $b -// }; -// (ref, $a:expr, $b:expr) => { -// $a -// }; -// } - -/// Annotate `x` with a lifetime if the first parameter -/// Typically to be used from another macro. -/// -/// ```ignore -/// maybe_ref!(ref, &'a V) // ➡ &'a V -/// maybe_ref!(noref, &'a V) // ➡ V -/// ``` -macro_rules! maybe_lifetime { - (ref, $x:ty) => { - $x - }; - (noref, &$lt:lifetime $x:ty) => { - $x - }; - (noref, &$x:ty) => { - $x - }; -}
--- a/src/nalgebra_support.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/nalgebra_support.rs Fri May 15 14:46:30 2026 -0500 @@ -8,107 +8,390 @@ [`num_traits`] does. */ -use nalgebra::{ - Matrix, Storage, StorageMut, OMatrix, Dim, DefaultAllocator, Scalar, - ClosedAddAssign, ClosedMulAssign, SimdComplexField, Vector, OVector, RealField, - LpNorm, UniformNorm -}; -use nalgebra::base::constraint::{ - ShapeConstraint, SameNumberOfRows, SameNumberOfColumns -}; -use nalgebra::base::dimension::*; -use nalgebra::base::allocator::Allocator; -use std::ops::Mul; -use num_traits::identities::{Zero, One}; +use crate::euclidean::*; +use crate::instance::{Decomposition, Instance, MyCow, Ownable, Space}; use crate::linops::*; -use crate::euclidean::*; -use crate::mapping::{Space, BasicDecomposition}; +use crate::norms::*; use crate::types::Float; -use crate::norms::*; -use crate::instance::Instance; +use nalgebra::base::allocator::Allocator; +use nalgebra::base::constraint::{DimEq, SameNumberOfColumns, SameNumberOfRows, ShapeConstraint}; +use nalgebra::base::dimension::*; +use nalgebra::{ + ArrayStorage, ClosedAddAssign, ClosedMulAssign, DefaultAllocator, Dim, LpNorm, Matrix, + MatrixView, OMatrix, OVector, RawStorage, RealField, SimdComplexField, Storage, StorageMut, + UniformNorm, VecStorage, Vector, ViewStorage, ViewStorageMut, U1, +}; +use num_traits::identities::Zero; +use std::ops::Mul; + +impl<S, M, N, E> Ownable for Matrix<E, M, N, S> +where + S: Storage<E, M, N>, + M: Dim, + N: Dim, + E: Float, + DefaultAllocator: Allocator<M, N>, +{ + type OwnedVariant = OMatrix<E, M, N>; + + #[inline] + fn into_owned(self) -> Self::OwnedVariant { + Matrix::into_owned(self) + } + + /// Returns an owned instance of a reference. + fn clone_owned(&self) -> Self::OwnedVariant { + Matrix::clone_owned(self) + } -impl<SM,N,M,E> Space for Matrix<E,N,M,SM> + fn cow_owned<'b>(self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + MyCow::Owned(self.into_owned()) + } + + fn ref_cow_owned<'b>(&'b self) -> MyCow<'b, Self::OwnedVariant> + where + Self: 'b, + { + MyCow::Owned(self.clone_owned()) + } +} + +trait StridesOk<E, N, M = U1, S = <DefaultAllocator as Allocator<N, M>>::Buffer<E>>: + DimEq<Dyn, S::RStride> + DimEq<Dyn, S::CStride> where - SM: Storage<E,N,M> + Clone, - N : Dim, M : Dim, E : Scalar + Zero + One + ClosedAddAssign + ClosedMulAssign, - DefaultAllocator : Allocator<N,M>, + S: RawStorage<E, N, M>, + E: Float, + N: Dim, + M: Dim, + DefaultAllocator: Allocator<N, M>, { - type Decomp = BasicDecomposition; +} + +impl<E, M> StridesOk<E, Dyn, M, VecStorage<E, Dyn, M>> for ShapeConstraint +where + M: Dim, + E: Float, + DefaultAllocator: Allocator<Dyn, M>, +{ } -impl<SM,SV,N,M,K,E> Mapping<Matrix<E,M,K,SV>> for Matrix<E,N,M,SM> -where SM: Storage<E,N,M>, SV: Storage<E,M,K> + Clone, - N : Dim, M : Dim, K : Dim, E : Scalar + Zero + One + ClosedAddAssign + ClosedMulAssign, - DefaultAllocator : Allocator<N,K>, - DefaultAllocator : Allocator<M,K>, - DefaultAllocator : Allocator<N,M>, - DefaultAllocator : Allocator<M,N> { - type Codomain = OMatrix<E,N,K>; +impl<E, const N: usize, const M: usize> StridesOk<E, Const<N>, Const<M>, ArrayStorage<E, N, M>> + for ShapeConstraint +where + E: Float, +{ +} + +macro_rules! strides_ok { + ($R:ty, $C:ty where $($qual:tt)*) => { + impl<'a, E, N, M, $($qual)*> StridesOk<E, N, M, ViewStorage<'a, E, N, M, $R, $C>> for ShapeConstraint + where + N: Dim, + M: Dim, + E: Float, + DefaultAllocator: Allocator<N, M>, + { + } + impl<'a, E, N, M, $($qual)*> StridesOk<E, N, M, ViewStorageMut<'a, E, N, M, $R, $C>> for ShapeConstraint + where + N: Dim, + M: Dim, + E: Float, + DefaultAllocator: Allocator<N, M>, + { + } + }; +} + +strides_ok!(Dyn, Dyn where ); +strides_ok!(Dyn, Const<C> where const C : usize); +strides_ok!(Const<R>, Dyn where const R : usize); +strides_ok!(Const<R>, Const<C> where const R : usize, const C : usize); + +impl<SM, N, M, E> Space for Matrix<E, N, M, SM> +where + SM: Storage<E, N, M>, + N: Dim, + M: Dim, + E: Float, + DefaultAllocator: Allocator<N, M>, + ShapeConstraint: StridesOk<E, N, M>, +{ + type Principal = OMatrix<E, N, M>; + type Decomp = MatrixDecomposition; +} + +#[derive(Copy, Clone, Debug)] +pub struct MatrixDecomposition; + +impl<E, M, K, S> Decomposition<Matrix<E, M, K, S>> for MatrixDecomposition +where + S: Storage<E, M, K>, + M: Dim, + K: Dim, + E: Float, + DefaultAllocator: Allocator<M, K>, + ShapeConstraint: StridesOk<E, M, K>, +{ + type Decomposition<'b> + = MyCow<'b, OMatrix<E, M, K>> + where + Matrix<E, M, K, S>: 'b; + type Reference<'b> + = MatrixView<'b, E, M, K, Dyn, Dyn> + where + Matrix<E, M, K, S>: 'b; #[inline] - fn apply<I : Instance<Matrix<E,M,K,SV>>>( - &self, x : I - ) -> Self::Codomain { - x.either(|owned| self.mul(owned), |refr| self.mul(refr)) + fn lift<'b>(r: Self::Reference<'b>) -> Self::Decomposition<'b> + where + S: 'b, + { + MyCow::Owned(r.into_owned()) } } +impl<S1, S2, M, K, E> Instance<Matrix<E, M, K, S1>, MatrixDecomposition> for Matrix<E, M, K, S2> +where + S1: Storage<E, M, K>, + S2: Storage<E, M, K>, + M: Dim, + K: Dim, + E: Float, + DefaultAllocator: Allocator<M, K>, + ShapeConstraint: StridesOk<E, M, K, S2> + StridesOk<E, M, K>, +{ + #[inline] + fn eval_ref<'b, R>( + &'b self, + f: impl FnOnce(<MatrixDecomposition as Decomposition<Matrix<E, M, K, S1>>>::Reference<'b>) -> R, + ) -> R + where + Self: 'b, + Matrix<E, M, K, S1>: 'b, + { + f(self.as_view::<M, K, Dyn, Dyn>()) + } -impl<'a, SM,SV,N,M,K,E> Linear<Matrix<E,M,K,SV>> for Matrix<E,N,M,SM> -where SM: Storage<E,N,M>, SV: Storage<E,M,K> + Clone, - N : Dim, M : Dim, K : Dim, E : Scalar + Zero + One + ClosedAddAssign + ClosedMulAssign, - DefaultAllocator : Allocator<N,K>, - DefaultAllocator : Allocator<M,K>, - DefaultAllocator : Allocator<N,M>, - DefaultAllocator : Allocator<M,N> { + #[inline] + fn own(self) -> OMatrix<E, M, K> { + self.into_owned() + } + + #[inline] + fn cow<'b>(self) -> MyCow<'b, OMatrix<E, M, K>> + where + Self: 'b, + { + self.cow_owned() + } + + #[inline] + fn decompose<'b>(self) -> MyCow<'b, OMatrix<E, M, K>> + where + Self: 'b, + { + self.cow_owned() + } +} + +impl<'a, S1, S2, M, K, E> Instance<Matrix<E, M, K, S1>, MatrixDecomposition> + for &'a Matrix<E, M, K, S2> +where + S1: Storage<E, M, K>, + S2: Storage<E, M, K>, + M: Dim, + K: Dim, + E: Float, + DefaultAllocator: Allocator<M, K>, + ShapeConstraint: StridesOk<E, M, K, S2> + StridesOk<E, M, K>, +{ + fn eval_ref<'b, R>( + &'b self, + f: impl FnOnce(<MatrixDecomposition as Decomposition<Matrix<E, M, K, S1>>>::Reference<'b>) -> R, + ) -> R + where + Self: 'b, + Matrix<E, M, K, S1>: 'b, + { + f((*self).as_view::<M, K, Dyn, Dyn>()) + } + + #[inline] + fn own(self) -> OMatrix<E, M, K> { + self.into_owned() + } + + #[inline] + fn cow<'b>(self) -> MyCow<'b, OMatrix<E, M, K>> + where + Self: 'b, + { + self.cow_owned() + } + + #[inline] + fn decompose<'b>(self) -> MyCow<'b, OMatrix<E, M, K>> + where + Self: 'b, + { + self.cow_owned() + } } -impl<SM,SV1,SV2,N,M,K,E> GEMV<E, Matrix<E,M,K,SV1>, Matrix<E,N,K,SV2>> for Matrix<E,N,M,SM> -where SM: Storage<E,N,M>, SV1: Storage<E,M,K> + Clone, SV2: StorageMut<E,N,K>, - N : Dim, M : Dim, K : Dim, E : Scalar + Zero + One + Float, - DefaultAllocator : Allocator<N,K>, - DefaultAllocator : Allocator<M,K>, - DefaultAllocator : Allocator<N,M>, - DefaultAllocator : Allocator<M,N> { +impl<'a, S1, M, SM, K, E> Instance<Matrix<E, M, K, S1>, MatrixDecomposition> + for MyCow<'a, Matrix<E, M, K, SM>> +where + S1: Storage<E, M, K>, + SM: Storage<E, M, K>, + M: Dim, + K: Dim, + E: Float, + DefaultAllocator: Allocator<M, K>, + ShapeConstraint: StridesOk<E, M, K, SM> + StridesOk<E, M, K>, +{ + #[inline] + fn eval_ref<'b, R>( + &'b self, + f: impl FnOnce(<MatrixDecomposition as Decomposition<Matrix<E, M, K, S1>>>::Reference<'b>) -> R, + ) -> R + where + Self: 'b, + Matrix<E, M, K, S1>: 'b, + { + f(self.as_view::<M, K, Dyn, Dyn>()) + } + + #[inline] + fn own(self) -> OMatrix<E, M, K> { + self.into_owned() + } + + #[inline] + fn cow<'b>(self) -> MyCow<'b, OMatrix<E, M, K>> + where + Self: 'b, + { + self.cow_owned() + } #[inline] - fn gemv<I : Instance<Matrix<E,M,K,SV1>>>( - &self, y : &mut Matrix<E,N,K,SV2>, α : E, x : I, β : E + fn decompose<'b>(self) -> MyCow<'b, OMatrix<E, M, K>> + where + Self: 'b, + { + self.cow_owned() + } +} + +impl<SM, N, M, K, E> Mapping<OMatrix<E, M, K>> for Matrix<E, N, M, SM> +where + SM: Storage<E, N, M>, + N: Dim, + M: Dim, + K: Dim, + E: Float, + DefaultAllocator: Allocator<M, K> + Allocator<N, K>, + ShapeConstraint: StridesOk<E, M, K> + StridesOk<E, N, K>, +{ + type Codomain = OMatrix<E, N, K>; + + #[inline] + fn apply<I: Instance<OMatrix<E, M, K>>>(&self, x: I) -> Self::Codomain { + x.eval_ref(|refr| self.mul(refr)) + } +} + +impl<'a, SM, N, M, K, E> Linear<OMatrix<E, M, K>> for Matrix<E, N, M, SM> +where + SM: Storage<E, N, M>, + N: Dim, + M: Dim, + K: Dim, + E: Float + ClosedMulAssign + ClosedAddAssign, + DefaultAllocator: Allocator<N, K> + Allocator<M, K>, + ShapeConstraint: StridesOk<E, M, K> + StridesOk<E, N, K>, +{ +} + +impl<SM, SV2, N, M, K, E> GEMV<E, OMatrix<E, M, K>, Matrix<E, N, K, SV2>> for Matrix<E, N, M, SM> +where + SM: Storage<E, N, M>, + SV2: StorageMut<E, N, K>, + N: Dim, + M: Dim, + K: Dim, + E: Float, + DefaultAllocator: Allocator<N, K> + Allocator<M, K>, + ShapeConstraint: StridesOk<E, M, K> + StridesOk<E, N, K>, +{ + #[inline] + fn gemv<I: Instance<OMatrix<E, M, K>>>( + &self, y: &mut Matrix<E, N, K, SV2>, α: E, x: I, β: E ) { x.eval(|x̃| Matrix::gemm(y, α, self, x̃, β)) } #[inline] - fn apply_mut<'a, I : Instance<Matrix<E,M,K,SV1>>>(&self, y : &mut Matrix<E,N,K,SV2>, x : I) { + fn apply_mut<'a, I: Instance<OMatrix<E, M, K>>>(&self, y: &mut Matrix<E, N, K, SV2>, x: I) { x.eval(|x̃| self.mul_to(x̃, y)) } } -impl<SM,SV1,M,E> AXPY<E, Vector<E,M,SV1>> for Vector<E,M,SM> -where SM: StorageMut<E,M> + Clone, SV1: Storage<E,M> + Clone, - M : Dim, E : Scalar + Zero + One + Float, - DefaultAllocator : Allocator<M> { - type Owned = OVector<E, M>; +impl<S, M, N, E> VectorSpace for Matrix<E, M, N, S> +where + S: Storage<E, M, N>, + M: Dim, + N: Dim, + E: Float, + DefaultAllocator: Allocator<M, N>, + ShapeConstraint: StridesOk<E, M, N>, +{ + type Field = E; + type PrincipalV = OMatrix<E, M, N>; #[inline] - fn axpy<I : Instance<Vector<E,M,SV1>>>(&mut self, α : E, x : I, β : E) { - x.eval(|x̃| Matrix::axpy(self, α, x̃, β)) + fn similar_origin(&self) -> Self::PrincipalV { + let (n, m) = self.shape_generic(); + OMatrix::zeros_generic(n, m) + } +} + +// This can only be implemented for the “principal” OMatrix as parameter, as otherwise +// we run into problems of multiple implementations when calling the methods. +impl<M, N, E, S> AXPY<OMatrix<E, M, N>> for Matrix<E, M, N, S> +where + S: StorageMut<E, M, N>, + M: Dim, + N: Dim, + E: Float, + DefaultAllocator: Allocator<M, N>, + ShapeConstraint: StridesOk<E, M, N>, +{ + #[inline] + fn axpy<I: Instance<OMatrix<E, M, N>>>(&mut self, α: E, x: I, β: E) { + x.eval(|x̃| { + assert_eq!(self.ncols(), x̃.ncols()); + // nalgebra does not implement axpy for matrices, and flattenining + // also seems difficult, so loop over columns. + for (mut y, ỹ) in self.column_iter_mut().zip(x̃.column_iter()) { + Vector::axpy(&mut y, α, &ỹ, β) + } + }) } #[inline] - fn copy_from<I : Instance<Vector<E,M,SV1>>>(&mut self, y : I) { - y.eval(|ỹ| Matrix::copy_from(self, ỹ)) + fn copy_from<I: Instance<OMatrix<E, M, N>>>(&mut self, y: I) { + y.eval_ref(|ỹ| Matrix::copy_from(self, &ỹ)) } #[inline] fn set_zero(&mut self) { self.iter_mut().for_each(|e| *e = E::ZERO); } - - #[inline] - fn similar_origin(&self) -> Self::Owned { - OVector::zeros_generic(M::from_usize(self.len()), Const) - } } /* Implemented automatically as Euclidean. @@ -125,26 +408,52 @@ } }*/ -impl<SM,M,E> Projection<E, Linfinity> for Vector<E,M,SM> -where SM: StorageMut<E,M> + Clone, - M : Dim, E : Scalar + Zero + One + Float + RealField, - DefaultAllocator : Allocator<M> { +impl<SM, M, E> Projection<E, Linfinity> for Vector<E, M, SM> +where + SM: StorageMut<E, M>, + M: Dim, + E: Float + RealField, + DefaultAllocator: Allocator<M>, + ShapeConstraint: StridesOk<E, M>, +{ #[inline] - fn proj_ball_mut(&mut self, ρ : E, _ : Linfinity) { - self.iter_mut().for_each(|v| *v = num_traits::clamp(*v, -ρ, ρ)) + fn proj_ball(self, ρ: E, exp: Linfinity) -> <Self as Space>::Principal { + let mut owned = self.into_owned(); + owned.proj_ball_mut(ρ, exp); + owned } } -impl<'own,SV1,SV2,SM,N,M,K,E> Adjointable<Matrix<E,M,K,SV1>, Matrix<E,N,K,SV2>> -for Matrix<E,N,M,SM> -where SM: Storage<E,N,M>, SV1: Storage<E,M,K> + Clone, SV2: Storage<E,N,K> + Clone, - N : Dim, M : Dim, K : Dim, E : Scalar + Zero + One + SimdComplexField, - DefaultAllocator : Allocator<N,K>, - DefaultAllocator : Allocator<M,K>, - DefaultAllocator : Allocator<N,M>, - DefaultAllocator : Allocator<M,N> { - type AdjointCodomain = OMatrix<E,M,K>; - type Adjoint<'a> = OMatrix<E,M,N> where SM : 'a; +impl<SM, M, E> ProjectionMut<E, Linfinity> for Vector<E, M, SM> +where + SM: StorageMut<E, M>, + M: Dim, + E: Float + RealField, + DefaultAllocator: Allocator<M>, + ShapeConstraint: StridesOk<E, M>, +{ + #[inline] + fn proj_ball_mut(&mut self, ρ: E, _: Linfinity) { + self.iter_mut() + .for_each(|v| *v = num_traits::clamp(*v, -ρ, ρ)) + } +} + +impl<'own, SM, N, M, K, E> Adjointable<OMatrix<E, M, K>, OMatrix<E, N, K>> for Matrix<E, N, M, SM> +where + SM: Storage<E, N, M>, + N: Dim, + M: Dim, + K: Dim, + E: Float + RealField, + DefaultAllocator: Allocator<N, K> + Allocator<M, K> + Allocator<M, N>, + ShapeConstraint: StridesOk<E, N, K> + StridesOk<E, M, K>, +{ + type AdjointCodomain = OMatrix<E, M, K>; + type Adjoint<'a> + = OMatrix<E, M, N> + where + SM: 'a; #[inline] fn adjoint(&self) -> Self::Adjoint<'_> { @@ -152,6 +461,25 @@ } } +impl<'own, SM, N, M, K, E> SimplyAdjointable<OMatrix<E, M, K>, OMatrix<E, N, K>> + for Matrix<E, N, M, SM> +where + SM: Storage<E, N, M>, + N: Dim, + M: Dim, + K: Dim, + E: Float + RealField, + DefaultAllocator: Allocator<N, K> + Allocator<M, K> + Allocator<M, N>, + ShapeConstraint: StridesOk<E, N, K> + StridesOk<E, M, K>, +{ + type AdjointCodomain = OMatrix<E, M, K>; + type SimpleAdjoint = OMatrix<E, M, N>; + + #[inline] + fn adjoint(&self) -> Self::SimpleAdjoint { + Matrix::adjoint(self) + } +} /// This function is [`nalgebra::EuclideanNorm::metric_distance`] without the `sqrt`. #[inline] fn metric_distance_squared<T, R1, C1, S1, R2, C2, S2>( @@ -160,7 +488,7 @@ m2: &Matrix<T, R2, C2, S2>, ) -> T::SimdRealField where - T: SimdComplexField, + T: SimdComplexField, R1: Dim, C1: Dim, S1: Storage<T, R1, C1>, @@ -175,40 +503,41 @@ }) } -// TODO: should allow different input storages in `Euclidean`. - -impl<E,M,S> Euclidean<E> -for Vector<E,M,S> -where M : Dim, - S : StorageMut<E,M> + Clone, - E : Float + Scalar + Zero + One + RealField, - DefaultAllocator : Allocator<M> { - - type Output = OVector<E, M>; +impl<E, M, N, S> Euclidean<E> for Matrix<E, M, N, S> +where + M: Dim, + N: Dim, + S: Storage<E, M, N>, + E: Float + RealField, + DefaultAllocator: Allocator<M, N>, + ShapeConstraint: StridesOk<E, M, N>, +{ + type PrincipalE = OMatrix<E, M, N>; #[inline] - fn dot<I : Instance<Self>>(&self, other : I) -> E { - Vector::<E,M,S>::dot(self, other.ref_instance()) + fn dot<I: Instance<Self>>(&self, other: I) -> E { + other.eval_ref(|ref r| Matrix::<E, M, N, S>::dot(self, r)) } #[inline] fn norm2_squared(&self) -> E { - Vector::<E,M,S>::norm_squared(self) + Matrix::<E, M, N, S>::norm_squared(self) } #[inline] - fn dist2_squared<I : Instance<Self>>(&self, other : I) -> E { - metric_distance_squared(self, other.ref_instance()) + fn dist2_squared<I: Instance<Self>>(&self, other: I) -> E { + other.eval_ref(|ref r| metric_distance_squared(self, r)) } } -impl<E,M,S> StaticEuclidean<E> -for Vector<E,M,S> -where M : DimName, - S : StorageMut<E,M> + Clone, - E : Float + Scalar + Zero + One + RealField, - DefaultAllocator : Allocator<M> { - +impl<E, M, S> StaticEuclidean<E> for Vector<E, M, S> +where + M: DimName, + S: Storage<E, M>, + E: Float + RealField, + DefaultAllocator: Allocator<M>, + ShapeConstraint: StridesOk<E, M>, +{ #[inline] fn origin() -> OVector<E, M> { OVector::zeros() @@ -216,13 +545,15 @@ } /// The default norm for `Vector` is [`L2`]. -impl<E,M,S> Normed<E> -for Vector<E,M,S> -where M : Dim, - S : Storage<E,M> + Clone, - E : Float + Scalar + Zero + One + RealField, - DefaultAllocator : Allocator<M> { - +impl<E, M, N, S> Normed<E> for Matrix<E, M, N, S> +where + M: Dim, + N: Dim, + S: Storage<E, M, N>, + E: Float + RealField, + DefaultAllocator: Allocator<M, N>, + ShapeConstraint: StridesOk<E, M, N>, +{ type NormExp = L2; #[inline] @@ -232,92 +563,106 @@ #[inline] fn is_zero(&self) -> bool { - Vector::<E,M,S>::norm_squared(self) == E::ZERO + Matrix::<E, M, N, S>::norm_squared(self) == E::ZERO } } -impl<E,M,S> HasDual<E> -for Vector<E,M,S> -where M : Dim, - S : Storage<E,M> + Clone, - E : Float + Scalar + Zero + One + RealField, - DefaultAllocator : Allocator<M> { - // TODO: Doesn't work with different storage formats. - type DualSpace = Self; +impl<E, M, N, S> HasDual<E> for Matrix<E, M, N, S> +where + M: Dim, + N: Dim, + S: Storage<E, M, N>, + E: Float + RealField, + DefaultAllocator: Allocator<M, N>, + ShapeConstraint: StridesOk<E, M, N>, +{ + type DualSpace = OMatrix<E, M, N>; + + fn dual_origin(&self) -> OMatrix<E, M, N> { + let (m, n) = self.shape_generic(); + OMatrix::zeros_generic(m, n) + } } -impl<E,M,S> Norm<E, L1> -for Vector<E,M,S> -where M : Dim, - S : Storage<E,M>, - E : Float + Scalar + Zero + One + RealField, - DefaultAllocator : Allocator<M> { - +impl<E, M, S> Norm<L1, E> for Vector<E, M, S> +where + M: Dim, + S: Storage<E, M>, + E: Float + RealField, +{ #[inline] - fn norm(&self, _ : L1) -> E { + fn norm(&self, _: L1) -> E { nalgebra::Norm::norm(&LpNorm(1), self) } } -impl<E,M,S> Dist<E, L1> -for Vector<E,M,S> -where M : Dim, - S : Storage<E,M> + Clone, - E : Float + Scalar + Zero + One + RealField, - DefaultAllocator : Allocator<M> { +impl<E, M, S> Dist<L1, E> for Vector<E, M, S> +where + M: Dim, + S: Storage<E, M>, + E: Float + RealField, + DefaultAllocator: Allocator<M>, + ShapeConstraint: StridesOk<E, M>, +{ #[inline] - fn dist<I : Instance<Self>>(&self, other : I, _ : L1) -> E { - nalgebra::Norm::metric_distance(&LpNorm(1), self, other.ref_instance()) + fn dist<I: Instance<Self>>(&self, other: I, _: L1) -> E { + other.eval_ref(|ref r| nalgebra::Norm::metric_distance(&LpNorm(1), self, r)) } } -impl<E,M,S> Norm<E, L2> -for Vector<E,M,S> -where M : Dim, - S : Storage<E,M>, - E : Float + Scalar + Zero + One + RealField, - DefaultAllocator : Allocator<M> { - +impl<E, M, N, S> Norm<L2, E> for Matrix<E, M, N, S> +where + M: Dim, + N: Dim, + S: Storage<E, M, N>, + E: Float + RealField, +{ #[inline] - fn norm(&self, _ : L2) -> E { + fn norm(&self, _: L2) -> E { nalgebra::Norm::norm(&LpNorm(2), self) } } -impl<E,M,S> Dist<E, L2> -for Vector<E,M,S> -where M : Dim, - S : Storage<E,M> + Clone, - E : Float + Scalar + Zero + One + RealField, - DefaultAllocator : Allocator<M> { +impl<E, M, N, S> Dist<L2, E> for Matrix<E, M, N, S> +where + M: Dim, + N: Dim, + S: Storage<E, M, N>, + E: Float + RealField, + DefaultAllocator: Allocator<M, N>, + ShapeConstraint: StridesOk<E, M, N>, +{ #[inline] - fn dist<I : Instance<Self>>(&self, other : I, _ : L2) -> E { - nalgebra::Norm::metric_distance(&LpNorm(2), self, other.ref_instance()) + fn dist<I: Instance<Self>>(&self, other: I, _: L2) -> E { + other.eval_ref(|ref r| nalgebra::Norm::metric_distance(&LpNorm(2), self, r)) } } -impl<E,M,S> Norm<E, Linfinity> -for Vector<E,M,S> -where M : Dim, - S : Storage<E,M>, - E : Float + Scalar + Zero + One + RealField, - DefaultAllocator : Allocator<M> { - +impl<E, M, N, S> Norm<Linfinity, E> for Matrix<E, M, N, S> +where + M: Dim, + N: Dim, + S: Storage<E, M, N>, + E: Float + RealField, +{ #[inline] - fn norm(&self, _ : Linfinity) -> E { + fn norm(&self, _: Linfinity) -> E { nalgebra::Norm::norm(&UniformNorm, self) } } -impl<E,M,S> Dist<E, Linfinity> -for Vector<E,M,S> -where M : Dim, - S : Storage<E,M> + Clone, - E : Float + Scalar + Zero + One + RealField, - DefaultAllocator : Allocator<M> { +impl<E, M, N, S> Dist<Linfinity, E> for Matrix<E, M, N, S> +where + M: Dim, + N: Dim, + S: Storage<E, M, N>, + E: Float + RealField, + DefaultAllocator: Allocator<M, N>, + ShapeConstraint: StridesOk<E, M, N>, +{ #[inline] - fn dist<I : Instance<Self>>(&self, other : I, _ : Linfinity) -> E { - nalgebra::Norm::metric_distance(&UniformNorm, self, other.ref_instance()) + fn dist<I: Instance<Self>>(&self, other: I, _: Linfinity) -> E { + other.eval_ref(|ref r| nalgebra::Norm::metric_distance(&UniformNorm, self, r)) } } @@ -329,15 +674,15 @@ /// from [`nalgebra`] conflicting with them. Only when absolutely necessary to work with /// nalgebra, one can convert to the nalgebra view of the same type using the methods of /// this trait. -pub trait ToNalgebraRealField : Float { +pub trait ToNalgebraRealField: Float { /// The nalgebra type corresponding to this type. Usually same as `Self`. /// /// This type only carries `nalgebra` traits. - type NalgebraType : RealField; + type NalgebraType: RealField; /// The “mixed” type corresponding to this type. Usually same as `Self`. /// /// This type carries both `num_traits` and `nalgebra` traits. - type MixedType : RealField + Float; + type MixedType: RealField + Float; /// Convert to the nalgebra view of `self`. fn to_nalgebra(self) -> Self::NalgebraType; @@ -346,10 +691,10 @@ fn to_nalgebra_mixed(self) -> Self::MixedType; /// Convert from the nalgebra view of `self`. - fn from_nalgebra(t : Self::NalgebraType) -> Self; + fn from_nalgebra(t: Self::NalgebraType) -> Self; /// Convert from the mixed (nalgebra and num_traits) view to `self`. - fn from_nalgebra_mixed(t : Self::MixedType) -> Self; + fn from_nalgebra_mixed(t: Self::MixedType) -> Self; } impl ToNalgebraRealField for f32 { @@ -357,17 +702,24 @@ type MixedType = f32; #[inline] - fn to_nalgebra(self) -> Self::NalgebraType { self } - - #[inline] - fn to_nalgebra_mixed(self) -> Self::MixedType { self } + fn to_nalgebra(self) -> Self::NalgebraType { + self + } #[inline] - fn from_nalgebra(t : Self::NalgebraType) -> Self { t } + fn to_nalgebra_mixed(self) -> Self::MixedType { + self + } #[inline] - fn from_nalgebra_mixed(t : Self::MixedType) -> Self { t } + fn from_nalgebra(t: Self::NalgebraType) -> Self { + t + } + #[inline] + fn from_nalgebra_mixed(t: Self::MixedType) -> Self { + t + } } impl ToNalgebraRealField for f64 { @@ -375,15 +727,22 @@ type MixedType = f64; #[inline] - fn to_nalgebra(self) -> Self::NalgebraType { self } + fn to_nalgebra(self) -> Self::NalgebraType { + self + } #[inline] - fn to_nalgebra_mixed(self) -> Self::MixedType { self } + fn to_nalgebra_mixed(self) -> Self::MixedType { + self + } #[inline] - fn from_nalgebra(t : Self::NalgebraType) -> Self { t } + fn from_nalgebra(t: Self::NalgebraType) -> Self { + t + } #[inline] - fn from_nalgebra_mixed(t : Self::MixedType) -> Self { t } + fn from_nalgebra_mixed(t: Self::MixedType) -> Self { + t + } } -
--- a/src/norms.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/norms.rs Fri May 15 14:46:30 2026 -0500 @@ -2,79 +2,84 @@ Norms, projections, etc. */ -use serde::Serialize; -use std::marker::PhantomData; +use crate::euclidean::*; +use crate::instance::Ownable; +use crate::linops::{ClosedVectorSpace, VectorSpace}; +use crate::mapping::{Instance, Mapping, Space}; use crate::types::*; -use crate::euclidean::*; -use crate::mapping::{Mapping, Space, Instance}; +use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; // // Abstract norms // -#[derive(Copy,Clone,Debug)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] /// Helper structure to convert a [`NormExponent`] into a [`Mapping`] -pub struct NormMapping<F : Float, E : NormExponent>{ - pub(crate) exponent : E, - _phantoms : PhantomData<F> +pub struct NormMapping<F: Float, E: NormExponent> { + pub(crate) exponent: E, + _phantoms: PhantomData<F>, } /// An exponent for norms. /// // Just a collection of desirable attributes for a marker type -pub trait NormExponent : Copy + Send + Sync + 'static { +pub trait NormExponent: Copy { /// Return the norm as a mappin - fn as_mapping<F : Float>(self) -> NormMapping<F, Self> { - NormMapping{ exponent : self, _phantoms : PhantomData } + fn as_mapping<F: Float>(self) -> NormMapping<F, Self> { + NormMapping { exponent: self, _phantoms: PhantomData } } } /// Exponent type for the 1-[`Norm`]. -#[derive(Copy,Debug,Clone,Serialize,Eq,PartialEq)] +#[derive(Copy, Debug, Clone, Serialize, Eq, PartialEq)] pub struct L1; impl NormExponent for L1 {} /// Exponent type for the 2-[`Norm`]. -#[derive(Copy,Debug,Clone,Serialize,Eq,PartialEq)] +#[derive(Copy, Debug, Clone, Serialize, Eq, PartialEq)] pub struct L2; impl NormExponent for L2 {} /// Exponent type for the ∞-[`Norm`]. -#[derive(Copy,Debug,Clone,Serialize,Eq,PartialEq)] +#[derive(Copy, Debug, Clone, Serialize, Eq, PartialEq)] pub struct Linfinity; impl NormExponent for Linfinity {} /// Exponent type for 2,1-[`Norm`]. /// (1-norm over a domain Ω, 2-norm of a vector at each point of the domain.) -#[derive(Copy,Debug,Clone,Serialize,Eq,PartialEq)] +#[derive(Copy, Debug, Clone, Serialize, Eq, PartialEq)] pub struct L21; impl NormExponent for L21 {} /// Norms for pairs (a, b). ‖(a,b)‖ = ‖(‖a‖_A, ‖b‖_B)‖_J /// For use with [`crate::direct_product::Pair`] -#[derive(Copy,Debug,Clone,Serialize,Eq,PartialEq)] +#[derive(Copy, Debug, Clone, Serialize, Eq, PartialEq)] pub struct PairNorm<A, B, J>(pub A, pub B, pub J); impl<A, B, J> NormExponent for PairNorm<A, B, J> -where A : NormExponent, B : NormExponent, J : NormExponent {} - +where + A: NormExponent, + B: NormExponent, + J: NormExponent, +{ +} /// A Huber/Moreau–Yosida smoothed [`L1`] norm. (Not a norm itself.) /// /// The parameter γ of this type is the smoothing factor. Zero means no smoothing, and higher /// values more smoothing. Behaviour with γ < 0 is undefined. -#[derive(Copy,Debug,Clone,Serialize,Eq,PartialEq)] -pub struct HuberL1<F : Float>(pub F); -impl<F : Float> NormExponent for HuberL1<F> {} +#[derive(Copy, Debug, Clone, Serialize, Eq, PartialEq)] +pub struct HuberL1<F: Float>(pub F); +impl<F: Float> NormExponent for HuberL1<F> {} /// A Huber/Moreau–Yosida smoothed [`L21`] norm. (Not a norm itself.) /// /// The parameter γ of this type is the smoothing factor. Zero means no smoothing, and higher /// values more smoothing. Behaviour with γ < 0 is undefined. -#[derive(Copy,Debug,Clone,Serialize,Eq,PartialEq)] -pub struct HuberL21<F : Float>(pub F); -impl<F : Float> NormExponent for HuberL21<F> {} - +#[derive(Copy, Debug, Clone, Serialize, Eq, PartialEq)] +pub struct HuberL21<F: Float>(pub F); +impl<F: Float> NormExponent for HuberL21<F> {} /// A normed space (type) with exponent or other type `Exponent` for the norm. /// @@ -86,27 +91,27 @@ /// /// println!("{}, {} {}", x.norm(L1), x.norm(L2), x.norm(Linfinity)) /// ``` -pub trait Norm<F : Num, Exponent : NormExponent> { +pub trait Norm<Exponent: NormExponent, F: Num = f64> { /// Calculate the norm. - fn norm(&self, _p : Exponent) -> F; + fn norm(&self, _p: Exponent) -> F; } /// Indicates that the `Self`-[`Norm`] is dominated by the `Exponent`-`Norm` on the space /// `Elem` with the corresponding field `F`. -pub trait Dominated<F : Num, Exponent : NormExponent, Elem> { +pub trait Dominated<F: Num, Exponent: NormExponent, Elem> { /// Indicates the factor $c$ for the inequality $‖x‖ ≤ C ‖x‖_p$. - fn norm_factor(&self, p : Exponent) -> F; + fn norm_factor(&self, p: Exponent) -> F; /// Given a norm-value $‖x‖_p$, calculates $C‖x‖_p$ such that $‖x‖ ≤ C‖x‖_p$ #[inline] - fn from_norm(&self, p_norm : F, p : Exponent) -> F { + fn from_norm(&self, p_norm: F, p: Exponent) -> F { p_norm * self.norm_factor(p) } } /// Trait for distances with respect to a norm. -pub trait Dist<F : Num, Exponent : NormExponent> : Norm<F, Exponent> + Space { +pub trait Dist<Exponent: NormExponent, F: Num = f64>: Norm<Exponent, F> + Space { /// Calculate the distance - fn dist<I : Instance<Self>>(&self, other : I, _p : Exponent) -> F; + fn dist<I: Instance<Self>>(&self, other: I, _p: Exponent) -> F; } /// Trait for Euclidean projections to the `Exponent`-[`Norm`]-ball. @@ -119,44 +124,48 @@ /// /// println!("{:?}, {:?}", x.proj_ball(1.0, L2), x.proj_ball(0.5, Linfinity)); /// ``` -pub trait Projection<F : Num, Exponent : NormExponent> : Norm<F, Exponent> + Sized -where F : Float { +pub trait Projection<F: Num, Exponent: NormExponent>: Ownable + Norm<Exponent, F> { /// Projection of `self` to the `q`-norm-ball of radius ρ. - fn proj_ball(mut self, ρ : F, q : Exponent) -> Self { - self.proj_ball_mut(ρ, q); - self - } - - /// In-place projection of `self` to the `q`-norm-ball of radius ρ. - fn proj_ball_mut(&mut self, ρ : F, q : Exponent); + fn proj_ball(self, ρ: F, q: Exponent) -> Self::OwnedVariant; } -/*impl<F : Float, E : Euclidean<F>> Norm<F, L2> for E { +pub trait ProjectionMut<F: Num, Exponent: NormExponent>: Projection<F, Exponent> { + /// In-place projection of `self` to the `q`-norm-ball of radius ρ. + fn proj_ball_mut(&mut self, ρ: F, q: Exponent); +} + +/*impl<F : Float, E : Euclidean<F>> Norm<L2, F> for E { #[inline] fn norm(&self, _p : L2) -> F { self.norm2() } fn dist(&self, other : &Self, _p : L2) -> F { self.dist2(other) } }*/ -impl<F : Float, E : Euclidean<F> + Norm<F, L2>> Projection<F, L2> for E { +impl<F: Float, E: Euclidean<F> + Norm<L2, F>> Projection<F, L2> for E { #[inline] - fn proj_ball(self, ρ : F, _p : L2) -> Self { self.proj_ball2(ρ) } - - #[inline] - fn proj_ball_mut(&mut self, ρ : F, _p : L2) { self.proj_ball2_mut(ρ) } + fn proj_ball(self, ρ: F, _p: L2) -> Self::OwnedVariant { + self.proj_ball2(ρ) + } } -impl<F : Float> HuberL1<F> { - fn apply(self, xnsq : F) -> F { +impl<F: Float, E: EuclideanMut<F> + Norm<L2, F>> ProjectionMut<F, L2> for E { + #[inline] + fn proj_ball_mut(&mut self, ρ: F, _p: L2) { + self.proj_ball2_mut(ρ) + } +} + +impl<F: Float> HuberL1<F> { + fn apply(self, xnsq: F) -> F { let HuberL1(γ) = self; let xn = xnsq.sqrt(); if γ == F::ZERO { xn } else { if xn > γ { - xn-γ / F::TWO - } else if xn<(-γ) { - -xn-γ / F::TWO + xn - γ / F::TWO + } else if xn < (-γ) { + -xn - γ / F::TWO } else { xnsq / (F::TWO * γ) } @@ -164,25 +173,25 @@ } } -impl<F : Float, E : Euclidean<F>> Norm<F, HuberL1<F>> for E { - fn norm(&self, huber : HuberL1<F>) -> F { +impl<F: Float, E: Euclidean<F> + Normed<F, NormExp = L2>> Norm<HuberL1<F>, F> for E { + fn norm(&self, huber: HuberL1<F>) -> F { huber.apply(self.norm2_squared()) } } -impl<F : Float, E : Euclidean<F>> Dist<F, HuberL1<F>> for E { - fn dist<I : Instance<Self>>(&self, other : I, huber : HuberL1<F>) -> F { +impl<F: Float, E: Euclidean<F> + Normed<F, NormExp = L2>> Dist<HuberL1<F>, F> for E { + fn dist<I: Instance<Self>>(&self, other: I, huber: HuberL1<F>) -> F { huber.apply(self.dist2_squared(other)) } } -// impl<F : Float, E : Norm<F, L2>> Norm<F, L21> for Vec<E> { +// impl<F : Float, E : Norm<L2, F>> Norm<L21, F> for Vec<E> { // fn norm(&self, _l21 : L21) -> F { // self.iter().map(|e| e.norm(L2)).sum() // } // } -// impl<F : Float, E : Dist<F, L2>> Dist<F, L21> for Vec<E> { +// impl<F : Float, E : Dist<F, L2>> Dist<L21, F> for Vec<E> { // fn dist<I : Instance<Self>>(&self, other : I, _l21 : L21) -> F { // self.iter().zip(other.iter()).map(|(e, g)| e.dist(g, L2)).sum() // } @@ -190,20 +199,21 @@ impl<E, F, Domain> Mapping<Domain> for NormMapping<F, E> where - F : Float, - E : NormExponent, - Domain : Space + Norm<F, E>, + F: Float, + E: NormExponent, + Domain: Space, + Domain::Principal: Norm<E, F>, { type Codomain = F; #[inline] - fn apply<I : Instance<Domain>>(&self, x : I) -> F { + fn apply<I: Instance<Domain>>(&self, x: I) -> F { x.eval(|r| r.norm(self.exponent)) } } -pub trait Normed<F : Num = f64> : Space + Norm<F, Self::NormExp> { - type NormExp : NormExponent; +pub trait Normed<F: Num = f64>: Space + Norm<Self::NormExp, F> { + type NormExp: NormExponent; fn norm_exponent(&self) -> Self::NormExp; @@ -214,33 +224,38 @@ // fn similar_origin(&self) -> Self; - fn is_zero(&self) -> bool; + fn is_zero(&self) -> bool { + self.norm_() == F::ZERO + } } -pub trait HasDual<F : Num = f64> : Normed<F> { - type DualSpace : Normed<F>; +pub trait HasDual<F: Num = f64>: Normed<F> + VectorSpace<Field = F> { + type DualSpace: Normed<F> + ClosedVectorSpace<Field = F>; + + fn dual_origin(&self) -> <Self::DualSpace as VectorSpace>::PrincipalV; } /// Automatically implemented trait for reflexive spaces -pub trait Reflexive<F : Num = f64> : HasDual<F> +pub trait Reflexive<F: Num = f64>: HasDual<F> where - Self::DualSpace : HasDual<F, DualSpace = Self> -{ } + Self::DualSpace: HasDual<F, DualSpace = Self::Principal>, +{ +} -impl<F : Num, X : HasDual<F>> Reflexive<F> for X -where - X::DualSpace : HasDual<F, DualSpace = X> -{ } +impl<F: Num, X: HasDual<F>> Reflexive<F> for X where + X::DualSpace: HasDual<F, DualSpace = Self::Principal> +{ +} -pub trait HasDualExponent : NormExponent { - type DualExp : NormExponent; +pub trait HasDualExponent: NormExponent { + type DualExp: NormExponent; fn dual_exponent(&self) -> Self::DualExp; } impl HasDualExponent for L2 { type DualExp = L2; - + #[inline] fn dual_exponent(&self) -> Self::DualExp { L2 @@ -249,17 +264,16 @@ impl HasDualExponent for L1 { type DualExp = Linfinity; - + #[inline] fn dual_exponent(&self) -> Self::DualExp { Linfinity } } - impl HasDualExponent for Linfinity { type DualExp = L1; - + #[inline] fn dual_exponent(&self) -> Self::DualExp { L1 @@ -271,49 +285,50 @@ ($exponent : ty) => { impl<C, F, D> Norm<F, Weighted<$exponent, C>> for D where - F : Float, - D : Norm<F, $exponent>, - C : Constant<Type = F>, + F: Float, + D: Norm<$exponent, F>, + C: Constant<Type = F>, { - fn norm(&self, e : Weighted<$exponent, C>) -> F { + fn norm(&self, e: Weighted<$exponent, C>) -> F { let v = e.weight.value(); assert!(v > F::ZERO); v * self.norm(e.base_fn) } } - impl<C : Constant> NormExponent for Weighted<$exponent, C> {} + impl<C: Constant> NormExponent for Weighted<$exponent, C> {} - impl<C : Constant> HasDualExponent for Weighted<$exponent, C> - where $exponent : HasDualExponent { + impl<C: Constant> HasDualExponent for Weighted<$exponent, C> + where + $exponent: HasDualExponent, + { type DualExp = Weighted<<$exponent as HasDualExponent>::DualExp, C::Type>; fn dual_exponent(&self) -> Self::DualExp { Weighted { - weight : C::Type::ONE / self.weight.value(), - base_fn : self.base_fn.dual_exponent() + weight: C::Type::ONE / self.weight.value(), + base_fn: self.base_fn.dual_exponent(), } } } - impl<C, F, T> Projection<F, Weighted<$exponent , C>> for T + impl<C, F, T> Projection<F, Weighted<$exponent, C>> for T where - T : Projection<F, $exponent >, - F : Float, - C : Constant<Type = F>, + T: Projection<F, $exponent>, + F: Float, + C: Constant<Type = F>, { - fn proj_ball(self, ρ : F, q : Weighted<$exponent , C>) -> Self { + fn proj_ball(self, ρ: F, q: Weighted<$exponent, C>) -> Self { self.proj_ball(ρ / q.weight.value(), q.base_fn) } - fn proj_ball_mut(&mut self, ρ : F, q : Weighted<$exponent , C>) { + fn proj_ball_mut(&mut self, ρ: F, q: Weighted<$exponent, C>) { self.proj_ball_mut(ρ / q.weight.value(), q.base_fn) } } - } + }; } //impl_weighted_norm!(L1); //impl_weighted_norm!(L2); //impl_weighted_norm!(Linfinity); -
--- a/src/operator_arithmetic.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/operator_arithmetic.rs Fri May 15 14:46:30 2026 -0500 @@ -2,72 +2,74 @@ Arithmetic of [`Mapping`]s. */ -use serde::Serialize; +use crate::instance::{ClosedSpace, Instance, Space}; +use crate::mapping::{DifferentiableImpl, DifferentiableMapping, Mapping}; use crate::types::*; -use crate::instance::{Space, Instance}; -use crate::mapping::{Mapping, DifferentiableImpl, DifferentiableMapping}; +use serde::Serialize; /// A trait for encoding constant [`Float`] values -pub trait Constant : Copy + Sync + Send + 'static + std::fmt::Debug + Into<Self::Type> { +pub trait Constant: Copy + Sync + Send + 'static + std::fmt::Debug + Into<Self::Type> { /// The type of the value - type Type : Float; + type Type: Float; /// Returns the value of the constant fn value(&self) -> Self::Type; } -impl<F : Float> Constant for F { +impl<F: Float> Constant for F { type Type = F; #[inline] - fn value(&self) -> F { *self } + fn value(&self) -> F { + *self + } } /// Weighting of a [`Mapping`] by scalar multiplication. -#[derive(Copy,Clone,Debug,Serialize)] -pub struct Weighted<T, C : Constant> { +#[derive(Copy, Clone, Debug, Serialize)] +pub struct Weighted<T, C: Constant> { /// The weight - pub weight : C, + pub weight: C, /// The base [`Mapping`] being weighted. - pub base_fn : T, + pub base_fn: T, } impl<T, C> Weighted<T, C> where - C : Constant, + C: Constant, { /// Construct from an iterator. - pub fn new(weight : C, base_fn : T) -> Self { - Weighted{ weight, base_fn } + pub fn new(weight: C, base_fn: T) -> Self { + Weighted { weight, base_fn } } } -impl<'a, T, V, D, F, C> Mapping<D> for Weighted<T, C> +impl<'a, T, D, F, C> Mapping<D> for Weighted<T, C> where - F : Float, - D : Space, - T : Mapping<D, Codomain=V>, - V : Space + ClosedMul<F>, - C : Constant<Type=F> + F: Float, + D: Space, + T: Mapping<D>, + T::Codomain: ClosedMul<F>, + C: Constant<Type = F>, { - type Codomain = V; + type Codomain = T::Codomain; #[inline] - fn apply<I : Instance<D>>(&self, x : I) -> Self::Codomain { + fn apply<I: Instance<D>>(&self, x: I) -> Self::Codomain { self.base_fn.apply(x) * self.weight.value() } } impl<'a, T, V, D, F, C> DifferentiableImpl<D> for Weighted<T, C> where - F : Float, - D : Space, - T : DifferentiableMapping<D, DerivativeDomain=V>, - V : Space + std::ops::Mul<F, Output=V>, - C : Constant<Type=F> + F: Float, + D: Space, + T: DifferentiableMapping<D, DerivativeDomain = V>, + V: ClosedSpace + std::ops::Mul<F, Output = V>, + C: Constant<Type = F>, { type Derivative = V; #[inline] - fn differential_impl<I : Instance<D>>(&self, x : I) -> Self::Derivative { + fn differential_impl<I: Instance<D>>(&self, x: I) -> Self::Derivative { self.base_fn.differential(x) * self.weight.value() } } @@ -76,9 +78,9 @@ #[derive(Serialize, Debug, Clone)] pub struct MappingSum<M>(Vec<M>); -impl< M> MappingSum<M> { +impl<M> MappingSum<M> { /// Construct from an iterator. - pub fn new<I : IntoIterator<Item = M>>(iter : I) -> Self { + pub fn new<I: IntoIterator<Item = M>>(iter: I) -> Self { MappingSum(iter.into_iter().collect()) } @@ -90,28 +92,26 @@ impl<Domain, M> Mapping<Domain> for MappingSum<M> where - Domain : Space + Clone, - M : Mapping<Domain>, - M::Codomain : std::iter::Sum + Clone + Domain: Space + Clone, + M: Mapping<Domain>, + M::Codomain: std::iter::Sum + Clone, { type Codomain = M::Codomain; - fn apply<I : Instance<Domain>>(&self, x : I) -> Self::Codomain { - let xr = x.ref_instance(); - self.0.iter().map(|c| c.apply(xr)).sum() + fn apply<I: Instance<Domain>>(&self, x: I) -> Self::Codomain { + x.eval_ref(|xr| self.0.iter().map(|c| c.apply(xr)).sum()) } } -impl<Domain, M> DifferentiableImpl<Domain> for MappingSum< M> +impl<Domain, M> DifferentiableImpl<Domain> for MappingSum<M> where - Domain : Space + Clone, - M : DifferentiableMapping<Domain>, - M :: DerivativeDomain : std::iter::Sum + Domain: Space, + M: DifferentiableMapping<Domain>, + M::DerivativeDomain: std::iter::Sum, { type Derivative = M::DerivativeDomain; - fn differential_impl<I : Instance<Domain>>(&self, x : I) -> Self::Derivative { - let xr = x.ref_instance(); - self.0.iter().map(|c| c.differential(xr)).sum() + fn differential_impl<I: Instance<Domain>>(&self, x: I) -> Self::Derivative { + x.eval_ref(|xr| self.0.iter().map(|c| c.differential(xr)).sum()) } }
--- a/src/parallelism.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/parallelism.rs Fri May 15 14:46:30 2026 -0500 @@ -7,24 +7,24 @@ For actually spawning scoped tasks in a thread pool, it currently uses [`rayon`]. */ -use std::sync::Once; +pub use rayon::{Scope, ThreadPool, ThreadPoolBuilder}; use std::num::NonZeroUsize; -use std::thread::available_parallelism; -pub use rayon::{Scope, ThreadPoolBuilder, ThreadPool}; use std::sync::atomic::{ AtomicUsize, - Ordering::{Release, Relaxed}, + Ordering::{Relaxed, Release}, }; +use std::sync::Once; +use std::thread::available_parallelism; #[cfg(feature = "use_custom_thread_pool")] type Pool = ThreadPool; #[cfg(not(feature = "use_custom_thread_pool"))] type Pool = GlobalPool; -const ONE : NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(1) }; -static mut TASK_OVERBUDGETING : AtomicUsize = AtomicUsize::new(1); -static mut N_THREADS : NonZeroUsize = ONE; -static mut POOL : Option<Pool> = None; +const ONE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(1) }; +static mut TASK_OVERBUDGETING: AtomicUsize = AtomicUsize::new(1); +static mut N_THREADS: NonZeroUsize = ONE; +static mut POOL: Option<Pool> = None; static INIT: Once = Once::new(); #[cfg(not(feature = "use_custom_thread_pool"))] @@ -38,7 +38,8 @@ pub fn scope<'scope, OP, R>(&self, op: OP) -> R where OP: FnOnce(&rayon::Scope<'scope>) -> R + Send, - R: Send { + R: Send, + { rayon::scope(op) } } @@ -51,17 +52,22 @@ /// /// This routine can only be called once. /// It also calls [`set_task_overbudgeting`] with $m = (n + 1) / 2$. -pub fn set_num_threads(n : NonZeroUsize) { +pub fn set_num_threads(n: NonZeroUsize) { INIT.call_once(|| unsafe { N_THREADS = n; let n = n.get(); set_task_overbudgeting((n + 1) / 2); POOL = if n > 1 { - #[cfg(feature = "use_custom_thread_pool")] { + #[cfg(feature = "use_custom_thread_pool")] + { Some(ThreadPoolBuilder::new().num_threads(n).build().unwrap()) } - #[cfg(not(feature = "use_custom_thread_pool"))] { - ThreadPoolBuilder::new().num_threads(n).build_global().unwrap(); + #[cfg(not(feature = "use_custom_thread_pool"))] + { + ThreadPoolBuilder::new() + .num_threads(n) + .build_global() + .unwrap(); Some(GlobalPool) } } else { @@ -74,20 +80,21 @@ /// /// The initial value is 1. Calling [`set_num_threads`] sets this to $m = (n + 1) / 2$, where /// $n$ is the number of threads. -pub fn set_task_overbudgeting(m : usize) { +pub fn set_task_overbudgeting(m: usize) { #[allow(static_mut_refs)] - unsafe { TASK_OVERBUDGETING.store(m, Relaxed) } + unsafe { + TASK_OVERBUDGETING.store(m, Relaxed) + } } /// Set the number of threads to the minimum of `n` and [`available_parallelism`]. /// /// This routine can only be called once. -pub fn set_max_threads(n : NonZeroUsize) { +pub fn set_max_threads(n: NonZeroUsize) { let available = available_parallelism().unwrap_or(ONE); set_num_threads(available.min(n)); } - /// Get the number of threads pub fn num_threads() -> NonZeroUsize { unsafe { N_THREADS } @@ -99,7 +106,9 @@ /// The pool has [`num_threads`]` - 1` threads. pub fn thread_pool() -> Option<&'static Pool> { #[allow(static_mut_refs)] - unsafe { POOL.as_ref() } + unsafe { + POOL.as_ref() + } } /// Get the number of thread pool workers. @@ -119,25 +128,25 @@ /// Initial multi-threaded state MultiThreadedInitial { /// Thread budget counter - budget : AtomicUsize, + budget: AtomicUsize, /// Thread pool - pool : &'scheduler Pool, + pool: &'scheduler Pool, }, /// Nested multi-threaded state MultiThreadedZoom { /// Thread budget reference - budget : &'scope AtomicUsize, - scope : &'scheduler Scope<'scope>, - } + budget: &'scope AtomicUsize, + scope: &'scheduler Scope<'scope>, + }, } /// Task execution scope for [`TaskBudget`]. pub enum TaskBudgetScope<'scope, 'scheduler> { SingleThreaded, MultiThreaded { - budget : &'scope AtomicUsize, - scope : &'scheduler Scope<'scope>, - } + budget: &'scope AtomicUsize, + scope: &'scheduler Scope<'scope>, + }, } impl<'scope, 'b> TaskBudget<'scope, 'b> { @@ -146,7 +155,7 @@ /// The number of tasks [executed][TaskBudgetScope::execute] in [scopes][TaskBudget::zoom] /// created through the budget is limited to [`num_threads()`]` + overbudget`. If `overbudget` /// is `None`, the [global setting][set_task_overbudgeting] is used.§ - pub fn init(overbudget : Option<usize>) -> Self { + pub fn init(overbudget: Option<usize>) -> Self { let n = num_threads().get(); #[allow(static_mut_refs)] let m = overbudget.unwrap_or_else(|| unsafe { TASK_OVERBUDGETING.load(Relaxed) }); @@ -161,14 +170,18 @@ } /// Initialise single-threaded thread budgeting. - pub fn none() -> Self { Self::SingleThreaded } + pub fn none() -> Self { + Self::SingleThreaded + } } impl<'scope, 'scheduler> TaskBudget<'scope, 'scheduler> { /// Create a sub-scope for launching tasks - pub fn zoom<'smaller, F, R : Send>(&self, scheduler : F) -> R - where 'scope : 'smaller, - F : for<'a> FnOnce(TaskBudgetScope<'smaller, 'a>) -> R + Send + 'smaller { + pub fn zoom<'smaller, F, R: Send>(&self, scheduler: F) -> R + where + 'scope: 'smaller, + F: for<'a> FnOnce(TaskBudgetScope<'smaller, 'a>) -> R + Send + 'smaller, + { match self { &Self::SingleThreaded => scheduler(TaskBudgetScope::SingleThreaded), &Self::MultiThreadedInitial { ref budget, pool } => { @@ -191,15 +204,16 @@ impl<'scope, 'scheduler> TaskBudgetScope<'scope, 'scheduler> { /// Queue a task or execute it in this thread if the thread budget is exhausted. - pub fn execute<F>(&self, job : F) - where F : for<'b> FnOnce(TaskBudget<'scope, 'b>) + Send + 'scope { + pub fn execute<F>(&self, job: F) + where + F: for<'b> FnOnce(TaskBudget<'scope, 'b>) + Send + 'scope, + { match self { Self::SingleThreaded => job(TaskBudget::SingleThreaded), Self::MultiThreaded { scope, budget } => { - let spawn = budget.fetch_update(Release, - Relaxed, - |n| (n > 1).then_some(n - 1)) - .is_ok(); + let spawn = budget + .fetch_update(Release, Relaxed, |n| (n > 1).then_some(n - 1)) + .is_ok(); if spawn { scope.spawn(|scope| { let task_budget = TaskBudget::MultiThreadedZoom { scope, budget }; @@ -216,8 +230,10 @@ /// Runs `scheduler` with a [`TaskBudget`]. /// -/// This corresponds to calling `scheduler` with [`TaskBudget::init(None)`]. -pub fn with_task_budget<'scope, F, R>(scheduler : F) -> R -where F : for<'b> FnOnce(TaskBudget<'scope, 'b>) -> R + 'scope { +/// This corresponds to calling `scheduler` with [`TaskBudget::init`]`(None)`. +pub fn with_task_budget<'scope, F, R>(scheduler: F) -> R +where + F: for<'b> FnOnce(TaskBudget<'scope, 'b>) -> R + 'scope, +{ scheduler(TaskBudget::init(None)) }
--- a/src/sets.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/sets.rs Fri May 15 14:46:30 2026 -0500 @@ -2,51 +2,57 @@ This module provides various sets and traits for them. */ -use std::ops::{RangeFull,RangeFrom,Range,RangeInclusive,RangeTo,RangeToInclusive}; -use crate::types::*; +use crate::euclidean::Euclidean; +use crate::instance::{BasicDecomposition, Instance, Space}; use crate::loc::Loc; -use crate::euclidean::Euclidean; -use crate::instance::{Space, Instance}; +use crate::types::*; use serde::Serialize; +use std::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; pub mod cube; pub use cube::Cube; /// Trait for arbitrary sets. The parameter `U` is the element type. -pub trait Set<U> where U : Space { +pub trait Set<U> +where + U: Space, +{ /// Check for element containment - fn contains<I : Instance<U>>(&self, item : I) -> bool; + fn contains<I: Instance<U>>(&self, item: I) -> bool; } /// Additional ordering (besides [`PartialOrd`]) of a subfamily of sets: /// greatest lower bound and least upper bound. -pub trait SetOrd : Sized { - /// Returns the smallest set of same class contain both parameters. - fn common(&self, other : &Self) -> Self; +pub trait SetOrd: Sized { + /// Returns the smallest set of same class contain both parameters. + fn common(&self, other: &Self) -> Self; - /// Returns the greatest set of same class contaied by n both parameter sets. - fn intersect(&self, other : &Self) -> Option<Self>; + /// Returns the greatest set of same class contaied by n both parameter sets. + fn intersect(&self, other: &Self) -> Option<Self>; } -impl<U, const N : usize> Set<Loc<U, N>> -for Cube<U,N> -where U : Num + PartialOrd + Sized { - fn contains<I : Instance<Loc<U, N>>>(&self, item : I) -> bool { - self.0.iter().zip(item.ref_instance().iter()).all(|(s, x)| s.contains(x)) +impl<U, const N: usize> Set<Loc<N, U>> for Cube<N, U> +where + U: Num + PartialOrd + Sized, +{ + fn contains<I: Instance<Loc<N, U>>>(&self, item: I) -> bool { + item.eval_ref(|r| self.0.iter().zip(r.iter()).all(|(s, x)| s.contains(x))) } } -impl<U : Space> Set<U> for RangeFull { - fn contains<I : Instance<U>>(&self, _item : I) -> bool { true } +impl<U: Space> Set<U> for RangeFull { + fn contains<I: Instance<U>>(&self, _item: I) -> bool { + true + } } macro_rules! impl_ranges { ($($range:ident),*) => { $( impl<U,Idx> Set<U> for $range<Idx> where - Idx : PartialOrd<U>, - U : PartialOrd<Idx> + Space, - Idx : PartialOrd + U : Space<Decomp=BasicDecomposition>, + U::Principal : PartialOrd<Idx>, + Idx : PartialOrd + PartialOrd<U::Principal>, { #[inline] fn contains<I : Instance<U>>(&self, item : I) -> bool { @@ -56,45 +62,56 @@ )* } } -impl_ranges!(RangeFrom,Range,RangeInclusive,RangeTo,RangeToInclusive); +impl_ranges!(RangeFrom, Range, RangeInclusive, RangeTo, RangeToInclusive); /// Halfspaces described by an orthogonal vector and an offset. /// /// The halfspace is $H = \\{ t v + a \mid a^⊤ v = 0 \\}$, where $v$ is the orthogonal /// vector and $t$ the offset. -#[derive(Clone,Copy,Debug,Serialize,Eq,PartialEq)] -pub struct Halfspace<A, F> where A : Euclidean<F>, F : Float { - pub orthogonal : A, - pub offset : F, +#[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq)] +pub struct Halfspace<A, F> +where + A: Euclidean<F>, + F: Float, +{ + pub orthogonal: A, + pub offset: F, } -impl<A,F> Halfspace<A,F> where A : Euclidean<F>, F : Float { +impl<A, F> Halfspace<A, F> +where + A: Euclidean<F>, + F: Float, +{ #[inline] - pub fn new(orthogonal : A, offset : F) -> Self { - Halfspace{ orthogonal : orthogonal, offset : offset } + pub fn new(orthogonal: A, offset: F) -> Self { + Halfspace { orthogonal: orthogonal, offset: offset } } } /// Trait for generating a halfspace spanned by another set `Self` of elements of type `U`. -pub trait SpannedHalfspace<F> where F : Float { +pub trait SpannedHalfspace<F> +where + F: Float, +{ /// Type of the orthogonal vector describing the halfspace. - type A : Euclidean<F>; + type A: Euclidean<F>; /// Returns the halfspace spanned by this set. fn spanned_halfspace(&self) -> Halfspace<Self::A, F>; } // TODO: Gram-Schmidt for higher N. -impl<F : Float> SpannedHalfspace<F> for [Loc<F, 1>; 2] { - type A = Loc<F, 1>; +impl<F: Float> SpannedHalfspace<F> for [Loc<1, F>; 2] { + type A = Loc<1, F>; fn spanned_halfspace(&self) -> Halfspace<Self::A, F> { let (x0, x1) = (self[0], self[1]); - Halfspace::new(x1-x0, x0[0]) + Halfspace::new(x1 - x0, x0[0]) } } // TODO: Gram-Schmidt for higher N. -impl<F : Float> SpannedHalfspace<F> for [Loc<F, 2>; 2] { - type A = Loc<F, 2>; +impl<F: Float> SpannedHalfspace<F> for [Loc<2, F>; 2] { + type A = Loc<2, F>; fn spanned_halfspace(&self) -> Halfspace<Self::A, F> { let (x0, x1) = (&self[0], &self[1]); let d = x1 - x0; @@ -104,29 +121,30 @@ } } -impl<A,F> Set<A> for Halfspace<A,F> +impl<A, F> Set<A> for Halfspace<A, F> where - A : Euclidean<F>, - F : Float, + A: Euclidean<F>, + F: Float, { #[inline] - fn contains<I : Instance<A>>(&self, item : I) -> bool { + fn contains<I: Instance<A>>(&self, item: I) -> bool { self.orthogonal.dot(item) >= self.offset } } /// Polygons defined by `N` `Halfspace`s. -#[derive(Clone,Copy,Debug,Eq,PartialEq)] -pub struct NPolygon<A, F, const N : usize>(pub [Halfspace<A,F>; N]) -where A : Euclidean<F>, F : Float; - -impl<A,F,const N : usize> Set<A> for NPolygon<A,F,N> +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct NPolygon<A, const N: usize, F = f64>(pub [Halfspace<A, F>; N]) where - A : Euclidean<F>, - F : Float, + A: Euclidean<F>, + F: Float; + +impl<A, F, const N: usize> Set<A> for NPolygon<A, N, F> +where + A: Euclidean<F>, + F: Float, { - fn contains<I : Instance<A>>(&self, item : I) -> bool { - let r = item.ref_instance(); - self.0.iter().all(|halfspace| halfspace.contains(r)) + fn contains<I: Instance<A>>(&self, item: I) -> bool { + item.eval_ref(|r| self.0.iter().all(|halfspace| halfspace.contains(r))) } }
--- a/src/sets/cube.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/sets/cube.rs Fri May 15 14:46:30 2026 -0500 @@ -16,25 +16,19 @@ ``` */ -use serde::ser::{Serialize, Serializer, SerializeTupleStruct}; -use crate::types::*; use crate::loc::Loc; +use crate::maputil::{map1, map1_indexed, map2, FixedLength, FixedLengthMut}; use crate::sets::SetOrd; -use crate::maputil::{ - FixedLength, - FixedLengthMut, - map1, - map1_indexed, - map2, -}; +use crate::types::*; +use serde::ser::{Serialize, SerializeTupleStruct, Serializer}; /// A multi-dimensional cube $∏_{i=1}^N [a_i, b_i)$ with the starting and ending points /// along $a_i$ and $b_i$ along each dimension of type `U`. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Cube<U : Num, const N : usize>(pub(super) [[U; 2]; N]); +pub struct Cube<const N: usize, U: Num = f64>(pub(super) [[U; 2]; N]); // Need to manually implement as [F; N] serialisation is provided only for some N. -impl<F : Num + Serialize, const N : usize> Serialize for Cube<F, N> +impl<F: Num + Serialize, const N: usize> Serialize for Cube<N, F> where F: Serialize, { @@ -50,7 +44,7 @@ } } -impl<A : Num, const N : usize> FixedLength<N> for Cube<A,N> { +impl<A: Num, const N: usize> FixedLength<N> for Cube<N, A> { type Iter = std::array::IntoIter<[A; 2], N>; type Elem = [A; 2]; #[inline] @@ -59,7 +53,7 @@ } } -impl<A : Num, const N : usize> FixedLengthMut<N> for Cube<A,N> { +impl<A: Num, const N: usize> FixedLengthMut<N> for Cube<N, A> { type IterMut<'a> = std::slice::IterMut<'a, [A; 2]>; #[inline] fn fl_iter_mut(&mut self) -> Self::IterMut<'_> { @@ -67,7 +61,7 @@ } } -impl<'a, A : Num, const N : usize> FixedLength<N> for &'a Cube<A,N> { +impl<'a, A: Num, const N: usize> FixedLength<N> for &'a Cube<N, A> { type Iter = std::slice::Iter<'a, [A; 2]>; type Elem = &'a [A; 2]; #[inline] @@ -76,15 +70,14 @@ } } - /// Iterator for [`Cube`] corners. -pub struct CubeCornersIter<'a, U : Num, const N : usize> { - index : usize, - cube : &'a Cube<U, N>, +pub struct CubeCornersIter<'a, U: Num, const N: usize> { + index: usize, + cube: &'a Cube<N, U>, } -impl<'a, U : Num, const N : usize> Iterator for CubeCornersIter<'a, U, N> { - type Item = Loc<U, N>; +impl<'a, U: Num, const N: usize> Iterator for CubeCornersIter<'a, U, N> { + type Item = Loc<N, U>; #[inline] fn next(&mut self) -> Option<Self::Item> { if self.index >= N { @@ -92,28 +85,30 @@ } else { let i = self.index; self.index += 1; - let arr = self.cube.map_indexed(|k, a, b| if (i>>k)&1 == 0 { a } else { b }); + let arr = self + .cube + .map_indexed(|k, a, b| if (i >> k) & 1 == 0 { a } else { b }); Some(arr.into()) } } } -impl<U : Num, const N : usize> Cube<U, N> { +impl<U: Num, const N: usize> Cube<N, U> { /// Maps `f` over the triples $\\{(i, a\_i, b\_i)\\}\_{i=1}^N$ /// of the cube $∏_{i=1}^N [a_i, b_i)$. #[inline] - pub fn map_indexed<T>(&self, f : impl Fn(usize, U, U) -> T) -> [T; N] { + pub fn map_indexed<T>(&self, f: impl Fn(usize, U, U) -> T) -> [T; N] { map1_indexed(self, |i, &[a, b]| f(i, a, b)) } /// Maps `f` over the tuples $\\{(a\_i, b\_i)\\}\_{i=1}^N$ /// of the cube $∏_{i=1}^N [a_i, b_i)$. #[inline] - pub fn map<T>(&self, f : impl Fn(U, U) -> T) -> [T; N] { + pub fn map<T>(&self, f: impl Fn(U, U) -> T) -> [T; N] { map1(self, |&[a, b]| f(a, b)) } - /// Iterates over the start and end coordinates $\{(a_i, b_i)\}_{i=1}^N$ of the cube along + /// Iterates over the start and end coordinates $\{(a_i, b_i)\}_{i=1}^N$ of the cube along /// each dimension. #[inline] pub fn iter_coords(&self) -> std::slice::Iter<'_, [U; 2]> { @@ -122,27 +117,27 @@ /// Returns the “start” coordinate $a_i$ of the cube $∏_{i=1}^N [a_i, b_i)$. #[inline] - pub fn start(&self, i : usize) -> U { + pub fn start(&self, i: usize) -> U { self.0[i][0] } /// Returns the end coordinate $a_i$ of the cube $∏_{i=1}^N [a_i, b_i)$. #[inline] - pub fn end(&self, i : usize) -> U { + pub fn end(&self, i: usize) -> U { self.0[i][1] } /// Returns the “start” $(a_1, … ,a_N)$ of the cube $∏_{i=1}^N [a_i, b_i)$ /// spanned between $(a_1, … ,a_N)$ and $(b_1, … ,b_N)$. #[inline] - pub fn span_start(&self) -> Loc<U, N> { + pub fn span_start(&self) -> Loc<N, U> { Loc::new(self.map(|a, _b| a)) } /// Returns the end $(b_1, … ,b_N)$ of the cube $∏_{i=1}^N [a_i, b_i)$ /// spanned between $(a_1, … ,a_N)$ and $(b_1, … ,b_N)$. #[inline] - pub fn span_end(&self) -> Loc<U, N> { + pub fn span_end(&self) -> Loc<N, U> { Loc::new(self.map(|_a, b| b)) } @@ -150,19 +145,22 @@ /// $∏_{i=1}^N [a_i, b_i)$. #[inline] pub fn iter_corners(&self) -> CubeCornersIter<'_, U, N> { - CubeCornersIter{ index : 0, cube : self } + CubeCornersIter { + index: 0, + cube: self, + } } /// Returns the width-`N`-tuple $(b_1-a_1, … ,b_N-a_N)$ of the cube $∏_{i=1}^N [a_i, b_i)$. #[inline] - pub fn width(&self) -> Loc<U, N> { - Loc::new(self.map(|a, b| b-a)) + pub fn width(&self) -> Loc<N, U> { + Loc::new(self.map(|a, b| b - a)) } /// Translates the cube $∏_{i=1}^N [a_i, b_i)$ by the `shift` $(s_1, … , s_N)$ to /// $∏_{i=1}^N [a_i+s_i, b_i+s_i)$. #[inline] - pub fn shift(&self, shift : &Loc<U, N>) -> Self { + pub fn shift(&self, shift: &Loc<N, U>) -> Self { let mut cube = self.clone(); for i in 0..N { cube.0[i][0] += shift[i]; @@ -173,144 +171,158 @@ /// Creates a new cube from an array. #[inline] - pub fn new(data : [[U; 2]; N]) -> Self { + pub fn new(data: [[U; 2]; N]) -> Self { Cube(data) } } -impl<F : Float, const N : usize> Cube<F, N> { +impl<F: Float, const N: usize> Cube<N, F> { /// Returns the centre of the cube - pub fn center(&self) -> Loc<F, N> { + pub fn center(&self) -> Loc<N, F> { map1(self, |&[a, b]| (a + b) / F::TWO).into() } } -impl<U : Num> Cube<U, 1> { +impl<U: Num> Cube<1, U> { /// Get the corners of the cube. /// /// TODO: generic implementation once const-generics can be involved in /// calculations. #[inline] - pub fn corners(&self) -> [Loc<U, 1>; 2] { + pub fn corners(&self) -> [Loc<1, U>; 2] { let [[a, b]] = self.0; [a.into(), b.into()] } } -impl<U : Num> Cube<U, 2> { +impl<U: Num> Cube<2, U> { /// Get the corners of the cube in counter-clockwise order. /// /// TODO: generic implementation once const-generics can be involved in /// calculations. #[inline] - pub fn corners(&self) -> [Loc<U, 2>; 4] { - let [[a1, b1], [a2, b2]]=self.0; - [[a1, a2].into(), - [b1, a2].into(), - [b1, b2].into(), - [a1, b2].into()] + pub fn corners(&self) -> [Loc<2, U>; 4] { + let [[a1, b1], [a2, b2]] = self.0; + [ + [a1, a2].into(), + [b1, a2].into(), + [b1, b2].into(), + [a1, b2].into(), + ] } } -impl<U : Num> Cube<U, 3> { +impl<U: Num> Cube<3, U> { /// Get the corners of the cube. /// /// TODO: generic implementation once const-generics can be involved in /// calculations. #[inline] - pub fn corners(&self) -> [Loc<U, 3>; 8] { - let [[a1, b1], [a2, b2], [a3, b3]]=self.0; - [[a1, a2, a3].into(), - [b1, a2, a3].into(), - [b1, b2, a3].into(), - [a1, b2, a3].into(), - [a1, b2, b3].into(), - [b1, b2, b3].into(), - [b1, a2, b3].into(), - [a1, a2, b3].into()] + pub fn corners(&self) -> [Loc<3, U>; 8] { + let [[a1, b1], [a2, b2], [a3, b3]] = self.0; + [ + [a1, a2, a3].into(), + [b1, a2, a3].into(), + [b1, b2, a3].into(), + [a1, b2, a3].into(), + [a1, b2, b3].into(), + [b1, b2, b3].into(), + [b1, a2, b3].into(), + [a1, a2, b3].into(), + ] } } // TODO: Implement Add and Sub of Loc to Cube, and Mul and Div by U : Num. -impl<U : Num, const N : usize> From<[[U; 2]; N]> for Cube<U, N> { +impl<U: Num, const N: usize> From<[[U; 2]; N]> for Cube<N, U> { #[inline] - fn from(data : [[U; 2]; N]) -> Self { + fn from(data: [[U; 2]; N]) -> Self { Cube(data) } } -impl<U : Num, const N : usize> From<Cube<U, N>> for [[U; 2]; N] { +impl<U: Num, const N: usize> From<Cube<N, U>> for [[U; 2]; N] { #[inline] - fn from(Cube(data) : Cube<U, N>) -> Self { + fn from(Cube(data): Cube<N, U>) -> Self { data } } - -impl<U, const N : usize> Cube<U, N> where U : Num + PartialOrd { +impl<U, const N: usize> Cube<N, U> +where + U: Num + PartialOrd, +{ /// Checks whether the cube is non-degenerate, i.e., the start coordinate /// of each axis is strictly less than the end coordinate. #[inline] pub fn nondegenerate(&self) -> bool { self.0.iter().all(|range| range[0] < range[1]) } - + /// Checks whether the cube intersects some `other` cube. /// Matching boundary points are not counted, so `U` is ideally a [`Float`]. #[inline] - pub fn intersects(&self, other : &Cube<U, N>) -> bool { - self.iter_coords().zip(other.iter_coords()).all(|([a1, b1], [a2, b2])| { - a1 < b2 && a2 < b1 - }) + pub fn intersects(&self, other: &Cube<N, U>) -> bool { + self.iter_coords() + .zip(other.iter_coords()) + .all(|([a1, b1], [a2, b2])| a1 < b2 && a2 < b1) } /// Checks whether the cube contains some `other` cube. - pub fn contains_set(&self, other : &Cube<U, N>) -> bool { - self.iter_coords().zip(other.iter_coords()).all(|([a1, b1], [a2, b2])| { - a1 <= a2 && b1 >= b2 - }) + pub fn contains_set(&self, other: &Cube<N, U>) -> bool { + self.iter_coords() + .zip(other.iter_coords()) + .all(|([a1, b1], [a2, b2])| a1 <= a2 && b1 >= b2) } /// Produces the point of minimum $ℓ^p$-norm within the cube `self` for any $p$-norm. /// This is the point where each coordinate is closest to zero. #[inline] - pub fn minnorm_point(&self) -> Loc<U, N> { + pub fn minnorm_point(&self) -> Loc<N, U> { let z = U::ZERO; // As always, we assume that a ≤ b. self.map(|a, b| { debug_assert!(a <= b); match (a < z, z < b) { - (false, _) => a, - (_, false) => b, - (true, true) => z + (false, _) => a, + (_, false) => b, + (true, true) => z, } - }).into() + }) + .into() } /// Produces the point of maximum $ℓ^p$-norm within the cube `self` for any $p$-norm. /// This is the point where each coordinate is furthest from zero. #[inline] - pub fn maxnorm_point(&self) -> Loc<U, N> { + pub fn maxnorm_point(&self) -> Loc<N, U> { let z = U::ZERO; // As always, we assume that a ≤ b. self.map(|a, b| { debug_assert!(a <= b); match (a < z, z < b) { - (false, _) => b, - (_, false) => a, + (false, _) => b, + (_, false) => a, // A this stage we must have a < 0 (so U must be signed), and want to check // whether |a| > |b|. We can do this without assuming U to actually implement // `Neg` by comparing whether 0 > a + b. - (true, true) => if z > a + b { a } else { b } + (true, true) => { + if z > a + b { + a + } else { + b + } + } } - }).into() + }) + .into() } } macro_rules! impl_common { ($($t:ty)*, $min:ident, $max:ident) => { $( - impl<const N : usize> SetOrd for Cube<$t, N> { + impl<const N : usize> SetOrd for Cube<N, $t> { #[inline] fn common(&self, other : &Self) -> Self { map2(self, other, |&[a1, b1], &[a2, b2]| { @@ -338,7 +350,7 @@ #[cfg(feature = "nightly")] impl_common!(f32 f64, minimum, maximum); -impl<U : Num, const N : usize> std::ops::Index<usize> for Cube<U, N> { +impl<U: Num, const N: usize> std::ops::Index<usize> for Cube<N, U> { type Output = [U; 2]; #[inline] fn index(&self, index: usize) -> &Self::Output { @@ -346,7 +358,7 @@ } } -impl<U : Num, const N : usize> std::ops::IndexMut<usize> for Cube<U, N> { +impl<U: Num, const N: usize> std::ops::IndexMut<usize> for Cube<N, U> { #[inline] fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.0[index]
--- a/src/types.rs Sun Apr 27 20:29:43 2025 -0500 +++ b/src/types.rs Fri May 15 14:46:30 2026 -0500 @@ -10,15 +10,12 @@ */ //use trait_set::trait_set; +pub use num_traits::cast::AsPrimitive; pub use num_traits::Float as NumTraitsFloat; // needed to re-export functions. -pub use num_traits::cast::AsPrimitive; pub use simba::scalar::{ - ClosedAdd, ClosedAddAssign, + ClosedAdd, ClosedAddAssign, ClosedDiv, ClosedDivAssign, ClosedMul, ClosedMulAssign, ClosedNeg, ClosedSub, ClosedSubAssign, - ClosedMul, ClosedMulAssign, - ClosedDiv, ClosedDivAssign, - ClosedNeg }; /// Typical integer type @@ -34,8 +31,8 @@ pub type float = f64; /// Casts of abstract numerical types to others via the standard `as` keyword. -pub trait CastFrom<T : 'static + Copy> : num_traits::cast::AsPrimitive<T> { - fn cast_from(other : T) -> Self; +pub trait CastFrom<T: 'static + Copy>: num_traits::cast::AsPrimitive<T> { + fn cast_from(other: T) -> Self; } macro_rules! impl_casts { @@ -58,53 +55,71 @@ f32 f64); /// Trait for general numeric types -pub trait Num : 'static + Copy + Sync + Send + num::Num + num_traits::NumAssign - + std::iter::Sum + std::iter::Product - + std::fmt::Debug + std::fmt::Display + serde::Serialize - + CastFrom<u8> + CastFrom<u16> + CastFrom<u32> + CastFrom<u64> - + CastFrom<u128> + CastFrom<usize> - + CastFrom<i8> + CastFrom<i16> + CastFrom<i32> + CastFrom<i64> - + CastFrom<i128> + CastFrom<isize> - + CastFrom<f32> + CastFrom<f64> - + crate::instance::Space { - - const ZERO : Self; - const ONE : Self; - const TWO : Self; +pub trait Num: + 'static + + Copy + + Sync + + Send + + num::Num + + num_traits::NumAssign + + std::iter::Sum + + std::iter::Product + + std::fmt::Debug + + std::fmt::Display + + serde::Serialize + + CastFrom<u8> + + CastFrom<u16> + + CastFrom<u32> + + CastFrom<u64> + + CastFrom<u128> + + CastFrom<usize> + + CastFrom<i8> + + CastFrom<i16> + + CastFrom<i32> + + CastFrom<i64> + + CastFrom<i128> + + CastFrom<isize> + + CastFrom<f32> + + CastFrom<f64> + + crate::instance::ClosedSpace +{ + const ZERO: Self; + const ONE: Self; + const TWO: Self; /// Generic version of `Self::MAX` - const RANGE_MAX : Self; + const RANGE_MAX: Self; /// Generic version of `Self::MIN` - const RANGE_MIN : Self; + const RANGE_MIN: Self; } /// Trait for signed numeric types -pub trait SignedNum : Num + num::Signed + std::ops::Neg<Output=Self> {} -impl<U : Num + num::Signed + std::ops::Neg<Output=Self>> SignedNum for U { } +pub trait SignedNum: Num + num::Signed + std::ops::Neg<Output = Self> {} +impl<U: Num + num::Signed + std::ops::Neg<Output = Self>> SignedNum for U {} /// Trait for floating point numbers -pub trait Float : SignedNum + num::Float /*+ From<Self::CompatibleSize>*/ { +pub trait Float: SignedNum + std::fmt::LowerExp + num::Float /*+ From<Self::CompatibleSize>*/ { // An unsigned integer that can be used for indexing operations and // converted to F without loss. //type CompatibleSize : CompatibleUnsigned<Self>; - const PI : Self; - const E : Self; - const EPSILON : Self; - const SQRT_2 : Self; - const INFINITY : Self; - const NEG_INFINITY : Self; - const NAN : Self; - const FRAC_2_SQRT_PI : Self; + const PI: Self; + const E: Self; + const EPSILON: Self; + const SQRT_2: Self; + const INFINITY: Self; + const NEG_INFINITY: Self; + const NAN: Self; + const FRAC_2_SQRT_PI: Self; } /// Trait for integers -pub trait Integer : Num + num::Integer {} +pub trait Integer: Num + num::Integer {} /// Trait for unsigned integers -pub trait Unsigned : Num + Integer + num::Unsigned {} +pub trait Unsigned: Num + Integer + num::Unsigned {} /// Trait for signed integers -pub trait Signed : SignedNum + Integer {} +pub trait Signed: SignedNum + Integer {} macro_rules! impl_num_consts { ($($type:ty)*) => { $( @@ -137,14 +152,14 @@ #[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))] type CompatibleSize = usize;*/ - const PI : Self = std::f64::consts::PI; - const E : Self = std::f64::consts::E; - const EPSILON : Self = std::f64::EPSILON; - const SQRT_2 : Self = std::f64::consts::SQRT_2; - const INFINITY : Self = std::f64::INFINITY; - const NEG_INFINITY : Self = std::f64::NEG_INFINITY; - const NAN : Self = std::f64::NAN; - const FRAC_2_SQRT_PI : Self = std::f64::consts::FRAC_2_SQRT_PI; + const PI: Self = std::f64::consts::PI; + const E: Self = std::f64::consts::E; + const EPSILON: Self = std::f64::EPSILON; + const SQRT_2: Self = std::f64::consts::SQRT_2; + const INFINITY: Self = std::f64::INFINITY; + const NEG_INFINITY: Self = std::f64::NEG_INFINITY; + const NAN: Self = std::f64::NAN; + const FRAC_2_SQRT_PI: Self = std::f64::consts::FRAC_2_SQRT_PI; } impl Float for f32 { @@ -155,14 +170,14 @@ type CompatibleSize = usize; */ - const PI : Self = std::f32::consts::PI; - const E : Self = std::f32::consts::E; - const EPSILON : Self = std::f32::EPSILON; - const SQRT_2 : Self = std::f32::consts::SQRT_2; - const INFINITY : Self = std::f32::INFINITY; - const NEG_INFINITY : Self = std::f32::NEG_INFINITY; - const NAN : Self = std::f32::NAN; - const FRAC_2_SQRT_PI : Self = std::f32::consts::FRAC_2_SQRT_PI; + const PI: Self = std::f32::consts::PI; + const E: Self = std::f32::consts::E; + const EPSILON: Self = std::f32::EPSILON; + const SQRT_2: Self = std::f32::consts::SQRT_2; + const INFINITY: Self = std::f32::INFINITY; + const NEG_INFINITY: Self = std::f32::NEG_INFINITY; + const NAN: Self = std::f32::NAN; + const FRAC_2_SQRT_PI: Self = std::f32::consts::FRAC_2_SQRT_PI; } /* @@ -171,4 +186,3 @@ pub trait CompatibleSigned<F : Float> = Signed + Into<F>; } */ -