From 1081053807023ecb43facd12fc2e46656e583495 Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Tue, 8 Jul 2025 16:41:05 +0800 Subject: [PATCH 1/8] =?UTF-8?q?Fix/FSRS=20simulator=20fallback=20to=20memo?= =?UTF-8?q?ry=5Fstate=5Ffrom=5Fsm2=20for=20after=20setting=20=E2=80=9CIgno?= =?UTF-8?q?re=20cards=20reviewed=20before=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rslib/src/scheduler/fsrs/simulator.rs | 87 +++++++++++++++------------ 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/rslib/src/scheduler/fsrs/simulator.rs b/rslib/src/scheduler/fsrs/simulator.rs index 34cc925d6ad..2d43cc4a79e 100644 --- a/rslib/src/scheduler/fsrs/simulator.rs +++ b/rslib/src/scheduler/fsrs/simulator.rs @@ -10,11 +10,13 @@ use fsrs::simulate; use fsrs::PostSchedulingFn; use fsrs::ReviewPriorityFn; use fsrs::SimulatorConfig; +use fsrs::FSRS; use itertools::Itertools; use rand::rngs::StdRng; use rand::Rng; use crate::card::CardQueue; +use crate::card::FsrsMemoryState; use crate::prelude::*; use crate::scheduler::states::fuzz::constrained_fuzz_bounds; use crate::scheduler::states::load_balancer::calculate_easy_days_modifiers; @@ -141,15 +143,27 @@ impl Collection { } } let days_elapsed = self.timing_today().unwrap().days_elapsed as i32; - let new_cards = cards - .iter() - .filter(|c| c.memory_state.is_none() || c.queue == CardQueue::New) - .count() - + req.deck_size as usize; + let new_cards = + cards.iter().filter(|c| c.queue == CardQueue::New).count() + req.deck_size as usize; + let fsrs = FSRS::new(Some(&req.params))?; + let historical_retention = req.desired_retention; let mut converted_cards = cards .into_iter() .filter(is_included_card) - .filter_map(|c| Card::convert(c, days_elapsed)) + .filter_map(|c| { + let memory_state = match c.memory_state { + Some(state) => state, + None => fsrs + .memory_state_from_sm2( + c.ease_factor(), + c.interval as f32, + historical_retention, + ) + .ok()? + .into(), + }; + Card::convert(c, days_elapsed, memory_state) + }) .collect_vec(); let introduced_today_count = self .search_cards(&format!("{} introduced:1", &req.search), SortMode::NoOrder)? @@ -251,39 +265,34 @@ impl Collection { } impl Card { - fn convert(card: Card, days_elapsed: i32) -> Option { - match card.memory_state { - Some(state) => match card.queue { - CardQueue::DayLearn | CardQueue::Review => { - let due = card.original_or_current_due(); - let relative_due = due - days_elapsed; - let last_date = (relative_due - card.interval as i32).min(0) as f32; - Some(fsrs::Card { - id: card.id.0, - difficulty: state.difficulty, - stability: state.stability, - last_date, - due: relative_due as f32, - interval: card.interval as f32, - lapses: card.lapses, - }) - } - CardQueue::New => None, - CardQueue::Learn | CardQueue::SchedBuried | CardQueue::UserBuried => { - Some(fsrs::Card { - id: card.id.0, - difficulty: state.difficulty, - stability: state.stability, - last_date: 0.0, - due: 0.0, - interval: card.interval as f32, - lapses: card.lapses, - }) - } - CardQueue::PreviewRepeat => None, - CardQueue::Suspended => None, - }, - None => None, + fn convert(card: Card, days_elapsed: i32, memory_state: FsrsMemoryState) -> Option { + match card.queue { + CardQueue::DayLearn | CardQueue::Review => { + let due = card.original_or_current_due(); + let relative_due = due - days_elapsed; + let last_date = (relative_due - card.interval as i32).min(0) as f32; + Some(fsrs::Card { + id: card.id.0, + difficulty: memory_state.difficulty, + stability: memory_state.stability, + last_date, + due: relative_due as f32, + interval: card.interval as f32, + lapses: card.lapses, + }) + } + CardQueue::New => None, + CardQueue::Learn | CardQueue::SchedBuried | CardQueue::UserBuried => Some(fsrs::Card { + id: card.id.0, + difficulty: memory_state.difficulty, + stability: memory_state.stability, + last_date: 0.0, + due: 0.0, + interval: card.interval as f32, + lapses: card.lapses, + }), + CardQueue::PreviewRepeat => None, + CardQueue::Suspended => None, } } } From 267f763b8ebff8ee134d40d3b3a8800a6989b654 Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Tue, 8 Jul 2025 16:56:24 +0800 Subject: [PATCH 2/8] add comment to fsrs_item_for_memory_state --- rslib/src/scheduler/fsrs/memory_state.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rslib/src/scheduler/fsrs/memory_state.rs b/rslib/src/scheduler/fsrs/memory_state.rs index 425d8da69ca..53ef2e3490a 100644 --- a/rslib/src/scheduler/fsrs/memory_state.rs +++ b/rslib/src/scheduler/fsrs/memory_state.rs @@ -377,6 +377,7 @@ pub(crate) fn fsrs_item_for_memory_state( Ok(None) } } else { + // no revlogs (new card or caused by ignore_revlogs_before) Ok(None) } } From d159aaa7041e2417f18ffeab86a4abdc2d28a506 Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Tue, 8 Jul 2025 17:40:17 +0800 Subject: [PATCH 3/8] Add historical retention field to FSRS review request and update related logic - Added `historical_retention` field to `SimulateFsrsReviewRequest` in `scheduler.proto`. - Updated `simulator.rs` to use `req.historical_retention` instead of the removed `desired_retention`. - Modified `FsrsOptions.svelte` to include `historicalRetention` in the options passed to the component. --- proto/anki/scheduler.proto | 1 + rslib/src/scheduler/fsrs/simulator.rs | 3 +-- ts/routes/deck-options/FsrsOptions.svelte | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/proto/anki/scheduler.proto b/proto/anki/scheduler.proto index 01f092a3931..1294b454389 100644 --- a/proto/anki/scheduler.proto +++ b/proto/anki/scheduler.proto @@ -404,6 +404,7 @@ message SimulateFsrsReviewRequest { repeated float easy_days_percentages = 10; deck_config.DeckConfig.Config.ReviewCardOrder review_order = 11; optional uint32 suspend_after_lapse_count = 12; + float historical_retention = 13; } message SimulateFsrsReviewResponse { diff --git a/rslib/src/scheduler/fsrs/simulator.rs b/rslib/src/scheduler/fsrs/simulator.rs index 2d43cc4a79e..4e0907eb376 100644 --- a/rslib/src/scheduler/fsrs/simulator.rs +++ b/rslib/src/scheduler/fsrs/simulator.rs @@ -146,7 +146,6 @@ impl Collection { let new_cards = cards.iter().filter(|c| c.queue == CardQueue::New).count() + req.deck_size as usize; let fsrs = FSRS::new(Some(&req.params))?; - let historical_retention = req.desired_retention; let mut converted_cards = cards .into_iter() .filter(is_included_card) @@ -157,7 +156,7 @@ impl Collection { .memory_state_from_sm2( c.ease_factor(), c.interval as f32, - historical_retention, + req.historical_retention, ) .ok()? .into(), diff --git a/ts/routes/deck-options/FsrsOptions.svelte b/ts/routes/deck-options/FsrsOptions.svelte index 706407889a3..cfdea341c1d 100644 --- a/ts/routes/deck-options/FsrsOptions.svelte +++ b/ts/routes/deck-options/FsrsOptions.svelte @@ -95,6 +95,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html newCardsIgnoreReviewLimit: $newCardsIgnoreReviewLimit, easyDaysPercentages: $config.easyDaysPercentages, reviewOrder: $config.reviewOrder, + historicalRetention: $config.historicalRetention, }); const DESIRED_RETENTION_LOW_THRESHOLD = 0.8; From 7c9e8ee82ff4064456c2848d95802e0580399b01 Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Tue, 8 Jul 2025 20:14:22 +0800 Subject: [PATCH 4/8] Update rslib/src/scheduler/fsrs/memory_state.rs Co-authored-by: user1823 <92206575+user1823@users.noreply.github.com> --- rslib/src/scheduler/fsrs/memory_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rslib/src/scheduler/fsrs/memory_state.rs b/rslib/src/scheduler/fsrs/memory_state.rs index 53ef2e3490a..b2640fa3e85 100644 --- a/rslib/src/scheduler/fsrs/memory_state.rs +++ b/rslib/src/scheduler/fsrs/memory_state.rs @@ -377,7 +377,7 @@ pub(crate) fn fsrs_item_for_memory_state( Ok(None) } } else { - // no revlogs (new card or caused by ignore_revlogs_before) + // no revlogs (new card or caused by ignore_revlogs_before or deleted revlogs) Ok(None) } } From 824da308c791b24a2077d3b79483a1c430cb7c7b Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Tue, 8 Jul 2025 20:14:31 +0800 Subject: [PATCH 5/8] Update rslib/src/scheduler/fsrs/simulator.rs Co-authored-by: user1823 <92206575+user1823@users.noreply.github.com> --- rslib/src/scheduler/fsrs/simulator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rslib/src/scheduler/fsrs/simulator.rs b/rslib/src/scheduler/fsrs/simulator.rs index 4e0907eb376..10d04821036 100644 --- a/rslib/src/scheduler/fsrs/simulator.rs +++ b/rslib/src/scheduler/fsrs/simulator.rs @@ -144,7 +144,7 @@ impl Collection { } let days_elapsed = self.timing_today().unwrap().days_elapsed as i32; let new_cards = - cards.iter().filter(|c| c.queue == CardQueue::New).count() + req.deck_size as usize; + cards.iter().filter(|c| c.ctype == CardType::New).count() + req.deck_size as usize; let fsrs = FSRS::new(Some(&req.params))?; let mut converted_cards = cards .into_iter() From cef825b5411529e76bce5c393c2461155d3bce5f Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Tue, 8 Jul 2025 20:35:24 +0800 Subject: [PATCH 6/8] pass ci --- rslib/src/scheduler/fsrs/simulator.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rslib/src/scheduler/fsrs/simulator.rs b/rslib/src/scheduler/fsrs/simulator.rs index 10d04821036..595f2a02870 100644 --- a/rslib/src/scheduler/fsrs/simulator.rs +++ b/rslib/src/scheduler/fsrs/simulator.rs @@ -16,6 +16,7 @@ use rand::rngs::StdRng; use rand::Rng; use crate::card::CardQueue; +use crate::card::CardType; use crate::card::FsrsMemoryState; use crate::prelude::*; use crate::scheduler::states::fuzz::constrained_fuzz_bounds; From 2c7241c0996f81838f5b93fec266a760f2347b4f Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Tue, 8 Jul 2025 20:36:21 +0800 Subject: [PATCH 7/8] Update rslib/src/scheduler/fsrs/simulator.rs Co-authored-by: user1823 <92206575+user1823@users.noreply.github.com> --- rslib/src/scheduler/fsrs/simulator.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rslib/src/scheduler/fsrs/simulator.rs b/rslib/src/scheduler/fsrs/simulator.rs index 595f2a02870..42810dd49b8 100644 --- a/rslib/src/scheduler/fsrs/simulator.rs +++ b/rslib/src/scheduler/fsrs/simulator.rs @@ -153,6 +153,7 @@ impl Collection { .filter_map(|c| { let memory_state = match c.memory_state { Some(state) => state, + // cards that lack memory states after compute_memory_state have no FSRS items, implying a truncated or ignored revlog None => fsrs .memory_state_from_sm2( c.ease_factor(), From b253983901d84c10dbed72afb49fb2e460155033 Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Tue, 8 Jul 2025 20:40:18 +0800 Subject: [PATCH 8/8] format --- rslib/src/scheduler/fsrs/simulator.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rslib/src/scheduler/fsrs/simulator.rs b/rslib/src/scheduler/fsrs/simulator.rs index 42810dd49b8..24379445aab 100644 --- a/rslib/src/scheduler/fsrs/simulator.rs +++ b/rslib/src/scheduler/fsrs/simulator.rs @@ -153,7 +153,8 @@ impl Collection { .filter_map(|c| { let memory_state = match c.memory_state { Some(state) => state, - // cards that lack memory states after compute_memory_state have no FSRS items, implying a truncated or ignored revlog + // cards that lack memory states after compute_memory_state have no FSRS items, + // implying a truncated or ignored revlog None => fsrs .memory_state_from_sm2( c.ease_factor(),