fitts 0.2.1

Spaced repetition scheduler using Fitts' Law for difficulty prediction and SM-2 for interval scheduling.
Documentation
# Fitts Memory Scheduler

Spaced repetition scheduler combining **SM-2** for interval scheduling with **Fitts' Law** for adaptive difficulty prediction.

## Key Innovation: Dual-Signal Capture

Traditional flashcard apps only capture **rating** (subjective). This library captures **both**:

| Signal            | Type       | Source     | Use                                          |
| ----------------- | ---------- | ---------- | -------------------------------------------- |
| **Rating**        | Subjective | User input | "How well did I recall?" → SM-2 intervals    |
| **Response Time** | Objective  | Measured   | "How fast did I recall?" → Fitts calibration |

A user might:

- Respond **fast** but rate **Hard** (implicit memory worked, felt unsure)
- Respond **slow** but rate **Easy** (was distracted, knew the answer)

By capturing both, the model **personalizes** to each user.

## Installation

```toml
[dependencies]
fitts = "0.1"
```

## Quick Start

```rust
use fitts::{FittsScheduler, CardState, Rating, ReviewInput};

fn main() {
    let mut scheduler = FittsScheduler::new();
    let card = CardState::default();

    // Predict difficulty before showing answer
    let (predicted_rt, retrievability) = scheduler.predict(&card);
    println!("Predicted: {:.2}s, Retrievability: {:.0}%", predicted_rt, retrievability * 100.0);

    // User responds... app measures time
    let input = ReviewInput::new(Rating::Good, 2500); // 2.5 seconds

    // Process review with both signals
    let result = scheduler.review(card, input);
    println!("Next review in {} days", result.card.interval_days);

    // See prediction error (model learns from this)
    if let Some(error) = result.prediction_error {
        println!("Prediction error: {:+.2}s", error);
    }
}
```

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│                     ReviewInput                              │
│              ┌──────────────┬──────────────┐                │
│              │   Rating     │ Response Time │                │
│              │ (subjective) │  (objective)  │                │
│              └──────┬───────┴───────┬──────┘                │
│                     │               │                        │
│              ┌──────▼─────┐  ┌──────▼──────┐                │
│              │    SM-2    │  │  Fitts Law  │                │
│              │            │  │             │                │
│              │ → interval │  │ → calibrate │                │
│              │ → ease     │  │ → personalize│                │
│              └──────┬─────┘  └──────┬──────┘                │
│                     │               │                        │
│              ┌──────▼───────────────▼──────┐                │
│              │       ReviewResult          │                │
│              │ • Updated card state        │                │
│              │ • Predicted vs actual RT    │                │
│              │ • Calibration results       │                │
│              └─────────────────────────────┘                │
└─────────────────────────────────────────────────────────────┘
```

## Adaptive Calibration

The Fitts model learns from your actual response times using **gradient descent**:

```rust
use fitts::{FittsScheduler, CardState, Rating, ReviewInput};

let mut scheduler = FittsScheduler::with_learning_rate(0.1);
let card = CardState::default();

// Initial prediction
let (initial, _) = scheduler.predict(&card);
println!("Initial prediction: {:.2}s", initial);

// After 15 reviews, the model adapts
// If user consistently responds in ~2 seconds, model learns this
for _ in 0..15 {
    let input = ReviewInput::new(Rating::Good, 2000);
    scheduler.review(card.clone(), input);
}

let (calibrated, _) = scheduler.predict(&card);
println!("Calibrated prediction: {:.2}s", calibrated);
// Prediction now closer to 2 seconds
```

## Algorithms

### SM-2 (Wozniak, 1987)

Classic spaced repetition algorithm:

```text
interval(0) = 1 day
interval(1) = 6 days
interval(n) = interval(n-1) × EF

EF' = EF + (0.1 - (5-q) × (0.08 + (5-q) × 0.02))
EF ≥ 1.3
```

**Note**: This implementation uses a 0-3 quality scale (Again/Hard/Good/Easy) instead of SM-2's original 0-5 scale. Quality values are linearly mapped to 1-5 internally for ease factor calculation: `q_scaled = 1 + q × (4/3)`.

### Fitts' Law (1954) + Memory Adaptation

Original (MacKenzie, 1992): `MT = a + b × log₂(D/W + 1)`

Memory adaptation:

- **Distance** = memory decay = `ln(1 + interval) / ease`
- **Width** = accessibility = `stability × ease`
- **ID** (Index of Difficulty) = `log₂(distance/accessibility + 1)`

Formula: `RT = a + b × ID`

### Gradient Descent Calibration (Pavlik & Anderson, 2008)

```text
error = RT_actual - RT_predicted
a_new = a + α × error
b_new = b + α × error × ID
```

Where:

- α is the learning rate (default: 0.05)
- ID is the Index of Difficulty (already computed as log₂ term)

### Retrievability (Memory Strength)

Uses logistic function based on response time:

```text
R = 1 / (1 + exp((RT - τ) / σ))
```

- When RT < τ (threshold): R > 0.5 (likely to recall)
- When RT = τ: R = 0.5 (50/50 chance)
- When RT > τ: R < 0.5 (unlikely to recall)

## API Reference

### ReviewInput

```rust
// Full input with both signals
let input = ReviewInput::new(Rating::Good, 2500);

// Rating only (backward compatible)
scheduler.review(card, Rating::Good);
```

### FittsScheduler

```rust
// Default scheduler
let mut scheduler = FittsScheduler::new();

// With custom learning rate
let mut scheduler = FittsScheduler::with_learning_rate(0.1);

// Predict difficulty
let (response_time, retrievability) = scheduler.predict(&card);

// Review with adaptation
let result = scheduler.review(card, input);

// Order cards by difficulty (hardest first)
scheduler.order_by_difficulty(&mut cards);
```

### ReviewResult

```rust
pub struct ReviewResult {
    pub card: CardState,           // Updated card state
    pub predicted_rt: f64,         // What model predicted
    pub actual_rt: Option<f64>,    // What actually happened
    pub prediction_error: Option<f64>,  // actual - predicted
    pub retrievability: f64,       // Memory strength (0-1)
    pub calibration: Option<CalibrationResult>,
}
```

### Rating & DifficultyLevel

Both use **4 values** for consistency:

| Rating | DifficultyLevel | SM-2 Quality | Status  |
| ------ | --------------- | ------------ | ------- |
| Again  | VeryHard        | 0            | Failure |
| Hard   | Hard            | 1            | Success |
| Good   | Medium          | 2            | Success |
| Easy   | Easy            | 3            | Success |

**Note**: Rating is subjective user input. Only `Again` resets the card. All others (`Hard`, `Good`, `Easy`) advance the card interval.

## Examples

```bash
# Basic usage
cargo run --example basic

# SM-2 interval progression
cargo run --example sm2_progression

# Fitts model predictions
cargo run --example fitts_model

# Adaptive calibration demo
cargo run --example adaptive_calibration
```

## Academic References

- **SM-2**: Wozniak, P.A. (1990). "Optimization of learning"
- **Fitts' Law**: Fitts, P.M. (1954). "The information capacity of the human motor system"
- **ACT-R Memory**: Anderson, J.R. (1993). "Rules of the Mind"
- **Adaptive Scheduling**: Pavlik, P.I. & Anderson, J.R. (2008). "Using a Model to Compute the Optimal Schedule of Practice"
- **Duolingo HLR**: Settles, B. & Meeder, B. (2016). "A Trainable Spaced Repetition Model for Language Learning"

## License

MIT OR Apache-2.0