From 402008950c9a67ca7415cd2ab8349d50757e36bd Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 6 Aug 2025 09:01:06 +0100 Subject: [PATCH] Feat/expected_workload_with_existing_cards implementation (#4243) * https://github.com/open-spaced-repetition/fsrs-rs/pull/355 * add is_included card * bump version * ./check * update package.lock * parallellify * bump fsrs --- Cargo.lock | 4 ++-- Cargo.toml | 4 +--- cargo/licenses.json | 2 +- rslib/src/deckconfig/service.rs | 20 +++++++++++++++++++- rslib/src/scheduler/fsrs/simulator.rs | 17 +++++++++++------ 5 files changed, 34 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9c09b75b..2d50d5388 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2214,9 +2214,9 @@ dependencies = [ [[package]] name = "fsrs" -version = "5.0.1" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df5aee516ebf9d4968364363b092371f988cd9fb628f7cae94ea422b6dd52f9c" +checksum = "04954cc67c3c11ee342a2ee1f5222bf76d73f7772df08d37dc9a6cdd73c467eb" dependencies = [ "burn", "itertools 0.14.0", diff --git a/Cargo.toml b/Cargo.toml index 4a6634909..27d14ce8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,10 +33,8 @@ git = "https://github.com/ankitects/linkcheck.git" rev = "184b2ca50ed39ca43da13f0b830a463861adb9ca" [workspace.dependencies.fsrs] -version = "5.0.1" +version = "5.1.0" # git = "https://github.com/open-spaced-repetition/fsrs-rs.git" -# branch = "Refactor/expected_workload_via_dp" -# rev = "a7f7efc10f0a26b14ee348cc7402155685f2a24f" # path = "../open-spaced-repetition/fsrs-rs" [workspace.dependencies] diff --git a/cargo/licenses.json b/cargo/licenses.json index 7bd0d984a..92713c098 100644 --- a/cargo/licenses.json +++ b/cargo/licenses.json @@ -1450,7 +1450,7 @@ }, { "name": "fsrs", - "version": "5.0.1", + "version": "5.1.0", "authors": "Open Spaced Repetition", "repository": "https://github.com/open-spaced-repetition/fsrs-rs", "license": "BSD-3-Clause", diff --git a/rslib/src/deckconfig/service.rs b/rslib/src/deckconfig/service.rs index 8cc33fc3a..11c4288d3 100644 --- a/rslib/src/deckconfig/service.rs +++ b/rslib/src/deckconfig/service.rs @@ -3,6 +3,8 @@ use std::collections::HashMap; use anki_proto::generic; +use rayon::iter::IntoParallelIterator; +use rayon::iter::ParallelIterator; use crate::collection::Collection; use crate::deckconfig::DeckConfSchema11; @@ -11,6 +13,7 @@ use crate::deckconfig::DeckConfigId; use crate::deckconfig::UpdateDeckConfigsRequest; use crate::error::Result; use crate::scheduler::fsrs::params::ignore_revlogs_before_date_to_ms; +use crate::scheduler::fsrs::simulator::is_included_card; impl crate::services::DeckConfigService for Collection { fn add_or_update_deck_config_legacy( @@ -103,6 +106,7 @@ impl crate::services::DeckConfigService for Collection { &mut self, input: anki_proto::deck_config::GetRetentionWorkloadRequest, ) -> Result { + let days_elapsed = self.timing_today().unwrap().days_elapsed as i32; let guard = self.search_cards_into_table(&input.search, crate::search::SortMode::NoOrder)?; @@ -112,12 +116,26 @@ impl crate::services::DeckConfigService for Collection { .get_revlog_entries_for_searched_cards_in_card_order()?; let config = guard.col.get_optimal_retention_parameters(revlogs)?; + let cards = guard + .col + .storage + .all_searched_cards()? + .into_iter() + .filter(is_included_card) + .filter_map(|c| crate::card::Card::convert(c.clone(), days_elapsed, c.memory_state?)) + .collect::>(); let costs = (70u32..=99u32) + .into_par_iter() .map(|dr| { Ok(( dr, - fsrs::expected_workload(&input.w, dr as f32 / 100., &config)?, + fsrs::expected_workload_with_existing_cards( + &input.w, + dr as f32 / 100., + &config, + &cards, + )?, )) }) .collect::>>()?; diff --git a/rslib/src/scheduler/fsrs/simulator.rs b/rslib/src/scheduler/fsrs/simulator.rs index 262768dee..a26afda9c 100644 --- a/rslib/src/scheduler/fsrs/simulator.rs +++ b/rslib/src/scheduler/fsrs/simulator.rs @@ -121,6 +121,12 @@ fn create_review_priority_fn( } } +pub(crate) fn is_included_card(c: &Card) -> bool { + c.queue != CardQueue::Suspended + && c.queue != CardQueue::PreviewRepeat + && c.ctype != CardType::New +} + impl Collection { pub fn simulate_request_to_config( &mut self, @@ -133,11 +139,6 @@ impl Collection { .get_revlog_entries_for_searched_cards_in_card_order()?; let mut cards = guard.col.storage.all_searched_cards()?; drop(guard); - fn is_included_card(c: &Card) -> bool { - c.queue != CardQueue::Suspended - && c.queue != CardQueue::PreviewRepeat - && c.ctype != CardType::New - } // calculate any missing memory state for c in &mut cards { if is_included_card(c) && c.memory_state.is_none() { @@ -306,7 +307,11 @@ impl Collection { } impl Card { - fn convert(card: Card, days_elapsed: i32, memory_state: FsrsMemoryState) -> Option { + pub(crate) 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();