Skip to content

Commit

Permalink
Fix/clamp the params and s (#148)
Browse files Browse the repository at this point in the history
* Fix/clamp the params and s

* bump version to 4.6.1
  • Loading branch information
ishiko732 authored Jan 22, 2025
1 parent ad71683 commit 4998232
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 22 deletions.
15 changes: 15 additions & 0 deletions __tests__/default.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
CLAMP_PARAMETERS,
createEmptyCard,
default_enable_fuzz,
default_maximum_interval,
Expand Down Expand Up @@ -45,6 +46,20 @@ describe('default params', () => {
0.05, 0.34, 1.26, 0.29, 2.61, 0.0, 0.0,
])
})

it('clamp w to limit the minimum', () => {
const w = Array.from({ length: 19 }, (_) => 0)
const params = generatorParameters({ w })
const w_min = CLAMP_PARAMETERS.map((x) => x[0])
expect(params.w).toEqual(w_min)
})

it('clamp w to limit the maximum', () => {
const w = Array.from({ length: 19 }, (_) => Number.MAX_VALUE)
const params = generatorParameters({ w })
const w_max = CLAMP_PARAMETERS.map((x) => x[1])
expect(params.w).toEqual(w_max)
})
})

describe('default Card', () => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ts-fsrs",
"version": "4.6.0",
"version": "4.6.1",
"description": "ts-fsrs is a versatile package based on TypeScript that supports ES modules, CommonJS, and UMD. It implements the Free Spaced Repetition Scheduler (FSRS) algorithm, enabling developers to integrate FSRS into their flashcard applications to enhance the user learning experience.",
"main": "dist/index.cjs",
"umd": "dist/index.umd.js",
Expand Down
16 changes: 9 additions & 7 deletions src/fsrs/algorithm.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { generatorParameters } from './default'
import { generatorParameters, S_MIN } from './default'
import { FSRSParameters, FSRSState, Grade, Rating } from './models'
import type { int } from './types'
import { clamp, get_fuzz_range } from './help'
Expand Down Expand Up @@ -225,7 +225,7 @@ export class FSRSAlgorithm {
(Math.exp((1 - r) * this.param.w[10]) - 1) *
hard_penalty *
easy_bound),
0.01,
S_MIN,
36500.0
).toFixed(8)
}
Expand All @@ -246,7 +246,7 @@ export class FSRSAlgorithm {
Math.pow(d, -this.param.w[12]) *
(Math.pow(s + 1, this.param.w[13]) - 1) *
Math.exp((1 - r) * this.param.w[14]),
0.01,
S_MIN,
36500.0
).toFixed(8)
}
Expand All @@ -260,7 +260,7 @@ export class FSRSAlgorithm {
next_short_term_stability(s: number, g: Grade): number {
return +clamp(
s * Math.exp(this.param.w[17] * (g - 3 + this.param.w[18])),
0.01,
S_MIN,
36500.0
).toFixed(8)
}
Expand Down Expand Up @@ -307,8 +307,10 @@ export class FSRSAlgorithm {
stability: s,
}
}
if (d < 1 || s < 0.01) {
throw new Error(`Invalid memory state { difficulty: ${d}, stability: ${s} }`)
if (d < 1 || s < S_MIN) {
throw new Error(
`Invalid memory state { difficulty: ${d}, stability: ${s} }`
)
}
const r = this.forgetting_curve(t, s)
const s_after_success = this.next_recall_stability(d, s, r, g)
Expand All @@ -322,7 +324,7 @@ export class FSRSAlgorithm {
w_18 = this.param.w[18]
}
const next_s_min = s / Math.exp(w_17 * w_18)
new_s = clamp(next_s_min, 0.01, s_after_fail)
new_s = clamp(+next_s_min.toFixed(8), S_MIN, s_after_fail)
}
if (t === 0 && this.param.enable_short_term) {
new_s = s_after_short_term
Expand Down
30 changes: 29 additions & 1 deletion src/fsrs/default.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Card, DateInput, FSRSParameters, State } from './models'
import { TypeConvert } from './convert'
import { version } from '../../package.json';
import { version } from '../../package.json'
import { clamp } from './help'

export const default_request_retention = 0.9
export const default_maximum_interval = 36500
Expand All @@ -14,6 +15,30 @@ export const default_enable_short_term = true

export const FSRSVersion: string = `v${version} using FSRS-5.0`

export const S_MIN = 0.01
export const INIT_S_MAX = 100.0
export const CLAMP_PARAMETERS: Array<[number /**min */, number /**max */]> = [
[S_MIN, INIT_S_MAX] /** initial stability (Again) */,
[S_MIN, INIT_S_MAX] /** initial stability (Hard) */,
[S_MIN, INIT_S_MAX] /** initial stability (Good) */,
[S_MIN, INIT_S_MAX] /** initial stability (Easy) */,
[1.0, 10.0] /** initial difficulty (Good) */,
[0.001, 4.0] /** initial difficulty (multiplier) */,
[0.001, 4.0] /** difficulty (multiplier) */,
[0.001, 0.75] /** difficulty (multiplier) */,
[0.0, 4.5] /** stability (exponent) */,
[0.0, 0.8] /** stability (negative power) */,
[0.001, 3.5] /** stability (exponent) */,
[0.001, 5.0] /** fail stability (multiplier) */,
[0.001, 0.25] /** fail stability (negative power) */,
[0.001, 0.9] /** fail stability (power) */,
[0.0, 4.0] /** fail stability (exponent) */,
[0.0, 1.0] /** stability (multiplier for Hard) */,
[1.0, 6.0] /** stability (multiplier for Easy) */,
[0.0, 2.0] /** short-term stability (exponent) */,
[0.0, 2.0] /** short-term stability (exponent) */,
]

export const generatorParameters = (
props?: Partial<FSRSParameters>
): FSRSParameters => {
Expand All @@ -29,6 +54,9 @@ export const generatorParameters = (
console.debug('[FSRS V5]auto fill w to 19 length')
}
}
w = w.map((w, index) =>
clamp(w, CLAMP_PARAMETERS[index][0], CLAMP_PARAMETERS[index][1])
)
return {
request_retention: props?.request_retention || default_request_retention,
maximum_interval: props?.maximum_interval || default_maximum_interval,
Expand Down
14 changes: 7 additions & 7 deletions src/fsrs/impl/basic_scheduler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { AbstractScheduler } from '../abstract_scheduler'
import { TypeConvert } from '../convert'
import { S_MIN } from '../default'
import { clamp } from '../help'
import {
type Card,
type Grade,
Expand Down Expand Up @@ -191,14 +193,12 @@ export default class BasicScheduler extends AbstractScheduler {
Math.exp(
this.algorithm.parameters.w[17] * this.algorithm.parameters.w[18]
)
next_again.stability = Math.min(
+nextSMin.toFixed(8),
this.algorithm.next_forget_stability(
difficulty,
stability,
retrievability
)
const s_after_fail = this.algorithm.next_forget_stability(
difficulty,
stability,
retrievability
)
next_again.stability = clamp(+nextSMin.toFixed(8), S_MIN, s_after_fail)

next_hard.difficulty = this.algorithm.next_difficulty(
difficulty,
Expand Down
12 changes: 6 additions & 6 deletions src/fsrs/impl/long_term_scheduler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { AbstractScheduler } from '../abstract_scheduler'
import { TypeConvert } from '../convert'
import { S_MIN } from '../default'
import { clamp } from '../help'
import {
type Card,
type Grade,
Expand Down Expand Up @@ -112,14 +114,12 @@ export default class LongTermScheduler extends AbstractScheduler {
difficulty,
Rating.Again
)
next_again.stability = Math.min(
const s_after_fail = this.algorithm.next_forget_stability(
difficulty,
stability,
this.algorithm.next_forget_stability(
difficulty,
stability,
retrievability
)
retrievability
)
next_again.stability = clamp(stability, S_MIN, s_after_fail)

next_hard.difficulty = this.algorithm.next_difficulty(
difficulty,
Expand Down

0 comments on commit 4998232

Please sign in to comment.