mirror of
https://github.com/ankitects/anki.git
synced 2025-11-25 22:17:12 -05:00
* Feat/support new cards ignore review limit in simulator * ./ninja fix:minilints & ./ninja format * use published crate * make newCardsIgnoreReviewLimit reactive * format --------- Co-authored-by: Damien Elmes <gpg@ankiweb.net>
115 lines
4.4 KiB
Rust
115 lines
4.4 KiB
Rust
// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
use anki_proto::scheduler::SimulateFsrsReviewRequest;
|
|
use anki_proto::scheduler::SimulateFsrsReviewResponse;
|
|
use fsrs::simulate;
|
|
use fsrs::SimulatorConfig;
|
|
use itertools::Itertools;
|
|
|
|
use crate::card::CardQueue;
|
|
use crate::prelude::*;
|
|
use crate::search::SortMode;
|
|
|
|
impl Collection {
|
|
pub fn simulate_review(
|
|
&mut self,
|
|
req: SimulateFsrsReviewRequest,
|
|
) -> Result<SimulateFsrsReviewResponse> {
|
|
let guard = self.search_cards_into_table(&req.search, SortMode::NoOrder)?;
|
|
let revlogs = guard
|
|
.col
|
|
.storage
|
|
.get_revlog_entries_for_searched_cards_in_card_order()?;
|
|
let cards = guard.col.storage.all_searched_cards()?;
|
|
drop(guard);
|
|
let days_elapsed = self.timing_today().unwrap().days_elapsed as i32;
|
|
let converted_cards = cards
|
|
.into_iter()
|
|
.filter(|c| c.queue != CardQueue::Suspended && c.queue != CardQueue::PreviewRepeat)
|
|
.filter_map(|c| Card::convert(c, days_elapsed, req.days_to_simulate))
|
|
.collect_vec();
|
|
let p = self.get_optimal_retention_parameters(revlogs)?;
|
|
let config = SimulatorConfig {
|
|
deck_size: req.deck_size as usize + converted_cards.len(),
|
|
learn_span: req.days_to_simulate as usize,
|
|
max_cost_perday: f32::MAX,
|
|
max_ivl: req.max_interval as f32,
|
|
learn_costs: p.learn_costs,
|
|
review_costs: p.review_costs,
|
|
first_rating_prob: p.first_rating_prob,
|
|
review_rating_prob: p.review_rating_prob,
|
|
first_rating_offsets: p.first_rating_offsets,
|
|
first_session_lens: p.first_session_lens,
|
|
forget_rating_offset: p.forget_rating_offset,
|
|
forget_session_len: p.forget_session_len,
|
|
loss_aversion: 1.0,
|
|
learn_limit: req.new_limit as usize,
|
|
review_limit: req.review_limit as usize,
|
|
new_cards_ignore_review_limit: req.new_cards_ignore_review_limit,
|
|
};
|
|
let result = simulate(
|
|
&config,
|
|
&req.params,
|
|
req.desired_retention,
|
|
None,
|
|
Some(converted_cards),
|
|
)?;
|
|
Ok(SimulateFsrsReviewResponse {
|
|
accumulated_knowledge_acquisition: result.memorized_cnt_per_day.to_vec(),
|
|
daily_review_count: result
|
|
.review_cnt_per_day
|
|
.iter()
|
|
.map(|x| *x as u32)
|
|
.collect_vec(),
|
|
daily_new_count: result
|
|
.learn_cnt_per_day
|
|
.iter()
|
|
.map(|x| *x as u32)
|
|
.collect_vec(),
|
|
daily_time_cost: result.cost_per_day.to_vec(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Card {
|
|
fn convert(card: Card, days_elapsed: i32, day_to_simulate: u32) -> Option<fsrs::Card> {
|
|
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 {
|
|
difficulty: state.difficulty,
|
|
stability: state.stability,
|
|
last_date,
|
|
due: relative_due as f32,
|
|
})
|
|
}
|
|
CardQueue::New => Some(fsrs::Card {
|
|
difficulty: 1e-10,
|
|
stability: 1e-10,
|
|
last_date: 0.0,
|
|
due: day_to_simulate as f32,
|
|
}),
|
|
CardQueue::Learn | CardQueue::SchedBuried | CardQueue::UserBuried => {
|
|
Some(fsrs::Card {
|
|
difficulty: state.difficulty,
|
|
stability: state.stability,
|
|
last_date: 0.0,
|
|
due: 0.0,
|
|
})
|
|
}
|
|
CardQueue::PreviewRepeat => None,
|
|
CardQueue::Suspended => None,
|
|
},
|
|
None => Some(fsrs::Card {
|
|
difficulty: 1e-10,
|
|
stability: 1e-10,
|
|
last_date: 0.0,
|
|
due: day_to_simulate as f32,
|
|
}),
|
|
}
|
|
}
|
|
}
|