fsrs 5.2.0

FSRS for Rust, including Optimizer and Scheduler
Documentation
use criterion::{Criterion, criterion_group, criterion_main};
use fsrs::{Card, expected_workload, expected_workload_with_existing_cards};
use fsrs::{DEFAULT_PARAMETERS, SimulationResult, SimulatorConfig, simulate};
use fsrs::{FSRS, FSRSError};
use rayon::iter::IntoParallelIterator;
use rayon::iter::ParallelIterator;
use std::hint::black_box;

pub(crate) fn parallel_simulate(config: &SimulatorConfig) -> Result<Vec<f32>, FSRSError> {
    (70..=99)
        .into_par_iter()
        .map(|i| {
            let SimulationResult {
                memorized_cnt_per_day,
                cost_per_day,
                ..
            } = simulate(
                config,
                &DEFAULT_PARAMETERS,
                i as f32 / 100.0,
                Some((i + 42).try_into().unwrap()),
                None,
            )?;
            let total_memorized = memorized_cnt_per_day[memorized_cnt_per_day.len() - 1];
            let total_cost = cost_per_day.iter().sum::<f32>();
            Ok(total_cost / total_memorized)
        })
        .collect()
}

pub(crate) fn optimal_retention(inf: &FSRS, config: &SimulatorConfig) -> f32 {
    inf.optimal_retention(config, &[], |_v| true, None, None)
        .unwrap()
}

pub(crate) fn run_expected_workload_for_30_retentions() {
    let config = SimulatorConfig::default();

    for i in 0..30 {
        let desired_retention = 0.70 + (i as f32 * 0.01);
        black_box(expected_workload(&DEFAULT_PARAMETERS, desired_retention, &config).unwrap());
    }
}

pub(crate) fn run_expected_workload_with_10000_existing_cards() {
    let config = SimulatorConfig {
        deck_size: 46500,
        learn_limit: 100,
        ..Default::default()
    };
    let mut cards = Vec::with_capacity(10000);
    for i in 0..10000 {
        cards.push(Card {
            id: i,
            difficulty: 5.0,
            stability: 5.0,
            last_date: 0.0,
            due: 10.0,
            interval: 10.0,
            lapses: 0,
        });
    }
    black_box(
        expected_workload_with_existing_cards(&DEFAULT_PARAMETERS, 0.9, &config, &cards).unwrap(),
    );
}

pub fn criterion_benchmark(c: &mut Criterion) {
    let fsrs = FSRS::new(Some(&DEFAULT_PARAMETERS)).unwrap();
    let config = SimulatorConfig {
        deck_size: 36500,
        learn_span: 90,
        max_cost_perday: f32::INFINITY,
        learn_limit: 100,
        review_limit: 600,
        ..Default::default()
    };
    c.bench_function("parallel_simulate", |b| {
        b.iter(|| black_box(parallel_simulate(&config)))
    });
    c.bench_function("optimal_retention", |b| {
        b.iter(|| black_box(optimal_retention(&fsrs, &config)))
    });
    c.bench_function("expected_workload_30_retentions", |b| {
        b.iter(run_expected_workload_for_30_retentions)
    });
    c.bench_function("expected_workload_with_10000_existing_cards", |b| {
        b.iter(run_expected_workload_with_10000_existing_cards)
    });
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);