1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
use anyhow::Result;
use ndarray::prelude::*;
use ndarray::{Array, ArrayBase, OwnedRepr};
use sobol_burley::sample;

/// Generates a 2-dimensional array containing a Sobol sequence within the given ranges.
///
/// This function samples the space using a Sobol sequence of `n_points` points. distributed along `range_params.len()` dimensions.
///
/// This function is used to initialize an optimization algorithm.
/// The generated Sobol sequence provides the initial sampling of the step to be used in the first cycle of the optimization algorithm.
///
/// # Arguments
///
/// * `n_points` - The number of points in the Sobol sequence.
/// * `range_params` - A vector of tuples, where each tuple represents the minimum and maximum value of a parameter.
/// * `seed` - The seed for the Sobol sequence generator.
///
/// # Returns
///
/// A 2D array where each row is a point in the Sobol sequence, and each column corresponds to a parameter.
/// The value of each parameter is scaled to be within the corresponding range.
///
pub fn generate(
    n_points: usize,
    range_params: &Vec<(f64, f64)>,
    seed: usize,
) -> Result<ArrayBase<OwnedRepr<f64>, Dim<[usize; 2]>>> {
    let n_params = range_params.len();
    let mut seq = Array::<f64, _>::zeros((n_points, n_params).f());
    for i in 0..n_points {
        let mut row = seq.slice_mut(s![i, ..]);
        let mut point: Vec<f64> = Vec::new();
        for j in 0..n_params {
            point.push(sample(i.try_into()?, j.try_into()?, seed as u32) as f64)
        }
        row.assign(&Array::from(point));
    }
    for i in 0..n_params {
        let mut column = seq.slice_mut(s![.., i]);
        let (min, max) = range_params.get(i).unwrap();
        column.par_mapv_inplace(|x| min + x * (max - min));
    }
    Ok(seq)
}

#[cfg(test)]
use crate::prelude::*;

#[test]
fn basic_sobol() {
    assert_eq!(
        initialization::sobol::generate(5, &vec![(0., 1.), (0., 1.), (0., 1.)], 347).unwrap(),
        ndarray::array![
            [0.10731887817382813, 0.14647412300109863, 0.5851038694381714],
            [0.9840304851531982, 0.7633365392684937, 0.19097506999969482],
            [0.38477110862731934, 0.734661340713501, 0.2616291046142578],
            [0.7023299932479858, 0.41038262844085693, 0.9158684015274048],
            [0.6016758680343628, 0.6171295642852783, 0.6263971328735352]
        ]
    )
}

#[test]
fn scaled_sobol() {
    assert_eq!(
        initialization::sobol::generate(5, &vec![(0., 1.), (0., 2.), (-1., 1.)], 347).unwrap(),
        ndarray::array![
            [
                0.10731887817382813,
                0.29294824600219727,
                0.17020773887634277
            ],
            [0.9840304851531982, 1.5266730785369873, -0.6180498600006104],
            [0.38477110862731934, 1.469322681427002, -0.4767417907714844],
            [0.7023299932479858, 0.8207652568817139, 0.8317368030548096],
            [0.6016758680343628, 1.2342591285705566, 0.2527942657470703]
        ]
    )
}

//TODO: It should be possible to avoid one of the for-loops
//this improvement should happen automatically if switching columns with rows.
//theta0 = hcat([a .+ (b - a) .* Sobol.next!(s) for i = 1:n_theta0]...)

//TODO: Implement alternative samplers, such as uniform and Normal distributions