ts-fsrs 5.2.2 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.umd.js CHANGED
@@ -76,8 +76,12 @@
76
76
  throw new Error(`Invalid state:[${value}]`);
77
77
  }
78
78
  static time(value) {
79
- if (typeof value === "object" && value instanceof Date) {
79
+ if (value instanceof Date) {
80
80
  return value;
81
+ }
82
+ const date = new Date(value);
83
+ if (typeof value === "object" && value !== null && !Number.isNaN(Date.parse(value) || +date)) {
84
+ return date;
81
85
  } else if (typeof value === "string") {
82
86
  const timestamp = Date.parse(value);
83
87
  if (!Number.isNaN(timestamp)) {
@@ -100,15 +104,19 @@
100
104
  }
101
105
  }
102
106
 
107
+ /* istanbul ignore next -- @preserve */
103
108
  Date.prototype.scheduler = function(t, isDay) {
104
109
  return date_scheduler(this, t, isDay);
105
110
  };
111
+ /* istanbul ignore next -- @preserve */
106
112
  Date.prototype.diff = function(pre, unit) {
107
113
  return date_diff(this, pre, unit);
108
114
  };
115
+ /* istanbul ignore next -- @preserve */
109
116
  Date.prototype.format = function() {
110
117
  return formatDate(this);
111
118
  };
119
+ /* istanbul ignore next -- @preserve */
112
120
  Date.prototype.dueFormat = function(last_review, unit, timeUnit) {
113
121
  return show_diff_message(this, last_review, unit, timeUnit);
114
122
  };
@@ -168,12 +176,15 @@
168
176
  }
169
177
  return `${Math.floor(diff)}${unit ? timeUnit[i] : ""}`;
170
178
  }
179
+ /* istanbul ignore next -- @preserve */
171
180
  function fixDate(value) {
172
181
  return TypeConvert.time(value);
173
182
  }
183
+ /* istanbul ignore next -- @preserve */
174
184
  function fixState(value) {
175
185
  return TypeConvert.state(value);
176
186
  }
187
+ /* istanbul ignore next -- @preserve */
177
188
  function fixRating(value) {
178
189
  return TypeConvert.rating(value);
179
190
  }
@@ -217,6 +228,10 @@
217
228
  function clamp(value, min, max) {
218
229
  return Math.min(Math.max(value, min), max);
219
230
  }
231
+ function roundTo(num, decimals) {
232
+ const factor = 10 ** decimals;
233
+ return Math.round(num * factor) / factor;
234
+ }
220
235
  function dateDiffInDays(last, cur) {
221
236
  const utc1 = Date.UTC(
222
237
  last.getUTCFullYear(),
@@ -496,7 +511,7 @@
496
511
  return prng;
497
512
  }
498
513
 
499
- const version="5.2.2";
514
+ const version="5.3.0";
500
515
 
501
516
  const default_request_retention = 0.9;
502
517
  const default_maximum_interval = 36500;
@@ -674,11 +689,11 @@
674
689
  const computeDecayFactor = (decayOrParams) => {
675
690
  const decay = typeof decayOrParams === "number" ? -decayOrParams : -decayOrParams[20];
676
691
  const factor = Math.exp(Math.pow(decay, -1) * Math.log(0.9)) - 1;
677
- return { decay, factor: +factor.toFixed(8) };
692
+ return { decay, factor: roundTo(factor, 8) };
678
693
  };
679
694
  function forgetting_curve(decayOrParams, elapsed_days, stability) {
680
695
  const { decay, factor } = computeDecayFactor(decayOrParams);
681
- return +Math.pow(1 + factor * elapsed_days / stability, decay).toFixed(8);
696
+ return roundTo(Math.pow(1 + factor * elapsed_days / stability, decay), 8);
682
697
  }
683
698
  class FSRSAlgorithm {
684
699
  constructor(params) {
@@ -720,7 +735,7 @@
720
735
  throw new Error("Requested retention rate should be in the range (0,1]");
721
736
  }
722
737
  const { decay, factor } = computeDecayFactor(this.param.w);
723
- return +((Math.pow(request_retention, 1 / decay) - 1) / factor).toFixed(8);
738
+ return roundTo((Math.pow(request_retention, 1 / decay) - 1) / factor, 8);
724
739
  }
725
740
  /**
726
741
  * Get the parameters of the algorithm.
@@ -787,8 +802,9 @@
787
802
  * @return {number} Difficulty $$D \in [1,10]$$
788
803
  */
789
804
  init_difficulty(g) {
790
- const d = this.param.w[4] - Math.exp((g - 1) * this.param.w[5]) + 1;
791
- return +d.toFixed(8);
805
+ const w = this.param.w;
806
+ const d = w[4] - Math.exp((g - 1) * w[5]) + 1;
807
+ return roundTo(d, 8);
792
808
  }
793
809
  /**
794
810
  * If fuzzing is disabled or ivl is less than 2.5, it returns the original interval.
@@ -823,7 +839,7 @@
823
839
  * @see https://github.com/open-spaced-repetition/fsrs4anki/issues/697
824
840
  */
825
841
  linear_damping(delta_d, old_d) {
826
- return +(delta_d * (10 - old_d) / 9).toFixed(8);
842
+ return roundTo(delta_d * (10 - old_d) / 9, 8);
827
843
  }
828
844
  /**
829
845
  * The formula used is :
@@ -851,9 +867,8 @@
851
867
  * @return {number} difficulty
852
868
  */
853
869
  mean_reversion(init, current) {
854
- return +(this.param.w[7] * init + (1 - this.param.w[7]) * current).toFixed(
855
- 8
856
- );
870
+ const w = this.param.w;
871
+ return roundTo(w[7] * init + (1 - w[7]) * current, 8);
857
872
  }
858
873
  /**
859
874
  * The formula used is :
@@ -865,13 +880,17 @@
865
880
  * @return {number} S^\prime_r new stability after recall
866
881
  */
867
882
  next_recall_stability(d, s, r, g) {
868
- const hard_penalty = Rating.Hard === g ? this.param.w[15] : 1;
869
- const easy_bound = Rating.Easy === g ? this.param.w[16] : 1;
870
- return +clamp(
871
- s * (1 + Math.exp(this.param.w[8]) * (11 - d) * Math.pow(s, -this.param.w[9]) * (Math.exp((1 - r) * this.param.w[10]) - 1) * hard_penalty * easy_bound),
872
- S_MIN,
873
- 36500
874
- ).toFixed(8);
883
+ const w = this.param.w;
884
+ const hard_penalty = Rating.Hard === g ? w[15] : 1;
885
+ const easy_bound = Rating.Easy === g ? w[16] : 1;
886
+ return roundTo(
887
+ clamp(
888
+ s * (1 + Math.exp(w[8]) * (11 - d) * Math.pow(s, -w[9]) * (Math.exp((1 - r) * w[10]) - 1) * hard_penalty * easy_bound),
889
+ S_MIN,
890
+ 36500
891
+ ),
892
+ 8
893
+ );
875
894
  }
876
895
  /**
877
896
  * The formula used is :
@@ -884,11 +903,15 @@
884
903
  * @return {number} S^\prime_f new stability after forgetting
885
904
  */
886
905
  next_forget_stability(d, s, r) {
887
- return +clamp(
888
- this.param.w[11] * Math.pow(d, -this.param.w[12]) * (Math.pow(s + 1, this.param.w[13]) - 1) * Math.exp((1 - r) * this.param.w[14]),
889
- S_MIN,
890
- 36500
891
- ).toFixed(8);
906
+ const w = this.param.w;
907
+ return roundTo(
908
+ clamp(
909
+ w[11] * Math.pow(d, -w[12]) * (Math.pow(s + 1, w[13]) - 1) * Math.exp((1 - r) * w[14]),
910
+ S_MIN,
911
+ 36500
912
+ ),
913
+ 8
914
+ );
892
915
  }
893
916
  /**
894
917
  * The formula used is :
@@ -897,9 +920,10 @@
897
920
  * @param {Grade} g Grade (Rating[0.again,1.hard,2.good,3.easy])
898
921
  */
899
922
  next_short_term_stability(s, g) {
900
- const sinc = Math.pow(s, -this.param.w[19]) * Math.exp(this.param.w[17] * (g - 3 + this.param.w[18]));
901
- const maskedSinc = g >= 3 ? Math.max(sinc, 1) : sinc;
902
- return +clamp(s * maskedSinc, S_MIN, 36500).toFixed(8);
923
+ const w = this.param.w;
924
+ const sinc = Math.pow(s, -w[19]) * Math.exp(w[17] * (g - 3 + w[18]));
925
+ const maskedSinc = g >= Rating.Hard ? Math.max(sinc, 1) : sinc;
926
+ return roundTo(clamp(s * maskedSinc, S_MIN, 36500), 8);
903
927
  }
904
928
  /**
905
929
  * Calculates the next state of memory based on the current state, time elapsed, and grade.
@@ -907,9 +931,10 @@
907
931
  * @param memory_state - The current state of memory, which can be null.
908
932
  * @param t - The time elapsed since the last review.
909
933
  * @param {Rating} g Grade (Rating[0.Manual,1.Again,2.Hard,3.Good,4.Easy])
934
+ * @param r - Optional retrievability value. If not provided, it will be calculated.
910
935
  * @returns The next state of memory with updated difficulty and stability.
911
936
  */
912
- next_state(memory_state, t, g) {
937
+ next_state(memory_state, t, g, r) {
913
938
  const { difficulty: d, stability: s } = memory_state != null ? memory_state : {
914
939
  difficulty: 0,
915
940
  stability: 0
@@ -937,22 +962,22 @@
937
962
  `Invalid memory state { difficulty: ${d}, stability: ${s} }`
938
963
  );
939
964
  }
940
- const r = this.forgetting_curve(t, s);
941
- const s_after_success = this.next_recall_stability(d, s, r, g);
942
- const s_after_fail = this.next_forget_stability(d, s, r);
943
- const s_after_short_term = this.next_short_term_stability(s, g);
944
- let new_s = s_after_success;
945
- if (g === 1) {
965
+ const w = this.param.w;
966
+ r = typeof r === "number" ? r : this.forgetting_curve(t, s);
967
+ let new_s;
968
+ if (t === 0 && this.param.enable_short_term) {
969
+ new_s = this.next_short_term_stability(s, g);
970
+ } else if (g === 1) {
971
+ const s_after_fail = this.next_forget_stability(d, s, r);
946
972
  let [w_17, w_18] = [0, 0];
947
973
  if (this.param.enable_short_term) {
948
- w_17 = this.param.w[17];
949
- w_18 = this.param.w[18];
974
+ w_17 = w[17];
975
+ w_18 = w[18];
950
976
  }
951
977
  const next_s_min = s / Math.exp(w_17 * w_18);
952
- new_s = clamp(+next_s_min.toFixed(8), S_MIN, s_after_fail);
953
- }
954
- if (t === 0 && this.param.enable_short_term) {
955
- new_s = s_after_short_term;
978
+ new_s = clamp(roundTo(next_s_min, 8), S_MIN, s_after_fail);
979
+ } else {
980
+ new_s = this.next_recall_stability(d, s, r, g);
956
981
  }
957
982
  const new_d = this.next_difficulty(d, g);
958
983
  return { difficulty: new_d, stability: new_s };
@@ -1041,9 +1066,7 @@
1041
1066
  if (exist) {
1042
1067
  return exist;
1043
1068
  }
1044
- const next = TypeConvert.card(this.current);
1045
- next.difficulty = clamp(this.algorithm.init_difficulty(grade), 1, 10);
1046
- next.stability = this.algorithm.init_stability(grade);
1069
+ const next = this.next_ds(this.elapsed_days, grade);
1047
1070
  this.applyLearningSteps(next, grade, State.Learning);
1048
1071
  const item = {
1049
1072
  card: next,
@@ -1057,14 +1080,11 @@
1057
1080
  if (exist) {
1058
1081
  return exist;
1059
1082
  }
1060
- const { state, difficulty, stability } = this.last;
1061
- const next = TypeConvert.card(this.current);
1062
- next.difficulty = this.algorithm.next_difficulty(difficulty, grade);
1063
- next.stability = this.algorithm.next_short_term_stability(stability, grade);
1083
+ const next = this.next_ds(this.elapsed_days, grade);
1064
1084
  this.applyLearningSteps(
1065
1085
  next,
1066
1086
  grade,
1067
- state
1087
+ this.last.state
1068
1088
  /** Learning or Relearning */
1069
1089
  );
1070
1090
  const item = {
@@ -1080,21 +1100,14 @@
1080
1100
  return exist;
1081
1101
  }
1082
1102
  const interval = this.elapsed_days;
1083
- const { difficulty, stability } = this.last;
1084
- const retrievability = this.algorithm.forgetting_curve(interval, stability);
1085
- const next_again = TypeConvert.card(this.current);
1086
- const next_hard = TypeConvert.card(this.current);
1087
- const next_good = TypeConvert.card(this.current);
1088
- const next_easy = TypeConvert.card(this.current);
1089
- this.next_ds(
1090
- next_again,
1091
- next_hard,
1092
- next_good,
1093
- next_easy,
1094
- difficulty,
1095
- stability,
1096
- retrievability
1103
+ const retrievability = this.algorithm.forgetting_curve(
1104
+ interval,
1105
+ this.current.stability
1097
1106
  );
1107
+ const next_again = this.next_ds(interval, Rating.Again, retrievability);
1108
+ const next_hard = this.next_ds(interval, Rating.Hard, retrievability);
1109
+ const next_good = this.next_ds(interval, Rating.Good, retrievability);
1110
+ const next_easy = this.next_ds(interval, Rating.Easy, retrievability);
1098
1111
  this.next_interval(next_hard, next_good, next_easy, interval);
1099
1112
  this.next_state(next_hard, next_good, next_easy);
1100
1113
  this.applyLearningSteps(next_again, Rating.Again, State.Relearning);
@@ -1124,50 +1137,20 @@
1124
1137
  /**
1125
1138
  * Review next_ds
1126
1139
  */
1127
- next_ds(next_again, next_hard, next_good, next_easy, difficulty, stability, retrievability) {
1128
- next_again.difficulty = this.algorithm.next_difficulty(
1129
- difficulty,
1130
- Rating.Again
1131
- );
1132
- const nextSMin = stability / Math.exp(
1133
- this.algorithm.parameters.w[17] * this.algorithm.parameters.w[18]
1134
- );
1135
- const s_after_fail = this.algorithm.next_forget_stability(
1136
- difficulty,
1137
- stability,
1138
- retrievability
1139
- );
1140
- next_again.stability = clamp(+nextSMin.toFixed(8), S_MIN, s_after_fail);
1141
- next_hard.difficulty = this.algorithm.next_difficulty(
1142
- difficulty,
1143
- Rating.Hard
1144
- );
1145
- next_hard.stability = this.algorithm.next_recall_stability(
1146
- difficulty,
1147
- stability,
1148
- retrievability,
1149
- Rating.Hard
1150
- );
1151
- next_good.difficulty = this.algorithm.next_difficulty(
1152
- difficulty,
1153
- Rating.Good
1154
- );
1155
- next_good.stability = this.algorithm.next_recall_stability(
1156
- difficulty,
1157
- stability,
1158
- retrievability,
1159
- Rating.Good
1160
- );
1161
- next_easy.difficulty = this.algorithm.next_difficulty(
1162
- difficulty,
1163
- Rating.Easy
1164
- );
1165
- next_easy.stability = this.algorithm.next_recall_stability(
1166
- difficulty,
1167
- stability,
1168
- retrievability,
1169
- Rating.Easy
1140
+ next_ds(t, g, r) {
1141
+ const next_state = this.algorithm.next_state(
1142
+ {
1143
+ difficulty: this.current.difficulty,
1144
+ stability: this.current.stability
1145
+ },
1146
+ t,
1147
+ g,
1148
+ r
1170
1149
  );
1150
+ const card = TypeConvert.card(this.current);
1151
+ card.difficulty = next_state.difficulty;
1152
+ card.stability = next_state.stability;
1153
+ return card;
1171
1154
  }
1172
1155
  /**
1173
1156
  * Review next_interval
@@ -1210,12 +1193,11 @@
1210
1193
  }
1211
1194
  this.current.scheduled_days = 0;
1212
1195
  this.current.elapsed_days = 0;
1213
- const next_again = TypeConvert.card(this.current);
1214
- const next_hard = TypeConvert.card(this.current);
1215
- const next_good = TypeConvert.card(this.current);
1216
- const next_easy = TypeConvert.card(this.current);
1217
- this.init_ds(next_again, next_hard, next_good, next_easy);
1218
1196
  const first_interval = 0;
1197
+ const next_again = this.next_ds(first_interval, Rating.Again);
1198
+ const next_hard = this.next_ds(first_interval, Rating.Hard);
1199
+ const next_good = this.next_ds(first_interval, Rating.Good);
1200
+ const next_easy = this.next_ds(first_interval, Rating.Easy);
1219
1201
  this.next_interval(
1220
1202
  next_again,
1221
1203
  next_hard,
@@ -1227,31 +1209,20 @@
1227
1209
  this.update_next(next_again, next_hard, next_good, next_easy);
1228
1210
  return this.next.get(grade);
1229
1211
  }
1230
- init_ds(next_again, next_hard, next_good, next_easy) {
1231
- next_again.difficulty = clamp(
1232
- this.algorithm.init_difficulty(Rating.Again),
1233
- 1,
1234
- 10
1235
- );
1236
- next_again.stability = this.algorithm.init_stability(Rating.Again);
1237
- next_hard.difficulty = clamp(
1238
- this.algorithm.init_difficulty(Rating.Hard),
1239
- 1,
1240
- 10
1241
- );
1242
- next_hard.stability = this.algorithm.init_stability(Rating.Hard);
1243
- next_good.difficulty = clamp(
1244
- this.algorithm.init_difficulty(Rating.Good),
1245
- 1,
1246
- 10
1247
- );
1248
- next_good.stability = this.algorithm.init_stability(Rating.Good);
1249
- next_easy.difficulty = clamp(
1250
- this.algorithm.init_difficulty(Rating.Easy),
1251
- 1,
1252
- 10
1212
+ next_ds(t, g, r) {
1213
+ const next_state = this.algorithm.next_state(
1214
+ {
1215
+ difficulty: this.current.difficulty,
1216
+ stability: this.current.stability
1217
+ },
1218
+ t,
1219
+ g,
1220
+ r
1253
1221
  );
1254
- next_easy.stability = this.algorithm.init_stability(Rating.Easy);
1222
+ const card = TypeConvert.card(this.current);
1223
+ card.difficulty = next_state.difficulty;
1224
+ card.stability = next_state.stability;
1225
+ return card;
1255
1226
  }
1256
1227
  /**
1257
1228
  * @see https://github.com/open-spaced-repetition/ts-fsrs/issues/98#issuecomment-2241923194
@@ -1265,72 +1236,20 @@
1265
1236
  return exist;
1266
1237
  }
1267
1238
  const interval = this.elapsed_days;
1268
- const { difficulty, stability } = this.last;
1269
- const retrievability = this.algorithm.forgetting_curve(interval, stability);
1270
- const next_again = TypeConvert.card(this.current);
1271
- const next_hard = TypeConvert.card(this.current);
1272
- const next_good = TypeConvert.card(this.current);
1273
- const next_easy = TypeConvert.card(this.current);
1274
- this.next_ds(
1275
- next_again,
1276
- next_hard,
1277
- next_good,
1278
- next_easy,
1279
- difficulty,
1280
- stability,
1281
- retrievability
1239
+ const retrievability = this.algorithm.forgetting_curve(
1240
+ interval,
1241
+ this.current.stability
1282
1242
  );
1243
+ const next_again = this.next_ds(interval, Rating.Again, retrievability);
1244
+ const next_hard = this.next_ds(interval, Rating.Hard, retrievability);
1245
+ const next_good = this.next_ds(interval, Rating.Good, retrievability);
1246
+ const next_easy = this.next_ds(interval, Rating.Easy, retrievability);
1283
1247
  this.next_interval(next_again, next_hard, next_good, next_easy, interval);
1284
1248
  this.next_state(next_again, next_hard, next_good, next_easy);
1285
1249
  next_again.lapses += 1;
1286
1250
  this.update_next(next_again, next_hard, next_good, next_easy);
1287
1251
  return this.next.get(grade);
1288
1252
  }
1289
- /**
1290
- * Review next_ds
1291
- */
1292
- next_ds(next_again, next_hard, next_good, next_easy, difficulty, stability, retrievability) {
1293
- next_again.difficulty = this.algorithm.next_difficulty(
1294
- difficulty,
1295
- Rating.Again
1296
- );
1297
- const s_after_fail = this.algorithm.next_forget_stability(
1298
- difficulty,
1299
- stability,
1300
- retrievability
1301
- );
1302
- next_again.stability = clamp(stability, S_MIN, s_after_fail);
1303
- next_hard.difficulty = this.algorithm.next_difficulty(
1304
- difficulty,
1305
- Rating.Hard
1306
- );
1307
- next_hard.stability = this.algorithm.next_recall_stability(
1308
- difficulty,
1309
- stability,
1310
- retrievability,
1311
- Rating.Hard
1312
- );
1313
- next_good.difficulty = this.algorithm.next_difficulty(
1314
- difficulty,
1315
- Rating.Good
1316
- );
1317
- next_good.stability = this.algorithm.next_recall_stability(
1318
- difficulty,
1319
- stability,
1320
- retrievability,
1321
- Rating.Good
1322
- );
1323
- next_easy.difficulty = this.algorithm.next_difficulty(
1324
- difficulty,
1325
- Rating.Easy
1326
- );
1327
- next_easy.stability = this.algorithm.next_recall_stability(
1328
- difficulty,
1329
- stability,
1330
- retrievability,
1331
- Rating.Easy
1332
- );
1333
- }
1334
1253
  /**
1335
1254
  * Review/New next_interval
1336
1255
  */
@@ -2055,6 +1974,7 @@
2055
1974
  exports.generatorParameters = generatorParameters;
2056
1975
  exports.get_fuzz_range = get_fuzz_range;
2057
1976
  exports.migrateParameters = migrateParameters;
1977
+ exports.roundTo = roundTo;
2058
1978
  exports.show_diff_message = show_diff_message;
2059
1979
 
2060
1980
  }));