Start on a 'get params' button
This commit is contained in:
parent
736054a2e4
commit
59759b468f
@ -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.
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -549,6 +549,7 @@ exposed_backend_list = [
|
||||
"compute_optimal_retention",
|
||||
"set_wants_abort",
|
||||
"evaluate_weights",
|
||||
"get_optimal_retention_parameters",
|
||||
]
|
||||
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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!(
|
||||
|
@ -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()}
|
||||
|
Loading…
Reference in New Issue
Block a user