Tue, 06 Dec 2022 14:12:20 +0200
v1.0.0-pre-arxiv (missing arXiv links)
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 | } |