|
2 | 2 |
|
3 | 3 | [](https://crates.io/crates/fsrs)  |
4 | 4 |
|
5 | | -This crate contains a Rust API for training FSRS parameters, and for using them to schedule cards. |
| 5 | +The Free Spaced Repetition Scheduler ([FSRS](https://github.com/open-spaced-repetition/fsrs4anki/wiki/The-Algorithm)) is a modern spaced repetition algorithm. It springs from [MaiMemo's DHP model](https://www.maimemo.com/paper/), which is a variant of the [DSR model](https://supermemo.guru/wiki/Three_component_model_of_memory) proposed by [Piotr Wozniak](https://supermemo.guru/wiki/Piotr_Wozniak). |
6 | 6 |
|
7 | | -The Free Spaced Repetition Scheduler ([FSRS](https://github.com/open-spaced-repetition/fsrs4anki/wiki/The-Algorithm)) is a modern spaced repetition algorithm. It springs from [MaiMemo's DHP model](https://www.maimemo.com/paper/), which is a variant of the [DSR model](https://supermemo.guru/wiki/Three_component_model_of_memory) proposed by [Piotr Wozniak](https://supermemo.guru/wiki/Piotr_Wozniak), the creator of SuperMemo. |
8 | | - |
9 | | -FSRS-rs is a Rust implementation of FSRS. It is designed to be used in [Anki](https://apps.ankiweb.net/), a popular spaced repetition software. [Anki 23.10](https://github.com/ankitects/anki/releases/tag/23.10) has already integrated FSRS as an alternative scheduler. |
| 7 | +FSRS-rs is a Rust implementation of FSRS with full training support using [Burn](https://github.com/tracel-ai/burn). It also provides simulation capabilities and basic scheduling functionality. |
10 | 8 |
|
11 | 9 | For more information about the algorithm, please refer to [the wiki page of FSRS](https://github.com/open-spaced-repetition/fsrs4anki/wiki/The-Algorithm). |
12 | 10 |
|
13 | 11 | --- |
14 | 12 |
|
15 | 13 | ## Quickstart |
16 | 14 |
|
17 | | -Read [this](https://github.com/open-spaced-repetition/fsrs4anki/wiki/The-Optimal-Retention) for an explanation of how to determine the optimal retention for your use case. |
| 15 | +### Add the crate |
| 16 | + |
| 17 | +Add FSRS to your project: |
| 18 | + |
| 19 | +```toml |
| 20 | +[dependencies] |
| 21 | +fsrs = "5.2.0" |
| 22 | +``` |
| 23 | + |
| 24 | +The scheduling example below also uses `chrono` to track review times: |
| 25 | + |
| 26 | +```toml |
| 27 | +chrono = { version = "0.4", default-features = false, features = ["std", "clock"] } |
| 28 | +``` |
| 29 | + |
| 30 | +Run `cargo run --example <name>` to see the complete samples ([`schedule`](examples/schedule.rs), [`migrate`](examples/migrate.rs), [`optimize`](examples/optimize.rs)). |
| 31 | + |
| 32 | +### Schedule reviews |
18 | 33 |
|
19 | 34 | ```rust |
20 | | -// Pick whichever percentage is to your liking (see above) |
21 | | -let optimal_retention = 0.75; |
22 | | -// Use default parameters/weights for the scheduler |
| 35 | +use chrono::{Duration, Utc}; |
| 36 | +use fsrs::{FSRS, MemoryState}; |
| 37 | + |
23 | 38 | let fsrs = FSRS::default(); |
| 39 | +let desired_retention = 0.9; |
| 40 | +let previous_state: Option<MemoryState> = None; |
| 41 | +let elapsed_days = 0; |
| 42 | + |
| 43 | +let next_states = fsrs.next_states(previous_state, desired_retention, elapsed_days)?; |
| 44 | +let review = next_states.good; |
| 45 | + |
| 46 | +let interval_days = review.interval.round().max(1.0) as u32; |
| 47 | +let due = Utc::now() + Duration::days(interval_days as i64); |
| 48 | +``` |
| 49 | + |
| 50 | +Replace `previous_state`/`elapsed_days` with a stored `MemoryState` and the number of days since the prior review when scheduling existing cards. Full example: [`examples/schedule.rs`](examples/schedule.rs). |
| 51 | + |
| 52 | +### Optimize parameters from review logs |
| 53 | + |
| 54 | +```rust |
| 55 | +use chrono::NaiveDate; |
| 56 | +use fsrs::{ComputeParametersInput, FSRSItem, FSRSReview, compute_parameters}; |
| 57 | + |
| 58 | +let history = vec![ |
| 59 | + (NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(), 3), |
| 60 | + (NaiveDate::from_ymd_opt(2023, 1, 5).unwrap(), 4), |
| 61 | +]; |
| 62 | + |
| 63 | +let mut accumulated = Vec::new(); |
| 64 | +let mut items = Vec::new(); |
| 65 | +let mut last = history[0].0; |
| 66 | + |
| 67 | +for (date, rating) in history { |
| 68 | + let delta_t = (date - last).num_days() as u32; |
| 69 | + accumulated.push(FSRSReview { rating, delta_t }); |
| 70 | + items.push(FSRSItem { |
| 71 | + reviews: accumulated.clone(), |
| 72 | + }); |
| 73 | + last = date; |
| 74 | +} |
| 75 | + |
| 76 | +let parameters = compute_parameters(ComputeParametersInput { |
| 77 | + // For best results, `train_set` should contain review histories from many cards. |
| 78 | + train_set: items, |
| 79 | + ..Default::default() |
| 80 | +})?; |
| 81 | +``` |
24 | 82 |
|
25 | | -// Create a completely new card |
26 | | -let day1_states = fsrs.next_states(None, optimal_retention, 0)?; |
| 83 | +Feed the optimizer a vector of `FSRSItem` instances built from your review history; the returned parameters can then be persisted or supplied to schedulers. Full example: [`examples/optimize.rs`](examples/optimize.rs). |
27 | 84 |
|
28 | | -// Rate as `hard` on the first day |
29 | | -let day1 = day1_states.hard; |
30 | | -dbg!(&day1); // scheduled as `in 4 days` |
| 85 | +### Migrate from SM-2 style data |
31 | 86 |
|
32 | | -// Now we review the card 2 days later |
33 | | -let day3_states = fsrs.next_states(Some(day1.memory), optimal_retention, 2)?; |
| 87 | +```rust |
| 88 | +use fsrs::{FSRS, FSRSItem, FSRSReview}; |
| 89 | + |
| 90 | +let fsrs = FSRS::default(); |
| 91 | +let sm2_retention = 0.9; |
| 92 | +let ease_factor = 2.5; |
| 93 | +let interval = 10.0; |
34 | 94 |
|
35 | | -// Rate as `good` this time |
36 | | -let day3 = day3_states.good; |
37 | | -dbg!(day3); |
| 95 | +let initial_state = fsrs.memory_state_from_sm2(ease_factor, interval, sm2_retention)?; |
| 96 | + |
| 97 | +let reviews = vec![ |
| 98 | + FSRSReview { rating: 3, delta_t: 5 }, |
| 99 | + FSRSReview { rating: 4, delta_t: 10 }, |
| 100 | +]; |
| 101 | + |
| 102 | +let memory_state = fsrs.memory_state( |
| 103 | + FSRSItem { reviews }, |
| 104 | + Some(initial_state), |
| 105 | +)?; |
38 | 106 | ``` |
39 | 107 |
|
| 108 | +Use `memory_state_from_sm2` when you only have the latest SM-2 ease/interval; pass the result as the starting point while you replay any partial review history. Full example: [`examples/migrate.rs`](examples/migrate.rs). |
| 109 | + |
40 | 110 | ## Online development |
41 | 111 |
|
42 | 112 | You can use <https://idx.google.com/import>. |
|
0 commit comments