rs_fsrs/
scheduler_basic.rs

1use chrono::{DateTime, Duration, Utc};
2
3use crate::{scheduler::Scheduler, Card, ImplScheduler, Parameters, Rating, SchedulingInfo};
4use crate::{Rating::*, State::*};
5pub struct BasicScheduler {
6    pub scheduler: Scheduler,
7}
8
9impl BasicScheduler {
10    pub fn new(parameters: Parameters, card: Card, now: DateTime<Utc>) -> Self {
11        Self {
12            scheduler: Scheduler::new(parameters, card, now),
13        }
14    }
15    fn new_state(&mut self, rating: Rating) -> SchedulingInfo {
16        if let Some(exist) = self.scheduler.next.get(&rating) {
17            return exist.clone();
18        }
19
20        let mut next = self.scheduler.current.clone();
21        next.difficulty = self.scheduler.parameters.init_difficulty(rating);
22        next.stability = self.scheduler.parameters.init_stability(rating);
23
24        match rating {
25            Again => {
26                next.scheduled_days = 0;
27                next.due = self.scheduler.now + Duration::minutes(1);
28                next.state = Learning;
29            }
30            Hard => {
31                next.scheduled_days = 0;
32                next.due = self.scheduler.now + Duration::minutes(5);
33                next.state = Learning;
34            }
35            Good => {
36                next.scheduled_days = 0;
37                next.due = self.scheduler.now + Duration::minutes(10);
38                next.state = Learning;
39            }
40            Easy => {
41                let easy_interval = self
42                    .scheduler
43                    .parameters
44                    .next_interval(next.stability, next.elapsed_days);
45                next.scheduled_days = easy_interval as i64;
46                next.due = self.scheduler.now + Duration::days(easy_interval as i64);
47                next.state = Review;
48            }
49        };
50        let item = SchedulingInfo {
51            card: next,
52            review_log: self.scheduler.build_log(rating),
53        };
54
55        self.scheduler.next.insert(rating, item.clone());
56        item
57    }
58
59    fn learning_state(&mut self, rating: Rating) -> SchedulingInfo {
60        if let Some(exist) = self.scheduler.next.get(&rating) {
61            return exist.clone();
62        }
63
64        let mut next = self.scheduler.current.clone();
65        let interval = self.scheduler.current.elapsed_days;
66        next.difficulty = self
67            .scheduler
68            .parameters
69            .next_difficulty(self.scheduler.last.difficulty, rating);
70        next.stability = self
71            .scheduler
72            .parameters
73            .short_term_stability(self.scheduler.last.stability, rating);
74
75        match rating {
76            Again => {
77                next.scheduled_days = 0;
78                next.due = self.scheduler.now + Duration::minutes(5);
79                next.state = self.scheduler.last.state;
80            }
81            Hard => {
82                next.scheduled_days = 0;
83                next.due = self.scheduler.now + Duration::minutes(10);
84                next.state = self.scheduler.last.state;
85            }
86            Good => {
87                let good_interval = self
88                    .scheduler
89                    .parameters
90                    .next_interval(next.stability, interval);
91                next.scheduled_days = good_interval as i64;
92                next.due = self.scheduler.now + Duration::days(good_interval as i64);
93                next.state = Review;
94            }
95            Easy => {
96                let good_stability = self
97                    .scheduler
98                    .parameters
99                    .short_term_stability(self.scheduler.last.stability, Good);
100                let good_interval = self
101                    .scheduler
102                    .parameters
103                    .next_interval(good_stability, interval);
104                let easy_interval = self
105                    .scheduler
106                    .parameters
107                    .next_interval(next.stability, interval)
108                    .max(good_interval + 1.0);
109                next.scheduled_days = easy_interval as i64;
110                next.due = self.scheduler.now + Duration::days(easy_interval as i64);
111                next.state = Review;
112            }
113        }
114        let item = SchedulingInfo {
115            card: next,
116            review_log: self.scheduler.build_log(rating),
117        };
118
119        self.scheduler.next.insert(rating, item.clone());
120        item
121    }
122
123    fn review_state(&mut self, rating: Rating) -> SchedulingInfo {
124        if let Some(exist) = self.scheduler.next.get(&rating) {
125            return exist.clone();
126        }
127
128        let next = self.scheduler.current.clone();
129        let interval = self.scheduler.current.elapsed_days;
130        let stability = self.scheduler.last.stability;
131        let difficulty = self.scheduler.last.difficulty;
132        let retrievability = self.scheduler.last.get_retrievability(self.scheduler.now);
133
134        let mut next_again = next.clone();
135        let mut next_hard = next.clone();
136        let mut next_good = next.clone();
137        let mut next_easy = next;
138
139        self.next_difficulty_stability(
140            &mut next_again,
141            &mut next_hard,
142            &mut next_good,
143            &mut next_easy,
144            difficulty,
145            stability,
146            retrievability,
147        );
148        self.next_interval(
149            &mut next_again,
150            &mut next_hard,
151            &mut next_good,
152            &mut next_easy,
153            interval,
154        );
155        self.next_state(
156            &mut next_again,
157            &mut next_hard,
158            &mut next_good,
159            &mut next_easy,
160        );
161        next_again.lapses += 1;
162
163        let item_again = SchedulingInfo {
164            card: next_again,
165            review_log: self.scheduler.build_log(Again),
166        };
167        let item_hard = SchedulingInfo {
168            card: next_hard,
169            review_log: self.scheduler.build_log(Hard),
170        };
171        let item_good = SchedulingInfo {
172            card: next_good,
173            review_log: self.scheduler.build_log(Good),
174        };
175        let item_easy = SchedulingInfo {
176            card: next_easy,
177            review_log: self.scheduler.build_log(Easy),
178        };
179
180        self.scheduler.next.insert(Again, item_again);
181        self.scheduler.next.insert(Hard, item_hard);
182        self.scheduler.next.insert(Good, item_good);
183        self.scheduler.next.insert(Easy, item_easy);
184
185        self.scheduler.next.get(&rating).unwrap().to_owned()
186    }
187
188    #[allow(clippy::too_many_arguments)]
189    fn next_difficulty_stability(
190        &self,
191        next_again: &mut Card,
192        next_hard: &mut Card,
193        next_good: &mut Card,
194        next_easy: &mut Card,
195        difficulty: f64,
196        stability: f64,
197        retrievability: f64,
198    ) {
199        next_again.difficulty = self.scheduler.parameters.next_difficulty(difficulty, Again);
200        next_again.stability =
201            self.scheduler
202                .parameters
203                .next_forget_stability(difficulty, stability, retrievability);
204
205        next_hard.difficulty = self.scheduler.parameters.next_difficulty(difficulty, Hard);
206        next_hard.stability = self.scheduler.parameters.next_recall_stability(
207            difficulty,
208            stability,
209            retrievability,
210            Hard,
211        );
212
213        next_good.difficulty = self.scheduler.parameters.next_difficulty(difficulty, Good);
214        next_good.stability = self.scheduler.parameters.next_recall_stability(
215            difficulty,
216            stability,
217            retrievability,
218            Good,
219        );
220
221        next_easy.difficulty = self.scheduler.parameters.next_difficulty(difficulty, Easy);
222        next_easy.stability = self.scheduler.parameters.next_recall_stability(
223            difficulty,
224            stability,
225            retrievability,
226            Easy,
227        );
228    }
229
230    fn next_interval(
231        &self,
232        next_again: &mut Card,
233        next_hard: &mut Card,
234        next_good: &mut Card,
235        next_easy: &mut Card,
236        elapsed_days: i64,
237    ) {
238        let mut hard_interval = self
239            .scheduler
240            .parameters
241            .next_interval(next_hard.stability, elapsed_days);
242        let mut good_interval = self
243            .scheduler
244            .parameters
245            .next_interval(next_good.stability, elapsed_days);
246        hard_interval = hard_interval.min(good_interval);
247        good_interval = good_interval.max(hard_interval + 1.0);
248        let easy_interval = self
249            .scheduler
250            .parameters
251            .next_interval(next_easy.stability, elapsed_days)
252            .max(good_interval + 1.0);
253
254        next_again.scheduled_days = 0;
255        next_again.due = self.scheduler.now + Duration::minutes(5);
256
257        next_hard.scheduled_days = hard_interval as i64;
258        next_hard.due = self.scheduler.now + Duration::days(hard_interval as i64);
259
260        next_good.scheduled_days = good_interval as i64;
261        next_good.due = self.scheduler.now + Duration::days(good_interval as i64);
262
263        next_easy.scheduled_days = easy_interval as i64;
264        next_easy.due = self.scheduler.now + Duration::days(easy_interval as i64);
265    }
266
267    fn next_state(
268        &self,
269        next_again: &mut Card,
270        next_hard: &mut Card,
271        next_good: &mut Card,
272        next_easy: &mut Card,
273    ) {
274        next_again.state = Relearning;
275        next_hard.state = Review;
276        next_good.state = Review;
277        next_easy.state = Review;
278    }
279}
280
281impl ImplScheduler for BasicScheduler {
282    fn review(&mut self, rating: Rating) -> SchedulingInfo {
283        match self.scheduler.last.state {
284            New => self.new_state(rating),
285            Learning | Relearning => self.learning_state(rating),
286            Review => self.review_state(rating),
287        }
288    }
289}