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.
FSRS models memory using two key variables:
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%.
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 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")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.118104The 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.00After 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%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 daysAfter 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.13Let’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 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.1542Key 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: