mirror of
https://github.com/ankitects/anki.git
synced 2026-01-13 22:13:58 -05:00
Added: Future retention targets
This commit is contained in:
parent
d5e5177166
commit
e3695e13ff
4 changed files with 109 additions and 6 deletions
|
|
@ -410,9 +410,19 @@ message SimulateFsrsReviewRequest {
|
||||||
|
|
||||||
message Stability {};
|
message Stability {};
|
||||||
|
|
||||||
|
message FutureMemorized {
|
||||||
|
int32 days = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
message AverageFutureMemorized {
|
||||||
|
int32 days = 1;
|
||||||
|
};
|
||||||
|
|
||||||
oneof kind {
|
oneof kind {
|
||||||
Memorized memorized = 1;
|
Memorized memorized = 1;
|
||||||
Stability stability = 2;
|
Stability stability = 2;
|
||||||
|
FutureMemorized future_memorized = 3;
|
||||||
|
AverageFutureMemorized average_future_memorized = 4;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,29 @@ pub struct ComputeRetentionProgress {
|
||||||
pub total: u32,
|
pub total: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn average_r_power_forgetting_curve(
|
||||||
|
learn_span: usize,
|
||||||
|
cards: &[fsrs::Card],
|
||||||
|
offset: f32,
|
||||||
|
decay: f32,
|
||||||
|
) -> f32 {
|
||||||
|
let factor = 0.9_f32.powf(1.0 / decay) - 1.0;
|
||||||
|
let exp = decay + 1.0;
|
||||||
|
let den_factor = factor * exp;
|
||||||
|
|
||||||
|
// Closure equivalent to the inner integral function
|
||||||
|
let integral_calc = |card: &fsrs::Card| -> f32 {
|
||||||
|
// Performs element-wise: (s / den_factor) * (1.0 + factor * t / s).powf(exp)
|
||||||
|
let t1 = learn_span as f32 - card.last_date;
|
||||||
|
let t2 = t1 + offset;
|
||||||
|
(card.stability / den_factor) * (1.0 + factor * t2 / card.stability).powf(exp)
|
||||||
|
- (card.stability / den_factor) * (1.0 + factor * t1 / card.stability).powf(exp)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate integral difference and divide by time difference element-wise
|
||||||
|
cards.iter().map(integral_calc).sum::<f32>() / offset
|
||||||
|
}
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
pub fn compute_optimal_retention(&mut self, req: SimulateFsrsReviewRequest) -> Result<f32> {
|
pub fn compute_optimal_retention(&mut self, req: SimulateFsrsReviewRequest) -> Result<f32> {
|
||||||
// Helper macro to wrap the closure for "CMRRTargetFn"s
|
// Helper macro to wrap the closure for "CMRRTargetFn"s
|
||||||
|
|
@ -31,17 +54,48 @@ impl Collection {
|
||||||
|
|
||||||
let target = match target_type {
|
let target = match target_type {
|
||||||
Some(Kind::Memorized(_)) => None,
|
Some(Kind::Memorized(_)) => None,
|
||||||
|
Some(Kind::FutureMemorized(settings)) => {
|
||||||
|
wrap!(move |SimulationResult {
|
||||||
|
cards,
|
||||||
|
cost_per_day,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
w| {
|
||||||
|
let total_cost = cost_per_day.iter().sum::<f32>();
|
||||||
|
total_cost
|
||||||
|
/ cards.iter().fold(0., |p, c| {
|
||||||
|
c.retention_on(w, days_to_simulate + settings.days as f32) + p
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Some(Kind::AverageFutureMemorized(settings)) => {
|
||||||
|
wrap!(move |SimulationResult {
|
||||||
|
cards,
|
||||||
|
cost_per_day,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
w| {
|
||||||
|
let total_cost = cost_per_day.iter().sum::<f32>();
|
||||||
|
total_cost
|
||||||
|
/ average_r_power_forgetting_curve(
|
||||||
|
days_to_simulate as usize,
|
||||||
|
cards,
|
||||||
|
settings.days as f32,
|
||||||
|
-w[20],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
Some(Kind::Stability(_)) => {
|
Some(Kind::Stability(_)) => {
|
||||||
wrap!(move |SimulationResult {
|
wrap!(move |SimulationResult {
|
||||||
cards,
|
cards,
|
||||||
cost_per_day,
|
cost_per_day,
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
params| {
|
w| {
|
||||||
let total_cost = cost_per_day.iter().sum::<f32>();
|
let total_cost = cost_per_day.iter().sum::<f32>();
|
||||||
total_cost
|
total_cost
|
||||||
/ cards.iter().fold(0., |p, c| {
|
/ cards.iter().fold(0., |p, c| {
|
||||||
p + (c.retention_on(params, days_to_simulate) * c.stability)
|
p + (c.retention_on(w, days_to_simulate) * c.stability)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -55,12 +109,9 @@ impl Collection {
|
||||||
}
|
}
|
||||||
let (mut config, cards) = self.simulate_request_to_config(&req)?;
|
let (mut config, cards) = self.simulate_request_to_config(&req)?;
|
||||||
|
|
||||||
dbg!(&target_type);
|
|
||||||
if let Some(Kind::Memorized(settings)) = target_type {
|
if let Some(Kind::Memorized(settings)) = target_type {
|
||||||
let loss_aversion = settings.loss_aversion;
|
let loss_aversion = settings.loss_aversion;
|
||||||
|
|
||||||
dbg!(&loss_aversion);
|
|
||||||
|
|
||||||
config.relearning_step_transitions[0][0] *= loss_aversion;
|
config.relearning_step_transitions[0][0] *= loss_aversion;
|
||||||
config.relearning_step_transitions[1][0] *= loss_aversion;
|
config.relearning_step_transitions[1][0] *= loss_aversion;
|
||||||
config.relearning_step_transitions[2][0] *= loss_aversion;
|
config.relearning_step_transitions[2][0] *= loss_aversion;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import { computeOptimalRetention, simulateFsrsReview } from "@generated/backend";
|
import { computeOptimalRetention, simulateFsrsReview } from "@generated/backend";
|
||||||
import { runWithBackendProgress } from "@tslib/progress";
|
import { runWithBackendProgress } from "@tslib/progress";
|
||||||
import {
|
import {
|
||||||
|
SimulateFsrsReviewRequest_CMRRTarget_AverageFutureMemorized,
|
||||||
|
SimulateFsrsReviewRequest_CMRRTarget_FutureMemorized,
|
||||||
SimulateFsrsReviewRequest_CMRRTarget_Memorized,
|
SimulateFsrsReviewRequest_CMRRTarget_Memorized,
|
||||||
SimulateFsrsReviewRequest_CMRRTarget_Stability,
|
SimulateFsrsReviewRequest_CMRRTarget_Stability,
|
||||||
type ComputeOptimalRetentionResponse,
|
type ComputeOptimalRetentionResponse,
|
||||||
|
|
@ -49,6 +51,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
export let onPresetChange: () => void;
|
export let onPresetChange: () => void;
|
||||||
|
|
||||||
let cmrrTargetType = DEFAULT_CMRR_TARGET;
|
let cmrrTargetType = DEFAULT_CMRR_TARGET;
|
||||||
|
// All added types must be updated in the proceeding switch statement.
|
||||||
let lastCmrrTargetType = cmrrTargetType;
|
let lastCmrrTargetType = cmrrTargetType;
|
||||||
$: if (simulateFsrsRequest?.target && cmrrTargetType !== lastCmrrTargetType) {
|
$: if (simulateFsrsRequest?.target && cmrrTargetType !== lastCmrrTargetType) {
|
||||||
switch (cmrrTargetType) {
|
switch (cmrrTargetType) {
|
||||||
|
|
@ -66,6 +69,22 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
value: new SimulateFsrsReviewRequest_CMRRTarget_Stability({}),
|
value: new SimulateFsrsReviewRequest_CMRRTarget_Stability({}),
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
case "futureMemorized":
|
||||||
|
simulateFsrsRequest.target.kind = {
|
||||||
|
case: "futureMemorized",
|
||||||
|
value: new SimulateFsrsReviewRequest_CMRRTarget_FutureMemorized({
|
||||||
|
days: 365,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "averageFutureMemorized":
|
||||||
|
simulateFsrsRequest.target.kind = {
|
||||||
|
case: "averageFutureMemorized",
|
||||||
|
value: new SimulateFsrsReviewRequest_CMRRTarget_AverageFutureMemorized(
|
||||||
|
{ days: 365 },
|
||||||
|
),
|
||||||
|
};
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
lastCmrrTargetType = cmrrTargetType;
|
lastCmrrTargetType = cmrrTargetType;
|
||||||
}
|
}
|
||||||
|
|
@ -454,12 +473,27 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
{#if simulateFsrsRequest.target?.kind.case === "memorized"}
|
{#if simulateFsrsRequest.target?.kind.case === "memorized"}
|
||||||
<SpinBoxFloatRow
|
<SpinBoxFloatRow
|
||||||
bind:value={simulateFsrsRequest.target.kind.value.lossAversion} defaultValue={1}>
|
bind:value={simulateFsrsRequest.target.kind.value
|
||||||
|
.lossAversion}
|
||||||
|
defaultValue={1}
|
||||||
|
>
|
||||||
<SettingTitle>
|
<SettingTitle>
|
||||||
{"Fail Cost Multiplier: "}
|
{"Fail Cost Multiplier: "}
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
</SpinBoxFloatRow>
|
</SpinBoxFloatRow>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if simulateFsrsRequest.target?.kind.case === "futureMemorized" || simulateFsrsRequest.target?.kind.case === "averageFutureMemorized"}
|
||||||
|
<SpinBoxFloatRow
|
||||||
|
bind:value={simulateFsrsRequest.target.kind.value.days}
|
||||||
|
defaultValue={365}
|
||||||
|
step={1}
|
||||||
|
>
|
||||||
|
<SettingTitle>
|
||||||
|
{"Days after simulation end: "}
|
||||||
|
</SettingTitle>
|
||||||
|
</SpinBoxFloatRow>
|
||||||
|
{/if}
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,14 @@ export function CMRRTargetChoices(): Choice<string>[] {
|
||||||
label: "Stability (Experimental)",
|
label: "Stability (Experimental)",
|
||||||
value: "stability",
|
value: "stability",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Future Retention (Experimental)",
|
||||||
|
value: "futureMemorized",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Average Future Retention (Experimental)",
|
||||||
|
value: "averageFutureMemorized",
|
||||||
|
},
|
||||||
] as const;
|
] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue