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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//
// A rust binding for the GSL library by Guillaume Gomez (guillaume1.gomez@gmail.com)
//

/*!
#Chebyshev Approximations

This chapter describes routines for computing Chebyshev approximations to univariate functions. A
Chebyshev approximation is a truncation of the series f(x) = \sum c_n T_n(x), where the Chebyshev
polynomials T_n(x) = \cos(n \arccos x) provide an orthogonal basis of polynomials on the interval
[-1,1] with the weight function 1 / \sqrt{1-x^2}. The first few Chebyshev polynomials are,
T_0(x) = 1, T_1(x) = x, T_2(x) = 2 x^2 - 1.

For further information see Abramowitz & Stegun, Chapter 22.

##Definitions

The approximation is made over the range [a,b] using order+1 terms, including the coefficient c[0].
The series is computed using the following convention,

f(x) = (c_0 / 2) + \sum_{n=1} c_n T_n(x)

which is needed when accessing the coefficients directly.

##References and Further Reading

The following paper describes the use of Chebyshev series,

R. Broucke, “Ten Subroutines for the Manipulation of Chebyshev Series [C1] (Algorithm 446)”.
Communications of the ACM 16(4), 254–256 (1973)
!*/

use ffi;
use enums;
use std::f64::consts::PI;
use c_vec::CSlice;

pub struct ChebSeries {
    c: *mut ffi::gsl_cheb_series,
    data: CSlice<f64>
}

impl ChebSeries {
    pub fn new(n: usize) -> Option<ChebSeries> {
        let tmp = unsafe { ffi::gsl_cheb_alloc(n) };

        if tmp.is_null() {
            None
        } else {
            unsafe {
                Some(ChebSeries {
                    c: tmp,
                    data: CSlice::new((*tmp).c, (*tmp).order as usize + 1)
                })
            }
        }
    }

    /// This function computes the Chebyshev approximation cs for the function f over the range
    /// (a,b) to the previously specified order. The computation of the Chebyshev approximation is
    /// an O(n^2) process, and requires n function evaluations.
    pub fn init<T>(&mut self, func: ::function<T>, a: f64, b: f64, param: &mut T) -> enums::Value {
        if a >= b {
            rgsl_error!("null function interval [a,b]", ::Value::Domain);
            ::Value::Failure
        } else {
            unsafe {
                (*self.c).a = a;
                (*self.c).b = b;

                let bma = 0.5 * (b - a);
                let bpa = 0.5 * (b + a);
                let fac = 2.0 / ((*self.c).order as f64 + 1.0);

                let mut tmp_vec = CSlice::new((*self.c).f, (*self.c).order_sp as usize + 1);
                for k in 0..((*self.c).order + 1) {
                    let y = (PI * (k as f64 + 0.5) / ((*self.c).order as f64 + 1f64)).cos();
                    tmp_vec.as_mut()[k as usize] = func(y * bma + bpa, param);
                }

                for j in 0..((*self.c).order + 1) {
                    let mut sum = 0f64;

                    for k in 0..((*self.c).order + 1) {
                        sum += tmp_vec.as_ref()[k as usize] * (PI * j as f64 * (k as f64 + 0.5) /
                               ((*self.c).order as f64 + 1f64)).cos();
                    }
                    self.data.as_mut()[j as usize] = fac * sum;
                }
            }
            ::Value::Success
        }
    }

    /// This function returns the order of Chebyshev series cs.
    pub fn order(&self) -> usize {
        unsafe { ffi::gsl_cheb_order(self.c) }
    }

    /// This function returns the size of the Chebyshev coefficient array c[] for the Chebyshev
    /// series cs.
    pub fn size(&self) -> usize {
        unsafe { ffi::gsl_cheb_size(self.c) }
    }

    /// This function returns a pointer to the coefficient array c[] location in memory for the
    /// Chebyshev series cs.
    pub fn as_slice<'r>(&'r self) -> &'r [f64] {
        self.data.as_ref()
    }

    /// This function returns a pointer to the coefficient array c[] location in memory for the
    /// Chebyshev series cs.
    pub fn as_mut_slice<'r>(&'r mut self) -> &'r mut [f64] {
        self.data.as_mut()
    }

    /// This function evaluates the Chebyshev series cs at a given point x.
    pub fn eval(&self, x: f64) -> f64 {
        unsafe { ffi::gsl_cheb_eval(self.c, x) }
    }

    /// This function computes the Chebyshev series cs at a given point x, estimating both the
    /// series result and its absolute error abserr.
    /// The error estimate is made from the first neglected term in the series.
    pub fn eval_err(&self, x: f64, result: &mut f64, abs_err: &mut f64) -> enums::Value {
        unsafe { ffi::gsl_cheb_eval_err(self.c, x, result, abs_err) }
    }

    /// This function evaluates the Chebyshev series cs at a given point x, to (at most) the given
    /// order order.
    pub fn eval_n(&self, order: usize, x: f64) -> f64 {
        unsafe { ffi::gsl_cheb_eval_n(self.c, order, x) }
    }

    /// This function evaluates a Chebyshev series cs at a given point x, estimating both the series
    /// result and its absolute error abserr, to (at most) the given order order. The error estimate
    /// is made from the first neglected term in the series.
    pub fn eval_n_err(&self, order: usize, x: f64, result: &mut f64,
                      abs_err: &mut f64) -> enums::Value {
        unsafe { ffi::gsl_cheb_eval_n_err(self.c, order, x, result, abs_err) }
    }

    /// This function computes the derivative of the series cs, storing the derivative coefficients
    /// in the previously allocated deriv. The two series cs and deriv must have been allocated with
    /// the same order.
    pub fn calc_deriv(&self, deriv: &mut ChebSeries) -> enums::Value {
        unsafe { ffi::gsl_cheb_calc_deriv(deriv.c, self.c) }
    }

    /// This function computes the integral of the series cs, storing the integral coefficients in
    /// the previously allocated integ. The two series cs and integ must have been allocated with
    /// the same order. The lower limit of the integration is taken to be the left hand end of the
    /// range a.
    pub fn calc_integ(&self, integ: &mut ChebSeries) -> enums::Value {
        unsafe { ffi::gsl_cheb_calc_integ(integ.c, self.c) }
    }
}

impl Drop for ChebSeries {
    fn drop(&mut self) {
        unsafe { ffi::gsl_cheb_free(self.c) };
        self.c = ::std::ptr::null_mut();
    }
}

impl ffi::FFI<ffi::gsl_cheb_series> for ChebSeries {
    fn wrap(c: *mut ffi::gsl_cheb_series) -> ChebSeries {
        unsafe {
            ChebSeries {
                c: c,
                data: CSlice::new((*c).c, (*c).order as usize + 1)
            }
        }
    }

    fn soft_wrap(r: *mut ffi::gsl_cheb_series) -> ChebSeries {
        Self::wrap(r)
    }

    fn unwrap_shared(c: &ChebSeries) -> *const ffi::gsl_cheb_series {
        c.c as *const _
    }

    fn unwrap_unique(c: &mut ChebSeries) -> *mut ffi::gsl_cheb_series {
        c.c
    }
}