diff --git a/Cargo.lock b/Cargo.lock index bcda51423..ce9210493 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 4660d94cc..eb42e090f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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] diff --git a/cargo/licenses.json b/cargo/licenses.json index 243d43afc..441aaed94 100644 --- a/cargo/licenses.json +++ b/cargo/licenses.json @@ -334,7 +334,7 @@ }, { "name": "bincode", - "version": "2.0.0-rc.3", + "version": "2.0.1", "authors": "Ty Overby |Zoey Riordan |Victor Koenders ", "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 ", "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 ", "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 ", "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 ", "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 ", "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 ", "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 ", "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 ", "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 ", "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 ", "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 |nathanielsimard ", "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 ", "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 ", "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 ", "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 ", "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 |David Tolnay ", "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 |David Tolnay ", "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 ", + "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 ", + "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 ", + "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", diff --git a/proto/anki/cards.proto b/proto/anki/cards.proto index eef1b8264..c120440e8 100644 --- a/proto/anki/cards.proto +++ b/proto/anki/cards.proto @@ -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; } diff --git a/proto/anki/deck_config.proto b/proto/anki/deck_config.proto index 1abb74e43..48cb71479 100644 --- a/proto/anki/deck_config.proto +++ b/proto/anki/deck_config.proto @@ -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; diff --git a/proto/anki/scheduler.proto b/proto/anki/scheduler.proto index 124f63d56..5eefe0030 100644 --- a/proto/anki/scheduler.proto +++ b/proto/anki/scheduler.proto @@ -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 { diff --git a/proto/anki/stats.proto b/proto/anki/stats.proto index 42d02029c..a5639f841 100644 --- a/proto/anki/stats.proto +++ b/proto/anki/stats.proto @@ -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 { diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index 4680caa0d..022708a80 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -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() diff --git a/rslib/src/card/mod.rs b/rslib/src/card/mod.rs index 98b7ef90c..8d7821e2c 100644 --- a/rslib/src/card/mod.rs +++ b/rslib/src/card/mod.rs @@ -95,6 +95,7 @@ pub struct Card { pub(crate) original_position: Option, pub(crate) memory_state: Option, pub(crate) desired_retention: Option, + pub(crate) decay: Option, /// 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(), } } diff --git a/rslib/src/card/service.rs b/rslib/src/card/service.rs index 63824b789..8f1421f25 100644 --- a/rslib/src/card/service.rs +++ b/rslib/src/card/service.rs @@ -106,6 +106,7 @@ impl TryFrom 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 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, } } diff --git a/rslib/src/deckconfig/mod.rs b/rslib/src/deckconfig/mod.rs index 21a75f521..c522ea18a 100644 --- a/rslib/src/deckconfig/mod.rs +++ b/rslib/src/deckconfig/mod.rs @@ -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 { - 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 diff --git a/rslib/src/deckconfig/schema11.rs b/rslib/src/deckconfig/schema11.rs index 73800f38e..2d862a3a0 100644 --- a/rslib/src/deckconfig/schema11.rs +++ b/rslib/src/deckconfig/schema11.rs @@ -74,6 +74,8 @@ pub struct DeckConfSchema11 { #[serde(default)] fsrs_params_5: Vec, #[serde(default)] + fsrs_params_6: Vec, + #[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 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 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", diff --git a/rslib/src/deckconfig/update.rs b/rslib/src/deckconfig/update.rs index 956786995..6d49bc5b1 100644 --- a/rslib/src/deckconfig/update.rs +++ b/rslib/src/deckconfig/update.rs @@ -50,7 +50,7 @@ impl Collection { deck: DeckId, ) -> Result { 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) => { diff --git a/rslib/src/scheduler/fsrs/memory_state.rs b/rslib/src/scheduler/fsrs/memory_state.rs index 90920f4bb..7d4346d06 100644 --- a/rslib/src/scheduler/fsrs/memory_state.rs +++ b/rslib/src/scheduler/fsrs/memory_state.rs @@ -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 diff --git a/rslib/src/scheduler/fsrs/retention.rs b/rslib/src/scheduler/fsrs/retention.rs index 23df5b33a..27ba5a997 100644 --- a/rslib/src/scheduler/fsrs/retention.rs +++ b/rslib/src/scheduler/fsrs/retention.rs @@ -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| { diff --git a/rslib/src/scheduler/fsrs/simulator.rs b/rslib/src/scheduler/fsrs/simulator.rs index eee625770..d04e57125 100644 --- a/rslib/src/scheduler/fsrs/simulator.rs +++ b/rslib/src/scheduler/fsrs/simulator.rs @@ -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, ) -> Option { // 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(¶ms) * 1000.0) as i32) + } RetrievabilityDescending => { - wrap!(|c| -(c.retrievability() * 1000.0) as i32) + wrap!(move |c| -(c.retrievability(¶ms) * 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, diff --git a/rslib/src/scheduler/service/mod.rs b/rslib/src/scheduler/service/mod.rs index d398ae65b..1d5d1ccb4 100644 --- a/rslib/src/scheduler/service/mod.rs +++ b/rslib/src/scheduler/service/mod.rs @@ -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, }) } diff --git a/rslib/src/stats/card.rs b/rslib/src/stats/card.rs index 6971311bf..cdea5193e 100644 --- a/rslib/src/stats/card.rs +++ b/rslib/src/stats/card.rs @@ -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()) diff --git a/rslib/src/stats/graphs/retrievability.rs b/rslib/src/stats/graphs/retrievability.rs index cd1b6105c..94f4d6bc9 100644 --- a/rslib/src/stats/graphs/retrievability.rs +++ b/rslib/src/stats/graphs/retrievability.rs @@ -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 diff --git a/rslib/src/storage/card/data.rs b/rslib/src/storage/card/data.rs index 8ef94146f..dd3b33928 100644 --- a/rslib/src/storage/card/data.rs +++ b/rslib/src/storage/card/data.rs @@ -42,6 +42,11 @@ pub(crate) struct CardData { deserialize_with = "default_on_invalid" )] pub(crate) fsrs_desired_retention: Option, + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "default_on_invalid" + )] + pub(crate) decay: Option, /// 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}"# ); } } diff --git a/rslib/src/storage/card/mod.rs b/rslib/src/storage/card/mod.rs index 35819bcc5..353537f90 100644 --- a/rslib/src/storage/card/mod.rs +++ b/rslib/src/storage/card/mod.rs @@ -85,6 +85,7 @@ fn row_to_card(row: &Row) -> result::Result { original_position: data.original_position, memory_state: data.memory_state(), desired_retention: data.fsrs_desired_retention, + decay: data.decay, custom_data: data.custom_data, }) } diff --git a/rslib/src/storage/sqlite.rs b/rslib/src/storage/sqlite.rs index c202bb0ef..e7057946d 100644 --- a/rslib/src/storage/sqlite.rs +++ b/rslib/src/storage/sqlite.rs @@ -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( diff --git a/rslib/src/sync/collection/chunks.rs b/rslib/src/sync/collection/chunks.rs index c840decf3..9d74ddb6c 100644 --- a/rslib/src/sync/collection/chunks.rs +++ b/rslib/src/sync/collection/chunks.rs @@ -332,6 +332,7 @@ impl From 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, } } diff --git a/ts/routes/card-info/CardInfo.svelte b/ts/routes/card-info/CardInfo.svelte index b332f131e..12ef472be 100644 --- a/ts/routes/card-info/CardInfo.svelte +++ b/ts/routes/card-info/CardInfo.svelte @@ -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; + })(); @@ -33,7 +43,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html {/if} {#if fsrsEnabled} - + {/if} {:else} diff --git a/ts/routes/card-info/ForgettingCurve.svelte b/ts/routes/card-info/ForgettingCurve.svelte index bb3a483f7..19af5e703 100644 --- a/ts/routes/card-info/ForgettingCurve.svelte +++ b/ts/routes/card-info/ForgettingCurve.svelte @@ -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, ); diff --git a/ts/routes/card-info/Revlog.svelte b/ts/routes/card-info/Revlog.svelte index dc410b4ab..02e9b12f4 100644 --- a/ts/routes/card-info/Revlog.svelte +++ b/ts/routes/card-info/Revlog.svelte @@ -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} - {#if fsrsEnabled}{/if} + {/if} diff --git a/ts/routes/card-info/forgetting-curve.ts b/ts/routes/card-info/forgetting-curve.ts index 9f0301fce..714032958 100644 --- a/ts/routes/card-info/forgetting-curve.ts +++ b/ts/routes/card-info/forgetting-curve.ts @@ -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); diff --git a/ts/routes/deck-options/FsrsOptions.svelte b/ts/routes/deck-options/FsrsOptions.svelte index 82c822a66..ef2487719 100644 --- a/ts/routes/deck-options/FsrsOptions.svelte +++ b/ts/routes/deck-options/FsrsOptions.svelte @@ -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
openHelpModal("modelParams")}> {tr.deckConfigWeights()} diff --git a/ts/routes/deck-options/SimulatorModal.svelte b/ts/routes/deck-options/SimulatorModal.svelte index c934deef1..c7d2e17e7 100644 --- a/ts/routes/deck-options/SimulatorModal.svelte +++ b/ts/routes/deck-options/SimulatorModal.svelte @@ -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];