Feat/FSRS-6 (#3929)

* Feat/FSRS-6

* update comment

* add decay to Card

* ./ninja fix:minilints

* pass check

* fix NaN in evaluation

* remove console

* decay should fallback to 0.5 when it's None.

* Update SimulatorModal.svelte

* Update a few comments

* Update FSRS decay defaults to use constants for better maintainability and clarity

* Update rslib/src/storage/card/data.rs
This commit is contained in:
Jarrett Ye 2025-04-25 14:44:34 +08:00 committed by GitHub
parent 1e6c8b2006
commit e096c462fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 312 additions and 181 deletions

154
Cargo.lock generated
View file

@ -142,7 +142,7 @@ dependencies = [
"serde_tuple",
"sha1",
"snafu",
"strum",
"strum 0.26.3",
"syn 2.0.96",
"tempfile",
"tokio",
@ -218,7 +218,7 @@ dependencies = [
"prost-types",
"serde",
"snafu",
"strum",
"strum 0.26.3",
]
[[package]]
@ -574,11 +574,12 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bincode"
version = "2.0.0-rc.3"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95"
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
dependencies = [
"serde",
"unty",
]
[[package]]
@ -664,9 +665,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "burn"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55af4c56b540bcf00cf1c7e13b1c60644734906495048afbd4a79aabd0a6efbe"
checksum = "22149f3b5ab6628e9e9c0b29156b906d32d36bbf76f2c34ad5ce1801f5b4486e"
dependencies = [
"burn-core",
"burn-train",
@ -674,9 +675,9 @@ dependencies = [
[[package]]
name = "burn-autodiff"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa53181463ef16220438e240f10e1e8cb2fcf1824dbc33b8f259a454ff5f46f"
checksum = "f2167ab07f9be5f2a027accba92d8dde02ea905f35844f8529bb2533b4fc8646"
dependencies = [
"burn-common",
"burn-tensor",
@ -687,9 +688,9 @@ dependencies = [
[[package]]
name = "burn-candle"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b49a6da72c10ac552b3c023d74dade9714c10aac0fc5f33cfc4ca389463b99e"
checksum = "aeef1204c4d33dd71a9628a311178eb149131c65234eb64e8201e27cf1ee1ba0"
dependencies = [
"burn-tensor",
"candle-core",
@ -699,9 +700,9 @@ dependencies = [
[[package]]
name = "burn-common"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a1471949b06002c984df9d753a084a79149841dd7935911d9e432b8478f9fd5"
checksum = "fb516d1faa50628828b3c2b79db3e483f20d62966f7dae68c6f21743f5f7e8ef"
dependencies = [
"cubecl-common",
"getrandom 0.2.15",
@ -712,9 +713,9 @@ dependencies = [
[[package]]
name = "burn-core"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f8ebbf7d5c8bdc269260bd8e7ce08e488e6625da19b3d80ca34a729d78a77ab"
checksum = "594c44ac9f2996c2c0b92f5a44a1287d41fca3954182601a4a29b628a5973357"
dependencies = [
"ahash",
"bincode",
@ -747,9 +748,9 @@ dependencies = [
[[package]]
name = "burn-cuda"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90534d6c7f909a8cad49470921dc3eb2b118f7a3c8bde313defba3f4cae3ac3"
checksum = "08fe1e5f285214d16cfd298453b807675c9a4ed742a35c8807be42af69e8ee97"
dependencies = [
"burn-jit",
"burn-tensor",
@ -762,9 +763,9 @@ dependencies = [
[[package]]
name = "burn-dataset"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b851cb5165da57871bed2c48a29673dde0ddbd198a39b2a37411b6adf6df6ad"
checksum = "92a5cde6c09c751fb6aafca10d8e18faa42fe18eef44b4768851575c20db6904"
dependencies = [
"csv",
"derive-new 0.7.0",
@ -774,17 +775,17 @@ dependencies = [
"sanitize-filename 0.6.0",
"serde",
"serde_json",
"strum",
"strum_macros",
"strum 0.26.3",
"strum_macros 0.26.4",
"tempfile",
"thiserror 2.0.11",
]
[[package]]
name = "burn-derive"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f784ffe0df57848ba232e5f40a1c1f5df3571df59bec99ba32bc7610fd9e811"
checksum = "2bb8f828a681946b07a87750ed0593d885e7b101653bd6a3bb1942976156bb48"
dependencies = [
"derive-new 0.7.0",
"proc-macro2",
@ -794,9 +795,9 @@ dependencies = [
[[package]]
name = "burn-hip"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd9fbfee77b3d2b67bf434b883ec6ed73f4f9bf1fd8d59f9dde217c7a4b5285d"
checksum = "84191ed69af8c48a133c05e0ee4dfe73d7a3b8f96e3ceec899b4f85b19072232"
dependencies = [
"burn-jit",
"burn-tensor",
@ -809,9 +810,9 @@ dependencies = [
[[package]]
name = "burn-jit"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6b06689c4e8d6cfdcaf0b0e168e58a931c3935414e48f4e3e3e85e8d7a77a0"
checksum = "6b723ddb46032953c4fb908feca57b470b0c9839f808fcd52de04a8510b88a23"
dependencies = [
"burn-common",
"burn-tensor",
@ -831,9 +832,9 @@ dependencies = [
[[package]]
name = "burn-ndarray"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "419fa3eda8cf9fddce0d156946b3d46642c10a41569b23e7855f775f862d310a"
checksum = "1b8ce3bd0f1e792b53610d291eb463d9790449688c4455a496c46266e46a179f"
dependencies = [
"atomic_float",
"burn-autodiff",
@ -842,7 +843,7 @@ dependencies = [
"derive-new 0.7.0",
"libm",
"matrixmultiply",
"ndarray 0.16.1",
"ndarray",
"num-traits",
"portable-atomic-util",
"rand",
@ -851,9 +852,9 @@ dependencies = [
[[package]]
name = "burn-router"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c39bdb6d5c749221741a362da9b3ea3157304f831ab4b4a6902725a1efaea159"
checksum = "b6a1a6cb08eccb65b112bc5853ac35c88faa8b04b03270bcfb1ac8a253a066bb"
dependencies = [
"burn-common",
"burn-tensor",
@ -864,9 +865,9 @@ dependencies = [
[[package]]
name = "burn-tensor"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24db20273a636d5340e5a29af142722e0a657491e6b3cfcceb1e62eb862b3b37"
checksum = "ab959e7da2e7514b959d841c93e8e026233aa77284f5d976099a8f5251e3ba99"
dependencies = [
"burn-common",
"bytemuck",
@ -885,9 +886,9 @@ dependencies = [
[[package]]
name = "burn-train"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714298cbc0c41f48d53cb1e6aeb6203b49b6110620517f69fbcc37a9b41cb6c8"
checksum = "c004e8c761ad50c568739581a2dab38aa2f4db723183fb189f130e6d2b4e0d1c"
dependencies = [
"async-channel",
"burn-core",
@ -906,9 +907,9 @@ dependencies = [
[[package]]
name = "burn-wgpu"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ef5b6c56da563a708b2da16f0559a061e7b93f3acae63903734ee978c9b9f93"
checksum = "5f80a3413527087e73042c807d41d6fd3a1e8a28eb519e3ad5e91f6354cbfb9d"
dependencies = [
"burn-jit",
"burn-tensor",
@ -2099,19 +2100,19 @@ dependencies = [
[[package]]
name = "fsrs"
version = "3.0.0"
source = "git+https://github.com/open-spaced-repetition/fsrs-rs.git?rev=08d90d1363b0c4722422bf0ef71ed8fd7d053f8a#08d90d1363b0c4722422bf0ef71ed8fd7d053f8a"
source = "git+https://github.com/open-spaced-repetition/fsrs-rs.git?rev=c7717682997a8a6d53d97c7196281e745c5b3c8e#c7717682997a8a6d53d97c7196281e745c5b3c8e"
dependencies = [
"burn",
"itertools 0.12.1",
"itertools 0.14.0",
"log",
"ndarray 0.15.6",
"ndarray",
"ndarray-rand",
"priority-queue",
"rand",
"rayon",
"serde",
"snafu",
"strum",
"strum 0.27.1",
]
[[package]]
@ -3254,18 +3255,18 @@ dependencies = [
[[package]]
name = "itertools"
version = "0.12.1"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.13.0"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
@ -3441,7 +3442,7 @@ dependencies = [
"linkcheck",
"regex",
"reqwest 0.12.8",
"strum",
"strum 0.26.3",
"tokio",
]
@ -3831,19 +3832,6 @@ dependencies = [
"tempfile",
]
[[package]]
name = "ndarray"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32"
dependencies = [
"matrixmultiply",
"num-complex",
"num-integer",
"num-traits",
"rawpointer",
]
[[package]]
name = "ndarray"
version = "0.16.1"
@ -3862,11 +3850,11 @@ dependencies = [
[[package]]
name = "ndarray-rand"
version = "0.14.0"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65608f937acc725f5b164dcf40f4f0bc5d67dc268ab8a649d3002606718c4588"
checksum = "f093b3db6fd194718dcdeea6bd8c829417deae904e3fcc7732dabcd4416d25d8"
dependencies = [
"ndarray 0.15.6",
"ndarray",
"rand",
"rand_distr",
]
@ -4608,9 +4596,9 @@ dependencies = [
[[package]]
name = "priority-queue"
version = "2.1.1"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d"
checksum = "ef08705fa1589a1a59aa924ad77d14722cb0cd97b67dd5004ed5f4a4873fce8d"
dependencies = [
"autocfg",
"equivalent",
@ -5561,9 +5549,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
[[package]]
name = "serde"
version = "1.0.217"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
@ -5590,9 +5578,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.217"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
@ -5869,7 +5857,16 @@ version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [
"strum_macros",
"strum_macros 0.26.4",
]
[[package]]
name = "strum"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
dependencies = [
"strum_macros 0.27.1",
]
[[package]]
@ -5885,6 +5882,19 @@ dependencies = [
"syn 2.0.96",
]
[[package]]
name = "strum_macros"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.96",
]
[[package]]
name = "subtle"
version = "2.6.1"
@ -6702,6 +6712,12 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "unty"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "url"
version = "2.5.4"

View file

@ -37,7 +37,7 @@ rev = "184b2ca50ed39ca43da13f0b830a463861adb9ca"
[workspace.dependencies.fsrs]
# version = "=2.0.3"
git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
rev = "08d90d1363b0c4722422bf0ef71ed8fd7d053f8a"
rev = "c7717682997a8a6d53d97c7196281e745c5b3c8e"
# path = "../open-spaced-repetition/fsrs-rs"
[workspace.dependencies]

View file

@ -334,7 +334,7 @@
},
{
"name": "bincode",
"version": "2.0.0-rc.3",
"version": "2.0.1",
"authors": "Ty Overby <ty@pre-alpha.com>|Zoey Riordan <zoey@dos.cafe>|Victor Koenders <bincode@trangar.com>",
"repository": "https://github.com/bincode-org/bincode",
"license": "MIT",
@ -415,7 +415,7 @@
},
{
"name": "burn",
"version": "0.16.0",
"version": "0.16.1",
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
"repository": "https://github.com/tracel-ai/burn",
"license": "Apache-2.0 OR MIT",
@ -424,7 +424,7 @@
},
{
"name": "burn-autodiff",
"version": "0.16.0",
"version": "0.16.1",
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-autodiff",
"license": "Apache-2.0 OR MIT",
@ -433,7 +433,7 @@
},
{
"name": "burn-candle",
"version": "0.16.0",
"version": "0.16.1",
"authors": "louisfd <louisfd94@gmail.com>",
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-candle",
"license": "Apache-2.0 OR MIT",
@ -442,7 +442,7 @@
},
{
"name": "burn-common",
"version": "0.16.0",
"version": "0.16.1",
"authors": "Dilshod Tadjibaev (@antimora)",
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-common",
"license": "Apache-2.0 OR MIT",
@ -451,7 +451,7 @@
},
{
"name": "burn-core",
"version": "0.16.0",
"version": "0.16.1",
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-core",
"license": "Apache-2.0 OR MIT",
@ -460,7 +460,7 @@
},
{
"name": "burn-cuda",
"version": "0.16.0",
"version": "0.16.1",
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-cuda",
"license": "Apache-2.0 OR MIT",
@ -469,7 +469,7 @@
},
{
"name": "burn-dataset",
"version": "0.16.0",
"version": "0.16.1",
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-dataset",
"license": "Apache-2.0 OR MIT",
@ -478,7 +478,7 @@
},
{
"name": "burn-derive",
"version": "0.16.0",
"version": "0.16.1",
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-derive",
"license": "Apache-2.0 OR MIT",
@ -487,7 +487,7 @@
},
{
"name": "burn-hip",
"version": "0.16.0",
"version": "0.16.1",
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-hip",
"license": "Apache-2.0 OR MIT",
@ -496,7 +496,7 @@
},
{
"name": "burn-jit",
"version": "0.16.0",
"version": "0.16.1",
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-jit",
"license": "Apache-2.0 OR MIT",
@ -505,7 +505,7 @@
},
{
"name": "burn-ndarray",
"version": "0.16.0",
"version": "0.16.1",
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-ndarray",
"license": "Apache-2.0 OR MIT",
@ -514,7 +514,7 @@
},
{
"name": "burn-router",
"version": "0.16.0",
"version": "0.16.1",
"authors": "guillaumelagrange <lagrange.guillaume.1@gmail.com>|nathanielsimard <nathaniel.simard.42@gmail.com>",
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-router",
"license": "Apache-2.0 OR MIT",
@ -523,7 +523,7 @@
},
{
"name": "burn-tensor",
"version": "0.16.0",
"version": "0.16.1",
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-tensor",
"license": "Apache-2.0 OR MIT",
@ -532,7 +532,7 @@
},
{
"name": "burn-train",
"version": "0.16.0",
"version": "0.16.1",
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-train",
"license": "Apache-2.0 OR MIT",
@ -541,7 +541,7 @@
},
{
"name": "burn-wgpu",
"version": "0.16.0",
"version": "0.16.1",
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-wgpu",
"license": "Apache-2.0 OR MIT",
@ -2071,7 +2071,7 @@
},
{
"name": "itertools",
"version": "0.12.1",
"version": "0.13.0",
"authors": "bluss",
"repository": "https://github.com/rust-itertools/itertools",
"license": "Apache-2.0 OR MIT",
@ -2080,7 +2080,7 @@
},
{
"name": "itertools",
"version": "0.13.0",
"version": "0.14.0",
"authors": "bluss",
"repository": "https://github.com/rust-itertools/itertools",
"license": "Apache-2.0 OR MIT",
@ -2429,15 +2429,6 @@
"license_file": null,
"description": "A wrapper over a platform's native TLS implementation"
},
{
"name": "ndarray",
"version": "0.15.6",
"authors": "Ulrik Sverdrup \"bluss\"|Jim Turner",
"repository": "https://github.com/rust-ndarray/ndarray",
"license": "Apache-2.0 OR MIT",
"license_file": null,
"description": "An n-dimensional array for general elements and for numerics. Lightweight array views and slicing; views support chunking and splitting."
},
{
"name": "ndarray",
"version": "0.16.1",
@ -2449,7 +2440,7 @@
},
{
"name": "ndarray-rand",
"version": "0.14.0",
"version": "0.15.0",
"authors": "bluss",
"repository": "https://github.com/rust-ndarray/ndarray",
"license": "Apache-2.0 OR MIT",
@ -2971,7 +2962,7 @@
},
{
"name": "priority-queue",
"version": "2.1.1",
"version": "2.3.1",
"authors": "Gianmarco Garrisi <gianmarcogarrisi@tutanota.com>",
"repository": "https://github.com/garro95/priority-queue",
"license": "LGPL-3.0-or-later OR MPL-2.0",
@ -3574,7 +3565,7 @@
},
{
"name": "serde",
"version": "1.0.217",
"version": "1.0.219",
"authors": "Erick Tryzelaar <erick.tryzelaar@gmail.com>|David Tolnay <dtolnay@gmail.com>",
"repository": "https://github.com/serde-rs/serde",
"license": "Apache-2.0 OR MIT",
@ -3601,7 +3592,7 @@
},
{
"name": "serde_derive",
"version": "1.0.217",
"version": "1.0.219",
"authors": "Erick Tryzelaar <erick.tryzelaar@gmail.com>|David Tolnay <dtolnay@gmail.com>",
"repository": "https://github.com/serde-rs/serde",
"license": "Apache-2.0 OR MIT",
@ -3851,6 +3842,15 @@
"license_file": null,
"description": "Helpful macros for working with enums and strings"
},
{
"name": "strum",
"version": "0.27.1",
"authors": "Peter Glotfelty <peter.glotfelty@microsoft.com>",
"repository": "https://github.com/Peternator7/strum",
"license": "MIT",
"license_file": null,
"description": "Helpful macros for working with enums and strings"
},
{
"name": "strum_macros",
"version": "0.26.4",
@ -3860,6 +3860,15 @@
"license_file": null,
"description": "Helpful macros for working with enums and strings"
},
{
"name": "strum_macros",
"version": "0.27.1",
"authors": "Peter Glotfelty <peter.glotfelty@microsoft.com>",
"repository": "https://github.com/Peternator7/strum",
"license": "MIT",
"license_file": null,
"description": "Helpful macros for working with enums and strings"
},
{
"name": "subtle",
"version": "2.6.1",
@ -4427,6 +4436,15 @@
"license_file": null,
"description": "Safe, fast, zero-panic, zero-crashing, zero-allocation parsing of untrusted inputs in Rust."
},
{
"name": "unty",
"version": "0.0.4",
"authors": "Victor Koenders <bincode@trang.ar>",
"repository": "https://github.com/bincode-org/unty",
"license": "Apache-2.0 OR MIT",
"license_file": null,
"description": "Explicitly types your generics"
},
{
"name": "url",
"version": "2.5.4",

View file

@ -50,6 +50,7 @@ message Card {
optional uint32 original_position = 18;
optional FsrsMemoryState memory_state = 20;
optional float desired_retention = 21;
optional float decay = 22;
string custom_data = 19;
}

View file

@ -122,9 +122,10 @@ message DeckConfig {
repeated float fsrs_params_4 = 3;
repeated float fsrs_params_5 = 5;
repeated float fsrs_params_6 = 6;
// consider saving remaining ones for fsrs param changes
reserved 6 to 8;
reserved 7 to 8;
uint32 new_per_day = 9;
uint32 reviews_per_day = 10;

View file

@ -432,17 +432,16 @@ message GetOptimalRetentionParametersResponse {
uint32 learn_span = 2;
float max_cost_perday = 3;
float max_ivl = 4;
repeated float learn_costs = 5;
repeated float review_costs = 6;
repeated float first_rating_prob = 7;
repeated float review_rating_prob = 8;
repeated float first_rating_offsets = 9;
repeated float first_session_lens = 10;
float forget_rating_offset = 11;
float forget_session_len = 12;
float loss_aversion = 13;
uint32 learn_limit = 14;
uint32 review_limit = 15;
repeated float first_rating_prob = 5;
repeated float review_rating_prob = 6;
float loss_aversion = 7;
uint32 learn_limit = 8;
uint32 review_limit = 9;
repeated float learning_step_transitions = 10;
repeated float relearning_step_transitions = 11;
repeated float state_rating_costs = 12;
uint32 learning_step_count = 13;
uint32 relearning_step_count = 14;
}
message EvaluateParamsRequest {

View file

@ -65,6 +65,7 @@ message CardStatsResponse {
string preset = 21;
optional string original_deck = 22;
optional float desired_retention = 23;
repeated float fsrs_params = 24;
}
message GraphsRequest {

View file

@ -4,6 +4,7 @@
use std::sync::Arc;
use fsrs::FSRS;
use fsrs::FSRS5_DEFAULT_DECAY;
use itertools::Itertools;
use strum::Display;
use strum::EnumIter;
@ -541,10 +542,13 @@ impl RowContext {
.memory_state
.as_ref()
.zip(self.cards[0].days_since_last_review(&self.timing))
.map(|(state, days_elapsed)| {
let r = FSRS::new(None)
.unwrap()
.current_retrievability((*state).into(), days_elapsed);
.zip(Some(self.cards[0].decay.unwrap_or(FSRS5_DEFAULT_DECAY)))
.map(|((state, days_elapsed), decay)| {
let r = FSRS::new(None).unwrap().current_retrievability(
(*state).into(),
days_elapsed,
decay,
);
format!("{:.0}%", r * 100.)
})
.unwrap_or_default()

View file

@ -95,6 +95,7 @@ pub struct Card {
pub(crate) original_position: Option<u32>,
pub(crate) memory_state: Option<FsrsMemoryState>,
pub(crate) desired_retention: Option<f32>,
pub(crate) decay: Option<f32>,
/// JSON object or empty; exposed through the reviewer for persisting custom
/// state
pub(crate) custom_data: String,
@ -145,6 +146,7 @@ impl Default for Card {
original_position: None,
memory_state: None,
desired_retention: None,
decay: None,
custom_data: String::new(),
}
}

View file

@ -106,6 +106,7 @@ impl TryFrom<anki_proto::cards::Card> for Card {
original_position: c.original_position,
memory_state: c.memory_state.map(Into::into),
desired_retention: c.desired_retention,
decay: c.decay,
custom_data: c.custom_data,
})
}
@ -134,6 +135,7 @@ impl From<Card> for anki_proto::cards::Card {
original_position: c.original_position,
memory_state: c.memory_state.map(Into::into),
desired_retention: c.desired_retention,
decay: c.decay,
custom_data: c.custom_data,
}
}

View file

@ -76,6 +76,7 @@ const DEFAULT_DECK_CONFIG_INNER: DeckConfigInner = DeckConfigInner {
bury_interday_learning: false,
fsrs_params_4: vec![],
fsrs_params_5: vec![],
fsrs_params_6: vec![],
desired_retention: 0.9,
other: Vec::new(),
historical_retention: 0.9,
@ -107,9 +108,11 @@ impl DeckConfig {
self.usn = usn;
}
/// Retrieve the FSRS 5.0 params, falling back on 4.x ones.
/// Retrieve the FSRS 6.0 params, falling back on 5.0 or 4.x ones.
pub fn fsrs_params(&self) -> &Vec<f32> {
if self.inner.fsrs_params_5.len() == 19 {
if self.inner.fsrs_params_6.len() == 21 {
&self.inner.fsrs_params_6
} else if self.inner.fsrs_params_5.len() == 19 {
&self.inner.fsrs_params_5
} else {
&self.inner.fsrs_params_4

View file

@ -74,6 +74,8 @@ pub struct DeckConfSchema11 {
#[serde(default)]
fsrs_params_5: Vec<f32>,
#[serde(default)]
fsrs_params_6: Vec<f32>,
#[serde(default)]
desired_retention: f32,
#[serde(default)]
ignore_revlogs_before_date: String,
@ -310,6 +312,7 @@ impl Default for DeckConfSchema11 {
bury_interday_learning: false,
fsrs_params_4: vec![],
fsrs_params_5: vec![],
fsrs_params_6: vec![],
desired_retention: 0.9,
sm2_retention: 0.9,
param_search: "".to_string(),
@ -391,6 +394,7 @@ impl From<DeckConfSchema11> for DeckConfig {
bury_interday_learning: c.bury_interday_learning,
fsrs_params_4: c.fsrs_params_4,
fsrs_params_5: c.fsrs_params_5,
fsrs_params_6: c.fsrs_params_6,
ignore_revlogs_before_date: c.ignore_revlogs_before_date,
easy_days_percentages: c.easy_days_percentages,
desired_retention: c.desired_retention,
@ -504,6 +508,7 @@ impl From<DeckConfig> for DeckConfSchema11 {
bury_interday_learning: i.bury_interday_learning,
fsrs_params_4: i.fsrs_params_4,
fsrs_params_5: i.fsrs_params_5,
fsrs_params_6: i.fsrs_params_6,
desired_retention: i.desired_retention,
sm2_retention: i.historical_retention,
param_search: i.param_search,
@ -532,6 +537,7 @@ static RESERVED_DECKCONF_KEYS: Set<&'static str> = phf_set! {
"newGatherPriority",
"fsrsWeights",
"fsrsParams5",
"fsrsParams6",
"desiredRetention",
"stopTimerOnAnswer",
"secondsToShowQuestion",

View file

@ -50,7 +50,7 @@ impl Collection {
deck: DeckId,
) -> Result<anki_proto::deck_config::DeckConfigsForUpdate> {
let mut defaults = DeckConfig::default();
defaults.inner.fsrs_params_5 = DEFAULT_PARAMETERS.into();
defaults.inner.fsrs_params_6 = DEFAULT_PARAMETERS.into();
let last_optimize = self.get_config_i32(I32ConfigKey::LastFsrsOptimize) as u32;
let days_since_last_fsrs_optimize = if last_optimize > 0 {
self.timing_today()?
@ -88,10 +88,14 @@ impl Collection {
// grab the config and sort it
let mut config = self.storage.all_deck_config()?;
config.sort_unstable_by(|a, b| a.name.cmp(&b.name));
// pre-fill empty fsrs 5 params with 4 params
// pre-fill empty fsrs params with older params
config.iter_mut().for_each(|c| {
if c.inner.fsrs_params_5.is_empty() {
c.inner.fsrs_params_5 = c.inner.fsrs_params_4.clone();
if c.inner.fsrs_params_6.is_empty() {
c.inner.fsrs_params_6 = if c.inner.fsrs_params_5.is_empty() {
c.inner.fsrs_params_4.clone()
} else {
c.inner.fsrs_params_5.clone()
};
}
});
@ -165,10 +169,11 @@ impl Collection {
// add/update provided configs
for conf in &mut req.configs {
// If the user has provided empty FSRS5 params, zero out any
// If the user has provided empty FSRS6 params, zero out any
// old params as well, so we don't fall back on them, which would
// be surprising as they're not shown in the GUI.
if conf.inner.fsrs_params_5.is_empty() {
if conf.inner.fsrs_params_6.is_empty() {
conf.inner.fsrs_params_5.clear();
conf.inner.fsrs_params_4.clear();
}
// check the provided parameters are valid before we save them
@ -370,7 +375,7 @@ impl Collection {
) {
Ok(params) => {
println!("{}: {:?}", config.name, params.params);
config.inner.fsrs_params_5 = params.params;
config.inner.fsrs_params_6 = params.params;
}
Err(AnkiError::Interrupted) => return Err(AnkiError::Interrupted),
Err(err) => {

View file

@ -7,6 +7,8 @@ use anki_proto::scheduler::ComputeMemoryStateResponse;
use fsrs::FSRSItem;
use fsrs::MemoryState;
use fsrs::FSRS;
use fsrs::FSRS5_DEFAULT_DECAY;
use fsrs::FSRS6_DEFAULT_DECAY;
use itertools::Itertools;
use super::params::ignore_revlogs_before_ms_from_config;
@ -76,6 +78,15 @@ impl Collection {
.then(|| Rescheduler::new(self))
.transpose()?;
let fsrs = FSRS::new(req.as_ref().map(|w| &w.params[..]).or(Some([].as_slice())))?;
let decay = req.as_ref().map(|w| {
if w.params.is_empty() {
FSRS6_DEFAULT_DECAY // default decay for FSRS-6
} else if w.params.len() < 21 {
FSRS5_DEFAULT_DECAY // default decay for FSRS-4.5 and FSRS-5
} else {
w.params[20]
}
});
let historical_retention = req.as_ref().map(|w| w.historical_retention);
let items = fsrs_items_for_memory_states(
&fsrs,
@ -94,6 +105,7 @@ impl Collection {
if let (Some(req), Some(item)) = (&req, item) {
card.set_memory_state(&fsrs, Some(item), historical_retention.unwrap())?;
card.desired_retention = desired_retention;
card.decay = decay;
// if rescheduling
if let Some(reviews) = &last_revlog_info {
// and we have a last review time for the card

View file

@ -66,21 +66,19 @@ impl Collection {
learn_span: req.days_to_simulate as usize,
max_cost_perday: f32::MAX,
max_ivl: req.max_interval as f32,
learn_costs: p.learn_costs,
review_costs: p.review_costs,
first_rating_prob: p.first_rating_prob,
review_rating_prob: p.review_rating_prob,
first_rating_offsets: p.first_rating_offsets,
first_session_lens: p.first_session_lens,
forget_rating_offset: p.forget_rating_offset,
forget_session_len: p.forget_session_len,
loss_aversion: req.loss_aversion as f32,
learn_limit,
review_limit: usize::MAX,
new_cards_ignore_review_limit: true,
suspend_after_lapses: None,
post_scheduling_fn,
review_priority_fn: None,
learning_step_transitions: p.learning_step_transitions,
relearning_step_transitions: p.relearning_step_transitions,
state_rating_costs: p.state_rating_costs,
learning_step_count: p.learning_step_count,
relearning_step_count: p.relearning_step_count,
},
&req.params,
|ip| {

View file

@ -75,6 +75,7 @@ pub(crate) fn apply_load_balance_and_easy_days(
fn create_review_priority_fn(
review_order: ReviewCardOrder,
deck_size: usize,
params: Vec<f32>,
) -> Option<ReviewPriorityFn> {
// Helper macro to wrap closure in ReviewPriorityFn
macro_rules! wrap {
@ -91,11 +92,12 @@ fn create_review_priority_fn(
// Interval-based ordering
IntervalsAscending => wrap!(|c| c.interval as i32),
IntervalsDescending => wrap!(|c| -(c.interval as i32)),
// Retrievability-based ordering
RetrievabilityAscending => wrap!(|c| (c.retrievability() * 1000.0) as i32),
RetrievabilityAscending => {
wrap!(move |c| (c.retrievability(&params) * 1000.0) as i32)
}
RetrievabilityDescending => {
wrap!(|c| -(c.retrievability() * 1000.0) as i32)
wrap!(move |c| -(c.retrievability(&params) * 1000.0) as i32)
}
// Due date ordering
@ -195,28 +197,26 @@ impl Collection {
.review_order
.try_into()
.ok()
.and_then(|order| create_review_priority_fn(order, deck_size));
.and_then(|order| create_review_priority_fn(order, deck_size, req.params.clone()));
let config = SimulatorConfig {
deck_size,
learn_span: req.days_to_simulate as usize,
max_cost_perday: f32::MAX,
max_ivl: req.max_interval as f32,
learn_costs: p.learn_costs,
review_costs: p.review_costs,
first_rating_prob: p.first_rating_prob,
review_rating_prob: p.review_rating_prob,
first_rating_offsets: p.first_rating_offsets,
first_session_lens: p.first_session_lens,
forget_rating_offset: p.forget_rating_offset,
forget_session_len: p.forget_session_len,
loss_aversion: 1.0,
learn_limit: req.new_limit as usize,
review_limit: req.review_limit as usize,
new_cards_ignore_review_limit: req.new_cards_ignore_review_limit,
suspend_after_lapses: req.suspend_after_lapse_count,
post_scheduling_fn,
review_priority_fn,
learning_step_transitions: p.learning_step_transitions,
relearning_step_transitions: p.relearning_step_transitions,
state_rating_costs: p.state_rating_costs,
learning_step_count: p.learning_step_count,
relearning_step_count: p.relearning_step_count,
};
let result = simulate(
&config,

View file

@ -321,17 +321,31 @@ impl crate::services::SchedulerService for Collection {
learn_span: simulator_config.learn_span as u32,
max_cost_perday: simulator_config.max_cost_perday,
max_ivl: simulator_config.max_ivl,
learn_costs: simulator_config.learn_costs.to_vec(),
review_costs: simulator_config.review_costs.to_vec(),
first_rating_prob: simulator_config.first_rating_prob.to_vec(),
review_rating_prob: simulator_config.review_rating_prob.to_vec(),
first_rating_offsets: simulator_config.first_rating_offsets.to_vec(),
first_session_lens: simulator_config.first_session_lens.to_vec(),
forget_rating_offset: simulator_config.forget_rating_offset,
forget_session_len: simulator_config.forget_session_len,
loss_aversion: simulator_config.loss_aversion,
loss_aversion: 1.0,
learn_limit: simulator_config.learn_limit as u32,
review_limit: simulator_config.review_limit as u32,
learning_step_transitions: simulator_config
.learning_step_transitions
.iter()
.flatten()
.cloned()
.collect(),
relearning_step_transitions: simulator_config
.relearning_step_transitions
.iter()
.flatten()
.cloned()
.collect(),
state_rating_costs: simulator_config
.state_rating_costs
.iter()
.flatten()
.cloned()
.collect(),
learning_step_count: simulator_config.learning_step_count as u32,
relearning_step_count: simulator_config.relearning_step_count as u32,
})
}

View file

@ -2,6 +2,7 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use fsrs::FSRS;
use fsrs::FSRS5_DEFAULT_DECAY;
use crate::card::CardType;
use crate::card::FsrsMemoryState;
@ -37,10 +38,11 @@ impl Collection {
let fsrs_retrievability = card
.memory_state
.zip(Some(days_elapsed))
.map(|(state, days)| {
.zip(Some(card.decay.unwrap_or(FSRS5_DEFAULT_DECAY)))
.map(|((state, days), decay)| {
FSRS::new(None)
.unwrap()
.current_retrievability(state.into(), days)
.current_retrievability(state.into(), days, decay)
});
let original_deck = if card.original_deck_id == DeckId(0) {
@ -75,6 +77,7 @@ impl Collection {
memory_state: card.memory_state.map(Into::into),
fsrs_retrievability,
custom_data: card.custom_data,
fsrs_params: preset.fsrs_params().to_vec(),
preset: preset.name,
original_deck: if original_deck != deck {
Some(original_deck.human_name())

View file

@ -3,6 +3,7 @@
use anki_proto::stats::graphs_response::Retrievability;
use fsrs::FSRS;
use fsrs::FSRS5_DEFAULT_DECAY;
use crate::prelude::TimestampSecs;
use crate::scheduler::timing::SchedTimingToday;
@ -30,7 +31,11 @@ impl GraphsContext {
entry.1 += 1;
if let Some(state) = card.memory_state {
let elapsed_days = card.days_since_last_review(&timing).unwrap_or_default();
let r = fsrs.current_retrievability(state.into(), elapsed_days);
let r = fsrs.current_retrievability(
state.into(),
elapsed_days,
card.decay.unwrap_or(FSRS5_DEFAULT_DECAY),
);
*retrievability
.retrievability

View file

@ -42,6 +42,11 @@ pub(crate) struct CardData {
deserialize_with = "default_on_invalid"
)]
pub(crate) fsrs_desired_retention: Option<f32>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "default_on_invalid"
)]
pub(crate) decay: Option<f32>,
/// A string representation of a JSON object storing optional data
/// associated with the card, so v3 custom scheduling code can persist
@ -57,6 +62,7 @@ impl CardData {
fsrs_stability: card.memory_state.as_ref().map(|m| m.stability),
fsrs_difficulty: card.memory_state.as_ref().map(|m| m.difficulty),
fsrs_desired_retention: card.desired_retention,
decay: card.decay,
custom_data: card.custom_data.clone(),
}
}
@ -87,6 +93,9 @@ impl CardData {
if let Some(v) = &mut self.fsrs_desired_retention {
round_to_places(v, 2)
}
if let Some(v) = &mut self.decay {
round_to_places(v, 3)
}
serde_json::to_string(&self).map_err(Into::into)
}
}
@ -159,11 +168,12 @@ mod test {
fsrs_stability: Some(123.45678),
fsrs_difficulty: Some(1.234567),
fsrs_desired_retention: Some(0.987654),
decay: Some(0.123456),
custom_data: "".to_string(),
};
assert_eq!(
data.convert_to_json().unwrap(),
r#"{"s":123.457,"d":1.235,"dr":0.99}"#
r#"{"s":123.457,"d":1.235,"dr":0.99,"decay":0.123}"#
);
}
}

View file

@ -85,6 +85,7 @@ fn row_to_card(row: &Row) -> result::Result<Card, rusqlite::Error> {
original_position: data.original_position,
memory_state: data.memory_state(),
desired_retention: data.fsrs_desired_retention,
decay: data.decay,
custom_data: data.custom_data,
})
}

View file

@ -11,6 +11,7 @@ use std::sync::Arc;
use fnv::FnvHasher;
use fsrs::FSRS;
use fsrs::FSRS5_DEFAULT_DECAY;
use regex::Regex;
use rusqlite::functions::FunctionFlags;
use rusqlite::params;
@ -325,10 +326,11 @@ fn add_extract_fsrs_retrievability(db: &Connection) -> rusqlite::Result<()> {
let review_day = due.saturating_sub(ivl);
days_elapsed.saturating_sub(review_day) as u32
};
let decay = card_data.decay.unwrap_or_default();
Ok(card_data.memory_state().map(|state| {
FSRS::new(None)
.unwrap()
.current_retrievability(state.into(), days_elapsed)
.current_retrievability(state.into(), days_elapsed, decay)
}))
},
)
@ -374,10 +376,11 @@ fn add_extract_fsrs_relative_retrievability(db: &Connection) -> rusqlite::Result
{
// avoid div by zero
desired_retrievability = desired_retrievability.max(0.0001);
let decay = card_data.decay.unwrap_or(FSRS5_DEFAULT_DECAY);
let current_retrievability = FSRS::new(None)
.unwrap()
.current_retrievability(state.into(), days_elapsed)
.current_retrievability(state.into(), days_elapsed, decay)
.max(0.0001);
return Ok(Some(

View file

@ -332,6 +332,7 @@ impl From<CardEntry> for Card {
original_position: data.original_position,
memory_state: data.memory_state(),
desired_retention: data.fsrs_desired_retention,
decay: data.decay,
custom_data: data.custom_data,
}
}

View file

@ -18,6 +18,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
$: fsrsEnabled = stats?.memoryState != null;
$: desiredRetention = stats?.desiredRetention ?? 0.9;
$: decay = (() => {
const paramsLength = stats?.fsrsParams?.length ?? 0;
if (paramsLength === 0) {
return 0.2; // default decay for FSRS-6
}
if (paramsLength < 21) {
return 0.5; // default decay for FSRS-4.5 and FSRS-5
}
return stats?.fsrsParams?.[20] ?? 0.2;
})();
</script>
<Container breakpoint="md" --gutter-inline="1rem" --gutter-block="0.5rem">
@ -33,7 +43,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{/if}
{#if fsrsEnabled}
<Row>
<ForgettingCurve revlog={stats.revlog} {desiredRetention} />
<ForgettingCurve revlog={stats.revlog} {desiredRetention} {decay} />
</Row>
{/if}
{:else}

View file

@ -21,6 +21,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let revlog: RevlogEntry[];
export let desiredRetention: number;
export let decay: number;
let svg: HTMLElement | SVGElement | null = null;
const bounds = defaultGraphBounds();
const title = tr.cardStatsFsrsForgettingCurveTitle();
@ -47,6 +48,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
svg as SVGElement,
bounds,
desiredRetention,
decay,
);
</script>

View file

@ -10,7 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { filterRevlogEntryByReviewKind } from "./forgetting-curve";
export let revlog: RevlogEntry[];
export let fsrsEnabled: boolean = false;
export const fsrsEnabled: boolean = false;
function reviewKindClass(entry: RevlogEntry): string {
switch (entry.reviewKind) {
@ -174,7 +174,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{/each}
</div>
</div>
{#if fsrsEnabled}{/if}
<!-- {#if fsrsEnabled}
<div class="column">
<div class="column-head">{tr2.cardStatsFsrsStability()}</div>
<div class="column-content right">
{#each revlogRows as row, _index}
<div>{row.stability}</div>
{/each}
</div>
</div>
{/if} -->
</div>
{/if}

View file

@ -11,12 +11,11 @@ import { axisBottom, axisLeft, line, max, min, pointer, scaleLinear, scaleTime,
import { type GraphBounds, setDataAvailable } from "../graphs/graph-helpers";
import { hideTooltip, showTooltip } from "../graphs/tooltip-utils.svelte";
const FACTOR = 19 / 81;
const DECAY = -0.5;
const MIN_POINTS = 1000;
function forgettingCurve(stability: number, daysElapsed: number): number {
return Math.pow((daysElapsed / stability) * FACTOR + 1.0, DECAY);
function forgettingCurve(stability: number, daysElapsed: number, decay: number): number {
const factor = Math.pow(0.9, 1 / -decay) - 1;
return Math.pow((daysElapsed / stability) * factor + 1.0, -decay);
}
interface DataPoint {
@ -68,7 +67,7 @@ export function filterRevlog(revlog: RevlogEntry[]): RevlogEntry[] {
return result.filter((entry) => filterRevlogEntryByReviewKind(entry));
}
export function prepareData(revlog: RevlogEntry[], maxDays: number) {
export function prepareData(revlog: RevlogEntry[], maxDays: number, decay: number) {
const data: DataPoint[] = [];
let lastReviewTime = 0;
let lastStability = 0;
@ -97,7 +96,7 @@ export function prepareData(revlog: RevlogEntry[], maxDays: number) {
let elapsedDays = 0;
while (elapsedDays < totalDaysElapsed - step) {
elapsedDays += step;
const retrievability = forgettingCurve(lastStability, elapsedDays);
const retrievability = forgettingCurve(lastStability, elapsedDays, decay);
data.push({
date: new Date((lastReviewTime + elapsedDays * 86400) * 1000),
daysSinceFirstLearn: data[data.length - 1].daysSinceFirstLearn + step,
@ -128,7 +127,7 @@ export function prepareData(revlog: RevlogEntry[], maxDays: number) {
let elapsedDays = 0;
while (elapsedDays < totalDaysSinceLastReview - step) {
elapsedDays += step;
const retrievability = forgettingCurve(lastStability, elapsedDays);
const retrievability = forgettingCurve(lastStability, elapsedDays, decay);
data.push({
date: new Date((lastReviewTime + elapsedDays * 86400) * 1000),
daysSinceFirstLearn: data[data.length - 1].daysSinceFirstLearn + step,
@ -138,7 +137,7 @@ export function prepareData(revlog: RevlogEntry[], maxDays: number) {
});
}
daysSinceFirstLearn += totalDaysSinceLastReview;
const retrievability = forgettingCurve(lastStability, totalDaysSinceLastReview);
const retrievability = forgettingCurve(lastStability, totalDaysSinceLastReview, decay);
data.push({
date: new Date(now * 1000),
daysSinceFirstLearn: daysSinceFirstLearn,
@ -151,7 +150,7 @@ export function prepareData(revlog: RevlogEntry[], maxDays: number) {
let previewDaysElapsed = 0;
while (previewDaysElapsed < previewDays) {
previewDaysElapsed += step;
const retrievability = forgettingCurve(lastStability, elapsedDays + previewDaysElapsed);
const retrievability = forgettingCurve(lastStability, elapsedDays + previewDaysElapsed, decay);
data.push({
date: new Date((now + previewDaysElapsed * 86400) * 1000),
daysSinceFirstLearn: data[data.length - 1].daysSinceFirstLearn + step,
@ -185,6 +184,7 @@ export function renderForgettingCurve(
svgElem: SVGElement,
bounds: GraphBounds,
desiredRetention: number,
decay: number,
) {
const svg = select(svgElem);
const trans = svg.transition().duration(600) as any;
@ -194,7 +194,7 @@ export function renderForgettingCurve(
}
const maxDays = calculateMaxDays(filteredRevlog, timeRange);
const data = prepareData(filteredRevlog, maxDays);
const data = prepareData(filteredRevlog, maxDays, decay);
if (data.length === 0) {
setDataAvailable(svg, false);

View file

@ -59,7 +59,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
$: computing = computingParams || checkingParams || computingRetention;
$: defaultparamSearch = `preset:"${state.getCurrentNameForSearch()}" -is:suspended`;
$: roundedRetention = Number($config.desiredRetention.toFixed(2));
$: desiredRetentionWarning = getRetentionWarning(roundedRetention);
$: desiredRetentionWarning = getRetentionWarning(
roundedRetention,
fsrsParams($config),
);
$: retentionWarningClass = getRetentionWarningClass(roundedRetention);
let computeRetentionProgress:
@ -89,8 +92,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
reviewOrder: $config.reviewOrder,
});
function getRetentionWarning(retention: number): string {
const decay = -0.5;
function getRetentionWarning(retention: number, params: number[]): string {
const decay = params.length > 20 ? -params[20] : -0.5; // default decay for FSRS-4.5 and FSRS-5
const factor = 0.9 ** (1 / decay) - 1;
const stability = 100;
const days = Math.round(
@ -167,7 +170,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
: tr.deckConfigFsrsParamsNoReviews();
setTimeout(() => alert(msg), 200);
} else {
$config.fsrsParams5 = resp.params;
$config.fsrsParams6 = resp.params;
}
if (computeParamsProgress) {
computeParamsProgress.current = computeParamsProgress.total;
@ -322,9 +325,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<div class="ms-1 me-1">
<ParamsInputRow
bind:value={$config.fsrsParams5}
bind:value={$config.fsrsParams6}
defaultValue={[]}
defaults={defaults.fsrsParams5}
defaults={defaults.fsrsParams6}
>
<SettingTitle on:click={() => openHelpModal("modelParams")}>
{tr.deckConfigWeights()}

View file

@ -178,7 +178,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
);
}
let easyDayPercentages = [...$config.easyDaysPercentages];
$: easyDayPercentages = [...$config.easyDaysPercentages];
</script>
<div class="modal" class:show={shown} class:d-block={shown} tabindex="-1">

View file

@ -461,7 +461,9 @@ export async function commitEditing(): Promise<void> {
}
export function fsrsParams(config: DeckConfig_Config): number[] {
if (config.fsrsParams5) {
if (config.fsrsParams6) {
return config.fsrsParams6;
} else if (config.fsrsParams5) {
return config.fsrsParams5;
} else {
return config.fsrsParams4;

View file

@ -2384,9 +2384,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.30001431, caniuse-lite@npm:^1.0.30001524, caniuse-lite@npm:^1.0.30001669":
version: 1.0.30001671
resolution: "caniuse-lite@npm:1.0.30001671"
checksum: 10c0/9bb81be7be641fdcdf4d3722b661d4204cc203a489c16080503a72b1605bd5c1061f8ae2452cc6c15d6957c818182824eb34e6569521051795f42cd14e844f99
version: 1.0.30001714
resolution: "caniuse-lite@npm:1.0.30001714"
checksum: 10c0/b0e3372f018c5c177912f0282af98049057d83c80846293a4e3df728644a622db42a9e8971d6b7708d76e0fd4e9f6d5ce93802cf4e6818de80fdf371dc0f6a06
languageName: node
linkType: hard