diff --git a/proto/anki/scheduler.proto b/proto/anki/scheduler.proto index 426045fef..94efd42d7 100644 --- a/proto/anki/scheduler.proto +++ b/proto/anki/scheduler.proto @@ -346,6 +346,7 @@ message ComputeFsrsParamsRequest { string search = 1; repeated float current_params = 2; int64 ignore_revlogs_before_ms = 3; + uint32 num_of_relearning_steps = 4; } message ComputeFsrsParamsResponse { diff --git a/rslib/src/deckconfig/update.rs b/rslib/src/deckconfig/update.rs index f56d0bf47..fca6ba30b 100644 --- a/rslib/src/deckconfig/update.rs +++ b/rslib/src/deckconfig/update.rs @@ -356,12 +356,14 @@ impl Collection { config.inner.param_search.clone() }; let ignore_revlogs_before_ms = ignore_revlogs_before_ms_from_config(config)?; + let num_of_relearning_steps = config.inner.relearn_steps.len(); match self.compute_params( &search, ignore_revlogs_before_ms, idx as u32 + 1, config_len, config.fsrs_params(), + num_of_relearning_steps, ) { Ok(params) => { println!("{}: {:?}", config.name, params.params); diff --git a/rslib/src/progress.rs b/rslib/src/progress.rs index a404808bf..c0858c40d 100644 --- a/rslib/src/progress.rs +++ b/rslib/src/progress.rs @@ -122,6 +122,13 @@ pub struct ProgressState { pub last_progress: Option, } +impl ProgressState { + pub fn reset(&mut self) { + self.want_abort = false; + self.last_progress = None; + } +} + #[derive(Clone, Copy, Debug)] pub enum Progress { MediaSync(MediaSyncProgress), @@ -320,6 +327,10 @@ impl Collection { ) -> ThrottlingProgressHandler

{ ThrottlingProgressHandler::new(self.state.progress.clone()) } + + pub(crate) fn clear_progress(&mut self) { + self.state.progress.lock().unwrap().reset(); + } } pub(crate) struct Incrementor<'f, F: 'f + FnMut(usize) -> Result<()>> { diff --git a/rslib/src/scheduler/fsrs/params.rs b/rslib/src/scheduler/fsrs/params.rs index 84fbed48b..f304c6e63 100644 --- a/rslib/src/scheduler/fsrs/params.rs +++ b/rslib/src/scheduler/fsrs/params.rs @@ -16,6 +16,7 @@ use chrono::NaiveTime; use fsrs::CombinedProgressState; use fsrs::FSRSItem; use fsrs::FSRSReview; +use fsrs::MemoryState; use fsrs::ModelEvaluation; use fsrs::FSRS; use itertools::Itertools; @@ -60,8 +61,9 @@ impl Collection { current_preset: u32, total_presets: u32, current_params: &Params, + num_of_relearning_steps: usize, ) -> Result { - let mut anki_progress = self.new_progress_handler::(); + self.clear_progress(); let timing = self.timing_today()?; let revlogs = self.revlog_for_srs(search)?; let (items, review_count) = @@ -74,31 +76,38 @@ impl Collection { fsrs_items, }); } - anki_progress.update(false, |p| { - p.current_preset = current_preset; - p.total_presets = total_presets; - })?; // adapt the progress handler to our built-in progress handling - let progress = CombinedProgressState::new_shared(); - let progress2 = progress.clone(); - let progress_thread = thread::spawn(move || { - let mut finished = false; - while !finished { - thread::sleep(Duration::from_millis(100)); - let mut guard = progress.lock().unwrap(); - if let Err(_err) = anki_progress.update(false, |s| { - s.total_iterations = guard.total() as u32; - s.current_iteration = guard.current() as u32; - s.reviews = review_count as u32; - finished = guard.finished(); - }) { - guard.want_abort = true; - return; + + let create_progress_thread = || -> Result<_> { + let mut anki_progress = self.new_progress_handler::(); + anki_progress.update(false, |p| { + p.current_preset = current_preset; + p.total_presets = total_presets; + })?; + let progress = CombinedProgressState::new_shared(); + let progress2 = progress.clone(); + let progress_thread = thread::spawn(move || { + let mut finished = false; + while !finished { + thread::sleep(Duration::from_millis(100)); + let mut guard = progress.lock().unwrap(); + if let Err(_err) = anki_progress.update(false, |s| { + s.total_iterations = guard.total() as u32; + s.current_iteration = guard.current() as u32; + s.reviews = review_count as u32; + finished = guard.finished(); + }) { + guard.want_abort = true; + return; + } } - } - }); - let mut params = - FSRS::new(None)?.compute_parameters(items.clone(), Some(progress2), true)?; + }); + Ok((progress2, progress_thread)) + }; + + let (progress, progress_thread) = create_progress_thread()?; + let fsrs = FSRS::new(None)?; + let mut params = fsrs.compute_parameters(items.clone(), Some(progress.clone()), true)?; progress_thread.join().ok(); if let Ok(fsrs) = FSRS::new(Some(current_params)) { let current_rmse = fsrs.evaluate(items.clone(), |_| true)?.rmse_bins; @@ -107,6 +116,27 @@ impl Collection { if current_rmse <= optimized_rmse { params = current_params.to_vec(); } + if num_of_relearning_steps > 1 { + let memory_state = MemoryState { + stability: 1.0, + difficulty: 1.0, + }; + let s_fail = optimized_fsrs + .next_states(Some(memory_state), 0.9, 2)? + .again; + let mut s_short_term = s_fail.memory; + for _ in 0..num_of_relearning_steps { + s_short_term = optimized_fsrs + .next_states(Some(s_short_term), 0.9, 0)? + .good + .memory; + } + if s_short_term.stability > memory_state.stability { + let (progress, progress_thread) = create_progress_thread()?; + params = fsrs.compute_parameters(items.clone(), Some(progress), false)?; + progress_thread.join().ok(); + } + } } Ok(ComputeFsrsParamsResponse { params, fsrs_items }) diff --git a/rslib/src/scheduler/service/mod.rs b/rslib/src/scheduler/service/mod.rs index b725c6b95..b66b1e565 100644 --- a/rslib/src/scheduler/service/mod.rs +++ b/rslib/src/scheduler/service/mod.rs @@ -264,6 +264,7 @@ impl crate::services::SchedulerService for Collection { 1, 1, &input.current_params, + input.num_of_relearning_steps as usize, ) } diff --git a/ts/routes/deck-options/FsrsOptions.svelte b/ts/routes/deck-options/FsrsOptions.svelte index fbdb1496c..9baedea15 100644 --- a/ts/routes/deck-options/FsrsOptions.svelte +++ b/ts/routes/deck-options/FsrsOptions.svelte @@ -151,12 +151,23 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html await runWithBackendProgress( async () => { const params = fsrsParams($config); + const RelearningSteps = $config.relearnSteps; + let numOfRelearningStepsInDay = 0; + let accumulatedTime = 0; + for (let i = 0; i < RelearningSteps.length; i++) { + accumulatedTime += RelearningSteps[i]; + if (accumulatedTime >= 1440) { + break; + } + numOfRelearningStepsInDay++; + } const resp = await computeFsrsParams({ search: $config.paramSearch ? $config.paramSearch : defaultparamSearch, ignoreRevlogsBeforeMs: getIgnoreRevlogsBeforeMs(), currentParams: params, + numOfRelearningSteps: numOfRelearningStepsInDay, }); const already_optimal =