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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -76,6 +76,7 @@ const DEFAULT_DECK_CONFIG_INNER: DeckConfigInner = DeckConfigInner {
bury_interday_learning: false, bury_interday_learning: false,
fsrs_params_4: vec![], fsrs_params_4: vec![],
fsrs_params_5: vec![], fsrs_params_5: vec![],
fsrs_params_6: vec![],
desired_retention: 0.9, desired_retention: 0.9,
other: Vec::new(), other: Vec::new(),
historical_retention: 0.9, historical_retention: 0.9,
@ -107,9 +108,11 @@ impl DeckConfig {
self.usn = usn; 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> { 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 &self.inner.fsrs_params_5
} else { } else {
&self.inner.fsrs_params_4 &self.inner.fsrs_params_4

View file

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

View file

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

View file

@ -7,6 +7,8 @@ use anki_proto::scheduler::ComputeMemoryStateResponse;
use fsrs::FSRSItem; use fsrs::FSRSItem;
use fsrs::MemoryState; use fsrs::MemoryState;
use fsrs::FSRS; use fsrs::FSRS;
use fsrs::FSRS5_DEFAULT_DECAY;
use fsrs::FSRS6_DEFAULT_DECAY;
use itertools::Itertools; use itertools::Itertools;
use super::params::ignore_revlogs_before_ms_from_config; use super::params::ignore_revlogs_before_ms_from_config;
@ -76,6 +78,15 @@ impl Collection {
.then(|| Rescheduler::new(self)) .then(|| Rescheduler::new(self))
.transpose()?; .transpose()?;
let fsrs = FSRS::new(req.as_ref().map(|w| &w.params[..]).or(Some([].as_slice())))?; 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 historical_retention = req.as_ref().map(|w| w.historical_retention);
let items = fsrs_items_for_memory_states( let items = fsrs_items_for_memory_states(
&fsrs, &fsrs,
@ -94,6 +105,7 @@ impl Collection {
if let (Some(req), Some(item)) = (&req, item) { if let (Some(req), Some(item)) = (&req, item) {
card.set_memory_state(&fsrs, Some(item), historical_retention.unwrap())?; card.set_memory_state(&fsrs, Some(item), historical_retention.unwrap())?;
card.desired_retention = desired_retention; card.desired_retention = desired_retention;
card.decay = decay;
// if rescheduling // if rescheduling
if let Some(reviews) = &last_revlog_info { if let Some(reviews) = &last_revlog_info {
// and we have a last review time for the card // 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, learn_span: req.days_to_simulate as usize,
max_cost_perday: f32::MAX, max_cost_perday: f32::MAX,
max_ivl: req.max_interval as f32, max_ivl: req.max_interval as f32,
learn_costs: p.learn_costs,
review_costs: p.review_costs,
first_rating_prob: p.first_rating_prob, first_rating_prob: p.first_rating_prob,
review_rating_prob: p.review_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, learn_limit,
review_limit: usize::MAX, review_limit: usize::MAX,
new_cards_ignore_review_limit: true, new_cards_ignore_review_limit: true,
suspend_after_lapses: None, suspend_after_lapses: None,
post_scheduling_fn, post_scheduling_fn,
review_priority_fn: None, 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, &req.params,
|ip| { |ip| {

View file

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

View file

@ -321,17 +321,31 @@ impl crate::services::SchedulerService for Collection {
learn_span: simulator_config.learn_span as u32, learn_span: simulator_config.learn_span as u32,
max_cost_perday: simulator_config.max_cost_perday, max_cost_perday: simulator_config.max_cost_perday,
max_ivl: simulator_config.max_ivl, 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(), first_rating_prob: simulator_config.first_rating_prob.to_vec(),
review_rating_prob: simulator_config.review_rating_prob.to_vec(), review_rating_prob: simulator_config.review_rating_prob.to_vec(),
first_rating_offsets: simulator_config.first_rating_offsets.to_vec(), loss_aversion: 1.0,
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,
learn_limit: simulator_config.learn_limit as u32, learn_limit: simulator_config.learn_limit as u32,
review_limit: simulator_config.review_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 // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use fsrs::FSRS; use fsrs::FSRS;
use fsrs::FSRS5_DEFAULT_DECAY;
use crate::card::CardType; use crate::card::CardType;
use crate::card::FsrsMemoryState; use crate::card::FsrsMemoryState;
@ -37,10 +38,11 @@ impl Collection {
let fsrs_retrievability = card let fsrs_retrievability = card
.memory_state .memory_state
.zip(Some(days_elapsed)) .zip(Some(days_elapsed))
.map(|(state, days)| { .zip(Some(card.decay.unwrap_or(FSRS5_DEFAULT_DECAY)))
.map(|((state, days), decay)| {
FSRS::new(None) FSRS::new(None)
.unwrap() .unwrap()
.current_retrievability(state.into(), days) .current_retrievability(state.into(), days, decay)
}); });
let original_deck = if card.original_deck_id == DeckId(0) { let original_deck = if card.original_deck_id == DeckId(0) {
@ -75,6 +77,7 @@ impl Collection {
memory_state: card.memory_state.map(Into::into), memory_state: card.memory_state.map(Into::into),
fsrs_retrievability, fsrs_retrievability,
custom_data: card.custom_data, custom_data: card.custom_data,
fsrs_params: preset.fsrs_params().to_vec(),
preset: preset.name, preset: preset.name,
original_deck: if original_deck != deck { original_deck: if original_deck != deck {
Some(original_deck.human_name()) Some(original_deck.human_name())

View file

@ -3,6 +3,7 @@
use anki_proto::stats::graphs_response::Retrievability; use anki_proto::stats::graphs_response::Retrievability;
use fsrs::FSRS; use fsrs::FSRS;
use fsrs::FSRS5_DEFAULT_DECAY;
use crate::prelude::TimestampSecs; use crate::prelude::TimestampSecs;
use crate::scheduler::timing::SchedTimingToday; use crate::scheduler::timing::SchedTimingToday;
@ -30,7 +31,11 @@ impl GraphsContext {
entry.1 += 1; entry.1 += 1;
if let Some(state) = card.memory_state { if let Some(state) = card.memory_state {
let elapsed_days = card.days_since_last_review(&timing).unwrap_or_default(); 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
.retrievability .retrievability

View file

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

View file

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

View file

@ -332,6 +332,7 @@ impl From<CardEntry> for Card {
original_position: data.original_position, original_position: data.original_position,
memory_state: data.memory_state(), memory_state: data.memory_state(),
desired_retention: data.fsrs_desired_retention, desired_retention: data.fsrs_desired_retention,
decay: data.decay,
custom_data: data.custom_data, 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; $: fsrsEnabled = stats?.memoryState != null;
$: desiredRetention = stats?.desiredRetention ?? 0.9; $: 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> </script>
<Container breakpoint="md" --gutter-inline="1rem" --gutter-block="0.5rem"> <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}
{#if fsrsEnabled} {#if fsrsEnabled}
<Row> <Row>
<ForgettingCurve revlog={stats.revlog} {desiredRetention} /> <ForgettingCurve revlog={stats.revlog} {desiredRetention} {decay} />
</Row> </Row>
{/if} {/if}
{:else} {: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 revlog: RevlogEntry[];
export let desiredRetention: number; export let desiredRetention: number;
export let decay: number;
let svg: HTMLElement | SVGElement | null = null; let svg: HTMLElement | SVGElement | null = null;
const bounds = defaultGraphBounds(); const bounds = defaultGraphBounds();
const title = tr.cardStatsFsrsForgettingCurveTitle(); 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, svg as SVGElement,
bounds, bounds,
desiredRetention, desiredRetention,
decay,
); );
</script> </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"; import { filterRevlogEntryByReviewKind } from "./forgetting-curve";
export let revlog: RevlogEntry[]; export let revlog: RevlogEntry[];
export let fsrsEnabled: boolean = false; export const fsrsEnabled: boolean = false;
function reviewKindClass(entry: RevlogEntry): string { function reviewKindClass(entry: RevlogEntry): string {
switch (entry.reviewKind) { switch (entry.reviewKind) {
@ -174,7 +174,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{/each} {/each}
</div> </div>
</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> </div>
{/if} {/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 { type GraphBounds, setDataAvailable } from "../graphs/graph-helpers";
import { hideTooltip, showTooltip } from "../graphs/tooltip-utils.svelte"; import { hideTooltip, showTooltip } from "../graphs/tooltip-utils.svelte";
const FACTOR = 19 / 81;
const DECAY = -0.5;
const MIN_POINTS = 1000; const MIN_POINTS = 1000;
function forgettingCurve(stability: number, daysElapsed: number): number { function forgettingCurve(stability: number, daysElapsed: number, decay: number): number {
return Math.pow((daysElapsed / stability) * FACTOR + 1.0, DECAY); const factor = Math.pow(0.9, 1 / -decay) - 1;
return Math.pow((daysElapsed / stability) * factor + 1.0, -decay);
} }
interface DataPoint { interface DataPoint {
@ -68,7 +67,7 @@ export function filterRevlog(revlog: RevlogEntry[]): RevlogEntry[] {
return result.filter((entry) => filterRevlogEntryByReviewKind(entry)); 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[] = []; const data: DataPoint[] = [];
let lastReviewTime = 0; let lastReviewTime = 0;
let lastStability = 0; let lastStability = 0;
@ -97,7 +96,7 @@ export function prepareData(revlog: RevlogEntry[], maxDays: number) {
let elapsedDays = 0; let elapsedDays = 0;
while (elapsedDays < totalDaysElapsed - step) { while (elapsedDays < totalDaysElapsed - step) {
elapsedDays += step; elapsedDays += step;
const retrievability = forgettingCurve(lastStability, elapsedDays); const retrievability = forgettingCurve(lastStability, elapsedDays, decay);
data.push({ data.push({
date: new Date((lastReviewTime + elapsedDays * 86400) * 1000), date: new Date((lastReviewTime + elapsedDays * 86400) * 1000),
daysSinceFirstLearn: data[data.length - 1].daysSinceFirstLearn + step, daysSinceFirstLearn: data[data.length - 1].daysSinceFirstLearn + step,
@ -128,7 +127,7 @@ export function prepareData(revlog: RevlogEntry[], maxDays: number) {
let elapsedDays = 0; let elapsedDays = 0;
while (elapsedDays < totalDaysSinceLastReview - step) { while (elapsedDays < totalDaysSinceLastReview - step) {
elapsedDays += step; elapsedDays += step;
const retrievability = forgettingCurve(lastStability, elapsedDays); const retrievability = forgettingCurve(lastStability, elapsedDays, decay);
data.push({ data.push({
date: new Date((lastReviewTime + elapsedDays * 86400) * 1000), date: new Date((lastReviewTime + elapsedDays * 86400) * 1000),
daysSinceFirstLearn: data[data.length - 1].daysSinceFirstLearn + step, daysSinceFirstLearn: data[data.length - 1].daysSinceFirstLearn + step,
@ -138,7 +137,7 @@ export function prepareData(revlog: RevlogEntry[], maxDays: number) {
}); });
} }
daysSinceFirstLearn += totalDaysSinceLastReview; daysSinceFirstLearn += totalDaysSinceLastReview;
const retrievability = forgettingCurve(lastStability, totalDaysSinceLastReview); const retrievability = forgettingCurve(lastStability, totalDaysSinceLastReview, decay);
data.push({ data.push({
date: new Date(now * 1000), date: new Date(now * 1000),
daysSinceFirstLearn: daysSinceFirstLearn, daysSinceFirstLearn: daysSinceFirstLearn,
@ -151,7 +150,7 @@ export function prepareData(revlog: RevlogEntry[], maxDays: number) {
let previewDaysElapsed = 0; let previewDaysElapsed = 0;
while (previewDaysElapsed < previewDays) { while (previewDaysElapsed < previewDays) {
previewDaysElapsed += step; previewDaysElapsed += step;
const retrievability = forgettingCurve(lastStability, elapsedDays + previewDaysElapsed); const retrievability = forgettingCurve(lastStability, elapsedDays + previewDaysElapsed, decay);
data.push({ data.push({
date: new Date((now + previewDaysElapsed * 86400) * 1000), date: new Date((now + previewDaysElapsed * 86400) * 1000),
daysSinceFirstLearn: data[data.length - 1].daysSinceFirstLearn + step, daysSinceFirstLearn: data[data.length - 1].daysSinceFirstLearn + step,
@ -185,6 +184,7 @@ export function renderForgettingCurve(
svgElem: SVGElement, svgElem: SVGElement,
bounds: GraphBounds, bounds: GraphBounds,
desiredRetention: number, desiredRetention: number,
decay: number,
) { ) {
const svg = select(svgElem); const svg = select(svgElem);
const trans = svg.transition().duration(600) as any; const trans = svg.transition().duration(600) as any;
@ -194,7 +194,7 @@ export function renderForgettingCurve(
} }
const maxDays = calculateMaxDays(filteredRevlog, timeRange); const maxDays = calculateMaxDays(filteredRevlog, timeRange);
const data = prepareData(filteredRevlog, maxDays); const data = prepareData(filteredRevlog, maxDays, decay);
if (data.length === 0) { if (data.length === 0) {
setDataAvailable(svg, false); 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; $: computing = computingParams || checkingParams || computingRetention;
$: defaultparamSearch = `preset:"${state.getCurrentNameForSearch()}" -is:suspended`; $: defaultparamSearch = `preset:"${state.getCurrentNameForSearch()}" -is:suspended`;
$: roundedRetention = Number($config.desiredRetention.toFixed(2)); $: roundedRetention = Number($config.desiredRetention.toFixed(2));
$: desiredRetentionWarning = getRetentionWarning(roundedRetention); $: desiredRetentionWarning = getRetentionWarning(
roundedRetention,
fsrsParams($config),
);
$: retentionWarningClass = getRetentionWarningClass(roundedRetention); $: retentionWarningClass = getRetentionWarningClass(roundedRetention);
let computeRetentionProgress: let computeRetentionProgress:
@ -89,8 +92,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
reviewOrder: $config.reviewOrder, reviewOrder: $config.reviewOrder,
}); });
function getRetentionWarning(retention: number): string { function getRetentionWarning(retention: number, params: number[]): string {
const decay = -0.5; 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 factor = 0.9 ** (1 / decay) - 1;
const stability = 100; const stability = 100;
const days = Math.round( const days = Math.round(
@ -167,7 +170,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
: tr.deckConfigFsrsParamsNoReviews(); : tr.deckConfigFsrsParamsNoReviews();
setTimeout(() => alert(msg), 200); setTimeout(() => alert(msg), 200);
} else { } else {
$config.fsrsParams5 = resp.params; $config.fsrsParams6 = resp.params;
} }
if (computeParamsProgress) { if (computeParamsProgress) {
computeParamsProgress.current = computeParamsProgress.total; 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"> <div class="ms-1 me-1">
<ParamsInputRow <ParamsInputRow
bind:value={$config.fsrsParams5} bind:value={$config.fsrsParams6}
defaultValue={[]} defaultValue={[]}
defaults={defaults.fsrsParams5} defaults={defaults.fsrsParams6}
> >
<SettingTitle on:click={() => openHelpModal("modelParams")}> <SettingTitle on:click={() => openHelpModal("modelParams")}>
{tr.deckConfigWeights()} {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> </script>
<div class="modal" class:show={shown} class:d-block={shown} tabindex="-1"> <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[] { export function fsrsParams(config: DeckConfig_Config): number[] {
if (config.fsrsParams5) { if (config.fsrsParams6) {
return config.fsrsParams6;
} else if (config.fsrsParams5) {
return config.fsrsParams5; return config.fsrsParams5;
} else { } else {
return config.fsrsParams4; return config.fsrsParams4;

View file

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