Fsrs.Sharp 2.0.0

dotnet add package Fsrs.Sharp --version 2.0.0
                    
NuGet\Install-Package Fsrs.Sharp -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Fsrs.Sharp" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Fsrs.Sharp" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Fsrs.Sharp" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Fsrs.Sharp --version 2.0.0
                    
#r "nuget: Fsrs.Sharp, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Fsrs.Sharp@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Fsrs.Sharp&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Fsrs.Sharp&version=2.0.0
                    
Install as a Cake Tool

FSRS-Sharp

FSRS-Sharp is a .NET port of the Python library implementing the Free Spaced Repetition Scheduler (FSRS-6).

The scheduler is verified review-for-review against py-fsrs 6.3.2: 64 scenarios and 692 reviews of golden output generated by the reference implementation itself, covering same-day gaps, exact whole-day gaps and fractional multi-day gaps.

Quick Start

Create Card
var card = new Card();
Create Scheduler
var scheduler = new Scheduler();
Rate and review the card
  • Rating.Again: forgot the card
  • Rating.Hard: remembered the card with serious difficulty
  • Rating.Good: remembered the card after a hesitation
  • Rating.Easy: remembered the card easily
var rating = Rating.Good;
var reviewResult = scheduler.ReviewCard(card, rating);
var reviewedCard = reviewResult.Card;
See when the card is due next
var due = reviewedCard.Due;
var timeSpan = due - DateTimeOffset.UtcNow;
Console.WriteLine($"Card due on {due}"); // Card due on 1/3/2026 12:35:43 PM
Console.WriteLine($"Card due in {timeSpan.TotalSeconds} seconds"); // Card due in 599 seconds

Retrievability

var retrievability = scheduler.GetCardRetrievability(reviewedCard);
Console.WriteLine($"There is a {retrievability} probability that this card is remembered."); // There is a 0.999 probability that this just reviewed card is remembered

Pass the clock explicitly to keep callers deterministic — useful in tests, and for asking "what will retrievability be at the due date?":

var atDue = scheduler.GetCardRetrievability(reviewedCard, reviewedCard.Due); // ~0.9

Custom Parameters

var scheduler = new Scheduler(new FsrsConfig
{
    DesiredRetention = 0.8,
    EnableFuzzing = false,
    MaximumInterval = 36500,
    LearningSteps = [TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(10)],
    RelearningSteps = [TimeSpan.FromMinutes(10)],
    Parameters = new FsrsParameters()
});

Parameters.Weights are a set of 21 model weights that affect how the FSRS scheduler will schedule future reviews. If you're not familiar with optimizing FSRS, it is best not to modify these default values.

Weights are supplied to the constructor and are read-only afterwards, so they are validated exactly once and can never drift out of the range the scheduler assumes:

var tuned = new FsrsParameters(weights: myOptimizedWeights); // throws ArgumentException if out of bounds
var scheduler = new Scheduler(new FsrsConfig { Parameters = tuned });

DesiredRetention is a value between 0 and 1 that sets the desired minimum retention rate for cards when scheduled with the scheduler. For example, with the default value of DesiredRetention=0.9, a card will be scheduled at a time in the future when the predicted probability of the user correctly recalling that card falls to 90%. A higher desired retention rate will lead to more reviews and a lower rate will lead to fewer reviews.

LearningSteps are custom time intervals that schedule new cards in the Learning state. By default, cards in the Learning state have short intervals of 1 minute then 10 minutes. You can also disable them with LearningSteps = [].

RelearningSteps are analogous to LearningSteps except they apply to cards in the Relearning state. Cards transition to the Relearning state if they were previously in the Review state, then were rated Again - this is also known as a 'lapse'. If you specify RelearningSteps = [], cards in the Review state, when lapsed, will not move to the Relearning state, but instead stay in the Review state.

MaximumInterval sets the cap for the maximum days into the future the scheduler is capable of scheduling cards. For example, if you never want the scheduler to schedule a card more than one year into the future, you'd set MaximumInterval = 365.

EnableFuzzing, if set to true, will apply a small amount of random 'fuzz' to calculated intervals. For example, a card that would've been due in 50 days, after fuzzing, might be due in 49, or 51 days.

Things worth knowing

These are properties of FSRS itself, not of this port, but they surprise people:

  • A review cannot be undone, only replayed. FSRS is not invertible. If you let a card be re-answered, keep the pre-review snapshot and re-review from it; chaining a second review on top lands the card in a state no single answer could produce.
  • A same-day repeat moves Due but not Stability. The short-term multiplier is floored at 1.0, so an extra same-day review is a clock reset for zero learning.
  • Failing a card early costs more stability than failing it on time, by a flat ~15%, because forget-stability scales by exp((1-R) * w14) and an early review has a higher R.

What changed in 2.0

2.0 is a correctness release. Behaviour now matches py-fsrs 6.3.2 exactly.

Fixed:

  • Elapsed time is measured in whole days, as the reference does. 1.x passed the fraction straight through, which understated retrievability and inflated the stability gain on every long-term review.
  • State.New cards schedule instead of throwing InvalidOperationException. A New card is promoted to Learning on its first review.
  • Out-of-range ratings are rejected. A Rating is an int, and neither model binding nor JsonStringEnumConverter range-checks one. Because the rating is used as an array index into the weights, (Rating)7 used to quietly read w6 and use it as an initial stability — silent memory-state corruption. It now throws ArgumentOutOfRangeException.
  • The Learning/Relearning step ladder no longer indexes past the end of its array when a card carries a step index from a scheduler configured with more steps.
  • The fuzzer is thread-safe. It used a per-instance Random, which a shared Scheduler under concurrent reviews could tear into returning 0 forever. It now uses Random.Shared.
  • FsrsParameters is immutable. Weights was a public mutable array aliasing Defaults, so editing one edited the other and both slipped past the constructor's bounds check.
  • GetCardRetrievability no longer reads the wall clock, and reads decay/factor from the same calculator the stability formulas use rather than from a config that may hold a different weight set.
  • A card with no LastReview is treated as a long-term review at R = 0, as the reference does, rather than as a same-day one.
  • ReviewLog.ReviewDuration no longer truncates a long to an int.

Breaking changes:

  • FsrsParameters.Weights / Defaults / LowerBounds / UpperBounds are IReadOnlyList<double> and constructor-only. Pass weights to the constructor instead of assigning the array.
  • IScheduler.GetCardRetrievability takes an optional DateTimeOffset? clock.
  • IFsrsCalculator gained Decay and Factor.
  • FsrsParameters moved to its own file (same FsrsSharp.Configuration namespace); its bound constants are now const rather than instance fields.

Optimizer

Not supported yet

References

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 87 9/5/2026
1.0.0 258 1/3/2026

Correctness release. Elapsed time is now measured in whole days as the reference does; State.New cards schedule instead of throwing; out-of-range ratings are rejected rather than silently indexing the weight array; the fuzzer is thread-safe; FsrsParameters is immutable. Breaking: FsrsParameters.Weights is IReadOnlyList and constructor-only, GetCardRetrievability takes an optional clock, and IFsrsCalculator exposes Decay/Factor.