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
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use libc::{c_double, c_int};
use std::slice;
use libffi::high::Closure4;


#[link(name = "gfortran")]
extern {
    /// Call `DLSODE` subroutine from ODEPACK
    ///
    /// For info on passed arguments look inside ODEPACK.
    pub fn dlsode_(f: extern fn(*const c_int, *const c_double, *mut c_double, *mut c_double),
               neq: &c_int,
               y: *mut c_double,
               t: &mut c_double,
               tout: &c_double,
               itol: &c_int,
               rtol: &c_double,
               atol: &c_double,
               itask: &c_int,
               istate: &mut c_int,
               iopt: &c_int,
               rwork: *mut c_double,
               lrw: &c_int,
               iwork: *mut c_int,
               liw: &c_int,
               jac: extern fn(&c_int, &c_double, *mut c_double, &c_int, &c_int, *const c_double, &c_int),
               mf: &c_int
               );
}

/// A dummy function to pass to `dlsode_` in case the user does not want to specify a Jacobian.
pub extern fn fake_jacobian(
    _neq: &c_int,
    _t:   &c_double,
    _y:   *mut c_double,
    _ml:  &c_int,
    _mu:  &c_int,
    _pd:  *const c_double,
    _nr:  &c_int
    ) { }


/// Solves system of ODEs for times in `t_dense`.
/// First time in `t_dense` has to be the initial time.
///
/// Each equation in the system of ODEs has the form:
/// 
/// > *dy/dt = f(y, t)*
/// 
/// The function expects the function *f* as the first argument `rhs`.
/// Initial state is given in `y0`.
///
/// # Example
///
/// ```
/// let y0 = [1.0];
/// let ts = vec![0.0, 1.0];
/// let f = |y: &[f64], t: &f64| {
///     let mut dy = vec![0.0];
///     dy[0] = *t * y[0]; 
///     dy
///     };
/// let sol = lsode::solve_ode(f, &y0, ts, 1e-6, 1e-6);
/// 
/// assert!((sol[1][0] - y0[0]*0.5_f64.exp()).abs() < 1e-3, "error too large");
/// ```
pub fn solve_ode<F>(
    rhs: F, 
    y0: &[f64],
    t_dense: Vec<f64>,
    atol: f64,
    rtol: f64,
    ) -> Vec<Vec<f64>> 
where F: Fn(&[f64], &f64) -> Vec<f64>
{

    let f = | n: *const c_int, t_ptr: *const c_double, y_ptr: *mut c_double, dy_ptr: *mut c_double | {
        let (dy, y, t) = unsafe {
            (
                slice::from_raw_parts_mut(dy_ptr, n as usize),
                slice::from_raw_parts(y_ptr, n as usize),
                *t_ptr
            )
        };
        let dy_new = rhs(y, &t);
        for (i, deriv) in dy_new.iter().enumerate() {
            dy[i] = *deriv
        }
    };
    let closure = Closure4::new(&f);
    let call = closure.code_ptr();

    let mut y: Vec<f64> = y0.to_vec();
    let n = y0.len();
    let mut t = t_dense[0];

    let itol = 1;
    let itask = 1;
    let iopt = 0;
    let mut istate = 1;
    let mf = 22;

    let lrw = 22 + 9*n + n*n;
    let liw = 20 + n;
    let mut rwork: Vec<f64> = (0..lrw).map(|_i| 0.0 as c_double).collect();
    let mut iwork: Vec<i32> = (0..liw).map(|_i| 0 as c_int).collect();

    let mut result = Vec::new();

    for tout in t_dense {
        unsafe { dlsode_(
                *call,
                &(n as i32),
                y.as_mut_ptr(),
                &mut t,
                &tout,
                &itol,
                &rtol,
                &atol,
                &itask,
                &mut istate,
                &iopt,
                rwork.as_mut_ptr(),
                &(lrw as i32),
                iwork.as_mut_ptr(),
                &(liw as i32),
                fake_jacobian,
                &mf
                ); }

        result.push(y.clone());
    }
    result
}

/// Generates vector from `start` to `stop` with constant  spacing.
///
/// # Example
///
/// ```
/// assert_eq!(lsode::linspace(0.0, 2.0, 3), vec![0.0, 1.0, 2.0]);
/// ```
pub fn linspace(start: f64, stop: f64, n_points: usize) -> Vec<f64> {
    (0..n_points).map(|i| start + i as f64*(stop - start)/(n_points - 1) as f64).collect()
}