From b22b3310d6cbac649b9558a8bc4636235bc5792d Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 1 Jul 2025 11:16:39 +0700 Subject: [PATCH] Revert "Feat/Cmrr target selector (#4116)" This reverts commit ad0dbb563a520baef1a72c0c6b43af845cce8c82. https://forums.ankiweb.net/t/anki-25-06-beta/62271/156 --- proto/anki/scheduler.proto | 25 ----- rslib/src/scheduler/fsrs/retention.rs | 107 +------------------ ts/routes/deck-options/FsrsOptions.svelte | 14 +-- ts/routes/deck-options/SimulatorModal.svelte | 94 +--------------- ts/routes/deck-options/choices.ts | 23 ---- 5 files changed, 8 insertions(+), 255 deletions(-) diff --git a/proto/anki/scheduler.proto b/proto/anki/scheduler.proto index 5e568aa92..1b7d44a83 100644 --- a/proto/anki/scheduler.proto +++ b/proto/anki/scheduler.proto @@ -402,31 +402,6 @@ message SimulateFsrsReviewRequest { repeated float easy_days_percentages = 10; deck_config.DeckConfig.Config.ReviewCardOrder review_order = 11; 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 { diff --git a/rslib/src/scheduler/fsrs/retention.rs b/rslib/src/scheduler/fsrs/retention.rs index 29f6b490d..4c21623bb 100644 --- a/rslib/src/scheduler/fsrs/retention.rs +++ b/rslib/src/scheduler/fsrs/retention.rs @@ -1,9 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // 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 fsrs::extract_simulator_config; -use fsrs::SimulationResult; use fsrs::SimulatorConfig; use fsrs::FSRS; @@ -16,115 +14,14 @@ pub struct ComputeRetentionProgress { 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::() / offset -} - impl Collection { pub fn compute_optimal_retention(&mut self, req: SimulateFsrsReviewRequest) -> Result { - // 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::(); - 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::(); - 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::(); - 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::(); let fsrs = FSRS::new(None)?; if req.days_to_simulate == 0 { invalid_input!("no days to simulate") } - let (mut 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; - } - + let (config, cards) = self.simulate_request_to_config(&req)?; Ok(fsrs .optimal_retention( &config, @@ -137,7 +34,7 @@ impl Collection { .is_ok() }, Some(cards), - target, + None, )? .clamp(0.7, 0.95)) } diff --git a/ts/routes/deck-options/FsrsOptions.svelte b/ts/routes/deck-options/FsrsOptions.svelte index fadaeba67..5ee3a5d17 100644 --- a/ts/routes/deck-options/FsrsOptions.svelte +++ b/ts/routes/deck-options/FsrsOptions.svelte @@ -7,11 +7,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html ComputeRetentionProgress, type ComputeParamsProgress, } from "@generated/anki/collection_pb"; - import { - SimulateFsrsReviewRequest, - SimulateFsrsReviewRequest_CMRRTarget, - SimulateFsrsReviewRequest_CMRRTarget_Memorized, - } from "@generated/anki/scheduler_pb"; + import { SimulateFsrsReviewRequest } from "@generated/anki/scheduler_pb"; import { computeFsrsParams, evaluateParams, @@ -99,14 +95,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html newCardsIgnoreReviewLimit: $newCardsIgnoreReviewLimit, easyDaysPercentages: $config.easyDaysPercentages, 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; diff --git a/ts/routes/deck-options/SimulatorModal.svelte b/ts/routes/deck-options/SimulatorModal.svelte index ef61054fd..09a05a653 100644 --- a/ts/routes/deck-options/SimulatorModal.svelte +++ b/ts/routes/deck-options/SimulatorModal.svelte @@ -18,30 +18,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { renderSimulationChart } from "../graphs/simulator"; import { computeOptimalRetention, simulateFsrsReview } from "@generated/backend"; import { runWithBackendProgress } from "@tslib/progress"; - import { - SimulateFsrsReviewRequest_CMRRTarget_AverageFutureMemorized, - SimulateFsrsReviewRequest_CMRRTarget_FutureMemorized, - SimulateFsrsReviewRequest_CMRRTarget_Memorized, - SimulateFsrsReviewRequest_CMRRTarget_Stability, - type ComputeOptimalRetentionResponse, - type SimulateFsrsReviewRequest, - type SimulateFsrsReviewResponse, + import type { + ComputeOptimalRetentionResponse, + SimulateFsrsReviewRequest, + SimulateFsrsReviewResponse, } from "@generated/anki/scheduler_pb"; import type { DeckOptionsState } from "./lib"; import SwitchRow from "$lib/components/SwitchRow.svelte"; import GlobalLabel from "./GlobalLabel.svelte"; import SpinBoxFloatRow from "./SpinBoxFloatRow.svelte"; - import { - DEFAULT_CMRR_TARGET, - CMRRTargetChoices, - reviewOrderChoices, - } from "./choices"; + import { reviewOrderChoices } from "./choices"; import EnumSelectorRow from "$lib/components/EnumSelectorRow.svelte"; import { DeckConfig_Config_LeechAction } from "@generated/anki/deck_config_pb"; import EasyDaysInput from "./EasyDaysInput.svelte"; import Warning from "./Warning.svelte"; import type { ComputeRetentionProgress } from "@generated/anki/collection_pb"; - import Item from "$lib/components/Item.svelte"; import Modal from "bootstrap/js/dist/modal"; 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 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; let simulateSubgraph: SimulateSubgraph = SimulateSubgraph.count; let tableData: TableDatum[] = []; @@ -470,42 +422,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html {#if computingRetention}
{computeRetentionProgressString}
{/if} - - - - - {"Target: "} - - - - - {#if simulateFsrsRequest.target?.kind.case === "memorized"} - - - {"Fail Cost Multiplier: "} - - - {/if} - - {#if simulateFsrsRequest.target?.kind.case === "futureMemorized" || simulateFsrsRequest.target?.kind.case === "averageFutureMemorized"} - - - {"Days after simulation end: "} - - - {/if}