Skip to content

Commit cc167a6

Browse files
committed
Add Fourier method
1 parent 530ffe4 commit cc167a6

File tree

2 files changed

+154
-3
lines changed

2 files changed

+154
-3
lines changed

src/field.rs

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,73 @@ pub fn summator_incompr(
181181
}
182182
}
183183

184+
/// The Fourier method for scalar fields.
185+
///
186+
/// Computes the periodic, isotropic spatial random field $u(x)$ by the Fourier
187+
/// method according to
188+
/// $$
189+
/// u(x) = \sum_{i=1}^N \sqrt{2S(k_i\Delta k}\left(
190+
/// z_{1,i} \cdot \cos(\langle k_i, x \rangle) +
191+
/// z_{2,i} \cdot \sin(\langle k_i, x \rangle)\right)
192+
/// $$
193+
/// with
194+
/// * $S$ being the spectrum of the covariance model
195+
/// * $N$ being the number of Fourier modes
196+
/// * $z_1, z_2$ being independent samples from a standard normal distribution
197+
/// * $k$ being the equidistant Fourier grid
198+
/// and are given by the argument `modes`.
199+
/// * $\Delta k$ being the cell size of the Fourier grid
200+
///
201+
/// # Arguments
202+
///
203+
/// * `spectrum_factor` - the pre-calculated factor $\sqrt{2S(k_i\Delta k)}
204+
/// <br>&nbsp;&nbsp;&nbsp;&nbsp; dim = Fourier modes $N$
205+
/// * `modes` - equidistant Fourier grid, $k$ in Eq. above
206+
/// <br>&nbsp;&nbsp;&nbsp;&nbsp;dim = (spatial dim. of field $d$, Fourier modes $N$)
207+
/// * `z1` - independent samples from a standard normal distribution
208+
/// <br>&nbsp;&nbsp;&nbsp;&nbsp;dim = Fourier modes $N$
209+
/// * `z2` - independent samples from a standard normal distribution
210+
/// <br>&nbsp;&nbsp;&nbsp;&nbsp;dim = Fourier modes $N$
211+
/// * `pos` - the position $x$ where the spatial random field is calculated
212+
/// <br>&nbsp;&nbsp;&nbsp;&nbsp;dim = (spatial dim. of field $d$, no. of spatial points where field is calculated $j$)
213+
/// * `num_threads` - the number of parallel threads used, if None, use rayon's default
214+
///
215+
/// # Returns
216+
///
217+
/// * `summed_modes` - the isotropic spatial field
218+
/// <br>&nbsp;&nbsp;&nbsp;&nbsp;dim = no. of spatial points where field is calculated $j$
219+
pub fn summator_fourier(
220+
spectrum_factor: ArrayView1<'_, f64>,
221+
modes: ArrayView2<'_, f64>,
222+
z1: ArrayView1<'_, f64>,
223+
z2: ArrayView1<'_, f64>,
224+
pos: ArrayView2<'_, f64>,
225+
num_threads: Option<usize>,
226+
) -> Array1<f64> {
227+
assert_eq!(modes.dim().0, pos.dim().0);
228+
assert_eq!(modes.dim().1, z1.dim());
229+
assert_eq!(modes.dim().1, z2.dim());
230+
231+
rayon::ThreadPoolBuilder::new()
232+
.num_threads(num_threads.unwrap_or(rayon::current_num_threads()))
233+
.build()
234+
.unwrap()
235+
.install(|| {
236+
Zip::from(pos.columns()).par_map_collect(|pos| {
237+
Zip::from(modes.columns())
238+
.and(spectrum_factor)
239+
.and(z1)
240+
.and(z2)
241+
.fold(0.0, |sum, k, &spectrum_factor, &z1, &z2| {
242+
let phase = k.dot(&pos);
243+
let z12 = spectrum_factor * (z1 * phase.cos() + z2 * phase.sin());
244+
245+
sum + z12
246+
})
247+
})
248+
})
249+
}
250+
184251
#[cfg(test)]
185252
mod tests {
186253
use super::*;
@@ -224,8 +291,73 @@ mod tests {
224291
}
225292
}
226293

294+
struct SetupFourier {
295+
spectrum_factor: Array1<f64>,
296+
modes: Array2<f64>,
297+
z_1: Array1<f64>,
298+
z_2: Array1<f64>,
299+
pos: Array2<f64>,
300+
}
301+
302+
impl SetupFourier {
303+
fn new() -> Self {
304+
Self {
305+
spectrum_factor: arr1(&[
306+
-2.15, 1.04, 0.69, -1.09, -1.54, -2.32, -1.81, -2.78, 1.57, -3.44,
307+
]),
308+
modes: arr2(&[
309+
[
310+
-2.15, 1.04, 0.69, -1.09, -1.54, -2.32, -1.81, -2.78, 1.57, -3.44,
311+
],
312+
[
313+
0.19, -1.24, -2.10, -2.86, -0.63, -0.51, -1.68, -0.07, 0.29, -0.007,
314+
],
315+
[
316+
0.98, -2.83, -0.10, 3.23, 0.51, 0.13, -1.03, 1.53, -0.51, 2.82,
317+
],
318+
]),
319+
z_1: arr1(&[
320+
-1.93, 0.46, 0.66, 0.02, -0.10, 1.29, 0.93, -1.14, 1.81, 1.47,
321+
]),
322+
z_2: arr1(&[
323+
-0.26, 0.98, -1.30, 0.66, 0.57, -0.25, -0.31, -0.29, 0.69, 1.14,
324+
]),
325+
pos: arr2(&[
326+
[0.00, 1.43, 2.86, 4.29, 5.71, 7.14, 9.57, 10.00],
327+
[-5.00, -3.57, -2.14, -0.71, 0.71, 2.14, 3.57, 5.00],
328+
[-6.00, -4.00, -2.00, 0.00, 2.00, 4.00, 6.00, 8.00],
329+
]),
330+
}
331+
}
332+
}
333+
334+
#[test]
335+
fn test_summate_fourier_2d() {
336+
let setup = SetupFourier::new();
337+
assert_eq!(
338+
summator_fourier(
339+
setup.spectrum_factor.view(),
340+
setup.modes.view(),
341+
setup.z_1.view(),
342+
setup.z_2.view(),
343+
setup.pos.view(),
344+
None,
345+
),
346+
arr1(&[
347+
1.0666558330143816,
348+
-3.5855143411414883,
349+
-2.70208228699285,
350+
9.808554698975039,
351+
0.01634921830347258,
352+
-2.2356422006860663,
353+
14.730786907708966,
354+
-2.851408419726332,
355+
])
356+
);
357+
}
358+
227359
#[test]
228-
fn test_summate_3d() {
360+
fn test_summate_2d() {
229361
let setup = Setup::new();
230362

231363
assert_eq!(
@@ -250,7 +382,7 @@ mod tests {
250382
}
251383

252384
#[test]
253-
fn test_summate_incompr_3d() {
385+
fn test_summate_incompr_2d() {
254386
let setup = Setup::new();
255387

256388
assert_ulps_eq!(

src/lib.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use numpy::{IntoPyArray, PyArray1, PyArray2, PyReadonlyArray1, PyReadonlyArray2};
1616
use pyo3::prelude::{pymodule, PyModule, PyResult, Python};
1717

18-
use field::{summator, summator_incompr};
18+
use field::{summator, summator_incompr, summator_fourier};
1919
use krige::{calculator_field_krige, calculator_field_krige_and_variance};
2020
use variogram::{
2121
variogram_directional, variogram_ma_structured, variogram_structured, variogram_unstructured,
@@ -64,6 +64,25 @@ fn gstools_core(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
6464
summator_incompr(cov_samples, z1, z2, pos, num_threads).into_pyarray(py)
6565
}
6666

67+
#[pyfn(m)]
68+
#[pyo3(name = "summate_fourier")]
69+
fn summate_fourier_py<'py>(
70+
py: Python<'py>,
71+
spectrum_factor: PyReadonlyArray1<f64>,
72+
modes: PyReadonlyArray2<f64>,
73+
z1: PyReadonlyArray1<f64>,
74+
z2: PyReadonlyArray1<f64>,
75+
pos: PyReadonlyArray2<f64>,
76+
num_threads: Option<usize>,
77+
) -> &'py PyArray1<f64> {
78+
let spectrum_factor = spectrum_factor.as_array();
79+
let modes = modes.as_array();
80+
let z1 = z1.as_array();
81+
let z2 = z2.as_array();
82+
let pos = pos.as_array();
83+
summator_fourier(spectrum_factor, modes, z1, z2, pos, num_threads).into_pyarray(py)
84+
}
85+
6786
#[pyfn(m)]
6887
#[pyo3(name = "calc_field_krige_and_variance")]
6988
fn calc_field_krige_and_variance_py<'py>(

0 commit comments

Comments
 (0)