I’ve just released fsrsr, an R package that provides bindings to fsrs-rs, the Rust implementation of the Free Spaced Repetition Scheduler (FSRS) algorithm. This means you can now use the state-of-the-art spaced repetition algorithm directly in R without the maintenance burden of a native implementation.
What is FSRS?
FSRS is a modern spaced repetition algorithm that outperforms traditional algorithms like SM-2 (used in Anki’s default scheduler). It uses a model based on the DSR (Difficulty, Stability, Retrievability) framework to predict memory states and optimize review intervals for long-term retention.
Why Bindings Instead of Native R?
Writing and maintaining a native R implementation of FSRS would be challenging:
The algorithm involves complex mathematical models that evolve with research
Performance matters when scheduling thousands of cards
Keeping pace with upstream changes requires ongoing effort
Here’s a simple example showing the core workflow:
library(fsrsr)
# Initialize a new card with a "Good" rating (3)
state <- fsrs_initial_state(3)
# $stability: 3.17
# $difficulty: 5.28
# After reviewing 3 days later with "Good" rating
new_state <- fsrs_next_state(
stability = state$stability,
difficulty = state$difficulty,
elapsed_days = 3,
rating = 3
)
# Calculate next interval for 90% target retention
interval <- fsrs_next_interval(new_state$stability, 0.9)
# Returns: days until next review
# Check recall probability after 5 days
prob <- fsrs_retrievability(new_state$stability, 5)
# Returns: 0.946 (94.6% chance of recall)
Research: Analyze spaced repetition data using R’s statistical tools
Custom SRS apps: Build R Shiny applications with proper scheduling
Simulation: Model learning outcomes under different review strategies
Data analysis: Process Anki export data with accurate FSRS calculations
Technical Details
The package uses extendr to generate R bindings from Rust code. The actual FSRS calculations happen in Rust via the fsrs-rs library (v2.0.4), with results passed back to R as native types.
A journey through packaging Python libraries for spaced repetition and Anki deck generation across multiple platforms.
As someone passionate about both medical education tools and open-source software, I recently embarked on a project to make several useful Python libraries available as native packages for FreeBSD and Arch Linux. This post documents the process and shares what I learned along the way.
The Motivation
Spaced repetition software like Anki has become indispensable for medical students and lifelong learners. However, the ecosystem of tools around Anki—libraries for generating decks programmatically, analyzing study data, and implementing scheduling algorithms—often requires manual installation via pip. This creates friction for users and doesn’t integrate well with system package managers.
My goal was to package three key Python libraries:
genanki – A library for programmatically generating Anki decks
fsrs – The Free Spaced Repetition Scheduler algorithm (used in Anki and other SRS apps)
ankipandas – A library for analyzing Anki collections using pandas DataFrames
Arch Linux User Repository (AUR)
The AUR is a community-driven repository for Arch Linux users. Creating packages here involves writing a PKGBUILD file that describes how to fetch, build, and install the software.
python-fsrs 6.3.0
The FSRS (Free Spaced Repetition Scheduler) algorithm represents the cutting edge of spaced repetition research. Version 6.x brought significant API changes, including renaming the main FSRS class to Scheduler.
genanki allows developers to create Anki decks programmatically—perfect for generating flashcards from databases, APIs, or other structured data sources.
FreeBSD’s ports system is more formal than the AUR, with stricter guidelines and a review process. Ports are submitted via Bugzilla and reviewed by committers before inclusion in the official ports tree.
py-genanki Port
Creating a FreeBSD port required several steps:
Setting up the port skeleton – Creating the Makefile, pkg-descr, and distinfo files
Handling dependencies – Mapping Python dependencies to existing FreeBSD ports
Patching setup.py – Removing the pytest-runner build dependency which doesn’t exist in FreeBSD ports
Testing the build – Running make and make install in a FreeBSD environment
One challenge was that genanki’s setup.py required pytest-runner as a build dependency, which doesn’t exist in FreeBSD ports. The solution was to create a patch file that removes this requirement:
One of the biggest challenges in packaging is mapping upstream dependencies to existing packages in the target ecosystem. For FreeBSD, this meant:
Searching /usr/ports for existing Python packages
Understanding the @${PY_FLAVOR} suffix for Python version flexibility
Discovering hidden dependencies (like chevron) that weren’t immediately obvious from the package metadata
Build System Quirks
Python packaging has evolved significantly, with projects using various combinations of:
setup.py with setuptools
pyproject.toml with various backends (setuptools, flit, hatch, poetry)
Legacy setup_requires patterns that don’t translate well to system packaging
Creating patches to work around these issues is a normal part of the porting process.
Testing Across Platforms
Running a FreeBSD VM (via VirtualBox) proved essential for testing ports before submission. The build process can reveal missing dependencies, incorrect paths, and other issues that only appear in the actual target environment.
Summary
Package
Version
AUR
FreeBSD
python-fsrs / py-fsrs
6.3.0
✅ Published
📝 Submitted
python-genanki / py-genanki
0.13.1
✅ Published
📝 Submitted
python-ankipandas
0.3.15
✅ Published
🔜 Planned
Get Involved
If you use these tools on Arch Linux or FreeBSD, I’d love to hear your feedback. And if you’re interested in contributing to open-source packaging:
If you use Anki for spaced repetition learning, you’ve probably wondered about your study patterns. How many cards have you reviewed? What’s your retention like? Which cards are giving you trouble?
I built ankiR to make this easy in R.
The Problem
Anki stores everything in a SQLite database, but accessing it requires writing raw SQL queries. Python users have ankipandas, but R users had nothing—until now.
Installation
# From GitHub
remotes::install_github("chrislongros/ankiR")
# Arch Linux (AUR)
yay -S r-ankir
Basic Usage
ankiR auto-detects your Anki profile and provides a tidy interface:
library(ankiR)
# See available profiles
anki_profiles()
# Load your data as tibbles
notes <- anki_notes()
cards <- anki_cards()
reviews <- anki_revlog()
# Quick stats
nrow(notes) # Total notes
nrow(cards) # Total cards
nrow(reviews) # Total reviews
FSRS Support
The killer feature: ankiR extracts FSRS parameters directly from your collection.
stability – memory stability in days (how long until you forget)
difficulty – card difficulty on a 1-10 scale
retention – your target retention rate (typically 0.9 = 90%)
decay – the decay parameter used in calculations
Example: Visualize Your Card Difficulty
library(ankiR)
library(dplyr)
library(ggplot2)
anki_cards_fsrs() |>
filter(!is.na(difficulty)) |>
ggplot(aes(difficulty)) +
geom_histogram(bins = 20, fill = "steelblue") +
labs(
title = "Card Difficulty Distribution",
x = "Difficulty (1-10)",
y = "Count"
) +
theme_minimal()
Example: Stability vs Difficulty
anki_cards_fsrs() |>
filter(!is.na(stability)) |>
ggplot(aes(difficulty, stability)) +
geom_point(alpha = 0.3, color = "steelblue") +
scale_y_log10() +
labs(
title = "Memory Stability vs Card Difficulty",
x = "Difficulty",
y = "Stability (days, log scale)"
) +
theme_minimal()
Example: Review History Over Time
anki_revlog() |>
count(review_date) |>
ggplot(aes(review_date, n)) +
geom_line(color = "steelblue") +
geom_smooth(method = "loess", se = FALSE, color = "red") +
labs(
title = "Daily Review History",
x = "Date",
y = "Reviews"
) +
theme_minimal()
Calculate Retrievability
You can also calculate the probability of recalling a card after N days:
# What's my retention after 7 days for a card with 30-day stability?
fsrs_retrievability(stability = 30, days_since_review = 7)
# Returns ~0.93 (93% chance of recall)
If you’re running Arch Linux and trying to build Anki from source, you may have encountered a frustrating build failure related to Python 3.14. Here’s what went wrong and how I fixed it.
The Problem
Arch Linux recently updated to Python 3.14.2 as the system default. When attempting to build Anki from the main branch, the build failed with this error:
error: the configured Python interpreter version (3.14) is newer than PyO3's maximum supported version (3.13)
= help: please check if an updated version of PyO3 is available. Current version: 0.23.3
The issue is that Anki depends on orjson, a fast JSON library written in Rust. This library uses PyO3 for Python bindings, and the bundled version (0.23.3) only supports Python up to 3.13.
Why UV_PYTHON and .python-version Didn’t Work
My first attempts involved setting the UV_PYTHON environment variable and creating a .python-version file in the repository root. Neither worked because Anki’s build runner wasn’t passing these settings through to uv when creating the virtual environment. The build system kept detecting and using /usr/bin/python3 (3.14.2) regardless.
The Solution
The fix is to install Python 3.13 via pyenv and put it first in your PATH so it gets detected before the system Python.
First, install pyenv and Python 3.13:
sudo pacman -S pyenv
pyenv install 3.13.1
Then, for your build session, prepend Python 3.13 to your PATH:
Alternatively, create a simple wrapper script for building Anki that sets the PATH temporarily.
Conclusion
This is a temporary issue that will resolve itself once orjson updates its bundled PyO3 to a version that supports Python 3.14. Until then, using pyenv to provide Python 3.13 is a clean workaround that doesn’t require downgrading your system Python or breaking other applications.
The Arch Linux philosophy of staying on the bleeding edge occasionally runs into these compatibility gaps with projects that have Rust dependencies—something to keep in mind when building from source.
The launcher now has a download mirror option for users in China, and no longer auto-downloads on first run. The version check should now also work with SOCKS proxies.
Feat/expected_workload_with_existing_cards implementation by @Luc-Mcgrady in #4243