| 66 // |
68 // |
| 67 // Convolutions for discrete measures |
69 // Convolutions for discrete measures |
| 68 // |
70 // |
| 69 |
71 |
| 70 /// A trait alias for simple convolution kernels. |
72 /// A trait alias for simple convolution kernels. |
| 71 pub trait SimpleConvolutionKernel<F: Float, const N: usize>: |
73 pub trait SimpleConvolutionKernel<const N: usize, F: Float = f64>: |
| 72 RealMapping<F, N> + Support<F, N> + Bounded<F> + Clone + 'static |
74 RealMapping<N, F> + Support<N, F> + Bounded<F> + Clone + 'static |
| 73 { |
75 { |
| 74 } |
76 } |
| 75 |
77 |
| 76 impl<T, F: Float, const N: usize> SimpleConvolutionKernel<F, N> for T where |
78 impl<T, F: Float, const N: usize> SimpleConvolutionKernel<N, F> for T where |
| 77 T: RealMapping<F, N> + Support<F, N> + Bounded<F> + Clone + 'static |
79 T: RealMapping<N, F> + Support<N, F> + Bounded<F> + Clone + 'static |
| 78 { |
80 { |
| 79 } |
81 } |
| 80 |
82 |
| 81 /// [`SupportGenerator`] for [`ConvolutionOp`]. |
83 /// [`SupportGenerator`] for [`ConvolutionOp`]. |
| 82 #[derive(Clone, Debug)] |
84 #[derive(Clone, Debug)] |
| 83 pub struct ConvolutionSupportGenerator<F: Float, K, const N: usize> |
85 pub struct ConvolutionSupportGenerator<K, const N: usize, F: Float = f64> |
| 84 where |
86 where |
| 85 K: SimpleConvolutionKernel<F, N>, |
87 K: SimpleConvolutionKernel<N, F>, |
| 86 { |
88 { |
| 87 kernel: K, |
89 kernel: K, |
| 88 centres: RNDM<F, N>, |
90 centres: RNDM<N, F>, |
| 89 } |
91 } |
| 90 |
92 |
| 91 impl<F: Float, K, const N: usize> ConvolutionSupportGenerator<F, K, N> |
93 impl<F: Float, K, const N: usize> ConvolutionSupportGenerator<K, N, F> |
| 92 where |
94 where |
| 93 K: SimpleConvolutionKernel<F, N>, |
95 K: SimpleConvolutionKernel<N, F>, |
| 94 { |
96 { |
| 95 /// Construct the convolution kernel corresponding to `δ`, i.e., one centered at `δ.x` and |
97 /// Construct the convolution kernel corresponding to `δ`, i.e., one centered at `δ.x` and |
| 96 /// weighted by `δ.α`. |
98 /// weighted by `δ.α`. |
| 97 #[inline] |
99 #[inline] |
| 98 fn construct_kernel<'a>( |
100 fn construct_kernel<'a>( |
| 99 &'a self, |
101 &'a self, |
| 100 δ: &'a DeltaMeasure<Loc<F, N>, F>, |
102 δ: &'a DeltaMeasure<Loc<N, F>, F>, |
| 101 ) -> Weighted<Shift<K, F, N>, F> { |
103 ) -> Weighted<Shift<K, N, F>, F> { |
| 102 self.kernel.clone().shift(δ.x).weigh(δ.α) |
104 self.kernel.clone().shift(δ.x).weigh(δ.α) |
| 103 } |
105 } |
| 104 |
106 |
| 105 /// This is a helper method for the implementation of [`ConvolutionSupportGenerator::all_data`]. |
107 /// This is a helper method for the implementation of [`ConvolutionSupportGenerator::all_data`]. |
| 106 /// It filters out `δ` with zero weight, and otherwise returns the corresponding convolution |
108 /// It filters out `δ` with zero weight, and otherwise returns the corresponding convolution |
| 107 /// kernel. The `id` is passed through as-is. |
109 /// kernel. The `id` is passed through as-is. |
| 108 #[inline] |
110 #[inline] |
| 109 fn construct_kernel_and_id_filtered<'a>( |
111 fn construct_kernel_and_id_filtered<'a>( |
| 110 &'a self, |
112 &'a self, |
| 111 (id, δ): (usize, &'a DeltaMeasure<Loc<F, N>, F>), |
113 (id, δ): (usize, &'a DeltaMeasure<Loc<N, F>, F>), |
| 112 ) -> Option<(usize, Weighted<Shift<K, F, N>, F>)> { |
114 ) -> Option<(usize, Weighted<Shift<K, N, F>, F>)> { |
| 113 (δ.α != F::ZERO).then(|| (id.into(), self.construct_kernel(δ))) |
115 (δ.α != F::ZERO).then(|| (id.into(), self.construct_kernel(δ))) |
| 114 } |
116 } |
| 115 } |
117 } |
| 116 |
118 |
| 117 impl<F: Float, K, const N: usize> SupportGenerator<F, N> for ConvolutionSupportGenerator<F, K, N> |
119 impl<F: Float, K, const N: usize> SupportGenerator<N, F> for ConvolutionSupportGenerator<K, N, F> |
| 118 where |
120 where |
| 119 K: SimpleConvolutionKernel<F, N>, |
121 K: SimpleConvolutionKernel<N, F>, |
| 120 { |
122 { |
| 121 type Id = usize; |
123 type Id = usize; |
| 122 type SupportType = Weighted<Shift<K, F, N>, F>; |
124 type SupportType = Weighted<Shift<K, N, F>, F>; |
| 123 type AllDataIter<'a> = FilterMapX< |
125 type AllDataIter<'a> = FilterMapX< |
| 124 'a, |
126 'a, |
| 125 Zip<RangeFrom<usize>, SpikeIter<'a, Loc<F, N>, F>>, |
127 Zip<RangeFrom<usize>, SpikeIter<'a, Loc<N, F>, F>>, |
| 126 Self, |
128 Self, |
| 127 (Self::Id, Self::SupportType), |
129 (Self::Id, Self::SupportType), |
| 128 >; |
130 >; |
| 129 |
131 |
| 130 #[inline] |
132 #[inline] |
| 148 /// Representation of a convolution operator $𝒟$. |
150 /// Representation of a convolution operator $𝒟$. |
| 149 #[derive(Clone, Debug)] |
151 #[derive(Clone, Debug)] |
| 150 pub struct ConvolutionOp<F, K, BT, const N: usize> |
152 pub struct ConvolutionOp<F, K, BT, const N: usize> |
| 151 where |
153 where |
| 152 F: Float + ToNalgebraRealField, |
154 F: Float + ToNalgebraRealField, |
| 153 BT: BTImpl<F, N, Data = usize>, |
155 BT: BTImpl<N, F, Data = usize>, |
| 154 K: SimpleConvolutionKernel<F, N>, |
156 K: SimpleConvolutionKernel<N, F>, |
| 155 { |
157 { |
| 156 /// Depth of the [`BT`] bisection tree for the outputs [`Mapping::apply`]. |
158 /// Depth of the [`BT`] bisection tree for the outputs [`Mapping::apply`]. |
| 157 depth: BT::Depth, |
159 depth: BT::Depth, |
| 158 /// Domain of the [`BT`] bisection tree for the outputs [`Mapping::apply`]. |
160 /// Domain of the [`BT`] bisection tree for the outputs [`Mapping::apply`]. |
| 159 domain: Cube<F, N>, |
161 domain: Cube<N, F>, |
| 160 /// The convolution kernel |
162 /// The convolution kernel |
| 161 kernel: K, |
163 kernel: K, |
| 162 _phantoms: PhantomData<(F, BT)>, |
164 _phantoms: PhantomData<(F, BT)>, |
| 163 } |
165 } |
| 164 |
166 |
| 165 impl<F, K, BT, const N: usize> ConvolutionOp<F, K, BT, N> |
167 impl<F, K, BT, const N: usize> ConvolutionOp<F, K, BT, N> |
| 166 where |
168 where |
| 167 F: Float + ToNalgebraRealField, |
169 F: Float + ToNalgebraRealField, |
| 168 BT: BTImpl<F, N, Data = usize>, |
170 BT: BTImpl<N, F, Data = usize>, |
| 169 K: SimpleConvolutionKernel<F, N>, |
171 K: SimpleConvolutionKernel<N, F>, |
| 170 { |
172 { |
| 171 /// Creates a new convolution operator $𝒟$ with `kernel` on `domain`. |
173 /// Creates a new convolution operator $𝒟$ with `kernel` on `domain`. |
| 172 /// |
174 /// |
| 173 /// The output of [`Mapping::apply`] is a [`BT`] of given `depth`. |
175 /// The output of [`Mapping::apply`] is a [`BT`] of given `depth`. |
| 174 pub fn new(depth: BT::Depth, domain: Cube<F, N>, kernel: K) -> Self { |
176 pub fn new(depth: BT::Depth, domain: Cube<N, F>, kernel: K) -> Self { |
| 175 ConvolutionOp { |
177 ConvolutionOp { |
| 176 depth: depth, |
178 depth: depth, |
| 177 domain: domain, |
179 domain: domain, |
| 178 kernel: kernel, |
180 kernel: kernel, |
| 179 _phantoms: PhantomData, |
181 _phantoms: PhantomData, |
| 180 } |
182 } |
| 181 } |
183 } |
| 182 |
184 |
| 183 /// Returns the support generator for this convolution operator. |
185 /// Returns the support generator for this convolution operator. |
| 184 fn support_generator(&self, μ: RNDM<F, N>) -> ConvolutionSupportGenerator<F, K, N> { |
186 fn support_generator(&self, μ: RNDM<N, F>) -> ConvolutionSupportGenerator<K, N, F> { |
| 185 // TODO: can we avoid cloning μ? |
187 // TODO: can we avoid cloning μ? |
| 186 ConvolutionSupportGenerator { |
188 ConvolutionSupportGenerator { |
| 187 kernel: self.kernel.clone(), |
189 kernel: self.kernel.clone(), |
| 188 centres: μ, |
190 centres: μ, |
| 189 } |
191 } |
| 193 pub fn kernel(&self) -> &K { |
195 pub fn kernel(&self) -> &K { |
| 194 &self.kernel |
196 &self.kernel |
| 195 } |
197 } |
| 196 } |
198 } |
| 197 |
199 |
| 198 impl<F, K, BT, const N: usize> Mapping<RNDM<F, N>> for ConvolutionOp<F, K, BT, N> |
200 impl<F, K, BT, const N: usize> Mapping<RNDM<N, F>> for ConvolutionOp<F, K, BT, N> |
| 199 where |
201 where |
| 200 F: Float + ToNalgebraRealField, |
202 F: Float + ToNalgebraRealField, |
| 201 BT: BTImpl<F, N, Data = usize>, |
203 BT: BTImpl<N, F, Data = usize>, |
| 202 K: SimpleConvolutionKernel<F, N>, |
204 K: SimpleConvolutionKernel<N, F>, |
| 203 Weighted<Shift<K, F, N>, F>: LocalAnalysis<F, BT::Agg, N>, |
205 Weighted<Shift<K, N, F>, F>: LocalAnalysis<F, BT::Agg, N>, |
| 204 { |
206 { |
| 205 type Codomain = BTFN<F, ConvolutionSupportGenerator<F, K, N>, BT, N>; |
207 type Codomain = BTFN<F, ConvolutionSupportGenerator<K, N, F>, BT, N>; |
| 206 |
208 |
| 207 fn apply<I>(&self, μ: I) -> Self::Codomain |
209 fn apply<I>(&self, μ: I) -> Self::Codomain |
| 208 where |
210 where |
| 209 I: Instance<RNDM<F, N>>, |
211 I: Instance<RNDM<N, F>>, |
| 210 { |
212 { |
| 211 let g = self.support_generator(μ.own()); |
213 let g = self.support_generator(μ.own()); |
| 212 BTFN::construct(self.domain.clone(), self.depth, g) |
214 BTFN::construct(self.domain.clone(), self.depth, g) |
| 213 } |
215 } |
| 214 } |
216 } |
| 215 |
217 |
| 216 /// [`ConvolutionOp`]s as linear operators over [`DiscreteMeasure`]s. |
218 /// [`ConvolutionOp`]s as linear operators over [`DiscreteMeasure`]s. |
| 217 impl<F, K, BT, const N: usize> Linear<RNDM<F, N>> for ConvolutionOp<F, K, BT, N> |
219 impl<F, K, BT, const N: usize> Linear<RNDM<N, F>> for ConvolutionOp<F, K, BT, N> |
| 218 where |
220 where |
| 219 F: Float + ToNalgebraRealField, |
221 F: Float + ToNalgebraRealField, |
| 220 BT: BTImpl<F, N, Data = usize>, |
222 BT: BTImpl<N, F, Data = usize>, |
| 221 K: SimpleConvolutionKernel<F, N>, |
223 K: SimpleConvolutionKernel<N, F>, |
| 222 Weighted<Shift<K, F, N>, F>: LocalAnalysis<F, BT::Agg, N>, |
224 Weighted<Shift<K, N, F>, F>: LocalAnalysis<F, BT::Agg, N>, |
| 223 { |
225 { |
| 224 } |
226 } |
| 225 |
227 |
| 226 impl<F, K, BT, const N: usize> BoundedLinear<RNDM<F, N>, Radon, Linfinity, F> |
228 impl<F, K, BT, const N: usize> BoundedLinear<RNDM<N, F>, Radon, Linfinity, F> |
| 227 for ConvolutionOp<F, K, BT, N> |
229 for ConvolutionOp<F, K, BT, N> |
| 228 where |
230 where |
| 229 F: Float + ToNalgebraRealField, |
231 F: Float + ToNalgebraRealField, |
| 230 BT: BTImpl<F, N, Data = usize>, |
232 BT: BTImpl<N, F, Data = usize>, |
| 231 K: SimpleConvolutionKernel<F, N>, |
233 K: SimpleConvolutionKernel<N, F>, |
| 232 Weighted<Shift<K, F, N>, F>: LocalAnalysis<F, BT::Agg, N>, |
234 Weighted<Shift<K, N, F>, F>: LocalAnalysis<F, BT::Agg, N>, |
| 233 { |
235 { |
| 234 fn opnorm_bound(&self, _: Radon, _: Linfinity) -> F { |
236 fn opnorm_bound(&self, _: Radon, _: Linfinity) -> DynResult<F> { |
| 235 // With μ = ∑_i α_i δ_{x_i}, we have |
237 // With μ = ∑_i α_i δ_{x_i}, we have |
| 236 // |𝒟μ|_∞ |
238 // |𝒟μ|_∞ |
| 237 // = sup_z |∑_i α_i φ(z - x_i)| |
239 // = sup_z |∑_i α_i φ(z - x_i)| |
| 238 // ≤ sup_z ∑_i |α_i| |φ(z - x_i)| |
240 // ≤ sup_z ∑_i |α_i| |φ(z - x_i)| |
| 239 // ≤ ∑_i |α_i| |φ|_∞ |
241 // ≤ ∑_i |α_i| |φ|_∞ |
| 240 // = |μ|_ℳ |φ|_∞ |
242 // = |μ|_ℳ |φ|_∞ |
| 241 self.kernel.bounds().uniform() |
243 Ok(self.kernel.bounds().uniform()) |
| 242 } |
244 } |
| 243 } |
245 } |
| 244 |
246 |
| 245 impl<F, K, BT, const N: usize> DiscreteMeasureOp<Loc<F, N>, F> for ConvolutionOp<F, K, BT, N> |
247 impl<'a, F, K, BT, const N: usize> NormExponent for &'a ConvolutionOp<F, K, BT, N> |
| 246 where |
248 where |
| 247 F: Float + ToNalgebraRealField, |
249 F: Float + ToNalgebraRealField, |
| 248 BT: BTImpl<F, N, Data = usize>, |
250 BT: BTImpl<N, F, Data = usize>, |
| 249 K: SimpleConvolutionKernel<F, N>, |
251 K: SimpleConvolutionKernel<N, F>, |
| 250 Weighted<Shift<K, F, N>, F>: LocalAnalysis<F, BT::Agg, N>, |
252 Weighted<Shift<K, N, F>, F>: LocalAnalysis<F, BT::Agg, N>, |
| 251 { |
253 { |
| 252 type PreCodomain = PreBTFN<F, ConvolutionSupportGenerator<F, K, N>, N>; |
254 } |
| |
255 |
| |
256 impl<'a, F, K, BT, const N: usize> Norm<&'a ConvolutionOp<F, K, BT, N>, F> for RNDM<N, F> |
| |
257 where |
| |
258 F: Float + ToNalgebraRealField, |
| |
259 BT: BTImpl<N, F, Data = usize>, |
| |
260 K: SimpleConvolutionKernel<N, F>, |
| |
261 Weighted<Shift<K, N, F>, F>: LocalAnalysis<F, BT::Agg, N>, |
| |
262 { |
| |
263 fn norm(&self, op𝒟: &'a ConvolutionOp<F, K, BT, N>) -> F { |
| |
264 self.apply(op𝒟.apply(self)).sqrt() |
| |
265 } |
| |
266 } |
| |
267 |
| |
268 impl<F, K, BT, const N: usize> DiscreteMeasureOp<Loc<N, F>, F> for ConvolutionOp<F, K, BT, N> |
| |
269 where |
| |
270 F: Float + ToNalgebraRealField, |
| |
271 BT: BTImpl<N, F, Data = usize>, |
| |
272 K: SimpleConvolutionKernel<N, F>, |
| |
273 Weighted<Shift<K, N, F>, F>: LocalAnalysis<F, BT::Agg, N>, |
| |
274 { |
| |
275 type PreCodomain = PreBTFN<F, ConvolutionSupportGenerator<K, N, F>, N>; |
| 253 |
276 |
| 254 fn findim_matrix<'a, I>(&self, points: I) -> DMatrix<F::MixedType> |
277 fn findim_matrix<'a, I>(&self, points: I) -> DMatrix<F::MixedType> |
| 255 where |
278 where |
| 256 I: ExactSizeIterator<Item = &'a Loc<F, N>> + Clone, |
279 I: ExactSizeIterator<Item = &'a Loc<N, F>> + Clone, |
| 257 { |
280 { |
| 258 // TODO: Preliminary implementation. It be best to use sparse matrices or |
281 // TODO: Preliminary implementation. It be best to use sparse matrices or |
| 259 // possibly explicit operators without matrices |
282 // possibly explicit operators without matrices |
| 260 let n = points.len(); |
283 let n = points.len(); |
| 261 let points_clone = points.clone(); |
284 let points_clone = points.clone(); |
| 266 } |
289 } |
| 267 |
290 |
| 268 /// A version of [`Mapping::apply`] that does not instantiate the [`BTFN`] codomain with |
291 /// A version of [`Mapping::apply`] that does not instantiate the [`BTFN`] codomain with |
| 269 /// a bisection tree, instead returning a [`PreBTFN`]. This can improve performance when |
292 /// a bisection tree, instead returning a [`PreBTFN`]. This can improve performance when |
| 270 /// the output is to be added as the right-hand-side operand to a proper BTFN. |
293 /// the output is to be added as the right-hand-side operand to a proper BTFN. |
| 271 fn preapply(&self, μ: RNDM<F, N>) -> Self::PreCodomain { |
294 fn preapply(&self, μ: RNDM<N, F>) -> Self::PreCodomain { |
| 272 BTFN::new_pre(self.support_generator(μ)) |
295 BTFN::new_pre(self.support_generator(μ)) |
| 273 } |
296 } |
| 274 } |
297 } |
| 275 |
298 |
| 276 /// Generates an scalar operation (e.g. [`std::ops::Mul`], [`std::ops::Div`]) |
299 /// Generates an scalar operation (e.g. [`std::ops::Mul`], [`std::ops::Div`]) |
| 277 /// for [`ConvolutionSupportGenerator`]. |
300 /// for [`ConvolutionSupportGenerator`]. |
| 278 macro_rules! make_convolutionsupportgenerator_scalarop_rhs { |
301 macro_rules! make_convolutionsupportgenerator_scalarop_rhs { |
| 279 ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { |
302 ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { |
| 280 impl<F: Float, K: SimpleConvolutionKernel<F, N>, const N: usize> std::ops::$trait_assign<F> |
303 impl<F: Float, K: SimpleConvolutionKernel<N, F>, const N: usize> std::ops::$trait_assign<F> |
| 281 for ConvolutionSupportGenerator<F, K, N> |
304 for ConvolutionSupportGenerator<K, N, F> |
| 282 { |
305 { |
| 283 fn $fn_assign(&mut self, t: F) { |
306 fn $fn_assign(&mut self, t: F) { |
| 284 self.centres.$fn_assign(t); |
307 self.centres.$fn_assign(t); |
| 285 } |
308 } |
| 286 } |
309 } |
| 287 |
310 |
| 288 impl<F: Float, K: SimpleConvolutionKernel<F, N>, const N: usize> std::ops::$trait<F> |
311 impl<F: Float, K: SimpleConvolutionKernel<N, F>, const N: usize> std::ops::$trait<F> |
| 289 for ConvolutionSupportGenerator<F, K, N> |
312 for ConvolutionSupportGenerator<K, N, F> |
| 290 { |
313 { |
| 291 type Output = ConvolutionSupportGenerator<F, K, N>; |
314 type Output = ConvolutionSupportGenerator<K, N, F>; |
| 292 fn $fn(mut self, t: F) -> Self::Output { |
315 fn $fn(mut self, t: F) -> Self::Output { |
| 293 std::ops::$trait_assign::$fn_assign(&mut self.centres, t); |
316 std::ops::$trait_assign::$fn_assign(&mut self.centres, t); |
| 294 self |
317 self |
| 295 } |
318 } |
| 296 } |
319 } |
| 297 impl<'a, F: Float, K: SimpleConvolutionKernel<F, N>, const N: usize> std::ops::$trait<F> |
320 impl<'a, F: Float, K: SimpleConvolutionKernel<N, F>, const N: usize> std::ops::$trait<F> |
| 298 for &'a ConvolutionSupportGenerator<F, K, N> |
321 for &'a ConvolutionSupportGenerator<K, N, F> |
| 299 { |
322 { |
| 300 type Output = ConvolutionSupportGenerator<F, K, N>; |
323 type Output = ConvolutionSupportGenerator<K, N, F>; |
| 301 fn $fn(self, t: F) -> Self::Output { |
324 fn $fn(self, t: F) -> Self::Output { |
| 302 ConvolutionSupportGenerator { |
325 ConvolutionSupportGenerator { |
| 303 kernel: self.kernel.clone(), |
326 kernel: self.kernel.clone(), |
| 304 centres: (&self.centres).$fn(t), |
327 centres: (&self.centres).$fn(t), |
| 305 } |
328 } |
| 312 make_convolutionsupportgenerator_scalarop_rhs!(Div, div, DivAssign, div_assign); |
335 make_convolutionsupportgenerator_scalarop_rhs!(Div, div, DivAssign, div_assign); |
| 313 |
336 |
| 314 /// Generates an unary operation (e.g. [`std::ops::Neg`]) for [`ConvolutionSupportGenerator`]. |
337 /// Generates an unary operation (e.g. [`std::ops::Neg`]) for [`ConvolutionSupportGenerator`]. |
| 315 macro_rules! make_convolutionsupportgenerator_unaryop { |
338 macro_rules! make_convolutionsupportgenerator_unaryop { |
| 316 ($trait:ident, $fn:ident) => { |
339 ($trait:ident, $fn:ident) => { |
| 317 impl<F: Float, K: SimpleConvolutionKernel<F, N>, const N: usize> std::ops::$trait |
340 impl<F: Float, K: SimpleConvolutionKernel<N, F>, const N: usize> std::ops::$trait |
| 318 for ConvolutionSupportGenerator<F, K, N> |
341 for ConvolutionSupportGenerator<K, N, F> |
| 319 { |
342 { |
| 320 type Output = ConvolutionSupportGenerator<F, K, N>; |
343 type Output = ConvolutionSupportGenerator<K, N, F>; |
| 321 fn $fn(mut self) -> Self::Output { |
344 fn $fn(mut self) -> Self::Output { |
| 322 self.centres = self.centres.$fn(); |
345 self.centres = self.centres.$fn(); |
| 323 self |
346 self |
| 324 } |
347 } |
| 325 } |
348 } |
| 326 |
349 |
| 327 impl<'a, F: Float, K: SimpleConvolutionKernel<F, N>, const N: usize> std::ops::$trait |
350 impl<'a, F: Float, K: SimpleConvolutionKernel<N, F>, const N: usize> std::ops::$trait |
| 328 for &'a ConvolutionSupportGenerator<F, K, N> |
351 for &'a ConvolutionSupportGenerator<K, N, F> |
| 329 { |
352 { |
| 330 type Output = ConvolutionSupportGenerator<F, K, N>; |
353 type Output = ConvolutionSupportGenerator<K, N, F>; |
| 331 fn $fn(self) -> Self::Output { |
354 fn $fn(self) -> Self::Output { |
| 332 ConvolutionSupportGenerator { |
355 ConvolutionSupportGenerator { |
| 333 kernel: self.kernel.clone(), |
356 kernel: self.kernel.clone(), |
| 334 centres: (&self.centres).$fn(), |
357 centres: (&self.centres).$fn(), |
| 335 } |
358 } |