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