FSRS intervals in Python not matching up with visualizer

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

1 Like

You forget to round(interval).

1 Like

Thanks, it’s way more accurate but still about 0.1 off. It probably won’t matter but I’m still trying to get it exact just in case. The docs didn’t say anything about rounding so I had no idea that was what I had to do.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.