mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Use separate field to store FSRS params
Will allow the user to keep using old params with older clients
This commit is contained in:
parent
26ae51fafd
commit
c45fa518d2
9 changed files with 68 additions and 28 deletions
|
@ -107,9 +107,11 @@ message DeckConfig {
|
|||
repeated float learn_steps = 1;
|
||||
repeated float relearn_steps = 2;
|
||||
|
||||
repeated float fsrs_weights = 3;
|
||||
repeated float fsrs_params_4 = 3;
|
||||
repeated float fsrs_params_5 = 5;
|
||||
|
||||
reserved 5 to 8;
|
||||
// consider saving remaining ones for fsrs param changes
|
||||
reserved 6 to 8;
|
||||
|
||||
uint32 new_per_day = 9;
|
||||
uint32 reviews_per_day = 10;
|
||||
|
|
|
@ -74,7 +74,8 @@ const DEFAULT_DECK_CONFIG_INNER: DeckConfigInner = DeckConfigInner {
|
|||
bury_new: false,
|
||||
bury_reviews: false,
|
||||
bury_interday_learning: false,
|
||||
fsrs_weights: vec![],
|
||||
fsrs_params_4: vec![],
|
||||
fsrs_params_5: vec![],
|
||||
desired_retention: 0.9,
|
||||
other: Vec::new(),
|
||||
historical_retention: 0.9,
|
||||
|
@ -105,6 +106,15 @@ impl DeckConfig {
|
|||
self.mtime_secs = TimestampSecs::now();
|
||||
self.usn = usn;
|
||||
}
|
||||
|
||||
/// Retrieve the FSRS 5.0 params, falling back on 4.x ones.
|
||||
pub fn fsrs_params(&self) -> &Vec<f32> {
|
||||
if self.inner.fsrs_params_5.len() == 19 {
|
||||
&self.inner.fsrs_params_5
|
||||
} else {
|
||||
&self.inner.fsrs_params_4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
|
|
|
@ -69,8 +69,10 @@ pub struct DeckConfSchema11 {
|
|||
#[serde(default)]
|
||||
bury_interday_learning: bool,
|
||||
|
||||
#[serde(default, rename = "fsrsWeights")]
|
||||
fsrs_params_4: Vec<f32>,
|
||||
#[serde(default)]
|
||||
fsrs_weights: Vec<f32>,
|
||||
fsrs_params_5: Vec<f32>,
|
||||
#[serde(default)]
|
||||
desired_retention: f32,
|
||||
#[serde(default)]
|
||||
|
@ -306,7 +308,8 @@ impl Default for DeckConfSchema11 {
|
|||
new_sort_order: 0,
|
||||
new_gather_priority: 0,
|
||||
bury_interday_learning: false,
|
||||
fsrs_weights: vec![],
|
||||
fsrs_params_4: vec![],
|
||||
fsrs_params_5: vec![],
|
||||
desired_retention: 0.9,
|
||||
sm2_retention: 0.9,
|
||||
weight_search: "".to_string(),
|
||||
|
@ -386,7 +389,8 @@ impl From<DeckConfSchema11> for DeckConfig {
|
|||
bury_new: c.new.bury,
|
||||
bury_reviews: c.rev.bury,
|
||||
bury_interday_learning: c.bury_interday_learning,
|
||||
fsrs_weights: c.fsrs_weights,
|
||||
fsrs_params_4: c.fsrs_params_4,
|
||||
fsrs_params_5: c.fsrs_params_5,
|
||||
ignore_revlogs_before_date: c.ignore_revlogs_before_date,
|
||||
easy_days_percentages: c.easy_days_percentages,
|
||||
desired_retention: c.desired_retention,
|
||||
|
@ -498,7 +502,8 @@ impl From<DeckConfig> for DeckConfSchema11 {
|
|||
new_sort_order: i.new_card_sort_order,
|
||||
new_gather_priority: i.new_card_gather_priority,
|
||||
bury_interday_learning: i.bury_interday_learning,
|
||||
fsrs_weights: i.fsrs_weights,
|
||||
fsrs_params_4: i.fsrs_params_4,
|
||||
fsrs_params_5: i.fsrs_params_5,
|
||||
desired_retention: i.desired_retention,
|
||||
sm2_retention: i.historical_retention,
|
||||
weight_search: i.weight_search,
|
||||
|
@ -526,6 +531,7 @@ static RESERVED_DECKCONF_KEYS: Set<&'static str> = phf_set! {
|
|||
"interdayLearningMix",
|
||||
"newGatherPriority",
|
||||
"fsrsWeights",
|
||||
"fsrsParams5",
|
||||
"desiredRetention",
|
||||
"stopTimerOnAnswer",
|
||||
"secondsToShowQuestion",
|
||||
|
|
|
@ -50,7 +50,7 @@ impl Collection {
|
|||
deck: DeckId,
|
||||
) -> Result<anki_proto::deck_config::DeckConfigsForUpdate> {
|
||||
let mut defaults = DeckConfig::default();
|
||||
defaults.inner.fsrs_weights = DEFAULT_PARAMETERS.into();
|
||||
defaults.inner.fsrs_params_5 = DEFAULT_PARAMETERS.into();
|
||||
let last_optimize = self.get_config_i32(I32ConfigKey::LastFsrsOptimize) as u32;
|
||||
let days_since_last_fsrs_optimize = if last_optimize > 0 {
|
||||
self.timing_today()?
|
||||
|
@ -88,6 +88,12 @@ impl Collection {
|
|||
// grab the config and sort it
|
||||
let mut config = self.storage.all_deck_config()?;
|
||||
config.sort_unstable_by(|a, b| a.name.cmp(&b.name));
|
||||
// pre-fill empty fsrs 5 params with 4 params
|
||||
config.iter_mut().for_each(|c| {
|
||||
if c.inner.fsrs_params_5.is_empty() {
|
||||
c.inner.fsrs_params_5 = c.inner.fsrs_params_4.clone();
|
||||
}
|
||||
});
|
||||
|
||||
// combine with use counts
|
||||
let counts = self.get_deck_config_use_counts()?;
|
||||
|
@ -159,8 +165,14 @@ impl Collection {
|
|||
|
||||
// add/update provided configs
|
||||
for conf in &mut req.configs {
|
||||
// If the user has provided empty FSRS5 params, zero out any
|
||||
// old params as well, so we don't fall back on them, which would
|
||||
// be surprising as they're not shown in the GUI.
|
||||
if conf.inner.fsrs_params_5.is_empty() {
|
||||
conf.inner.fsrs_params_4.clear();
|
||||
}
|
||||
// check the provided parameters are valid before we save them
|
||||
FSRS::new(Some(&conf.inner.fsrs_weights))?;
|
||||
FSRS::new(Some(conf.fsrs_params()))?;
|
||||
self.add_or_update_deck_config(conf)?;
|
||||
configs_after_update.insert(conf.id, conf.clone());
|
||||
}
|
||||
|
@ -201,7 +213,7 @@ impl Collection {
|
|||
let previous_order = previous_config
|
||||
.map(|c| c.inner.new_card_insert_order())
|
||||
.unwrap_or_default();
|
||||
let previous_weights = previous_config.map(|c| &c.inner.fsrs_weights);
|
||||
let previous_weights = previous_config.map(|c| c.fsrs_params());
|
||||
let previous_retention = previous_config.map(|c| c.inner.desired_retention);
|
||||
|
||||
// if a selected (sub)deck, or its old config was removed, update deck to point
|
||||
|
@ -228,7 +240,7 @@ impl Collection {
|
|||
}
|
||||
|
||||
// if weights differ, memory state needs to be recomputed
|
||||
let current_weights = current_config.map(|c| &c.inner.fsrs_weights);
|
||||
let current_weights = current_config.map(|c| c.fsrs_params());
|
||||
let current_retention = current_config.map(|c| c.inner.desired_retention);
|
||||
if fsrs_toggled
|
||||
|| previous_weights != current_weights
|
||||
|
@ -252,7 +264,7 @@ impl Collection {
|
|||
let weights = config.and_then(|c| {
|
||||
if req.fsrs {
|
||||
Some(UpdateMemoryStateRequest {
|
||||
weights: c.inner.fsrs_weights.clone(),
|
||||
weights: c.fsrs_params().clone(),
|
||||
desired_retention: c.inner.desired_retention,
|
||||
max_interval: c.inner.maximum_review_interval,
|
||||
reschedule: req.fsrs_reschedule,
|
||||
|
@ -349,11 +361,11 @@ impl Collection {
|
|||
ignore_revlogs_before_ms,
|
||||
idx as u32 + 1,
|
||||
config_len,
|
||||
&config.inner.fsrs_weights,
|
||||
config.fsrs_params(),
|
||||
) {
|
||||
Ok(weights) => {
|
||||
println!("{}: {:?}", config.name, weights.weights);
|
||||
config.inner.fsrs_weights = weights.weights;
|
||||
config.inner.fsrs_params_5 = weights.weights;
|
||||
}
|
||||
Err(AnkiError::Interrupted) => return Err(AnkiError::Interrupted),
|
||||
Err(err) => {
|
||||
|
|
|
@ -431,7 +431,7 @@ impl Collection {
|
|||
let config = self.home_deck_config(deck.config_id(), card.original_deck_id)?;
|
||||
let fsrs_enabled = self.get_config_bool(BoolKey::Fsrs);
|
||||
let fsrs_next_states = if fsrs_enabled {
|
||||
let fsrs = FSRS::new(Some(&config.inner.fsrs_weights))?;
|
||||
let fsrs = FSRS::new(Some(config.fsrs_params()))?;
|
||||
if card.memory_state.is_none() && card.ctype != CardType::New {
|
||||
// Card has been moved or imported into an FSRS deck after weights were set,
|
||||
// and will need its initial memory state to be calculated based on review
|
||||
|
|
|
@ -154,7 +154,7 @@ impl Collection {
|
|||
.or_not_found(conf_id)?;
|
||||
let desired_retention = config.inner.desired_retention;
|
||||
let historical_retention = config.inner.historical_retention;
|
||||
let fsrs = FSRS::new(Some(&config.inner.fsrs_weights))?;
|
||||
let fsrs = FSRS::new(Some(config.fsrs_params()))?;
|
||||
let revlog = self.revlog_for_srs(SearchNode::CardIds(card.id.to_string()))?;
|
||||
let item = single_card_revlog_to_item(
|
||||
&fsrs,
|
||||
|
|
|
@ -130,7 +130,7 @@ impl Collection {
|
|||
.get_deck_config(conf_id)?
|
||||
.or_not_found(conf_id)?;
|
||||
let historical_retention = config.inner.historical_retention;
|
||||
let fsrs = FSRS::new(Some(&config.inner.fsrs_weights))?;
|
||||
let fsrs = FSRS::new(Some(config.fsrs_params()))?;
|
||||
let next_day_at = self.timing_today()?.next_day_at;
|
||||
let ignore_before = ignore_revlogs_before_ms_from_config(&config)?;
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import SwitchRow from "$lib/components/SwitchRow.svelte";
|
||||
|
||||
import GlobalLabel from "./GlobalLabel.svelte";
|
||||
import type { DeckOptionsState } from "./lib";
|
||||
import { fsrsParams, type DeckOptionsState } from "./lib";
|
||||
import SpinBoxFloatRow from "./SpinBoxFloatRow.svelte";
|
||||
import SpinBoxRow from "./SpinBoxRow.svelte";
|
||||
import Warning from "./Warning.svelte";
|
||||
|
@ -82,7 +82,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
|
||||
const simulateFsrsRequest = new SimulateFsrsReviewRequest({
|
||||
weights: $config.fsrsWeights,
|
||||
weights: fsrsParams($config),
|
||||
desiredRetention: $config.desiredRetention,
|
||||
deckSize: 0,
|
||||
daysToSimulate: 365,
|
||||
|
@ -137,26 +137,28 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
try {
|
||||
await runWithBackendProgress(
|
||||
async () => {
|
||||
const params = fsrsParams($config);
|
||||
const resp = await computeFsrsWeights({
|
||||
search: $config.weightSearch
|
||||
? $config.weightSearch
|
||||
: defaultWeightSearch,
|
||||
ignoreRevlogsBeforeMs: getIgnoreRevlogsBeforeMs(),
|
||||
currentWeights: $config.fsrsWeights,
|
||||
currentWeights: params,
|
||||
});
|
||||
if (
|
||||
($config.fsrsWeights.length &&
|
||||
$config.fsrsWeights.every(
|
||||
(params.length &&
|
||||
params.every(
|
||||
(n, i) => n.toFixed(4) === resp.weights[i].toFixed(4),
|
||||
)) ||
|
||||
resp.weights.length === 0
|
||||
) {
|
||||
setTimeout(() => alert(tr.deckConfigFsrsParamsOptimal()), 100);
|
||||
} else {
|
||||
$config.fsrsParams5 = resp.weights;
|
||||
}
|
||||
if (computeWeightsProgress) {
|
||||
computeWeightsProgress.current = computeWeightsProgress.total;
|
||||
}
|
||||
$config.fsrsWeights = resp.weights;
|
||||
},
|
||||
(progress) => {
|
||||
if (progress.value.case === "computeWeights") {
|
||||
|
@ -187,7 +189,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
? $config.weightSearch
|
||||
: defaultWeightSearch;
|
||||
const resp = await evaluateWeights({
|
||||
weights: $config.fsrsWeights,
|
||||
weights: fsrsParams($config),
|
||||
search,
|
||||
ignoreRevlogsBeforeMs: getIgnoreRevlogsBeforeMs(),
|
||||
});
|
||||
|
@ -230,7 +232,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
await runWithBackendProgress(
|
||||
async () => {
|
||||
optimalRetentionRequest.maxInterval = $config.maximumReviewInterval;
|
||||
optimalRetentionRequest.weights = $config.fsrsWeights;
|
||||
optimalRetentionRequest.weights = fsrsParams($config);
|
||||
optimalRetentionRequest.search = `preset:"${state.getCurrentName()}" -is:suspended`;
|
||||
const resp = await computeOptimalRetention(optimalRetentionRequest);
|
||||
optimalRetention = resp.optimalRetention;
|
||||
|
@ -311,7 +313,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
try {
|
||||
await runWithBackendProgress(
|
||||
async () => {
|
||||
simulateFsrsRequest.weights = $config.fsrsWeights;
|
||||
simulateFsrsRequest.weights = fsrsParams($config);
|
||||
simulateFsrsRequest.desiredRetention = $config.desiredRetention;
|
||||
simulateFsrsRequest.search = `preset:"${state.getCurrentName()}" -is:suspended`;
|
||||
simulateProgressString = "processing...";
|
||||
|
@ -360,9 +362,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
<div class="ms-1 me-1">
|
||||
<WeightsInputRow
|
||||
bind:value={$config.fsrsWeights}
|
||||
bind:value={$config.fsrsParams5}
|
||||
defaultValue={[]}
|
||||
defaults={defaults.fsrsWeights}
|
||||
defaults={defaults.fsrsParams5}
|
||||
>
|
||||
<SettingTitle on:click={() => openHelpModal("modelWeights")}>
|
||||
{tr.deckConfigWeights()}
|
||||
|
|
|
@ -415,3 +415,11 @@ export async function commitEditing(): Promise<void> {
|
|||
}
|
||||
await tick();
|
||||
}
|
||||
|
||||
export function fsrsParams(config: DeckConfig_Config): number[] {
|
||||
if (config.fsrsParams5) {
|
||||
return config.fsrsParams5;
|
||||
} else {
|
||||
return config.fsrsParams4;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue