Hi. For a science fair project, I’m trying to create a way to compute optimal retention for FSRS more accurately by directly accounting for higher-order, relational, interleaved, and other learning techniques that slow forgetting.
I need to code the FSRS algorithm in Python, but when I test the algorithm, setting it for example within 2 months with “good” pressed each time, it’s slightly off from the intervals given by the visualizer by a few decimals. Same with the difficulty and stability. I’ve spent hours already trying to find what’s wrong but can’t figure it out. Would appreciate any kind of help!
Algorithm code
import math
# Default parameters FSRS-5
w = {0:0.40255, 1:1.18385, 2:3.173, 3:15.69105, 4:7.1949, 5:0.5345, 6:1.4604, 7:0.0046, 8:1.54575, 9:0.1192,
10:1.01925, 11:1.9395, 12:0.11, 13:0.29605, 14:2.2698, 15:0.2315, 16:2.9898, 17:0.51655, 18:0.6621}
baseDecay = -0.5
factor = 19/81
minDiffculty = 1
maxDifficulty = 10
# Algorithm configuration
class FSRSContext:
def __init__(self, E=0):
self.decay = baseDecay * (100 / (100 + E)) # E=0 would be original FSRS-5
# E=100 would make forgetting curve decay rate decrease by half and so on
def retrievability(self, t, S):
return math.pow(1 + factor * t/S, self.decay)
def calculateInterval(self, r, S):
return (S/factor) * (math.pow(r, 1/self.decay) - 1)
def initialDifficulty(grade):
return min(max(w[4] - math.exp(w[5] * (grade - 1)) + 1, minDiffculty), maxDifficulty)
def initialStability(grade):
return w[grade-1]
def updateDifficulty(currentD, grade):
deltaD = -w[6] * (grade - 3)
dPrime = currentD + deltaD * ((10 - currentD)/9)
dTarget = initialDifficulty(4)
return w[7] * dTarget + (1 - w[7]) * dPrime
def stabilityIncrease(D, S, R, grade):
w15 = w[15] if grade == 2 else 1
w16 = w[16] if grade == 4 else 1
return 1 + w15 * w16 * math.exp(w[8]) * (11 - D) * math.pow(S, -w[9]) * (math.exp(w[10] * (1-R)) - 1)
def updateStability(currentS, D, R, grade):
if grade == 1:
newS = w[11] * math.pow(D, -w[12]) * (math.pow(currentS + 1, w[13]) -1) * math.exp(w[14] *(1-R))
return min(newS, currentS)
else:
sInc = stabilityIncrease(D, currentS, R, grade)
return currentS * sInc
def updateSameDayStability(currentS, grade):
return currentS * math.exp(w[17] * (grade - 3 + w[18]))
Simulation code (not source of error but to test and display intervals)
from test1 import *
fsrs = FSRSContext(E=0)
currentDifficulty = initialDifficulty(3)
currentStability = initialStability(3)
days = 0
reviewCount = 1
desiredRetention = 0.90
while days < 60:
interval = fsrs.calculateInterval(desiredRetention, currentStability)
print(f"Review #{reviewCount}: Interval: {interval:.1f} days, difficulty: {currentDifficulty:.2f}, Stability: {currentStability:.2f}")
R = fsrs.retrievability(interval, currentStability)
currentDifficulty = updateDifficulty(currentDifficulty, 3)
currentStability = updateStability(currentStability, currentDifficulty, R, 3)
days = days + interval
reviewCount = reviewCount + 1
visualizer stability 1-4 should be 3.17 (correct), 10.74, 34.58, 100.75
Is instead 3.17, 11.14, 35.29, 102.03
visualizer difficulty: 47.58, 47.48, 47.37, 47.27
Is instead 5.28, 5.27, 5.26, 5.25