Tue, 06 Dec 2022 14:12:38 +0200
Added tag v1.0.0-pre-arxiv for changeset b71edfd403aa
| 0 | 1 | |
| 2 | //! Implementation of the indicator function of a ball with respect to various norms. | |
| 3 | use float_extras::f64::{tgamma as gamma}; | |
| 4 | use numeric_literals::replace_float_literals; | |
| 5 | use serde::Serialize; | |
| 6 | use alg_tools::types::*; | |
| 7 | use alg_tools::norms::*; | |
| 8 | use alg_tools::loc::Loc; | |
| 9 | use alg_tools::sets::Cube; | |
| 10 | use alg_tools::bisection_tree::{ | |
| 11 | Support, | |
| 12 | Constant, | |
| 13 | Bounds, | |
| 14 | LocalAnalysis, | |
| 15 | GlobalAnalysis, | |
| 16 | }; | |
| 17 | use alg_tools::mapping::Apply; | |
| 18 | use alg_tools::maputil::array_init; | |
| 19 | use alg_tools::coefficients::factorial; | |
| 20 | ||
| 21 | use super::base::*; | |
| 22 | ||
| 23 | /// Representation of the indicator of the ball $𝔹_q = \\{ x ∈ ℝ^N \mid \\|x\\|\_q ≤ r \\}$, | |
| 24 | /// where $q$ is the `Exponent`, and $r$ is the radius [`Constant`] `C`. | |
| 25 | #[derive(Copy,Clone,Serialize,Debug,Eq,PartialEq)] | |
| 26 | pub struct BallIndicator<C : Constant, Exponent : NormExponent, const N : usize> { | |
| 27 | /// The radius of the ball. | |
| 28 | pub r : C, | |
| 29 | /// The exponent $q$ of the norm creating the ball | |
| 30 | pub exponent : Exponent, | |
| 31 | } | |
| 32 | ||
| 33 | /// Alias for the representation of the indicator of the $∞$-norm-ball | |
| 34 | /// $𝔹_∞ = \\{ x ∈ ℝ^N \mid \\|x\\|\_∞ ≤ c \\}$. | |
| 35 | pub type CubeIndicator<C, const N : usize> = BallIndicator<C, Linfinity, N>; | |
| 36 | ||
| 37 | #[replace_float_literals(C::Type::cast_from(literal))] | |
| 38 | impl<'a, F : Float, C : Constant<Type=F>, Exponent : NormExponent, const N : usize> | |
| 39 | Apply<&'a Loc<C::Type, N>> | |
| 40 | for BallIndicator<C, Exponent, N> | |
| 41 | where Loc<F, N> : Norm<F, Exponent> { | |
| 42 | type Output = C::Type; | |
| 43 | #[inline] | |
| 44 | fn apply(&self, x : &'a Loc<C::Type, N>) -> Self::Output { | |
| 45 | let r = self.r.value(); | |
| 46 | let n = x.norm(self.exponent); | |
| 47 | if n <= r { | |
| 48 | 1.0 | |
| 49 | } else { | |
| 50 | 0.0 | |
| 51 | } | |
| 52 | } | |
| 53 | } | |
| 54 | ||
| 55 | impl<F : Float, C : Constant<Type=F>, Exponent : NormExponent, const N : usize> | |
| 56 | Apply<Loc<C::Type, N>> | |
| 57 | for BallIndicator<C, Exponent, N> | |
| 58 | where Loc<F, N> : Norm<F, Exponent> { | |
| 59 | type Output = C::Type; | |
| 60 | #[inline] | |
| 61 | fn apply(&self, x : Loc<C::Type, N>) -> Self::Output { | |
| 62 | self.apply(&x) | |
| 63 | } | |
| 64 | } | |
| 65 | ||
| 66 | ||
| 67 | impl<'a, F : Float, C : Constant<Type=F>, Exponent : NormExponent, const N : usize> | |
| 68 | Support<C::Type, N> | |
| 69 | for BallIndicator<C, Exponent, N> | |
| 70 | where Loc<F, N> : Norm<F, Exponent>, | |
| 71 | Linfinity : Dominated<F, Exponent, Loc<F, N>> { | |
| 72 | ||
| 73 | #[inline] | |
| 74 | fn support_hint(&self) -> Cube<F,N> { | |
| 75 | let r = Linfinity.from_norm(self.r.value(), self.exponent); | |
| 76 | array_init(|| [-r, r]).into() | |
| 77 | } | |
| 78 | ||
| 79 | #[inline] | |
| 80 | fn in_support(&self, x : &Loc<F,N>) -> bool { | |
| 81 | let r = Linfinity.from_norm(self.r.value(), self.exponent); | |
| 82 | x.norm(self.exponent) <= r | |
| 83 | } | |
| 84 | ||
| 85 | /// This can only really work in a reasonable fashion for N=1. | |
| 86 | #[inline] | |
| 87 | fn bisection_hint(&self, cube : &Cube<F, N>) -> [Option<F>; N] { | |
| 88 | let r = Linfinity.from_norm(self.r.value(), self.exponent); | |
| 89 | cube.map(|a, b| symmetric_interval_hint(r, a, b)) | |
| 90 | } | |
| 91 | } | |
| 92 | ||
| 93 | #[replace_float_literals(F::cast_from(literal))] | |
| 94 | impl<'a, F : Float, C : Constant<Type=F>, Exponent : NormExponent, const N : usize> | |
| 95 | GlobalAnalysis<F, Bounds<F>> | |
| 96 | for BallIndicator<C, Exponent, N> | |
| 97 | where Loc<F, N> : Norm<F, Exponent> { | |
| 98 | #[inline] | |
| 99 | fn global_analysis(&self) -> Bounds<F> { | |
| 100 | Bounds(0.0, 1.0) | |
| 101 | } | |
| 102 | } | |
| 103 | ||
| 104 | #[replace_float_literals(F::cast_from(literal))] | |
| 105 | impl<'a, F : Float, C : Constant<Type=F>, Exponent : NormExponent, const N : usize> | |
| 106 | Norm<F, Linfinity> | |
| 107 | for BallIndicator<C, Exponent, N> | |
| 108 | where Loc<F, N> : Norm<F, Exponent> { | |
| 109 | #[inline] | |
| 110 | fn norm(&self, _ : Linfinity) -> F { | |
| 111 | 1.0 | |
| 112 | } | |
| 113 | } | |
| 114 | ||
| 115 | #[replace_float_literals(F::cast_from(literal))] | |
| 116 | impl<'a, F : Float, C : Constant<Type=F>, const N : usize> | |
| 117 | Norm<F, L1> | |
| 118 | for BallIndicator<C, L1, N> { | |
| 119 | #[inline] | |
| 120 | fn norm(&self, _ : L1) -> F { | |
| 121 | // Using https://en.wikipedia.org/wiki/Volume_of_an_n-ball#Balls_in_Lp_norms, | |
| 122 | // we have V_N^1(r) = (2r)^N / N! | |
| 123 | let r = self.r.value(); | |
| 124 | if N==1 { | |
| 125 | 2.0 * r | |
| 126 | } else if N==2 { | |
| 127 | r*r | |
| 128 | } else { | |
| 129 | (2.0 * r).powi(N as i32) * F::cast_from(factorial(N)) | |
| 130 | } | |
| 131 | } | |
| 132 | } | |
| 133 | ||
| 134 | #[replace_float_literals(F::cast_from(literal))] | |
| 135 | impl<'a, F : Float, C : Constant<Type=F>, const N : usize> | |
| 136 | Norm<F, L1> | |
| 137 | for BallIndicator<C, L2, N> { | |
| 138 | #[inline] | |
| 139 | fn norm(&self, _ : L1) -> F { | |
| 140 | // See https://en.wikipedia.org/wiki/Volume_of_an_n-ball#The_volume. | |
| 141 | let r = self.r.value(); | |
| 142 | let π = F::PI; | |
| 143 | if N==1 { | |
| 144 | 2.0 * r | |
| 145 | } else if N==2 { | |
| 146 | π * (r * r) | |
| 147 | } else { | |
| 148 | let ndiv2 = F::cast_from(N) / 2.0; | |
| 149 | let γ = F::cast_from(gamma((ndiv2 + 1.0).as_())); | |
| 150 | π.powf(ndiv2) / γ * r.powi(N as i32) | |
| 151 | } | |
| 152 | } | |
| 153 | } | |
| 154 | ||
| 155 | #[replace_float_literals(F::cast_from(literal))] | |
| 156 | impl<'a, F : Float, C : Constant<Type=F>, const N : usize> | |
| 157 | Norm<F, L1> | |
| 158 | for BallIndicator<C, Linfinity, N> { | |
| 159 | #[inline] | |
| 160 | fn norm(&self, _ : L1) -> F { | |
| 161 | let two_r = 2.0 * self.r.value(); | |
| 162 | two_r.powi(N as i32) | |
| 163 | } | |
| 164 | } | |
| 165 | ||
| 166 | ||
| 167 | macro_rules! indicator_local_analysis { | |
| 168 | ($exponent:ident) => { | |
| 169 | impl<'a, F : Float, C : Constant<Type=F>, const N : usize> | |
| 170 | LocalAnalysis<F, Bounds<F>, N> | |
| 171 | for BallIndicator<C, $exponent, N> | |
| 172 | where Loc<F, N> : Norm<F, $exponent>, | |
| 173 | Linfinity : Dominated<F, $exponent, Loc<F, N>> { | |
| 174 | #[inline] | |
| 175 | fn local_analysis(&self, cube : &Cube<F, N>) -> Bounds<F> { | |
| 176 | // The function is maximised/minimised where the 2-norm is minimised/maximised. | |
| 177 | let lower = self.apply(cube.maxnorm_point()); | |
| 178 | let upper = self.apply(cube.minnorm_point()); | |
| 179 | Bounds(lower, upper) | |
| 180 | } | |
| 181 | } | |
| 182 | } | |
| 183 | } | |
| 184 | ||
| 185 | indicator_local_analysis!(L1); | |
| 186 | indicator_local_analysis!(L2); | |
| 187 | indicator_local_analysis!(Linfinity); | |
| 188 | ||
| 189 | ||
| 190 | #[replace_float_literals(F::cast_from(literal))] | |
| 191 | impl<'a, F : Float, R, const N : usize> Apply<&'a Loc<F, N>> | |
| 192 | for AutoConvolution<CubeIndicator<R, N>> | |
| 193 | where R : Constant<Type=F> { | |
| 194 | type Output = F; | |
| 195 | ||
| 196 | #[inline] | |
| 197 | fn apply(&self, y : &'a Loc<F, N>) -> F { | |
| 198 | let two_r = 2.0 * self.0.r.value(); | |
| 199 | // This is just a product of one-dimensional versions | |
| 200 | y.iter().map(|&x| { | |
| 201 | 0.0.max(two_r - x.abs()) | |
| 202 | }).product() | |
| 203 | } | |
| 204 | } | |
| 205 | ||
| 206 | impl<F : Float, R, const N : usize> Apply<Loc<F, N>> | |
| 207 | for AutoConvolution<CubeIndicator<R, N>> | |
| 208 | where R : Constant<Type=F> { | |
| 209 | type Output = F; | |
| 210 | ||
| 211 | #[inline] | |
| 212 | fn apply(&self, y : Loc<F, N>) -> F { | |
| 213 | self.apply(&y) | |
| 214 | } | |
| 215 | } | |
| 216 | ||
| 217 | #[replace_float_literals(F::cast_from(literal))] | |
| 218 | impl<F : Float, R, const N : usize> Support<F, N> | |
| 219 | for AutoConvolution<CubeIndicator<R, N>> | |
| 220 | where R : Constant<Type=F> { | |
| 221 | #[inline] | |
| 222 | fn support_hint(&self) -> Cube<F, N> { | |
| 223 | let two_r = 2.0 * self.0.r.value(); | |
| 224 | array_init(|| [-two_r, two_r]).into() | |
| 225 | } | |
| 226 | ||
| 227 | #[inline] | |
| 228 | fn in_support(&self, y : &Loc<F, N>) -> bool { | |
| 229 | let two_r = 2.0 * self.0.r.value(); | |
| 230 | y.iter().all(|x| x.abs() <= two_r) | |
| 231 | } | |
| 232 | ||
| 233 | #[inline] | |
| 234 | fn bisection_hint(&self, cube : &Cube<F, N>) -> [Option<F>; N] { | |
| 235 | let two_r = 2.0 * self.0.r.value(); | |
| 236 | cube.map(|c, d| symmetric_interval_hint(two_r, c, d)) | |
| 237 | } | |
| 238 | } | |
| 239 | ||
| 240 | #[replace_float_literals(F::cast_from(literal))] | |
| 241 | impl<F : Float, R, const N : usize> GlobalAnalysis<F, Bounds<F>> | |
| 242 | for AutoConvolution<CubeIndicator<R, N>> | |
| 243 | where R : Constant<Type=F> { | |
| 244 | #[inline] | |
| 245 | fn global_analysis(&self) -> Bounds<F> { | |
| 246 | Bounds(0.0, self.apply(Loc::ORIGIN)) | |
| 247 | } | |
| 248 | } | |
| 249 | ||
| 250 | impl<F : Float, R, const N : usize> LocalAnalysis<F, Bounds<F>, N> | |
| 251 | for AutoConvolution<CubeIndicator<R, N>> | |
| 252 | where R : Constant<Type=F> { | |
| 253 | #[inline] | |
| 254 | fn local_analysis(&self, cube : &Cube<F, N>) -> Bounds<F> { | |
| 255 | // The function is maximised/minimised where the absolute value is minimised/maximised. | |
| 256 | let lower = self.apply(cube.maxnorm_point()); | |
| 257 | let upper = self.apply(cube.minnorm_point()); | |
| 258 | Bounds(lower, upper) | |
| 259 | } | |
| 260 | } |