Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/option to enable FSRS short-term scheduler when (re)learning steps run out && speed up features based on simulation #3505

16 changes: 14 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
11 changes: 10 additions & 1 deletion cargo/licenses.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions proto/anki/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions pylib/anki/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,16 @@ def _set_enable_load_balancer(self, value: bool) -> None:
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
##########################################################################

Expand Down
1 change: 1 addition & 0 deletions rslib/src/backend/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions rslib/src/config/bool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub enum BoolKey {
WithDeckConfigs,
Fsrs,
LoadBalancerEnabled,
FsrsShortTermWithStepsEnabled,
#[strum(to_string = "normalize_note_text")]
NormalizeNoteText,
#[strum(to_string = "dayLearnFirst")]
Expand Down
7 changes: 6 additions & 1 deletion rslib/src/preferences.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
})
}

Expand All @@ -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(())
}

Expand Down
5 changes: 5 additions & 0 deletions rslib/src/scheduler/answering/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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,
Expand All @@ -467,6 +471,7 @@ impl Collection {
now: TimestampSecs::now(),
fsrs_next_states,
desired_retention,
fsrs_short_term_with_steps,
})
}

Expand Down
15 changes: 8 additions & 7 deletions rslib/src/scheduler/fsrs/weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,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 {
Expand Down
9 changes: 6 additions & 3 deletions rslib/src/scheduler/states/learning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions rslib/src/scheduler/states/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -147,6 +148,7 @@ impl<'a> StateContext<'a> {
good: 0,
},
fsrs_next_states: None,
fsrs_short_term_with_steps_enabled: false,
}
}
}
Expand Down
12 changes: 9 additions & 3 deletions rslib/src/scheduler/states/relearning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
4 changes: 3 additions & 1 deletion rslib/src/scheduler/states/review.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down