Expose memory state computation to Python

Closes #2676
This commit is contained in:
Damien Elmes 2023-09-25 11:05:47 +10:00
parent 4dc9890845
commit e6aaeb85e9
5 changed files with 63 additions and 1 deletions

View file

@ -52,6 +52,7 @@ service SchedulerService {
rpc ComputeOptimalRetention(ComputeOptimalRetentionRequest)
returns (ComputeOptimalRetentionResponse);
rpc EvaluateWeights(EvaluateWeightsRequest) returns (EvaluateWeightsResponse);
rpc ComputeMemoryState(cards.CardId) returns (ComputeMemoryStateResponse);
}
// Implicitly includes any of the above methods that are not listed in the
@ -383,3 +384,8 @@ message EvaluateWeightsResponse {
float log_loss = 1;
float rmse_bins = 2;
}
message ComputeMemoryStateResponse {
optional cards.FsrsMemoryState state = 1;
float desired_retention = 2;
}

View file

@ -127,6 +127,13 @@ class CardIdsLimit:
ExportLimit = Union[DeckIdLimit, NoteIdsLimit, CardIdsLimit, None]
@dataclass
class ComputedMemoryState:
desired_retention: float
stability: float | None = None
difficulty: float | None = None
@dataclass
class AddNoteRequest:
note: Note
@ -1320,6 +1327,17 @@ class Collection(DeprecatedNamesMixin):
def extract_cloze_for_typing(self, text: str, ordinal: int) -> str:
return self._backend.extract_cloze_for_typing(text=text, ordinal=ordinal)
def compute_memory_state(self, card_id: CardId) -> ComputedMemoryState:
resp = self._backend.compute_memory_state(card_id)
if resp.HasField("state"):
return ComputedMemoryState(
desired_retention=resp.desired_retention,
stability=resp.state.stability,
difficulty=resp.state.difficulty,
)
else:
return ComputedMemoryState(desired_retention=resp.desired_retention)
# Timeboxing
##########################################################################
# fixme: there doesn't seem to be a good reason why this code is in main.py

View file

@ -1,10 +1,13 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use anki_proto::scheduler::ComputeMemoryStateResponse;
use fsrs::FSRS;
use crate::card::FsrsMemoryState;
use crate::prelude::*;
use crate::scheduler::fsrs::weights::fsrs_items_for_memory_state;
use crate::scheduler::fsrs::weights::single_card_revlog_to_items;
use crate::scheduler::fsrs::weights::Weights;
use crate::search::JoinSearches;
use crate::search::Negated;
@ -57,4 +60,33 @@ impl Collection {
}
Ok(())
}
pub fn compute_memory_state(&mut self, card_id: CardId) -> Result<ComputeMemoryStateResponse> {
let card = self.storage.get_card(card_id)?.or_not_found(card_id)?;
let deck_id = card.original_deck_id.or(card.deck_id);
let deck = self.get_deck(deck_id)?.or_not_found(card.deck_id)?;
let conf_id = DeckConfigId(deck.normal()?.config_id);
let config = self
.storage
.get_deck_config(conf_id)?
.or_not_found(conf_id)?;
let desired_retention = config.inner.desired_retention;
let fsrs = FSRS::new(Some(&config.inner.fsrs_weights))?;
let revlog = self.revlog_for_srs(SearchNode::CardIds(card.id.to_string()))?;
let items = single_card_revlog_to_items(revlog, self.timing_today()?.next_day_at, false);
if let Some(mut items) = items {
if let Some(last) = items.pop() {
let state = fsrs.memory_state(last);
let state = FsrsMemoryState::from(state);
return Ok(ComputeMemoryStateResponse {
state: Some(state.into()),
desired_retention,
});
}
}
Ok(ComputeMemoryStateResponse {
state: None,
desired_retention,
})
}
}

View file

@ -130,7 +130,7 @@ pub(crate) fn fsrs_items_for_memory_state(
/// `[1,2,3]`, we create FSRSItems corresponding to `[1,2]` and `[1,2,3]`
/// in training, and `[1]`, [1,2]` and `[1,2,3]` when calculating memory
/// state.
fn single_card_revlog_to_items(
pub(crate) fn single_card_revlog_to_items(
mut entries: Vec<RevlogEntry>,
next_day_at: TimestampSecs,
training: bool,

View file

@ -4,8 +4,10 @@
mod answering;
mod states;
use anki_proto::cards;
use anki_proto::generic;
use anki_proto::scheduler;
use anki_proto::scheduler::ComputeMemoryStateResponse;
use anki_proto::scheduler::ComputeOptimalRetentionRequest;
use anki_proto::scheduler::ComputeOptimalRetentionResponse;
use anki_proto::scheduler::GetOptimalRetentionParametersResponse;
@ -277,4 +279,8 @@ impl crate::services::SchedulerService for Collection {
params: Some(params),
})
}
fn compute_memory_state(&mut self, input: cards::CardId) -> Result<ComputeMemoryStateResponse> {
self.compute_memory_state(input.into())
}
}