@hackage haskell-fsrs7.0.0

FSRS-7, the Free Spaced Repetition Scheduler

haskell-fsrs

CI

A Haskell implementation of FSRS-7, the seventh version of the Free Spaced Repetition Scheduler — the memory model behind Anki's scheduler.

FSRS predicts when you are about to forget a flashcard so it can be shown to you just before that happens. It tracks two numbers per card:

  • stability — the memory's half-life, in days;
  • difficulty — how hard this particular card is for you, on a 1–10 scale;

and derives retrievability, the probability that you can recall the card right now.

The package version tracks the algorithm version, the way py-fsrs and fsrs-rs do: 7.x.y implements FSRS-7.

What is new in FSRS-7

FSRS-7 has 35 parameters, up from FSRS-6's 21. Three things changed:

  • The forgetting curve is a mixture of two power laws rather than one, with the mixing weights themselves depending on stability. That is where six of the new parameters go, and it means the curve has no closed-form inverse — so computing an interval is a root-find, not a formula.
  • The stability update runs twice, once with a long-term weight block and once with a short-term one, and the two are blended by a smooth transition function of the elapsed time. FSRS-6 instead switched between two separate formulas on a same-day / not-same-day flag.
  • Intervals are genuinely continuous. Every earlier version was designed around whole-day intervals; FSRS-7 is the first that gives realistic predictions for same-day reviews. Ten minutes is 10 / 1440 days and the model means it.

Getting started

$ stack build
$ stack test
$ stack run          # a small demo: one card, graded Good ten times

Using it

import FSRS

-- Grade a brand-new card Good, then grade it again a week later.
firstReview, secondReview :: MemoryState
firstReview  = nextMemoryState defaultParameters Nothing 0 Good
secondReview = nextMemoryState defaultParameters (Just firstReview) 7 Good

-- When should it come back, if we want a 90% chance of recall?
whenDue :: Days
whenDue = nextIntervalDays defaultParameters 0.9 (memoryStability secondReview)

-- How likely are we to recall it three days from now?
odds :: Retrievability
odds = retrievability defaultParameters 3 (memoryStability secondReview)

Whole-card scheduling — learning steps, due dates, lapses, fuzz — lives in FSRS.Scheduler:

import FSRS

session :: UTCTime -> (Card, ReviewLog)
session now = reviewCard defaultScheduler (newCard now) Good now

reviewCard is deterministic. If you want Anki-style interval fuzzing, use reviewCardFuzzed and hand it the random sample yourself, so scheduling stays a pure function of its inputs.

Optimising the 35 weights against a user's own review history is not part of this package. Use the upstream optimiser and feed the result to mkParameters.

Modules

Module What is in it
FSRS Re-exports everything below.
FSRS.Types Rating, MemoryState, the type synonyms.
FSRS.Parameters The 35 weights, their bounds, validation, typed views onto the blocks.
FSRS.Algorithm The model: forgetting curve, difficulty, stability, interval inversion.
FSRS.Scheduler Cards, due dates, learning steps, fuzz.

Provenance

FSRS.Algorithm is a transcription of the reference implementation the upstream authors benchmark against: srs-benchmark, models/fsrs_v7.py and models/fsrs_v7_interval_penalty.py (revision 8c11619).

Two things are worth knowing about the default weights:

  • The published defaults use 1.3 for w15 and w24, the easy bonus of the two stability blocks. The Default Parameters section of the srs-benchmark README still lists 1.15; that block has not been touched since 2026-03-18, while the model itself was changed to 1.3 three days later (commit e274ac3). This package follows the model.
  • The parameter bounds in parameterBounds come from the clipper the upstream optimiser applies after every gradient step, so any weights a real optimiser produces will satisfy them.

The scheduling policy in FSRS.Scheduler is not specified upstream — only the memory model is. It follows the reference scheduler from py-fsrs, adapted to FSRS-7's continuous intervals.

Tests

Two complementary suites, 113 test cases in all:

  • Golden vectors — 2,589 of them, covering every function of the model across three parameter sets, generated by reference/fsrs7_reference.py, a pure-Python transcription of the same upstream source. Both implementations perform the same floating-point operations in the same order, so they are checked to a relative tolerance of 1e-12.
  • Properties — invariants that should hold for every parameter vector inside the valid box: retrievability is a probability and decreases with time, a better rating never means less stability, difficulty stays in range however long the history, the interval solver really does land on the desired retention, and so on.

A few properties hold only for well-behaved weights and say so. Because FSRS-7 re-weights its two power laws by stability, adversarial-but-in-bounds weights can make retrievability fall as stability grows; the properties about how the model responds to stability are therefore stated for defaultParameters.

To regenerate the golden vectors after touching the reference:

$ python3 reference/gen_golden.py

The reference gets two checks of its own, both standard-library only:

$ python3 reference/test_reference.py   # the model's invariants, in Python
$ python3 reference/check_golden.py     # the committed vectors still match it

check_golden.py compares numerically rather than by git diff. exp and pow are not required by IEEE-754 to be correctly rounded, so the last bit of a literal can legitimately differ between the machine that generated the file and the one checking it; a textual diff would go red for reasons that have nothing to do with the model.

Continuous integration

.github/workflows/ci.yml builds and tests with Stack under --pedantic (-Wall -Werror), smoke-tests the demo, and runs both reference checks.

Licence

MIT. See LICENSE.