fsrs-rs Documentation

repository·main·Indexed 16 days ago

https://github.com/open-spaced-repetition/fsrs-rs

A Rust implementation of the Free Spaced Repetition Scheduler (FSRS) algorithm (version 6.6.2). The library provides tools for scheduling reviews, optimizing algorithm parameters from user history, and simulating memory performance. It includes features for migrating from SM-2 style data, an experimental optimizer for the FSRS6 Cost ADR policy, and official bindings for C, Python, Node.js, Dart, and PHP.

Tokens
13.4K
Snippets
52
Records
61
Agent score
64%

What's inside fsrs-rs

  1. Install the fsrs crate

    main

    To add FSRS to your Rust project, use cargo add fsrs. If you are following the scheduling examples, you will also need the chrono crate to manage review timestamps.

    cargo add fsrs
    # Required for tracking review times in examples
    chrono = { version = "0.4", default-features = false, features = ["std", "clock"] }
  2. Migrate from SM-2 style data to FSRS

    main

    If you are migrating from an SM-2 based system, you can initialize a MemoryState using the last known SM-2 ease_factor and interval via memory_state_from_sm2.

    To reconstruct the full FSRS state, you can then replay the partial review history using the memory_state method, passing the SM-2 derived state as the Some(initial_state) argument.

    use fsrs::{FSRS, FSRSItem, FSRSReview};
    
    let fsrs = FSRS::default();
    let sm2_retention = 0.9;
    let ease_factor = 2.5;
    let interval = 10.0;
    
    let initial_state = fsrs.memory_state_from_sm2(ease_factor, interval, sm2_retention)?;
    
    let reviews = vec![
        FSRSReview { rating: 3, delta_t: 5 },
        FSRSReview { rating: 4, delta_t: 10 },
    ];
    
    let memory_state = fsrs.memory_state(
        FSRSItem { reviews },
        Some(initial_state),
    )?;
  3. Train and use a single-user Cost ADR policy

    main

    The crate includes an experimental optimizer for the FSRS6 Cost ADR policy. This searches for a 15-parameter cost-conditioned desired-retention policy.

    To run the training example, you must enable the experimental_cost_adr feature:

    cargo run --release --features experimental_cost_adr --example cost_adr

    You can pass command-line arguments to the example to override simulation defaults:

    • --days: Simulation duration.
    • --deck: Number of cards in the deck.
    • --pop: Population size.
    • --gen: Number of generations.

    Example command:

    cargo run --release --features experimental_cost_adr --example cost_adr -- --days 90 --deck 2000 --pop 8 --gen 5
  4. Core FSRS types and modules

    main

    The fsrs-rs crate provides several key modules for spaced repetition scheduling, training, and simulation:

    • model: Contains the FSRS struct (the main model) and check_and_fill_parameters.
    • inference: Provides types for understanding card progress and state, including ItemState, MemoryState, ItemProgress, and NextStates. It also includes functions like current_retrievability.
    • training: Used to optimize model parameters based on review history via compute_parameters and TrainingConfig.
    • dataset: Defines the data structures for reviews and items, specifically FSRSItem and FSRSReview.
    • simulation: Allows for simulating long-term scheduling outcomes using simulate and various configuration types like SimulatorConfig and Card.
  5. Customize review priority with `ReviewPriorityFn`

    main

    When the simulation hits a review_limit, you can use a review_priority_fn to decide which cards to prioritize. This is useful for simulating different study strategies (e.g., prioritizing high-difficulty cards vs. low-stability cards).

    The function receives a &Card and returns an i32 score. Higher scores indicate higher priority.

    Common Strategies:

    • High Difficulty First: |card| -(card.difficulty * 100.0) as i32 (Note: negative because higher score = higher priority).
    • High Retrievability First: |card| (card.retrievability() * 1000.0) as i32.
    • Low Stability First: |card| (card.stability * 100.0) as i32.
    • Early Due First: |card| card.scheduled_due() as i32.
    // Example: Prioritizing cards with low stability
    config.review_priority_fn = Some(ReviewPriorityFn::new(|card: &Card| {
        (card.stability * 100.0) as i32
    }));
  6. Track training progress with `CombinedProgressState`

    main

    If you need to monitor the training process (e.g., for a UI), you can pass a shared CombinedProgressState into ComputeParametersInput.

    CombinedProgressState manages multiple ProgressState objects (one for each training split) and provides methods to check if training is finished or if an abort has been requested via want_abort.

    use std::sync::{Arc, Mutex};
    // ...
    let progress = Arc::new(Mutex::new(CombinedProgressState::default()));
    
    // Pass this 'progress' into your ComputeParametersInput
  7. How stability parameter initialization works

    main

    The initialization process follows these steps to derive personalized FSRS parameters:

    1. Dataset Preparation: Filters FSRSItems to include only those with exactly one long-term review. It groups these by the first rating received and the time delta ($\Delta t$) of the subsequent long-term review.
    2. Parameter Search: For each rating level, it performs a ternary search to find the stability value that minimizes a loss function. The loss function is based on a power forgetting curve and incorporates Laplace smoothing to handle sparse data.
    3. Smoothing and Filling: Since a user might not have data for all four rating levels, the algorithm uses interpolation (based on weights $w_1=0.41$ and $w_2=0.54$) to estimate missing stability values. This ensures a continuous and logical progression of stability across ratings (e.g., ensuring stability for rating 2 is between rating 1 and 3).
    4. Clamping: Finally, all calculated values are clamped between S_MIN and INIT_S_MAX (100.0) to ensure they remain within valid bounds.
  8. Initialize the FSRS model

    main

    You can create an FSRS instance using default parameters (optimized for average learning habits) or by providing a custom set of parameters.

    To use default parameters:

    use fsrs::FSRS;
    
    let fsrs = FSRS::default();

    To use custom parameters, pass a slice of f32 to FSRS::new(). The length of the slice determines how the parameters are interpreted (e.g., 0, 17, 19, or 21 elements). If the slice is empty, it defaults to the standard 21 parameters.

    use fsrs::FSRS;
    
    let custom_params = [
        0.212, 1.2931, 2.3065, 8.2956, 6.4133, 0.8334, 3.0194,
        0.001, 1.8722, 0.1666, 0.796, 1.4835, 0.0614, 0.2629,
        1.6483, 0.6014, 1.8729, 0.5425, 0.0912, 0.0658, 0.1542
    ];
    
    let fsrs = FSRS::new(&custom_params).expect("Custom parameters should be valid");
    use fsrs::FSRS;
    
    let fsrs = FSRS::default();
    
    let custom_params = [
        0.212, 1.2931, 2.3065, 8.2956, 6.4133, 0.8334, 3.0194,
        0.001, 1.8722, 0.1666, 0.796, 1.4835, 0.0614, 0.2629,
        1.6483, 0.6014, 1.8729, 0.5425, 0.0912, 0.0658, 0.1542
    ];
    
    let fsrs = FSRS::new(&custom_params).expect("Custom parameters should be valid");
  9. Optimize FSRS parameters from review logs

    main

    You can optimize FSRS parameters by feeding your review history into the compute_parameters function.

    1. Construct a history of (date, rating) tuples.
    2. Convert this history into a vector of FSRSItem instances. Each FSRSItem contains a vector of FSRSReview objects representing the accumulated review history for a single card.
    3. Pass these items into compute_parameters using a ComputeParametersInput struct.

    For best results, the train_set should include review histories from many different cards.

    use chrono::NaiveDate;
    use fsrs::{ComputeParametersInput, FSRSItem, FSRSReview, compute_parameters};
    
    let history = vec![
        (NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(), 3),
        (NaiveDate::from_ymd_opt(2023, 1, 5).unwrap(), 4),
    ];
    
    let mut accumulated = Vec::new();
    let mut items = Vec::new();
    let mut last = history[0].0;
    
    for (date, rating) in history {
        let delta_t = (date - last).num_days() as u32;
        accumulated.push(FSRSReview { rating, delta_t });
        items.push(FSRSItem {
            reviews: accumulated.clone(),
        });
        last = date;
    }
    
    let parameters = compute_parameters(ComputeParametersInput {
        // For best results, `train_set` should contain review histories from many cards.
        train_set: items,
        ..Default::default()
    })?;
  10. Schedule reviews with FSRS

    main

    To schedule the next review for a card, use the next_states method on an FSRS instance.

    • previous_state: An Option<MemoryState> representing the card's last known state. Use None for new cards.
    • desired_retention: A float (e.g., 0.9) representing the target retention rate.
    • elapsed_days: The number of days since the last review.

    The method returns a struct containing different states (like good, hard, etc.) for each possible user response. You can then calculate the next due date using the returned interval.

    use chrono::{Duration, Utc};
    use fsrs::{FSRS, MemoryState};
    
    let fsrs = FSRS::default();
    let desired_retention = 0.9;
    let previous_state: Option<MemoryState> = None;
    let elapsed_days = 0;
    
    let next_states = fsrs.next_states(previous_state, desired_retention, elapsed_days)?;
    let review = next_states.good;
    
    let interval_days = review.interval.round().max(1.0) as u32;
    let due = Utc::now() + Duration::days(interval_days as i64);
  11. Configure CostAdrEvaluationConfig

    main

    The CostAdrEvaluationConfig struct defines how a policy is evaluated against a baseline.

    Key fields:

    • cost_weights: A vector of cost weights to test the policy against.
    • baseline_desired_retentions: A vector of target retentions used to establish the baseline performance.
    • seed: Optional seed for reproducible evaluations.
    let evaluation_config = CostAdrEvaluationConfig {
        cost_weights: vec![0.0, 1.0, 2.0, 4.0],
        baseline_desired_retentions: vec![0.5, 0.6, 0.7, 0.8, 0.9],
        seed: Some(42),
    };
  12. Configure training hyperparameters with `TrainingConfig`

    main

    The TrainingConfig struct allows you to fine-tune the optimization process. The default values are:

    • num_epochs: 5
    • batch_size: 512
    • seed: 2023
    • learning_rate: 0.04
    • max_seq_len: 256
    • gamma: 1.0 (L2 regularization strength)

    Note: batch_size must be greater than 0, and learning_rate and gamma must be finite values.

    let config = TrainingConfig {
        num_epochs: 10,
        batch_size: 128,
        seed: 42,
        learning_rate: 0.01,
        max_seq_len: 128,
        gamma: 0.5,
    };