Project

fsrs_ruby

0.0
No release in over 3 years
A complete Ruby port of the TypeScript FSRS v6.0 algorithm for spaced repetition scheduling. Implements exponential difficulty, linear damping, and 21-parameter optimization for optimal review timing in flashcard and learning applications.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 13.0
~> 3.12
~> 0.22
 Project Readme

FSRS Ruby - v6.0

A complete Ruby port of the FSRS (Free Spaced Repetition Scheduler) algorithm version 6.0. Claude Sonnet 4.5 by Antrhopic was used to generate this port.

Features

  • FSRS v6.0 Algorithm: Exponential difficulty formula, linear damping, 21 parameters
  • Short-term Learning: Minute-based scheduling with learning steps (e.g., ['1m', '10m'])
  • State Machine: NEW → LEARNING → REVIEW ↔ RELEARNING
  • Parameter Migration: Automatic migration from v4/v5 to v6 format
  • Fuzzing: Optional interval randomization using Alea PRNG
  • Strategy Pattern: Pluggable schedulers, learning steps, and seed strategies
  • Cross-validated: Outputs match TypeScript implementation to 8 decimal places

Requirements

  • Ruby >= 3.1.0

Installation

Add this line to your application's Gemfile:

gem 'fsrs_ruby'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install fsrs_ruby

Usage

Basic Example

require 'fsrs_ruby'

# Create FSRS instance with default parameters
fsrs = FsrsRuby.new

# Create a new card
card = FsrsRuby.create_empty_card(Time.now)

# Preview all possible ratings (Again, Hard, Good, Easy)
preview = fsrs.repeat(card, Time.now)

# Access results for each rating
good_result = preview[FsrsRuby::Rating::GOOD]
puts "If rated GOOD:"
puts "  Next review: #{good_result.card.due}"
puts "  Difficulty: #{good_result.card.difficulty}"
puts "  Stability: #{good_result.card.stability}"

# Apply a specific rating
result = fsrs.next(card, Time.now, FsrsRuby::Rating::GOOD)
updated_card = result.card

Custom Parameters

fsrs = FsrsRuby.new(
  request_retention: 0.9,           # Target 90% retention
  maximum_interval: 36500,          # Max interval in days (~100 years)
  enable_short_term: true,          # Use minute-based learning steps
  learning_steps: ['1m', '10m'],    # Learning: 1 minute, then 10 minutes
  relearning_steps: ['10m'],        # Relearning: 10 minutes
  enable_fuzz: false                # Disable interval randomization
)

Getting Retrievability

# Get memory retention probability
retrievability = fsrs.get_retrievability(card, Time.now)
# => "95.23%"

# Get as decimal
retrievability = fsrs.get_retrievability(card, Time.now, format: false)
# => 0.9523

Rollback and Forget

# Rollback a review
previous_card = fsrs.rollback(updated_card, review_log)

# Reset card to NEW state
forgotten = fsrs.forget(card, Time.now, reset_count: true)

Custom Strategies

# Custom seed strategy
fsrs.use_strategy(:seed, ->(scheduler) {
  "#{scheduler.current.id}_#{scheduler.current.reps}"
})

# Custom learning steps strategy
fsrs.use_strategy(:learning_steps, ->(params, state, cur_step) {
  # Return custom step logic
  {}
})

Algorithm Overview

State Transitions

NEW → LEARNING → REVIEW ↔ RELEARNING
  • NEW: Card never reviewed
  • LEARNING: Initial learning phase with short intervals
  • REVIEW: Long-term review phase
  • RELEARNING: Re-learning after forgetting (lapse)

Ratings

  • Again (1): Complete failure, restart learning
  • Hard (2): Difficult to recall
  • Good (3): Recalled correctly with effort
  • Easy (4): Recalled easily

Key Formulas (v6.0)

Initial Difficulty (Exponential):

D₀(G) = w[4] - exp((G-1) × w[5]) + 1

Next Difficulty (with Linear Damping):

Δd = -w[6] × (G - 3)
D' = D + linear_damping(Δd, D)
linear_damping(Δd, D) = (Δd × (10 - D)) / 9

Forgetting Curve:

R(t,S) = (1 + FACTOR × t / S)^DECAY
where: decay = -w[20], factor = exp(ln(0.9)/decay) - 1

Development

After checking out the repo, run:

$ bundle install
$ bundle exec rake spec

Cross-Validation

This implementation has been cross-validated against the TypeScript FSRS v6 implementation. All core formulas match to 8 decimal places.

See VERIFICATION_REPORT.md for detailed verification results.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/ondrejrohon/fsrs_ruby.

Please read CONTRIBUTING.md for details on how to contribute.

This project is intended to be a safe, welcoming space for collaboration. Contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Credits

This gem is a Ruby port of the TypeScript FSRS v6.0 implementation, developed with AI assistance and thoroughly cross-validated.

The FSRS algorithm was created by Jarrett Ye and the open-spaced-repetition community.

Verification

This implementation has been cross-validated against the TypeScript version with:

  • ✅ 125 passing tests
  • ✅ 89.87% code coverage
  • ✅ Algorithm accuracy to 8 decimal places
  • ✅ All known issues fixed and validated