Start on a 'get params' button

This commit is contained in:
Damien Elmes 2023-09-17 12:58:13 +10:00
parent 736054a2e4
commit 59759b468f
7 changed files with 138 additions and 87 deletions

View file

@ -327,6 +327,7 @@ deck-config-analyze-button = Analyze
deck-config-desired-retention = Desired retention deck-config-desired-retention = Desired retention
deck-config-smaller-is-better = Smaller numbers indicate better memory estimates. deck-config-smaller-is-better = Smaller numbers indicate better memory estimates.
deck-config-steps-too-large-for-fsrs = When FSRS is enabled, interday (re)learning steps are not recommended. deck-config-steps-too-large-for-fsrs = When FSRS is enabled, interday (re)learning steps are not recommended.
deck-config-get-params = Get Params
## NO NEED TO TRANSLATE. This text is no longer used by Anki, and will be removed in the future. ## NO NEED TO TRANSLATE. This text is no longer used by Anki, and will be removed in the future.

View file

@ -47,6 +47,8 @@ service SchedulerService {
rpc RepositionDefaults(generic.Empty) returns (RepositionDefaultsResponse); rpc RepositionDefaults(generic.Empty) returns (RepositionDefaultsResponse);
rpc ComputeFsrsWeights(ComputeFsrsWeightsRequest) rpc ComputeFsrsWeights(ComputeFsrsWeightsRequest)
returns (ComputeFsrsWeightsResponse); returns (ComputeFsrsWeightsResponse);
rpc GetOptimalRetentionParameters(GetOptimalRetentionParametersRequest)
returns (GetOptimalRetentionParametersResponse);
rpc ComputeOptimalRetention(ComputeOptimalRetentionRequest) rpc ComputeOptimalRetention(ComputeOptimalRetentionRequest)
returns (ComputeOptimalRetentionResponse); returns (ComputeOptimalRetentionResponse);
rpc EvaluateWeights(EvaluateWeightsRequest) returns (EvaluateWeightsResponse); rpc EvaluateWeights(EvaluateWeightsRequest) returns (EvaluateWeightsResponse);
@ -338,6 +340,14 @@ message ComputeFsrsWeightsResponse {
message ComputeOptimalRetentionRequest { message ComputeOptimalRetentionRequest {
repeated float weights = 1; repeated float weights = 1;
OptimalRetentionParameters params = 2;
}
message ComputeOptimalRetentionResponse {
float optimal_retention = 1;
}
message OptimalRetentionParameters {
uint32 deck_size = 2; uint32 deck_size = 2;
uint32 days_to_simulate = 3; uint32 days_to_simulate = 3;
uint32 max_seconds_of_study_per_day = 4; uint32 max_seconds_of_study_per_day = 4;
@ -356,8 +366,12 @@ message ComputeOptimalRetentionRequest {
double review_rating_probability_easy = 17; double review_rating_probability_easy = 17;
} }
message ComputeOptimalRetentionResponse { message GetOptimalRetentionParametersRequest {
float optimal_retention = 1; string search = 1;
}
message GetOptimalRetentionParametersResponse {
OptimalRetentionParameters params = 1;
} }
message EvaluateWeightsRequest { message EvaluateWeightsRequest {

View file

@ -549,6 +549,7 @@ exposed_backend_list = [
"compute_optimal_retention", "compute_optimal_retention",
"set_wants_abort", "set_wants_abort",
"evaluate_weights", "evaluate_weights",
"get_optimal_retention_parameters",
] ]

View file

@ -2,10 +2,12 @@
// 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::ComputeOptimalRetentionRequest; use anki_proto::scheduler::ComputeOptimalRetentionRequest;
use anki_proto::scheduler::OptimalRetentionParameters;
use fsrs::SimulatorConfig; use fsrs::SimulatorConfig;
use fsrs::FSRS; use fsrs::FSRS;
use crate::prelude::*; use crate::prelude::*;
use crate::search::SortMode;
#[derive(Default, Clone, Copy, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct ComputeRetentionProgress { pub struct ComputeRetentionProgress {
@ -20,29 +22,26 @@ impl Collection {
) -> Result<f32> { ) -> Result<f32> {
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)?;
let p = req.params.as_ref().or_invalid("missing params")?;
Ok(fsrs.optimal_retention( Ok(fsrs.optimal_retention(
&SimulatorConfig { &SimulatorConfig {
deck_size: req.deck_size as usize, deck_size: p.deck_size as usize,
learn_span: req.days_to_simulate as usize, learn_span: p.days_to_simulate as usize,
max_cost_perday: req.max_seconds_of_study_per_day as f64, max_cost_perday: p.max_seconds_of_study_per_day as f64,
max_ivl: req.max_interval as f64, max_ivl: p.max_interval as f64,
recall_costs: [ recall_costs: [p.recall_secs_hard, p.recall_secs_good, p.recall_secs_easy],
req.recall_secs_hard, forget_cost: p.forget_secs as f64,
req.recall_secs_good, learn_cost: p.learn_secs as f64,
req.recall_secs_easy,
],
forget_cost: req.forget_secs as f64,
learn_cost: req.learn_secs as f64,
first_rating_prob: [ first_rating_prob: [
req.first_rating_probability_again, p.first_rating_probability_again,
req.first_rating_probability_hard, p.first_rating_probability_hard,
req.first_rating_probability_good, p.first_rating_probability_good,
req.first_rating_probability_easy, p.first_rating_probability_easy,
], ],
review_rating_prob: [ review_rating_prob: [
req.review_rating_probability_hard, p.review_rating_probability_hard,
req.review_rating_probability_good, p.review_rating_probability_good,
req.review_rating_probability_easy, p.review_rating_probability_easy,
], ],
}, },
&req.weights, &req.weights,
@ -56,4 +55,42 @@ impl Collection {
}, },
)? as f32) )? as f32)
} }
pub fn get_optimal_retention_parameters(
&mut self,
search: &str,
) -> Result<OptimalRetentionParameters> {
let guard = self.search_cards_into_table(search, SortMode::NoOrder)?;
let deck_size = guard.cards as u32;
// if you need access to cards too:
// let cards = self.storage.all_searched_cards()?;
let _revlogs = guard
.col
.storage
.get_revlog_entries_for_searched_cards_in_order()?;
// todo: compute values from revlogs
let params = OptimalRetentionParameters {
deck_size,
days_to_simulate: 365,
max_seconds_of_study_per_day: 1800,
// this should be filled in by the frontend based on their configured value
max_interval: 0,
recall_secs_hard: 14.0,
recall_secs_good: 10.0,
recall_secs_easy: 6.0,
forget_secs: 50,
learn_secs: 20,
first_rating_probability_again: 0.15,
first_rating_probability_hard: 0.2,
first_rating_probability_good: 0.6,
first_rating_probability_easy: 0.05,
review_rating_probability_hard: 0.3,
review_rating_probability_good: 0.6,
review_rating_probability_easy: 0.1,
};
Ok(params)
}
} }

View file

@ -8,6 +8,7 @@ use anki_proto::generic;
use anki_proto::scheduler; use anki_proto::scheduler;
use anki_proto::scheduler::ComputeOptimalRetentionRequest; use anki_proto::scheduler::ComputeOptimalRetentionRequest;
use anki_proto::scheduler::ComputeOptimalRetentionResponse; use anki_proto::scheduler::ComputeOptimalRetentionResponse;
use anki_proto::scheduler::GetOptimalRetentionParametersResponse;
use crate::prelude::*; use crate::prelude::*;
use crate::scheduler::new::NewCardDueOrder; use crate::scheduler::new::NewCardDueOrder;
@ -266,4 +267,14 @@ impl crate::services::SchedulerService for Collection {
rmse_bins: ret.rmse_bins, rmse_bins: ret.rmse_bins,
}) })
} }
fn get_optimal_retention_parameters(
&mut self,
input: scheduler::GetOptimalRetentionParametersRequest,
) -> Result<scheduler::GetOptimalRetentionParametersResponse> {
self.get_optimal_retention_parameters(&input.search)
.map(|params| GetOptimalRetentionParametersResponse {
params: Some(params),
})
}
} }

View file

@ -894,7 +894,10 @@ mod test {
vec![Search(Deck("default one".into()))] vec![Search(Deck("default one".into()))]
); );
assert_eq!(parse("preset:default")?, vec![Search(Preset("default".into()))]); assert_eq!(
parse("preset:default")?,
vec![Search(Preset("default".into()))]
);
assert_eq!(parse("note:basic")?, vec![Search(Notetype("basic".into()))]); assert_eq!(parse("note:basic")?, vec![Search(Notetype("basic".into()))]);
assert_eq!( assert_eq!(

View file

@ -7,11 +7,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
ComputeRetentionProgress, ComputeRetentionProgress,
type ComputeWeightsProgress, type ComputeWeightsProgress,
} from "@tslib/anki/collection_pb"; } from "@tslib/anki/collection_pb";
import { ComputeOptimalRetentionRequest } from "@tslib/anki/scheduler_pb"; import { OptimalRetentionParameters } from "@tslib/anki/scheduler_pb";
import { import {
computeFsrsWeights, computeFsrsWeights,
computeOptimalRetention, computeOptimalRetention,
evaluateWeights, evaluateWeights,
getOptimalRetentionParameters,
setWantsAbort, setWantsAbort,
} from "@tslib/backend"; } from "@tslib/backend";
import * as tr from "@tslib/ftl"; import * as tr from "@tslib/ftl";
@ -38,25 +39,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
| ComputeRetentionProgress | ComputeRetentionProgress
| undefined; | undefined;
const computeOptimalRequest = new ComputeOptimalRetentionRequest({ let optimalParams = new OptimalRetentionParameters({});
deckSize: 10000,
daysToSimulate: 365,
maxSecondsOfStudyPerDay: 1800,
maxInterval: 36500,
recallSecsHard: 14.0,
recallSecsGood: 10.0,
recallSecsEasy: 6.0,
forgetSecs: 50,
learnSecs: 20,
firstRatingProbabilityAgain: 0.15,
firstRatingProbabilityHard: 0.2,
firstRatingProbabilityGood: 0.6,
firstRatingProbabilityEasy: 0.05,
reviewRatingProbabilityHard: 0.3,
reviewRatingProbabilityGood: 0.6,
reviewRatingProbabilityEasy: 0.1,
});
async function computeWeights(): Promise<void> { async function computeWeights(): Promise<void> {
if (computing) { if (computing) {
await setWantsAbort({}); await setWantsAbort({});
@ -142,8 +125,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
try { try {
await runWithBackendProgress( await runWithBackendProgress(
async () => { async () => {
computeOptimalRequest.weights = $config.fsrsWeights; const resp = await computeOptimalRetention({
const resp = await computeOptimalRetention(computeOptimalRequest); params: optimalParams,
weights: $config.fsrsWeights,
});
$config.desiredRetention = resp.optimalRetention; $config.desiredRetention = resp.optimalRetention;
if (computeRetentionProgress) { if (computeRetentionProgress) {
computeRetentionProgress.current = computeRetentionProgress.current =
@ -161,6 +146,23 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
} }
async function getRetentionParams(): Promise<void> {
if (computing) {
return;
}
computing = true;
try {
// await
const resp = await getOptimalRetentionParameters({
search: `preset:"${state.getCurrentName()}"`,
});
optimalParams = resp.params!;
optimalParams.maxInterval = $config.maximumReviewInterval;
} finally {
computing = false;
}
}
$: computeWeightsProgressString = renderWeightProgress(computeWeightsProgress); $: computeWeightsProgressString = renderWeightProgress(computeWeightsProgress);
$: computeRetentionProgressString = renderRetentionProgress( $: computeRetentionProgressString = renderRetentionProgress(
computeRetentionProgress, computeRetentionProgress,
@ -246,108 +248,90 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
Deck size: Deck size:
<br /> <br />
<input type="number" bind:value={computeOptimalRequest.deckSize} /> <input type="number" bind:value={optimalParams.deckSize} />
<br /> <br />
Days to simulate Days to simulate
<br /> <br />
<input type="number" bind:value={computeOptimalRequest.daysToSimulate} /> <input type="number" bind:value={optimalParams.daysToSimulate} />
<br /> <br />
Max seconds of study per day: Max seconds of study per day:
<br /> <br />
<input <input type="number" bind:value={optimalParams.maxSecondsOfStudyPerDay} />
type="number"
bind:value={computeOptimalRequest.maxSecondsOfStudyPerDay}
/>
<br />
Maximum interval:
<br />
<input type="number" bind:value={computeOptimalRequest.maxInterval} />
<br /> <br />
Seconds to forget a card (again): Seconds to forget a card (again):
<br /> <br />
<input type="number" bind:value={computeOptimalRequest.forgetSecs} /> <input type="number" bind:value={optimalParams.forgetSecs} />
<br /> <br />
Seconds to recall a card (hard): Seconds to recall a card (hard):
<br /> <br />
<input type="number" bind:value={computeOptimalRequest.recallSecsHard} /> <input type="number" bind:value={optimalParams.recallSecsHard} />
<br /> <br />
Seconds to recall a card (good): Seconds to recall a card (good):
<br /> <br />
<input type="number" bind:value={computeOptimalRequest.recallSecsGood} /> <input type="number" bind:value={optimalParams.recallSecsGood} />
<br /> <br />
Seconds to recall a card (easy): Seconds to recall a card (easy):
<br /> <br />
<input type="number" bind:value={computeOptimalRequest.recallSecsEasy} /> <input type="number" bind:value={optimalParams.recallSecsEasy} />
<br /> <br />
Seconds to learn a card: Seconds to learn a card:
<br /> <br />
<input type="number" bind:value={computeOptimalRequest.learnSecs} /> <input type="number" bind:value={optimalParams.learnSecs} />
<br /> <br />
First rating probability (again): First rating probability (again):
<br /> <br />
<input <input type="number" bind:value={optimalParams.firstRatingProbabilityAgain} />
type="number"
bind:value={computeOptimalRequest.firstRatingProbabilityAgain}
/>
<br /> <br />
First rating probability (hard): First rating probability (hard):
<br /> <br />
<input <input type="number" bind:value={optimalParams.firstRatingProbabilityHard} />
type="number"
bind:value={computeOptimalRequest.firstRatingProbabilityHard}
/>
<br /> <br />
First rating probability (good): First rating probability (good):
<br /> <br />
<input <input type="number" bind:value={optimalParams.firstRatingProbabilityGood} />
type="number"
bind:value={computeOptimalRequest.firstRatingProbabilityGood}
/>
<br /> <br />
First rating probability (easy): First rating probability (easy):
<br /> <br />
<input <input type="number" bind:value={optimalParams.firstRatingProbabilityEasy} />
type="number"
bind:value={computeOptimalRequest.firstRatingProbabilityEasy}
/>
<br /> <br />
Review rating probability (hard): Review rating probability (hard):
<br /> <br />
<input <input type="number" bind:value={optimalParams.reviewRatingProbabilityHard} />
type="number"
bind:value={computeOptimalRequest.reviewRatingProbabilityHard}
/>
<br /> <br />
Review rating probability (good): Review rating probability (good):
<br /> <br />
<input <input type="number" bind:value={optimalParams.reviewRatingProbabilityGood} />
type="number"
bind:value={computeOptimalRequest.reviewRatingProbabilityGood}
/>
<br /> <br />
Review rating probability (easy): Review rating probability (easy):
<br /> <br />
<input <input type="number" bind:value={optimalParams.reviewRatingProbabilityEasy} />
type="number"
bind:value={computeOptimalRequest.reviewRatingProbabilityEasy}
/>
<br /> <br />
<button
class="btn {computing ? 'btn-warning' : 'btn-primary'}"
on:click={() => getRetentionParams()}
>
{#if computing}
{tr.actionsCancel()}
{:else}
{tr.deckConfigGetParams()}
{/if}
</button>
<button <button
class="btn {computing ? 'btn-warning' : 'btn-primary'}" class="btn {computing ? 'btn-warning' : 'btn-primary'}"
on:click={() => computeRetention()} on:click={() => computeRetention()}