My Rust code is much longer than the equivalent python one, with wrong result :!


#1

I’m converting an existing python code here to Rust the number of lines increased so much, with so much type changes, and more worst with wrong result! I’m very knew to Rust, not sure if I’m doing something wrong, or there is a way to optimize it:

The main function is:

fn main() {
    let series = [30,21,29,31,40,48,53,47,37,39,31,29,17,9,20,24,27,35,41,38,
          27,31,27,26,21,13,21,18,33,35,40,36,22,24,21,20,17,14,17,19,
          26,29,40,31,20,24,18,26,17,9,17,21,28,32,46,33,23,28,22,27,
          18,8,17,21,31,34,44,38,31,30,26,32];

    triple_exponential_smoothing(&series, 12, 0.716, 0.029, 0.993, 24);
}

The triple_exponential_smoothing is calling two other functions, which I tested, and they are giving correct results:

First one:

fn initial_trend(series: &[i32], slen: i32) -> f32{

    let mut sum = 0.0;
    for i in 0..slen as usize {  // in Python: for i in range(slen)
        sum += (series[i + slen as usize] as f32 - series[i] as f32) / slen as f32;
    }
    return sum / slen as f32;
}

Which is a conversion of Python code:

def initial_trend(series, slen):
    sum = 0.0
    for i in range(slen):
        sum += float(series[i+slen] - series[i]) / slen
    return sum / slen

# >>> initial_trend(series, 12)
# -0.7847222222222222

The second one is:

fn initial_seasonal_components (series: &[i32], slen: i32) -> Vec<f32>{
    let mut seasonals = Vec::new();
    let n_seasons = series.len() as i32 / slen;
    // # compute season averages
    let season_chunks = series //season_averages
        .chunks(slen as usize)
        .collect::<Vec<_>>();
    let season_averages = season_chunks
        .iter()
        .map(|chunk| chunk.iter().sum::<i32>() as f32/ chunk.len() as f32)
        .collect::<Vec<f32>>();
    // # compute initial values
    for i in 0..slen as usize {
        let mut sum_of_vals_over_avg = 0.0;
        for j in 0..n_seasons as usize {
            sum_of_vals_over_avg += series[i + j * slen as usize] as f32 - season_averages[j] as f32;
        }
        seasonals.push(sum_of_vals_over_avg / n_seasons as f32);
    }
    return seasonals;
}

Which is conversion of the below Python code:

def initial_seasonal_components(series, slen):
    seasonals = {}
    season_averages = []
    n_seasons = int(len(series)/slen)
    # compute season averages
    for j in range(n_seasons):
        season_averages.append(sum(series[slen*j:slen*j+slen])/float(slen))
    # compute initial values
    for i in range(slen):
        sum_of_vals_over_avg = 0.0
        for j in range(n_seasons):
            sum_of_vals_over_avg += series[slen*j+i]-season_averages[j]
        seasonals[i] = sum_of_vals_over_avg/n_seasons
    return seasonals

# >>> initial_seasonal_components(series, 12)
# {0: -7.4305555555555545, 1: -15.097222222222221, 2: -7.263888888888888, 3: -5.097222222222222, 4: 3.402777777777778, 5: 8.069444444444445, 6: 16.569444444444446, 7: 9.736111111111112, 8: -0.7638888888888887, 9: 1.902777777777778, 10: -3.263888888888889, 11: -0.7638888888888887}

The The error looks to be in this functions:

fn triple_exponential_smoothing(
    series: &[i32], slen: i32, alpha: f32, beta: f32, gamma: f32, n_preds: i32){
    let mut result: Vec<f32> = Vec::new();
    let mut seasonals = initial_seasonal_components(&series, slen);
    println!("The seasonalities are: {:#?}", seasonals);
    let mut smooth = 0.0;
    let mut trend= 0.0;
    // for i in range(len(series)+n_preds):
    for i in 0..(series.len() + n_preds as usize) as usize {
        match i {
            0 => {  // # initial values
                smooth = series[0] as f32;
                trend = initial_trend(&series, slen);
                println!("The initial_trend is: {:#?}", trend);
                result.push(series[0] as f32);
            },
            i if i >= series.len() => {  // # we are forecasting
                let m = i - series.len() + 1;
                result.push((smooth as usize + m * trend as usize) as f32 +
                    seasonals[i % slen as usize])
            },
            _ => {
                let val = series[i];
                let last_smooth = smooth;
                smooth = alpha * (val as f32 - seasonals[i % slen as usize]) +
                    (1.0 - alpha)*(smooth + trend);
                trend = beta * (smooth - last_smooth) + (1.0 - beta) * trend;
                seasonals[i % slen as usize] = gamma * (val as f32 - smooth) +
                    (1 - gamma as usize) as f32 * seasonals[i % slen as usize];
                result.push(smooth + trend + seasonals[i % slen as usize]);
            }
        }
    }
    println!("The forecast is: {:#?}", result);
}

Which is a conversion of the below Python code:

def triple_exponential_smoothing(series, slen, alpha, beta, gamma, n_preds):
    result = []
    seasonals = initial_seasonal_components(series, slen)
    for i in range(len(series)+n_preds):
        if i == 0: # initial values
            smooth = series[0]
            trend = initial_trend(series, slen)
            result.append(series[0])
            continue
        if i >= len(series): # we are forecasting
            m = i - len(series) + 1
            result.append((smooth + m*trend) + seasonals[i%slen])
        else:
            val = series[i]
            last_smooth, smooth = smooth, alpha*(val-seasonals[i%slen]) + (1-alpha)*(smooth+trend)
            trend = beta * (smooth-last_smooth) + (1-beta)*trend
            seasonals[i%slen] = gamma*(val-smooth) + (1-gamma)*seasonals[i%slen]
            result.append(smooth+trend+seasonals[i%slen])
    return result

# # forecast 24 points (i.e. two seasons)
# >>> triple_exponential_smoothing(series, 12, 0.716, 0.029, 0.993, 24)
# [30, 20.34449316666667, 28.410051892109554, 30.438122252647577, 39.466817731253066, ...

PlayGround is there, appreciate any comment to optimize the code and fixing its error.


#2

Some general comments:

  • try to minimize (and possibly to reduce to zero) the number of “as” casts in your code. into()/from() and try_from() help;
  • Try to replace some raw loops with iterators;
  • The triple_exponential_smoothing function has some arguments that are easy to confuse at the calling point because Rust currently doesn’t have named arguments. To avoid the problem you could try to pack some arguments in structs/tuples.
  • Using “return” at the end of Rust functions isn’t much idiomatic.

#3

Thanks, A sI mentioned, Im brand new to Rust, can you help with fixing the last code block with the comments you provided, so I understand in a better way what you mean!


#4

You are rounding floats to integers in your math in two places. Whenever Python mixes floats and ints, it converts the ints to floats. So you need to do the same thing.

Change line 60 to

                result.push((smooth + m as f32 * trend) +

and line 70 to

                    (1.0 - gamma) as f32 * seasonals[i % slen as usize];

#5

Thanks a lot, it is working fine now :slight_smile:


#6

Awesome! I’m glad that helped.


#7

Also, something worth noting is that in Python, the floating point type is double floating point, which would be f64 in Rust. This likely would introduce small differences in accuracy, although likely nothing major.


#8

A little streamlined version:

https://play.rust-lang.org/?gist=0bd893bdb65f93655a4ee93a05bc7c51&version=stable&mode=debug&edition=2015


#9

Thanks @xfix sure will keep in mind for future.


#10

It could be little for you @birkenfeld, but it is awesome for me, thanks a lot