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-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-get-params = Get Params
## 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 ComputeFsrsWeights(ComputeFsrsWeightsRequest)
returns (ComputeFsrsWeightsResponse);
rpc GetOptimalRetentionParameters(GetOptimalRetentionParametersRequest)
returns (GetOptimalRetentionParametersResponse);
rpc ComputeOptimalRetention(ComputeOptimalRetentionRequest)
returns (ComputeOptimalRetentionResponse);
rpc EvaluateWeights(EvaluateWeightsRequest) returns (EvaluateWeightsResponse);
@ -338,6 +340,14 @@ message ComputeFsrsWeightsResponse {
message ComputeOptimalRetentionRequest {
repeated float weights = 1;
OptimalRetentionParameters params = 2;
}
message ComputeOptimalRetentionResponse {
float optimal_retention = 1;
}
message OptimalRetentionParameters {
uint32 deck_size = 2;
uint32 days_to_simulate = 3;
uint32 max_seconds_of_study_per_day = 4;
@ -356,8 +366,12 @@ message ComputeOptimalRetentionRequest {
double review_rating_probability_easy = 17;
}
message ComputeOptimalRetentionResponse {
float optimal_retention = 1;
message GetOptimalRetentionParametersRequest {
string search = 1;
}
message GetOptimalRetentionParametersResponse {
OptimalRetentionParameters params = 1;
}
message EvaluateWeightsRequest {

View File

@ -549,6 +549,7 @@ exposed_backend_list = [
"compute_optimal_retention",
"set_wants_abort",
"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
use anki_proto::scheduler::ComputeOptimalRetentionRequest;
use anki_proto::scheduler::OptimalRetentionParameters;
use fsrs::SimulatorConfig;
use fsrs::FSRS;
use crate::prelude::*;
use crate::search::SortMode;
#[derive(Default, Clone, Copy, Debug)]
pub struct ComputeRetentionProgress {
@ -20,29 +22,26 @@ impl Collection {
) -> Result<f32> {
let mut anki_progress = self.new_progress_handler::<ComputeRetentionProgress>();
let fsrs = FSRS::new(None)?;
let p = req.params.as_ref().or_invalid("missing params")?;
Ok(fsrs.optimal_retention(
&SimulatorConfig {
deck_size: req.deck_size as usize,
learn_span: req.days_to_simulate as usize,
max_cost_perday: req.max_seconds_of_study_per_day as f64,
max_ivl: req.max_interval as f64,
recall_costs: [
req.recall_secs_hard,
req.recall_secs_good,
req.recall_secs_easy,
],
forget_cost: req.forget_secs as f64,
learn_cost: req.learn_secs as f64,
deck_size: p.deck_size as usize,
learn_span: p.days_to_simulate as usize,
max_cost_perday: p.max_seconds_of_study_per_day as f64,
max_ivl: p.max_interval as f64,
recall_costs: [p.recall_secs_hard, p.recall_secs_good, p.recall_secs_easy],
forget_cost: p.forget_secs as f64,
learn_cost: p.learn_secs as f64,
first_rating_prob: [
req.first_rating_probability_again,
req.first_rating_probability_hard,
req.first_rating_probability_good,
req.first_rating_probability_easy,
p.first_rating_probability_again,
p.first_rating_probability_hard,
p.first_rating_probability_good,
p.first_rating_probability_easy,
],
review_rating_prob: [
req.review_rating_probability_hard,
req.review_rating_probability_good,
req.review_rating_probability_easy,
p.review_rating_probability_hard,
p.review_rating_probability_good,
p.review_rating_probability_easy,
],
},
&req.weights,
@ -56,4 +55,42 @@ impl Collection {
},
)? 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::ComputeOptimalRetentionRequest;
use anki_proto::scheduler::ComputeOptimalRetentionResponse;
use anki_proto::scheduler::GetOptimalRetentionParametersResponse;
use crate::prelude::*;
use crate::scheduler::new::NewCardDueOrder;
@ -266,4 +267,14 @@ impl crate::services::SchedulerService for Collection {
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()))]
);
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!(

View File

@ -7,11 +7,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
ComputeRetentionProgress,
type ComputeWeightsProgress,
} from "@tslib/anki/collection_pb";
import { ComputeOptimalRetentionRequest } from "@tslib/anki/scheduler_pb";
import { OptimalRetentionParameters } from "@tslib/anki/scheduler_pb";
import {
computeFsrsWeights,
computeOptimalRetention,
evaluateWeights,
getOptimalRetentionParameters,
setWantsAbort,
} from "@tslib/backend";
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
| undefined;
const computeOptimalRequest = new ComputeOptimalRetentionRequest({
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,
});
let optimalParams = new OptimalRetentionParameters({});
async function computeWeights(): Promise<void> {
if (computing) {
await setWantsAbort({});
@ -142,8 +125,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
try {
await runWithBackendProgress(
async () => {
computeOptimalRequest.weights = $config.fsrsWeights;
const resp = await computeOptimalRetention(computeOptimalRequest);
const resp = await computeOptimalRetention({
params: optimalParams,
weights: $config.fsrsWeights,
});
$config.desiredRetention = resp.optimalRetention;
if (computeRetentionProgress) {
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);
$: computeRetentionProgressString = renderRetentionProgress(
computeRetentionProgress,
@ -246,108 +248,90 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
Deck size:
<br />
<input type="number" bind:value={computeOptimalRequest.deckSize} />
<input type="number" bind:value={optimalParams.deckSize} />
<br />
Days to simulate
<br />
<input type="number" bind:value={computeOptimalRequest.daysToSimulate} />
<input type="number" bind:value={optimalParams.daysToSimulate} />
<br />
Max seconds of study per day:
<br />
<input
type="number"
bind:value={computeOptimalRequest.maxSecondsOfStudyPerDay}
/>
<br />
Maximum interval:
<br />
<input type="number" bind:value={computeOptimalRequest.maxInterval} />
<input type="number" bind:value={optimalParams.maxSecondsOfStudyPerDay} />
<br />
Seconds to forget a card (again):
<br />
<input type="number" bind:value={computeOptimalRequest.forgetSecs} />
<input type="number" bind:value={optimalParams.forgetSecs} />
<br />
Seconds to recall a card (hard):
<br />
<input type="number" bind:value={computeOptimalRequest.recallSecsHard} />
<input type="number" bind:value={optimalParams.recallSecsHard} />
<br />
Seconds to recall a card (good):
<br />
<input type="number" bind:value={computeOptimalRequest.recallSecsGood} />
<input type="number" bind:value={optimalParams.recallSecsGood} />
<br />
Seconds to recall a card (easy):
<br />
<input type="number" bind:value={computeOptimalRequest.recallSecsEasy} />
<input type="number" bind:value={optimalParams.recallSecsEasy} />
<br />
Seconds to learn a card:
<br />
<input type="number" bind:value={computeOptimalRequest.learnSecs} />
<input type="number" bind:value={optimalParams.learnSecs} />
<br />
First rating probability (again):
<br />
<input
type="number"
bind:value={computeOptimalRequest.firstRatingProbabilityAgain}
/>
<input type="number" bind:value={optimalParams.firstRatingProbabilityAgain} />
<br />
First rating probability (hard):
<br />
<input
type="number"
bind:value={computeOptimalRequest.firstRatingProbabilityHard}
/>
<input type="number" bind:value={optimalParams.firstRatingProbabilityHard} />
<br />
First rating probability (good):
<br />
<input
type="number"
bind:value={computeOptimalRequest.firstRatingProbabilityGood}
/>
<input type="number" bind:value={optimalParams.firstRatingProbabilityGood} />
<br />
First rating probability (easy):
<br />
<input
type="number"
bind:value={computeOptimalRequest.firstRatingProbabilityEasy}
/>
<input type="number" bind:value={optimalParams.firstRatingProbabilityEasy} />
<br />
Review rating probability (hard):
<br />
<input
type="number"
bind:value={computeOptimalRequest.reviewRatingProbabilityHard}
/>
<input type="number" bind:value={optimalParams.reviewRatingProbabilityHard} />
<br />
Review rating probability (good):
<br />
<input
type="number"
bind:value={computeOptimalRequest.reviewRatingProbabilityGood}
/>
<input type="number" bind:value={optimalParams.reviewRatingProbabilityGood} />
<br />
Review rating probability (easy):
<br />
<input
type="number"
bind:value={computeOptimalRequest.reviewRatingProbabilityEasy}
/>
<input type="number" bind:value={optimalParams.reviewRatingProbabilityEasy} />
<br />
<button
class="btn {computing ? 'btn-warning' : 'btn-primary'}"
on:click={() => getRetentionParams()}
>
{#if computing}
{tr.actionsCancel()}
{:else}
{tr.deckConfigGetParams()}
{/if}
</button>
<button
class="btn {computing ? 'btn-warning' : 'btn-primary'}"
on:click={() => computeRetention()}