Skip to content

Commit 43e8607

Browse files
authored
Fix/re-optimize FSRS if short-term param is weird (#3742)
* Fix/re-optimize FSRS if short-term param is weird * Reset progress when another run is required (dae) * only count the same-day steps * Fix flicker when optimizing again (dae)
1 parent 5883e4e commit 43e8607

File tree

6 files changed

+80
-24
lines changed

6 files changed

+80
-24
lines changed

proto/anki/scheduler.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ message ComputeFsrsParamsRequest {
346346
string search = 1;
347347
repeated float current_params = 2;
348348
int64 ignore_revlogs_before_ms = 3;
349+
uint32 num_of_relearning_steps = 4;
349350
}
350351

351352
message ComputeFsrsParamsResponse {

rslib/src/deckconfig/update.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,12 +356,14 @@ impl Collection {
356356
config.inner.param_search.clone()
357357
};
358358
let ignore_revlogs_before_ms = ignore_revlogs_before_ms_from_config(config)?;
359+
let num_of_relearning_steps = config.inner.relearn_steps.len();
359360
match self.compute_params(
360361
&search,
361362
ignore_revlogs_before_ms,
362363
idx as u32 + 1,
363364
config_len,
364365
config.fsrs_params(),
366+
num_of_relearning_steps,
365367
) {
366368
Ok(params) => {
367369
println!("{}: {:?}", config.name, params.params);

rslib/src/progress.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ pub struct ProgressState {
122122
pub last_progress: Option<Progress>,
123123
}
124124

125+
impl ProgressState {
126+
pub fn reset(&mut self) {
127+
self.want_abort = false;
128+
self.last_progress = None;
129+
}
130+
}
131+
125132
#[derive(Clone, Copy, Debug)]
126133
pub enum Progress {
127134
MediaSync(MediaSyncProgress),
@@ -320,6 +327,10 @@ impl Collection {
320327
) -> ThrottlingProgressHandler<P> {
321328
ThrottlingProgressHandler::new(self.state.progress.clone())
322329
}
330+
331+
pub(crate) fn clear_progress(&mut self) {
332+
self.state.progress.lock().unwrap().reset();
333+
}
323334
}
324335

325336
pub(crate) struct Incrementor<'f, F: 'f + FnMut(usize) -> Result<()>> {

rslib/src/scheduler/fsrs/params.rs

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use chrono::NaiveTime;
1616
use fsrs::CombinedProgressState;
1717
use fsrs::FSRSItem;
1818
use fsrs::FSRSReview;
19+
use fsrs::MemoryState;
1920
use fsrs::ModelEvaluation;
2021
use fsrs::FSRS;
2122
use itertools::Itertools;
@@ -60,8 +61,9 @@ impl Collection {
6061
current_preset: u32,
6162
total_presets: u32,
6263
current_params: &Params,
64+
num_of_relearning_steps: usize,
6365
) -> Result<ComputeFsrsParamsResponse> {
64-
let mut anki_progress = self.new_progress_handler::<ComputeParamsProgress>();
66+
self.clear_progress();
6567
let timing = self.timing_today()?;
6668
let revlogs = self.revlog_for_srs(search)?;
6769
let (items, review_count) =
@@ -74,31 +76,38 @@ impl Collection {
7476
fsrs_items,
7577
});
7678
}
77-
anki_progress.update(false, |p| {
78-
p.current_preset = current_preset;
79-
p.total_presets = total_presets;
80-
})?;
8179
// adapt the progress handler to our built-in progress handling
82-
let progress = CombinedProgressState::new_shared();
83-
let progress2 = progress.clone();
84-
let progress_thread = thread::spawn(move || {
85-
let mut finished = false;
86-
while !finished {
87-
thread::sleep(Duration::from_millis(100));
88-
let mut guard = progress.lock().unwrap();
89-
if let Err(_err) = anki_progress.update(false, |s| {
90-
s.total_iterations = guard.total() as u32;
91-
s.current_iteration = guard.current() as u32;
92-
s.reviews = review_count as u32;
93-
finished = guard.finished();
94-
}) {
95-
guard.want_abort = true;
96-
return;
80+
81+
let create_progress_thread = || -> Result<_> {
82+
let mut anki_progress = self.new_progress_handler::<ComputeParamsProgress>();
83+
anki_progress.update(false, |p| {
84+
p.current_preset = current_preset;
85+
p.total_presets = total_presets;
86+
})?;
87+
let progress = CombinedProgressState::new_shared();
88+
let progress2 = progress.clone();
89+
let progress_thread = thread::spawn(move || {
90+
let mut finished = false;
91+
while !finished {
92+
thread::sleep(Duration::from_millis(100));
93+
let mut guard = progress.lock().unwrap();
94+
if let Err(_err) = anki_progress.update(false, |s| {
95+
s.total_iterations = guard.total() as u32;
96+
s.current_iteration = guard.current() as u32;
97+
s.reviews = review_count as u32;
98+
finished = guard.finished();
99+
}) {
100+
guard.want_abort = true;
101+
return;
102+
}
97103
}
98-
}
99-
});
100-
let mut params =
101-
FSRS::new(None)?.compute_parameters(items.clone(), Some(progress2), true)?;
104+
});
105+
Ok((progress2, progress_thread))
106+
};
107+
108+
let (progress, progress_thread) = create_progress_thread()?;
109+
let fsrs = FSRS::new(None)?;
110+
let mut params = fsrs.compute_parameters(items.clone(), Some(progress.clone()), true)?;
102111
progress_thread.join().ok();
103112
if let Ok(fsrs) = FSRS::new(Some(current_params)) {
104113
let current_rmse = fsrs.evaluate(items.clone(), |_| true)?.rmse_bins;
@@ -107,6 +116,27 @@ impl Collection {
107116
if current_rmse <= optimized_rmse {
108117
params = current_params.to_vec();
109118
}
119+
if num_of_relearning_steps > 1 {
120+
let memory_state = MemoryState {
121+
stability: 1.0,
122+
difficulty: 1.0,
123+
};
124+
let s_fail = optimized_fsrs
125+
.next_states(Some(memory_state), 0.9, 2)?
126+
.again;
127+
let mut s_short_term = s_fail.memory;
128+
for _ in 0..num_of_relearning_steps {
129+
s_short_term = optimized_fsrs
130+
.next_states(Some(s_short_term), 0.9, 0)?
131+
.good
132+
.memory;
133+
}
134+
if s_short_term.stability > memory_state.stability {
135+
let (progress, progress_thread) = create_progress_thread()?;
136+
params = fsrs.compute_parameters(items.clone(), Some(progress), false)?;
137+
progress_thread.join().ok();
138+
}
139+
}
110140
}
111141

112142
Ok(ComputeFsrsParamsResponse { params, fsrs_items })

rslib/src/scheduler/service/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ impl crate::services::SchedulerService for Collection {
264264
1,
265265
1,
266266
&input.current_params,
267+
input.num_of_relearning_steps as usize,
267268
)
268269
}
269270

ts/routes/deck-options/FsrsOptions.svelte

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,23 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
151151
await runWithBackendProgress(
152152
async () => {
153153
const params = fsrsParams($config);
154+
const RelearningSteps = $config.relearnSteps;
155+
let numOfRelearningStepsInDay = 0;
156+
let accumulatedTime = 0;
157+
for (let i = 0; i < RelearningSteps.length; i++) {
158+
accumulatedTime += RelearningSteps[i];
159+
if (accumulatedTime >= 1440) {
160+
break;
161+
}
162+
numOfRelearningStepsInDay++;
163+
}
154164
const resp = await computeFsrsParams({
155165
search: $config.paramSearch
156166
? $config.paramSearch
157167
: defaultparamSearch,
158168
ignoreRevlogsBeforeMs: getIgnoreRevlogsBeforeMs(),
159169
currentParams: params,
170+
numOfRelearningSteps: numOfRelearningStepsInDay,
160171
});
161172
162173
const already_optimal =

0 commit comments

Comments
 (0)