refactor optimal retention to minimize workload per memorization & fix progress goes backwards (#3065)

* Feat/optimal retention for minimal workload per memorization

* ./ninja fix:minilints

* update to FSRS-rs 0.5.2

* update to FSRS-rs 0.5.3

* ./ninja fix:minilints

* 'estimated retention' -> 'predicted optimal retention'; add warning (dae)
This commit is contained in:
Jarrett Ye 2024-03-11 17:16:37 +08:00 committed by GitHub
parent 04fe3655e2
commit 8c9d7d64d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 22 additions and 35 deletions

4
Cargo.lock generated
View file

@ -1792,9 +1792,9 @@ dependencies = [
[[package]] [[package]]
name = "fsrs" name = "fsrs"
version = "0.5.0" version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c7e6a1986cc2b7a64445d84e2c453ecd8d95dcf90b797205c54573697e10b17" checksum = "50f8d2ba5394c6e36fa01d88df181206bcae8b7a4ef3c5653eb4d635e3f595e4"
dependencies = [ dependencies = [
"burn", "burn",
"itertools 0.12.1", "itertools 0.12.1",

View file

@ -35,7 +35,7 @@ git = "https://github.com/ankitects/linkcheck.git"
rev = "184b2ca50ed39ca43da13f0b830a463861adb9ca" rev = "184b2ca50ed39ca43da13f0b830a463861adb9ca"
[workspace.dependencies.fsrs] [workspace.dependencies.fsrs]
version = "0.5.0" version = "0.5.3"
# git = "https://github.com/open-spaced-repetition/fsrs-rs.git" # git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
# rev = "58ca25ed2bc4bb1dc376208bbcaed7f5a501b941" # rev = "58ca25ed2bc4bb1dc376208bbcaed7f5a501b941"
# path = "../open-spaced-repetition/fsrs-rs" # path = "../open-spaced-repetition/fsrs-rs"

View file

@ -1198,7 +1198,7 @@
}, },
{ {
"name": "fsrs", "name": "fsrs",
"version": "0.5.0", "version": "0.5.3",
"authors": "Open Spaced Repetition", "authors": "Open Spaced Repetition",
"repository": "https://github.com/open-spaced-repetition/fsrs-rs", "repository": "https://github.com/open-spaced-repetition/fsrs-rs",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",

View file

@ -357,7 +357,7 @@ deck-config-get-params = Get Params
deck-config-fsrs-on-all-clients = deck-config-fsrs-on-all-clients =
Please ensure all of your Anki clients are Anki(Mobile) 23.10+ or AnkiDroid 2.17+. FSRS will Please ensure all of your Anki clients are Anki(Mobile) 23.10+ or AnkiDroid 2.17+. FSRS will
not work correctly if one of your clients is older. not work correctly if one of your clients is older.
deck-config-estimated-retention = Estimated retention: { $num } deck-config-predicted-optimal-retention = Predicted optimal retention: { $num }
deck-config-complete = { $num }% complete. deck-config-complete = { $num }% complete.
deck-config-iterations = Iteration: { $count }... deck-config-iterations = Iteration: { $count }...
deck-config-reschedule-cards-on-change = Reschedule cards on change deck-config-reschedule-cards-on-change = Reschedule cards on change

View file

@ -373,12 +373,10 @@ message FsrsReview {
message ComputeOptimalRetentionRequest { message ComputeOptimalRetentionRequest {
repeated float weights = 1; repeated float weights = 1;
uint32 deck_size = 2; uint32 days_to_simulate = 2;
uint32 days_to_simulate = 3; uint32 max_interval = 3;
uint32 max_minutes_of_study_per_day = 4; string search = 4;
uint32 max_interval = 5; double loss_aversion = 5;
string search = 6;
double loss_aversion = 7;
} }
message ComputeOptimalRetentionResponse { message ComputeOptimalRetentionResponse {

View file

@ -28,12 +28,15 @@ impl Collection {
invalid_input!("no days to simulate") invalid_input!("no days to simulate")
} }
let p = self.get_optimal_retention_parameters(&req.search)?; let p = self.get_optimal_retention_parameters(&req.search)?;
let learn_span = req.days_to_simulate as usize;
let learn_limit = 10;
let deck_size = learn_span * learn_limit;
Ok(fsrs Ok(fsrs
.optimal_retention( .optimal_retention(
&SimulatorConfig { &SimulatorConfig {
deck_size: req.deck_size as usize, deck_size,
learn_span: req.days_to_simulate as usize, learn_span: req.days_to_simulate as usize,
max_cost_perday: req.max_minutes_of_study_per_day as f64 * 60.0, max_cost_perday: f64::MAX,
max_ivl: req.max_interval as f64, max_ivl: req.max_interval as f64,
recall_costs: [p.recall_secs_hard, p.recall_secs_good, p.recall_secs_easy], recall_costs: [p.recall_secs_hard, p.recall_secs_good, p.recall_secs_easy],
forget_cost: p.forget_secs, forget_cost: p.forget_secs,
@ -50,7 +53,7 @@ impl Collection {
p.review_rating_probability_easy, p.review_rating_probability_easy,
], ],
loss_aversion: req.loss_aversion, loss_aversion: req.loss_aversion,
learn_limit: usize::MAX, learn_limit,
review_limit: usize::MAX, review_limit: usize::MAX,
}, },
&req.weights, &req.weights,

View file

@ -55,9 +55,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
| undefined; | undefined;
const optimalRetentionRequest = new ComputeOptimalRetentionRequest({ const optimalRetentionRequest = new ComputeOptimalRetentionRequest({
deckSize: 10000,
daysToSimulate: 365, daysToSimulate: 365,
maxMinutesOfStudyPerDay: 30,
lossAversion: 2.5, lossAversion: 2.5,
}); });
$: if (optimalRetentionRequest.daysToSimulate > 3650) { $: if (optimalRetentionRequest.daysToSimulate > 3650) {
@ -249,7 +247,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
if (!retention) { if (!retention) {
return ""; return "";
} }
return tr.deckConfigEstimatedRetention({ num: retention.toFixed(2) }); return tr.deckConfigPredictedOptimalRetention({ num: retention.toFixed(2) });
} }
</script> </script>
@ -348,15 +346,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<details> <details>
<summary>{tr.deckConfigComputeOptimalRetention()} (experimental)</summary> <summary>{tr.deckConfigComputeOptimalRetention()} (experimental)</summary>
<SpinBoxRow
bind:value={optimalRetentionRequest.deckSize}
defaultValue={10000}
min={100}
max={99999}
>
<SettingTitle>Deck size</SettingTitle>
</SpinBoxRow>
<SpinBoxRow <SpinBoxRow
bind:value={optimalRetentionRequest.daysToSimulate} bind:value={optimalRetentionRequest.daysToSimulate}
defaultValue={365} defaultValue={365}
@ -366,15 +355,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<SettingTitle>Days to simulate</SettingTitle> <SettingTitle>Days to simulate</SettingTitle>
</SpinBoxRow> </SpinBoxRow>
<SpinBoxRow
bind:value={optimalRetentionRequest.maxMinutesOfStudyPerDay}
defaultValue={30}
min={1}
max={1800}
>
<SettingTitle>Minutes study/day</SettingTitle>
</SpinBoxRow>
<button <button
class="btn {computingRetention ? 'btn-warning' : 'btn-primary'}" class="btn {computingRetention ? 'btn-warning' : 'btn-primary'}"
disabled={!computingRetention && computing} disabled={!computingRetention && computing}
@ -389,6 +369,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{#if optimalRetention} {#if optimalRetention}
{estimatedRetention(optimalRetention)} {estimatedRetention(optimalRetention)}
{#if optimalRetention > $config.desiredRetention}
<Warning
warning="Your desired retention is below optimal. Increasing it is recommended."
className="alert-warning"
/>
{/if}
{/if} {/if}
<div>{computeRetentionProgressString}</div> <div>{computeRetentionProgressString}</div>
</details> </details>