Class: Fsrs::Scheduler

Inherits:
Object
  • Object
show all
Defined in:
lib/fsrs/fsrs.rb

Overview

Scheduler

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeScheduler

Returns a new instance of Scheduler.



211
212
213
214
215
# File 'lib/fsrs/fsrs.rb', line 211

def initialize
  @p = Parameters.new
  @decay = -0.5
  @factor = (0.9**(1 / @decay)) - 1
end

Instance Attribute Details

#decayObject

Returns the value of attribute decay.



209
210
211
# File 'lib/fsrs/fsrs.rb', line 209

def decay
  @decay
end

#factorObject

Returns the value of attribute factor.



209
210
211
# File 'lib/fsrs/fsrs.rb', line 209

def factor
  @factor
end

#pObject

Returns the value of attribute p.



209
210
211
# File 'lib/fsrs/fsrs.rb', line 209

def p
  @p
end

Instance Method Details

#forgetting_curve(elapsed_days, stability) ⇒ Object



303
304
305
# File 'lib/fsrs/fsrs.rb', line 303

def forgetting_curve(elapsed_days, stability)
  (1 + (factor * elapsed_days / stability))**decay
end

#init_difficulty(r) ⇒ Object



299
300
301
# File 'lib/fsrs/fsrs.rb', line 299

def init_difficulty(r)
  [[self.p.w[4] - (self.p.w[5] * (r - 3)), 1].max, 10].min
end

#init_ds(s) ⇒ Object



273
274
275
276
277
278
279
280
281
282
# File 'lib/fsrs/fsrs.rb', line 273

def init_ds(s)
  s.again.difficulty = init_difficulty(Rating::AGAIN)
  s.again.stability = init_stability(Rating::AGAIN)
  s.hard.difficulty = init_difficulty(Rating::HARD)
  s.hard.stability = init_stability(Rating::HARD)
  s.good.difficulty = init_difficulty(Rating::GOOD)
  s.good.stability = init_stability(Rating::GOOD)
  s.easy.difficulty = init_difficulty(Rating::EASY)
  s.easy.stability = init_stability(Rating::EASY)
end

#init_stability(r) ⇒ Object



295
296
297
# File 'lib/fsrs/fsrs.rb', line 295

def init_stability(r)
  [self.p.w[r - 1], 0.1].max
end

#mean_reversion(init, current) ⇒ Object



317
318
319
# File 'lib/fsrs/fsrs.rb', line 317

def mean_reversion(init, current)
  (self.p.w[7] * init) + ((1 - self.p.w[7]) * current)
end

#next_difficulty(d, r) ⇒ Object



312
313
314
315
# File 'lib/fsrs/fsrs.rb', line 312

def next_difficulty(d, r)
  next_d = d - (self.p.w[6] * (r - 3))
  mean_reversion(self.p.w[4], next_d).clamp(1, 10)
end

#next_ds(s, last_d, last_s, retrievability) ⇒ Object



284
285
286
287
288
289
290
291
292
293
# File 'lib/fsrs/fsrs.rb', line 284

def next_ds(s, last_d, last_s, retrievability)
  s.again.difficulty = next_difficulty(last_d, Rating::AGAIN)
  s.again.stability = next_forget_stability(last_d, last_s, retrievability)
  s.hard.difficulty = next_difficulty(last_d, Rating::HARD)
  s.hard.stability = next_recall_stability(last_d, last_s, retrievability, Rating::HARD)
  s.good.difficulty = next_difficulty(last_d, Rating::GOOD)
  s.good.stability = next_recall_stability(last_d, last_s, retrievability, Rating::GOOD)
  s.easy.difficulty = next_difficulty(last_d, Rating::EASY)
  s.easy.stability = next_recall_stability(last_d, last_s, retrievability, Rating::EASY)
end

#next_forget_stability(d, s, r) ⇒ Object



327
328
329
# File 'lib/fsrs/fsrs.rb', line 327

def next_forget_stability(d, s, r)
  self.p.w[11] * (d**-self.p.w[12]) * (((s + 1)**self.p.w[13]) - 1) * Math.exp((1 - r) * self.p.w[14])
end

#next_interval(s) ⇒ Object



307
308
309
310
# File 'lib/fsrs/fsrs.rb', line 307

def next_interval(s)
  new_interval = s / factor * ((self.p.request_retention**(1 / decay)) - 1)
  new_interval.round.clamp(1, self.p.maximum_interval)
end

#next_recall_stability(d, s, r, rating) ⇒ Object



321
322
323
324
325
# File 'lib/fsrs/fsrs.rb', line 321

def next_recall_stability(d, s, r, rating)
  hard_penalty = rating == Rating::HARD ? self.p.w[15] : 1
  easy_bonus = rating == Rating::EASY ? self.p.w[16] : 1
  s * (1 + (Math.exp(self.p.w[8]) * (11 - d) * (s**-self.p.w[9]) * (Math.exp((1 - r) * self.p.w[10]) - 1) * hard_penalty * easy_bonus))
end

#repeat(card, now) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/fsrs/fsrs.rb', line 217

def repeat(card, now)
  raise Fsrs::InvalidDateError unless now.utc?

  card = card.clone
  card.elapsed_days = if card.state == State::NEW
                        0
                      else
                        (now - card.last_review).to_i
                      end
  card.last_review = now
  card.reps += 1
  card_scheduler = CardScheduler.new(card)
  card_scheduler.update_state(card.state)

  case card.state
  when State::NEW
    schedule_new_state(card_scheduler, now)
  when State::LEARNING, State::RELEARNING
    schedule_learning_relearning_state(card_scheduler, now)
  when State::REVIEW
    schedule_review_state(card_scheduler, card, now)
  end
  card_scheduler.record_log(card, now)
end

#schedule_learning_relearning_state(s, now) ⇒ Object



252
253
254
255
256
257
# File 'lib/fsrs/fsrs.rb', line 252

def schedule_learning_relearning_state(s, now)
  hard_interval = 0
  good_interval = next_interval(s.good.stability)
  easy_interval = [next_interval(s.easy.stability), good_interval + 1].max
  s.schedule(now, hard_interval, good_interval, easy_interval)
end

#schedule_new_state(s, now) ⇒ Object



242
243
244
245
246
247
248
249
250
# File 'lib/fsrs/fsrs.rb', line 242

def schedule_new_state(s, now)
  init_ds(s)
  s.again.due = now + 60
  s.hard.due = now + (5 * 60)
  s.good.due = now + (10 * 60)
  easy_interval = next_interval(s.easy.stability)
  s.easy.scheduled_days = easy_interval
  s.easy.due = now + easy_interval.days
end

#schedule_review_state(s, card, now) ⇒ Object



259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/fsrs/fsrs.rb', line 259

def schedule_review_state(s, card, now)
  interval = card.elapsed_days
  last_d = card.difficulty
  last_s = card.stability
  retrievability = forgetting_curve(interval, last_s)
  next_ds(s, last_d, last_s, retrievability)
  hard_interval = next_interval(s.hard.stability)
  good_interval = next_interval(s.good.stability)
  hard_interval = [hard_interval, good_interval].min
  good_interval = [good_interval, hard_interval + 1].max
  easy_interval = [next_interval(s.easy.stability), good_interval + 1].max
  s.schedule(now, hard_interval, good_interval, easy_interval)
end