From e411f68bafb80213207f7e89b798c01d8ea90432 Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Sun, 10 Mar 2024 14:50:33 +0800 Subject: [PATCH] Feat/minimal workload for optimal retention (#89) * Feat/minimal workload for optimal retention * remove unnecessary arguments * bump version --- pyproject.toml | 2 +- src/fsrs_optimizer/fsrs_optimizer.py | 14 +++++--------- src/fsrs_optimizer/fsrs_simulator.py | 26 +++++++++++++------------- tests/__init__.py | 0 tests/simulator_test.py | 27 +++++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/simulator_test.py diff --git a/pyproject.toml b/pyproject.toml index ad741c8..30b12ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "FSRS-Optimizer" -version = "4.26.0" +version = "4.26.1" readme = "README.md" dependencies = [ "matplotlib>=3.7.0", diff --git a/src/fsrs_optimizer/fsrs_optimizer.py b/src/fsrs_optimizer/fsrs_optimizer.py index e796de7..11110d8 100644 --- a/src/fsrs_optimizer/fsrs_optimizer.py +++ b/src/fsrs_optimizer/fsrs_optimizer.py @@ -1200,13 +1200,9 @@ def predict_memory_states(self): def find_optimal_retention( self, - deck_size=10000, learn_span=365, - max_cost_perday=1800, max_ivl=36500, - learn_limit_perday=math.inf, - review_limit_perday=math.inf, - loss_aversion=2.5, + loss_aversion=1, verbose=True, ): """should not be called before predict_memory_states""" @@ -1261,11 +1257,11 @@ def find_optimal_retention( simulate_config = { "w": self.w, - "deck_size": deck_size, + "deck_size": learn_span * 10, "learn_span": learn_span, - "max_cost_perday": max_cost_perday, - "learn_limit_perday": learn_limit_perday, - "review_limit_perday": review_limit_perday, + "max_cost_perday": math.inf, + "learn_limit_perday": 10, + "review_limit_perday": math.inf, "max_ivl": max_ivl, "recall_costs": self.recall_costs, "forget_cost": forget_cost, diff --git a/src/fsrs_optimizer/fsrs_simulator.py b/src/fsrs_optimizer/fsrs_simulator.py index a043b84..651a083 100644 --- a/src/fsrs_optimizer/fsrs_simulator.py +++ b/src/fsrs_optimizer/fsrs_simulator.py @@ -33,7 +33,7 @@ def next_interval(s, r): ] col = {key: i for i, key in enumerate(columns)} -SAMPLE_SIZE = 5 +SAMPLE_SIZE = 4 def simulate( @@ -208,7 +208,7 @@ def sample( ): memorization = [] for i in range(SAMPLE_SIZE): - _, _, _, memorized_cnt_per_day, _ = simulate( + _, _, _, memorized_cnt_per_day, cost_per_day = simulate( w, request_retention=r, deck_size=deck_size, @@ -224,7 +224,7 @@ def sample( review_rating_prob=review_rating_prob, seed=42 + i, ) - memorization.append(memorized_cnt_per_day[-1]) + memorization.append(cost_per_day.sum() / memorized_cnt_per_day[-1]) return np.mean(memorization) @@ -236,15 +236,15 @@ def bracket(xa=0.75, xb=0.95, maxiter=20, **kwargs): gold = 1.6180339 verysmall_num = 1e-21 - fa = -sample(xa, **kwargs) - fb = -sample(xb, **kwargs) + fa = sample(xa, **kwargs) + fb = sample(xb, **kwargs) funccalls = 2 if fa < fb: # Switch so fa > fb xa, xb = xb, xa fa, fb = fb, fa xc = max(min(xb + gold * (xb - xa), u_lim), l_lim) - fc = -sample(xc, **kwargs) + fc = sample(xc, **kwargs) funccalls += 1 iter = 0 @@ -265,7 +265,7 @@ def bracket(xa=0.75, xb=0.95, maxiter=20, **kwargs): iter += 1 if (w - xc) * (xb - w) > 0.0: - fw = -sample(w, **kwargs) + fw = sample(w, **kwargs) funccalls += 1 if fw < fc: xa = max(min(xb, u_lim), l_lim) @@ -278,14 +278,14 @@ def bracket(xa=0.75, xb=0.95, maxiter=20, **kwargs): fc = fw break w = max(min(xc + gold * (xc - xb), u_lim), l_lim) - fw = -sample(w, **kwargs) + fw = sample(w, **kwargs) funccalls += 1 elif (w - wlim) * (wlim - xc) >= 0.0: w = wlim - fw = -sample(w, **kwargs) + fw = sample(w, **kwargs) funccalls += 1 elif (w - wlim) * (xc - w) > 0.0: - fw = -sample(w, **kwargs) + fw = sample(w, **kwargs) funccalls += 1 if fw < fc: xb = max(min(xc, u_lim), l_lim) @@ -293,11 +293,11 @@ def bracket(xa=0.75, xb=0.95, maxiter=20, **kwargs): w = max(min(xc + gold * (xc - xb), u_lim), l_lim) fb = fc fc = fw - fw = -sample(w, **kwargs) + fw = sample(w, **kwargs) funccalls += 1 else: w = max(min(xc + gold * (xc - xb), u_lim), l_lim) - fw = -sample(w, **kwargs) + fw = sample(w, **kwargs) funccalls += 1 xa = max(min(xb, u_lim), l_lim) xb = max(min(xc, u_lim), l_lim) @@ -382,7 +382,7 @@ def brent(tol=0.01, maxiter=20, **kwargs): u = x - tol1 else: u = x + rat - fu = -sample(u, **kwargs) # calculate new output value + fu = sample(u, **kwargs) # calculate new output value funccalls += 1 if fu > fx: # if it's bigger than current diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/simulator_test.py b/tests/simulator_test.py new file mode 100644 index 0000000..595a265 --- /dev/null +++ b/tests/simulator_test.py @@ -0,0 +1,27 @@ +from src.fsrs_optimizer import * + + +class Test_Simulator: + def test_simulate(self): + (_, _, _, memorized_cnt_per_day, _) = simulate( + w=DEFAULT_WEIGHT, forget_cost=125 + ) + assert memorized_cnt_per_day[-1] == 3145.3779679589484 + + def test_optimal_retention(self): + default_params = { + "w": DEFAULT_WEIGHT, + "deck_size": 10000, + "learn_span": 1000, + "max_cost_perday": math.inf, + "learn_limit_perday": 10, + "review_limit_perday": math.inf, + "max_ivl": 36500, + "recall_costs": np.array([14, 10, 6]), + "forget_cost": 50, + "learn_cost": 20, + "first_rating_prob": np.array([0.15, 0.2, 0.6, 0.05]), + "review_rating_prob": np.array([0.3, 0.6, 0.1]), + } + r = optimal_retention(**default_params) + assert r == 0.7791796050312