mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Feat/option to enable FSRS short-term scheduler when (re)learning steps run out && speed up features based on simulation (#3505)
* Update to FSRS-rs v1.3.2 * add fsrs_short_term_with_steps_enabled to config * ./ninja fix:minilints * fix defaults_for_testing * if current parameters are invalid, skip comparison fix #3498 * fix redundant_field_names * cargo clippy --fix * Update to FSRS-rs v1.3.3 * Update to FSRS-rs v1.3.4 * Avoid an extra config lookup on each card answer (dae)
This commit is contained in:
parent
b09326cddd
commit
6ff309e08f
14 changed files with 78 additions and 19 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -1860,15 +1860,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fsrs"
|
||||
version = "1.3.1"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2434366942bf285f3c0691e68d731e56f4e1fc1d8ec7b6a0e9411e94eda6ffbd"
|
||||
checksum = "a05b1fdbaa34f9bcd605ad50477eea51b27b3f60f973021bd9ee1b5943169150"
|
||||
dependencies = [
|
||||
"burn",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"ndarray",
|
||||
"ndarray-rand",
|
||||
"priority-queue",
|
||||
"rand",
|
||||
"rayon",
|
||||
"serde",
|
||||
|
@ -4276,6 +4277,17 @@ dependencies = [
|
|||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "priority-queue"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"equivalent",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.2.0"
|
||||
|
|
|
@ -35,7 +35,7 @@ git = "https://github.com/ankitects/linkcheck.git"
|
|||
rev = "184b2ca50ed39ca43da13f0b830a463861adb9ca"
|
||||
|
||||
[workspace.dependencies.fsrs]
|
||||
version = "=1.3.1"
|
||||
version = "=1.3.4"
|
||||
# git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
|
||||
# rev = "58ca25ed2bc4bb1dc376208bbcaed7f5a501b941"
|
||||
# path = "../open-spaced-repetition/fsrs-rs"
|
||||
|
|
|
@ -1225,7 +1225,7 @@
|
|||
},
|
||||
{
|
||||
"name": "fsrs",
|
||||
"version": "1.3.1",
|
||||
"version": "1.3.4",
|
||||
"authors": "Open Spaced Repetition",
|
||||
"repository": "https://github.com/open-spaced-repetition/fsrs-rs",
|
||||
"license": "BSD-3-Clause",
|
||||
|
@ -2753,6 +2753,15 @@
|
|||
"license_file": null,
|
||||
"description": "A minimal `syn` syntax tree pretty-printer"
|
||||
},
|
||||
{
|
||||
"name": "priority-queue",
|
||||
"version": "2.1.1",
|
||||
"authors": "Gianmarco Garrisi <gianmarcogarrisi@tutanota.com>",
|
||||
"repository": "https://github.com/garro95/priority-queue",
|
||||
"license": "LGPL-3.0-or-later OR MPL-2.0",
|
||||
"license_file": null,
|
||||
"description": "A Priority Queue implemented as a heap with a function to efficiently change the priority of an item."
|
||||
},
|
||||
{
|
||||
"name": "proc-macro-crate",
|
||||
"version": "3.2.0",
|
||||
|
|
|
@ -55,6 +55,7 @@ message ConfigKey {
|
|||
SHIFT_POSITION_OF_EXISTING_CARDS = 24;
|
||||
RENDER_LATEX = 25;
|
||||
LOAD_BALANCER_ENABLED = 26;
|
||||
FSRS_SHORT_TERM_WITH_STEPS_ENABLED = 27;
|
||||
}
|
||||
enum String {
|
||||
SET_DUE_BROWSER = 0;
|
||||
|
@ -117,6 +118,7 @@ message Preferences {
|
|||
bool show_intervals_on_buttons = 4;
|
||||
uint32 time_limit_secs = 5;
|
||||
bool load_balancer_enabled = 6;
|
||||
bool fsrs_short_term_with_steps_enabled = 7;
|
||||
}
|
||||
message Editing {
|
||||
bool adding_defaults_to_current_deck = 1;
|
||||
|
|
|
@ -992,6 +992,16 @@ class Collection(DeprecatedNamesMixin):
|
|||
fget=_get_enable_load_balancer, fset=_set_enable_load_balancer
|
||||
)
|
||||
|
||||
def _get_enable_fsrs_short_term_with_steps(self) -> bool:
|
||||
return self.get_config_bool(Config.Bool.FSRS_SHORT_TERM_WITH_STEPS_ENABLED)
|
||||
|
||||
def _set_enable_fsrs_short_term_with_steps(self, value: bool) -> None:
|
||||
self.set_config_bool(Config.Bool.FSRS_SHORT_TERM_WITH_STEPS_ENABLED, value)
|
||||
|
||||
fsrs_short_term_with_steps_enabled = property(
|
||||
fget=_get_enable_fsrs_short_term_with_steps,
|
||||
fset=_set_enable_fsrs_short_term_with_steps,
|
||||
)
|
||||
# Stats
|
||||
##########################################################################
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ impl From<BoolKeyProto> for BoolKey {
|
|||
BoolKeyProto::ShiftPositionOfExistingCards => BoolKey::ShiftPositionOfExistingCards,
|
||||
BoolKeyProto::RenderLatex => BoolKey::RenderLatex,
|
||||
BoolKeyProto::LoadBalancerEnabled => BoolKey::LoadBalancerEnabled,
|
||||
BoolKeyProto::FsrsShortTermWithStepsEnabled => BoolKey::FsrsShortTermWithStepsEnabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ pub enum BoolKey {
|
|||
WithDeckConfigs,
|
||||
Fsrs,
|
||||
LoadBalancerEnabled,
|
||||
FsrsShortTermWithStepsEnabled,
|
||||
#[strum(to_string = "normalize_note_text")]
|
||||
NormalizeNoteText,
|
||||
#[strum(to_string = "dayLearnFirst")]
|
||||
|
|
|
@ -99,6 +99,8 @@ impl Collection {
|
|||
.get_config_bool(BoolKey::ShowIntervalsAboveAnswerButtons),
|
||||
time_limit_secs: self.get_answer_time_limit_secs(),
|
||||
load_balancer_enabled: self.get_config_bool(BoolKey::LoadBalancerEnabled),
|
||||
fsrs_short_term_with_steps_enabled: self
|
||||
.get_config_bool(BoolKey::FsrsShortTermWithStepsEnabled),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -119,7 +121,10 @@ impl Collection {
|
|||
)?;
|
||||
self.set_answer_time_limit_secs(s.time_limit_secs)?;
|
||||
self.set_config_bool_inner(BoolKey::LoadBalancerEnabled, s.load_balancer_enabled)?;
|
||||
|
||||
self.set_config_bool_inner(
|
||||
BoolKey::FsrsShortTermWithStepsEnabled,
|
||||
s.fsrs_short_term_with_steps_enabled,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ struct CardStateUpdater {
|
|||
fsrs_next_states: Option<NextStates>,
|
||||
/// Set if FSRS is enabled.
|
||||
desired_retention: Option<f32>,
|
||||
fsrs_short_term_with_steps: bool,
|
||||
}
|
||||
|
||||
impl CardStateUpdater {
|
||||
|
@ -110,6 +111,7 @@ impl CardStateUpdater {
|
|||
Default::default()
|
||||
},
|
||||
fsrs_next_states: self.fsrs_next_states.clone(),
|
||||
fsrs_short_term_with_steps_enabled: self.fsrs_short_term_with_steps,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -458,6 +460,8 @@ impl Collection {
|
|||
None
|
||||
};
|
||||
let desired_retention = fsrs_enabled.then_some(config.inner.desired_retention);
|
||||
let fsrs_short_term_with_steps =
|
||||
self.get_config_bool(BoolKey::FsrsShortTermWithStepsEnabled);
|
||||
Ok(CardStateUpdater {
|
||||
fuzz_seed: get_fuzz_seed(&card, false),
|
||||
card,
|
||||
|
@ -467,6 +471,7 @@ impl Collection {
|
|||
now: TimestampSecs::now(),
|
||||
fsrs_next_states,
|
||||
desired_retention,
|
||||
fsrs_short_term_with_steps,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -97,13 +97,14 @@ impl Collection {
|
|||
}
|
||||
}
|
||||
});
|
||||
let fsrs = FSRS::new(Some(current_weights))?;
|
||||
let current_rmse = fsrs.evaluate(items.clone(), |_| true)?.rmse_bins;
|
||||
let mut weights = fsrs.compute_parameters(items.clone(), Some(progress2))?;
|
||||
let optimized_fsrs = FSRS::new(Some(&weights))?;
|
||||
let optimized_rmse = optimized_fsrs.evaluate(items.clone(), |_| true)?.rmse_bins;
|
||||
if current_rmse <= optimized_rmse {
|
||||
weights = current_weights.to_vec();
|
||||
let mut weights = FSRS::new(None)?.compute_parameters(items.clone(), Some(progress2))?;
|
||||
if let Ok(fsrs) = FSRS::new(Some(current_weights)) {
|
||||
let current_rmse = fsrs.evaluate(items.clone(), |_| true)?.rmse_bins;
|
||||
let optimized_fsrs = FSRS::new(Some(&weights))?;
|
||||
let optimized_rmse = optimized_fsrs.evaluate(items.clone(), |_| true)?.rmse_bins;
|
||||
if current_rmse <= optimized_rmse {
|
||||
weights = current_weights.to_vec();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ComputeFsrsWeightsResponse {
|
||||
|
|
|
@ -51,7 +51,8 @@ impl LearnState {
|
|||
let (interval, short_term) = if let Some(states) = &ctx.fsrs_next_states {
|
||||
(
|
||||
states.again.interval,
|
||||
ctx.steps.is_empty() && states.again.interval < 0.5,
|
||||
(ctx.fsrs_short_term_with_steps_enabled || ctx.steps.is_empty())
|
||||
&& states.again.interval < 0.5,
|
||||
)
|
||||
} else {
|
||||
(ctx.graduating_interval_good as f32, false)
|
||||
|
@ -96,7 +97,8 @@ impl LearnState {
|
|||
let (interval, short_term) = if let Some(states) = &ctx.fsrs_next_states {
|
||||
(
|
||||
states.hard.interval,
|
||||
ctx.steps.is_empty() && states.hard.interval < 0.5,
|
||||
(ctx.fsrs_short_term_with_steps_enabled || ctx.steps.is_empty())
|
||||
&& states.hard.interval < 0.5,
|
||||
)
|
||||
} else {
|
||||
(ctx.graduating_interval_good as f32, false)
|
||||
|
@ -141,7 +143,8 @@ impl LearnState {
|
|||
let (interval, short_term) = if let Some(states) = &ctx.fsrs_next_states {
|
||||
(
|
||||
states.good.interval,
|
||||
ctx.steps.is_empty() && states.good.interval < 0.5,
|
||||
(ctx.fsrs_short_term_with_steps_enabled || ctx.steps.is_empty())
|
||||
&& states.good.interval < 0.5,
|
||||
)
|
||||
} else {
|
||||
(ctx.graduating_interval_good as f32, false)
|
||||
|
|
|
@ -88,6 +88,7 @@ pub(crate) struct StateContext<'a> {
|
|||
/// range.
|
||||
pub fuzz_factor: Option<f32>,
|
||||
pub fsrs_next_states: Option<NextStates>,
|
||||
pub fsrs_short_term_with_steps_enabled: bool,
|
||||
|
||||
// learning
|
||||
pub steps: LearningSteps<'a>,
|
||||
|
@ -147,6 +148,7 @@ impl<'a> StateContext<'a> {
|
|||
good: 0,
|
||||
},
|
||||
fsrs_next_states: None,
|
||||
fsrs_short_term_with_steps_enabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,9 @@ impl RelearnState {
|
|||
},
|
||||
review: again_review,
|
||||
};
|
||||
if ctx.relearn_steps.is_empty() && interval < 0.5 {
|
||||
if (ctx.fsrs_short_term_with_steps_enabled || ctx.relearn_steps.is_empty())
|
||||
&& interval < 0.5
|
||||
{
|
||||
again_relearn.into()
|
||||
} else {
|
||||
again_review.into()
|
||||
|
@ -112,7 +114,9 @@ impl RelearnState {
|
|||
},
|
||||
review: hard_review,
|
||||
};
|
||||
if ctx.relearn_steps.is_empty() && interval < 0.5 {
|
||||
if (ctx.fsrs_short_term_with_steps_enabled || ctx.relearn_steps.is_empty())
|
||||
&& interval < 0.5
|
||||
{
|
||||
hard_relearn.into()
|
||||
} else {
|
||||
hard_review.into()
|
||||
|
@ -162,7 +166,9 @@ impl RelearnState {
|
|||
},
|
||||
review: good_review,
|
||||
};
|
||||
if ctx.relearn_steps.is_empty() && interval < 0.5 {
|
||||
if (ctx.fsrs_short_term_with_steps_enabled || ctx.relearn_steps.is_empty())
|
||||
&& interval < 0.5
|
||||
{
|
||||
good_relearn.into()
|
||||
} else {
|
||||
good_review.into()
|
||||
|
|
|
@ -124,7 +124,9 @@ impl ReviewState {
|
|||
review: again_review,
|
||||
}
|
||||
.into()
|
||||
} else if ctx.relearn_steps.is_empty() && scheduled_days < 0.5 {
|
||||
} else if (ctx.fsrs_short_term_with_steps_enabled || ctx.relearn_steps.is_empty())
|
||||
&& scheduled_days < 0.5
|
||||
{
|
||||
again_relearn.into()
|
||||
} else {
|
||||
again_review.into()
|
||||
|
|
Loading…
Reference in a new issue