Getting Started with rfsrs

Introduction

rfsrs provides R bindings for the FSRS (Free Spaced Repetition Scheduler) algorithm. FSRS is a modern spaced repetition algorithm that models human memory more accurately than traditional algorithms like SM-2.

library(rfsrs)

Understanding FSRS

The Memory Model

FSRS models memory using two key variables:

  1. Stability (S): Represents how long a memory will last. Higher stability means slower forgetting. Specifically, stability is the number of days until retrievability drops to 90%.

  2. Difficulty (D): Represents how hard the material is to learn. Ranges from 1-10. Higher difficulty means stability grows more slowly with each review.

Retrievability

Retrievability is the probability that you can recall a memory at a given time.

# Visualize the forgetting curve for different stability values
stability_values <- c(1, 5, 10, 30)
days <- 0:60

# Simple base R plot
plot(NULL, xlim = c(0, 60), ylim = c(0, 1),
     xlab = "Days since last review", ylab = "Retrievability",
     main = "Forgetting Curves for Different Stability Values")
colors <- c("red", "orange", "blue", "green")
for (i in seq_along(stability_values)) {
  s <- stability_values[i]
  r <- sapply(days, function(d) fsrs_recall_probability(s, d))
  lines(days, r, col = colors[i], lwd = 2)
}
legend("topright", legend = paste("S =", stability_values, "days"), 
       col = colors, lwd = 2)
abline(h = 0.9, lty = 2, col = "gray")

Basic Usage

Creating a New Card

When you first learn a card, you create an initial memory state based on your first rating:

# Rating scale: 1=Again, 2=Hard, 3=Good, 4=Easy
state <- fsrs_new_card_state(rating = 3)  # First review, rated "Good"
print(state)
#> $stability
#> [1] 2.3065
#> 
#> $difficulty
#> [1] 2.118104

The initial stability depends on your rating:

ratings <- 1:4
rating_names <- c("Again", "Hard", "Good", "Easy")

for (i in ratings) {
  s <- fsrs_new_card_state(rating = i)
  cat(sprintf("%s (rating=%d): stability=%.2f, difficulty=%.2f\n",
              rating_names[i], i, s$stability, s$difficulty))
}
#> Again (rating=1): stability=0.21, difficulty=6.41
#> Hard (rating=2): stability=1.29, difficulty=5.11
#> Good (rating=3): stability=2.31, difficulty=2.12
#> Easy (rating=4): stability=8.30, difficulty=1.00

Checking Retrievability

After a review, you can check how well you’ll remember the card over time:

state <- fsrs_new_card_state(rating = 3)

cat("Retrievability after:\n")
#> Retrievability after:
for (days in c(1, 3, 7, 14, 30)) {
  r <- fsrs_recall_probability(state$stability, elapsed_days = days)
  cat(sprintf("  %2d days: %.1f%%\n", days, r * 100))
}
#>    1 days: 95.3%
#>    3 days: 87.5%
#>    7 days: 76.4%
#>   14 days: 64.2%
#>   30 days: 49.7%

Getting the Next Interval

FSRS calculates the optimal interval for your next review based on desired retention:

state <- fsrs_new_card_state(rating = 3)

# Get interval for 90% desired retention (default)
interval_90 <- fsrs_interval(state$stability, desired_retention = 0.9)
cat(sprintf("Interval for 90%% retention: %.1f days\n", interval_90))
#> Interval for 90% retention: 2.3 days

# Get interval for 85% desired retention (fewer reviews)
interval_85 <- fsrs_interval(state$stability, desired_retention = 0.85)
cat(sprintf("Interval for 85%% retention: %.1f days\n", interval_85))
#> Interval for 85% retention: 4.4 days

# Get interval for 95% desired retention (more reviews)
interval_95 <- fsrs_interval(state$stability, desired_retention = 0.95)
cat(sprintf("Interval for 95%% retention: %.1f days\n", interval_95))
#> Interval for 95% retention: 0.9 days

Simulating a Review

After reviewing a card, update the memory state:

# Initial state
state <- fsrs_new_card_state(rating = 3)
cat(sprintf("Initial: stability=%.2f\n", state$stability))
#> Initial: stability=2.31

# Review after 2 days, rate "Good"
state <- fsrs_next_memory_state(
  stability = state$stability,
  difficulty = state$difficulty,
  elapsed_days = 2,
  rating = 3
)
cat(sprintf("After review 1: stability=%.2f\n", state$stability))
#> After review 1: stability=10.96

# Review after 5 days, rate "Good"
state <- fsrs_next_memory_state(
  stability = state$stability,
  difficulty = state$difficulty,
  elapsed_days = 5,
  rating = 3
)
cat(sprintf("After review 2: stability=%.2f\n", state$stability))
#> After review 2: stability=30.13

Simulating Long-term Learning

Let’s simulate learning a card over many reviews:

simulate_learning <- function(n_reviews = 10, rating = 3, desired_retention = 0.9) {
  state <- fsrs_new_card_state(rating = rating)
  
  results <- data.frame(
    review = 0,
    day = 0,
    stability = state$stability,
    difficulty = state$difficulty,
    interval = NA
  )
  
  current_day <- 0
  
  for (i in 1:n_reviews) {
    interval <- fsrs_interval(state$stability, desired_retention)
    current_day <- current_day + interval
    state <- fsrs_next_memory_state(state$stability, state$difficulty, interval, rating,
                                       desired_retention)
    
    results <- rbind(results, data.frame(
      review = i,
      day = current_day,
      stability = state$stability,
      difficulty = state$difficulty,
      interval = interval
    ))
  }
  
  return(results)
}

# Simulate 10 reviews, always rating "Good"
learning <- simulate_learning(n_reviews = 10, rating = 3)
print(learning)
#>    review         day   stability difficulty    interval
#> 1       0     0.00000     2.30650   2.118104          NA
#> 2       1     2.30650    10.96434   2.111214     2.30650
#> 3       2    13.27084    46.28025   2.104331    10.96434
#> 4       3    59.55109   162.86232   2.097455    46.28025
#> 5       4   222.41341   497.44724   2.090586   162.86232
#> 6       5   719.86065  1345.52930   2.083724   497.44724
#> 7       6  2065.38994  3292.27979   2.076869  1345.52930
#> 8       7  5357.66973  7397.82861   2.070020  3292.27979
#> 9       8 12755.49834 15465.86719   2.063179  7397.82861
#> 10      9 28221.36553 30394.12891   2.056344 15465.86719
#> 11     10 58615.49444 36500.00000   2.049516 30394.12891
# Simulate a realistic learner who sometimes struggles
simulate_realistic <- function(ratings, desired_retention = 0.9) {
  state <- fsrs_new_card_state(rating = ratings[1])

  results <- data.frame(
    review = 0,
    day = 0,
    stability = state$stability,
    difficulty = state$difficulty,
    interval = NA,
    rating = ratings[1]
  )

  current_day <- 0

  for (i in seq_along(ratings)[-1]) {
    interval <- fsrs_interval(state$stability, desired_retention)
    current_day <- current_day + interval
    state <- fsrs_next_memory_state(state$stability, state$difficulty, interval,
                                    ratings[i], desired_retention)

    results <- rbind(results, data.frame(
      review = i - 1,
      day = current_day,
      stability = state$stability,
      difficulty = state$difficulty,
      interval = interval,
      rating = ratings[i]
    ))
  }

  results
}

# Always "Good" (idealized) vs. a realistic mix of ratings
ideal <- simulate_learning(n_reviews = 10, rating = 3)
realistic <- simulate_realistic(c(3, 2, 3, 1, 3, 3, 2, 3, 3, 3, 3))

# Log-scale plot comparing both scenarios
plot(ideal$review, ideal$stability, type = "b", pch = 19, log = "y",
     xlab = "Review Number", ylab = "Stability (days, log scale)",
     main = "Stability Growth Over Reviews",
     ylim = range(c(realistic$stability, ideal$stability)))
lines(realistic$review, realistic$stability, type = "b", pch = 17, col = "steelblue")
legend("topleft", legend = c("Always Good", "Mixed ratings"),
       pch = c(19, 17), col = c("black", "steelblue"), lty = 1)
grid()

FSRS Parameters

FSRS uses 21 parameters that control the algorithm’s behavior:

params <- fsrs_parameters()
cat("FSRS-6 default parameters (", length(params), " values):\n", sep = "")
#> FSRS-6 default parameters (21 values):
print(round(params, 4))
#>  [1] 0.2120 1.2931 2.3065 8.2956 6.4133 0.8334 3.0194 0.0010 1.8722 0.1666
#> [11] 0.7960 1.4835 0.0614 0.2629 1.6483 0.6014 1.8729 0.5425 0.0912 0.0658
#> [21] 0.1542

Summary

Key functions:

Function Description
fsrs_parameters() Get the 21 default FSRS-6 parameters
fsrs_new_card_state(rating) Create initial memory state for a new card
fsrs_next_memory_state(stability, difficulty, elapsed_days, rating) Calculate next state after review
fsrs_interval(stability, desired_retention) Get optimal interval for desired retention
fsrs_recall_probability(stability, elapsed_days) Calculate probability of recall

For more information, see: