mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Revert "Feat/Cmrr target selector (#4116)"
This reverts commit ad0dbb563a
.
https://forums.ankiweb.net/t/anki-25-06-beta/62271/156
This commit is contained in:
parent
7720c7de1a
commit
b22b3310d6
5 changed files with 8 additions and 255 deletions
|
@ -402,31 +402,6 @@ message SimulateFsrsReviewRequest {
|
||||||
repeated float easy_days_percentages = 10;
|
repeated float easy_days_percentages = 10;
|
||||||
deck_config.DeckConfig.Config.ReviewCardOrder review_order = 11;
|
deck_config.DeckConfig.Config.ReviewCardOrder review_order = 11;
|
||||||
optional uint32 suspend_after_lapse_count = 12;
|
optional uint32 suspend_after_lapse_count = 12;
|
||||||
// For CMRR
|
|
||||||
message CMRRTarget {
|
|
||||||
message Memorized {
|
|
||||||
float loss_aversion = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
message Stability {};
|
|
||||||
|
|
||||||
message FutureMemorized {
|
|
||||||
int32 days = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
message AverageFutureMemorized {
|
|
||||||
int32 days = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
oneof kind {
|
|
||||||
Memorized memorized = 1;
|
|
||||||
Stability stability = 2;
|
|
||||||
FutureMemorized future_memorized = 3;
|
|
||||||
AverageFutureMemorized average_future_memorized = 4;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
optional CMRRTarget target = 13;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message SimulateFsrsReviewResponse {
|
message SimulateFsrsReviewResponse {
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
use anki_proto::scheduler::simulate_fsrs_review_request::cmrr_target::Kind;
|
|
||||||
use anki_proto::scheduler::SimulateFsrsReviewRequest;
|
use anki_proto::scheduler::SimulateFsrsReviewRequest;
|
||||||
use fsrs::extract_simulator_config;
|
use fsrs::extract_simulator_config;
|
||||||
use fsrs::SimulationResult;
|
|
||||||
use fsrs::SimulatorConfig;
|
use fsrs::SimulatorConfig;
|
||||||
use fsrs::FSRS;
|
use fsrs::FSRS;
|
||||||
|
|
||||||
|
@ -16,115 +14,14 @@ 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
|
|
||||||
macro_rules! wrap {
|
|
||||||
($f:expr) => {
|
|
||||||
Some(fsrs::CMRRTargetFn(std::sync::Arc::new($f)))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let target_type = req.target.unwrap().kind;
|
|
||||||
|
|
||||||
let days_to_simulate = req.days_to_simulate as f32;
|
|
||||||
|
|
||||||
let target = match target_type {
|
|
||||||
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(_)) => {
|
|
||||||
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| {
|
|
||||||
p + (c.retention_on(w, days_to_simulate) * c.stability)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut anki_progress = self.new_progress_handler::<ComputeRetentionProgress>();
|
let mut anki_progress = self.new_progress_handler::<ComputeRetentionProgress>();
|
||||||
let fsrs = FSRS::new(None)?;
|
let fsrs = FSRS::new(None)?;
|
||||||
if req.days_to_simulate == 0 {
|
if req.days_to_simulate == 0 {
|
||||||
invalid_input!("no days to simulate")
|
invalid_input!("no days to simulate")
|
||||||
}
|
}
|
||||||
let (mut config, cards) = self.simulate_request_to_config(&req)?;
|
let (config, cards) = self.simulate_request_to_config(&req)?;
|
||||||
|
|
||||||
if let Some(Kind::Memorized(settings)) = target_type {
|
|
||||||
let loss_aversion = settings.loss_aversion;
|
|
||||||
|
|
||||||
config.relearning_step_transitions[0][0] *= loss_aversion;
|
|
||||||
config.relearning_step_transitions[1][0] *= loss_aversion;
|
|
||||||
config.relearning_step_transitions[2][0] *= loss_aversion;
|
|
||||||
|
|
||||||
config.learning_step_transitions[0][0] *= loss_aversion;
|
|
||||||
config.learning_step_transitions[1][0] *= loss_aversion;
|
|
||||||
config.learning_step_transitions[2][0] *= loss_aversion;
|
|
||||||
|
|
||||||
config.state_rating_costs[0][0] *= loss_aversion;
|
|
||||||
config.state_rating_costs[1][0] *= loss_aversion;
|
|
||||||
config.state_rating_costs[2][0] *= loss_aversion;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(fsrs
|
Ok(fsrs
|
||||||
.optimal_retention(
|
.optimal_retention(
|
||||||
&config,
|
&config,
|
||||||
|
@ -137,7 +34,7 @@ impl Collection {
|
||||||
.is_ok()
|
.is_ok()
|
||||||
},
|
},
|
||||||
Some(cards),
|
Some(cards),
|
||||||
target,
|
None,
|
||||||
)?
|
)?
|
||||||
.clamp(0.7, 0.95))
|
.clamp(0.7, 0.95))
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
ComputeRetentionProgress,
|
ComputeRetentionProgress,
|
||||||
type ComputeParamsProgress,
|
type ComputeParamsProgress,
|
||||||
} from "@generated/anki/collection_pb";
|
} from "@generated/anki/collection_pb";
|
||||||
import {
|
import { SimulateFsrsReviewRequest } from "@generated/anki/scheduler_pb";
|
||||||
SimulateFsrsReviewRequest,
|
|
||||||
SimulateFsrsReviewRequest_CMRRTarget,
|
|
||||||
SimulateFsrsReviewRequest_CMRRTarget_Memorized,
|
|
||||||
} from "@generated/anki/scheduler_pb";
|
|
||||||
import {
|
import {
|
||||||
computeFsrsParams,
|
computeFsrsParams,
|
||||||
evaluateParams,
|
evaluateParams,
|
||||||
|
@ -99,14 +95,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
newCardsIgnoreReviewLimit: $newCardsIgnoreReviewLimit,
|
newCardsIgnoreReviewLimit: $newCardsIgnoreReviewLimit,
|
||||||
easyDaysPercentages: $config.easyDaysPercentages,
|
easyDaysPercentages: $config.easyDaysPercentages,
|
||||||
reviewOrder: $config.reviewOrder,
|
reviewOrder: $config.reviewOrder,
|
||||||
target: new SimulateFsrsReviewRequest_CMRRTarget({
|
|
||||||
kind: {
|
|
||||||
case: "memorized",
|
|
||||||
value: new SimulateFsrsReviewRequest_CMRRTarget_Memorized({
|
|
||||||
lossAversion: 1.6,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const DESIRED_RETENTION_LOW_THRESHOLD = 0.8;
|
const DESIRED_RETENTION_LOW_THRESHOLD = 0.8;
|
||||||
|
|
|
@ -18,30 +18,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import { renderSimulationChart } from "../graphs/simulator";
|
import { renderSimulationChart } from "../graphs/simulator";
|
||||||
import { computeOptimalRetention, simulateFsrsReview } from "@generated/backend";
|
import { computeOptimalRetention, simulateFsrsReview } from "@generated/backend";
|
||||||
import { runWithBackendProgress } from "@tslib/progress";
|
import { runWithBackendProgress } from "@tslib/progress";
|
||||||
import {
|
import type {
|
||||||
SimulateFsrsReviewRequest_CMRRTarget_AverageFutureMemorized,
|
ComputeOptimalRetentionResponse,
|
||||||
SimulateFsrsReviewRequest_CMRRTarget_FutureMemorized,
|
SimulateFsrsReviewRequest,
|
||||||
SimulateFsrsReviewRequest_CMRRTarget_Memorized,
|
SimulateFsrsReviewResponse,
|
||||||
SimulateFsrsReviewRequest_CMRRTarget_Stability,
|
|
||||||
type ComputeOptimalRetentionResponse,
|
|
||||||
type SimulateFsrsReviewRequest,
|
|
||||||
type SimulateFsrsReviewResponse,
|
|
||||||
} from "@generated/anki/scheduler_pb";
|
} from "@generated/anki/scheduler_pb";
|
||||||
import type { DeckOptionsState } from "./lib";
|
import type { DeckOptionsState } from "./lib";
|
||||||
import SwitchRow from "$lib/components/SwitchRow.svelte";
|
import SwitchRow from "$lib/components/SwitchRow.svelte";
|
||||||
import GlobalLabel from "./GlobalLabel.svelte";
|
import GlobalLabel from "./GlobalLabel.svelte";
|
||||||
import SpinBoxFloatRow from "./SpinBoxFloatRow.svelte";
|
import SpinBoxFloatRow from "./SpinBoxFloatRow.svelte";
|
||||||
import {
|
import { reviewOrderChoices } from "./choices";
|
||||||
DEFAULT_CMRR_TARGET,
|
|
||||||
CMRRTargetChoices,
|
|
||||||
reviewOrderChoices,
|
|
||||||
} from "./choices";
|
|
||||||
import EnumSelectorRow from "$lib/components/EnumSelectorRow.svelte";
|
import EnumSelectorRow from "$lib/components/EnumSelectorRow.svelte";
|
||||||
import { DeckConfig_Config_LeechAction } from "@generated/anki/deck_config_pb";
|
import { DeckConfig_Config_LeechAction } from "@generated/anki/deck_config_pb";
|
||||||
import EasyDaysInput from "./EasyDaysInput.svelte";
|
import EasyDaysInput from "./EasyDaysInput.svelte";
|
||||||
import Warning from "./Warning.svelte";
|
import Warning from "./Warning.svelte";
|
||||||
import type { ComputeRetentionProgress } from "@generated/anki/collection_pb";
|
import type { ComputeRetentionProgress } from "@generated/anki/collection_pb";
|
||||||
import Item from "$lib/components/Item.svelte";
|
|
||||||
import Modal from "bootstrap/js/dist/modal";
|
import Modal from "bootstrap/js/dist/modal";
|
||||||
|
|
||||||
export let state: DeckOptionsState;
|
export let state: DeckOptionsState;
|
||||||
|
@ -50,45 +41,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
export let openHelpModal: (key: string) => void;
|
export let openHelpModal: (key: string) => void;
|
||||||
export let onPresetChange: () => void;
|
export let onPresetChange: () => void;
|
||||||
|
|
||||||
let cmrrTargetType = DEFAULT_CMRR_TARGET;
|
|
||||||
// All added types must be updated in the proceeding switch statement.
|
|
||||||
let lastCmrrTargetType = cmrrTargetType;
|
|
||||||
$: if (simulateFsrsRequest?.target && cmrrTargetType !== lastCmrrTargetType) {
|
|
||||||
switch (cmrrTargetType) {
|
|
||||||
case "memorized":
|
|
||||||
simulateFsrsRequest.target.kind = {
|
|
||||||
case: "memorized",
|
|
||||||
value: new SimulateFsrsReviewRequest_CMRRTarget_Memorized({
|
|
||||||
lossAversion: 1.6,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case "stability":
|
|
||||||
simulateFsrsRequest.target.kind = {
|
|
||||||
case: "stability",
|
|
||||||
value: new SimulateFsrsReviewRequest_CMRRTarget_Stability({}),
|
|
||||||
};
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = state.currentConfig;
|
const config = state.currentConfig;
|
||||||
let simulateSubgraph: SimulateSubgraph = SimulateSubgraph.count;
|
let simulateSubgraph: SimulateSubgraph = SimulateSubgraph.count;
|
||||||
let tableData: TableDatum[] = [];
|
let tableData: TableDatum[] = [];
|
||||||
|
@ -470,42 +422,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
{#if computingRetention}
|
{#if computingRetention}
|
||||||
<div>{computeRetentionProgressString}</div>
|
<div>{computeRetentionProgressString}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Item>
|
|
||||||
<EnumSelectorRow
|
|
||||||
choices={CMRRTargetChoices()}
|
|
||||||
bind:value={cmrrTargetType}
|
|
||||||
defaultValue={DEFAULT_CMRR_TARGET}
|
|
||||||
>
|
|
||||||
<SettingTitle>
|
|
||||||
{"Target: "}
|
|
||||||
</SettingTitle>
|
|
||||||
</EnumSelectorRow>
|
|
||||||
</Item>
|
|
||||||
|
|
||||||
{#if simulateFsrsRequest.target?.kind.case === "memorized"}
|
|
||||||
<SpinBoxFloatRow
|
|
||||||
bind:value={simulateFsrsRequest.target.kind.value
|
|
||||||
.lossAversion}
|
|
||||||
defaultValue={1.6}
|
|
||||||
>
|
|
||||||
<SettingTitle>
|
|
||||||
{"Fail Cost Multiplier: "}
|
|
||||||
</SettingTitle>
|
|
||||||
</SpinBoxFloatRow>
|
|
||||||
{/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
|
||||||
|
|
|
@ -199,29 +199,6 @@ export function questionActionChoices(): Choice<DeckConfig_Config_QuestionAction
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_CMRR_TARGET = "memorized";
|
|
||||||
|
|
||||||
export function CMRRTargetChoices(): Choice<string>[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: "Memorized (Default)",
|
|
||||||
value: "memorized",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Stability (Experimental)",
|
|
||||||
value: "stability",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Post Abandon Memorized (Experimental)",
|
|
||||||
value: "futureMemorized",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Average Post Abandon Memorized (Experimental)",
|
|
||||||
value: "averageFutureMemorized",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function difficultyOrders(fsrs: boolean): Choice<DeckConfig_Config_ReviewCardOrder>[] {
|
function difficultyOrders(fsrs: boolean): Choice<DeckConfig_Config_ReviewCardOrder>[] {
|
||||||
const order = [
|
const order = [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue