diff --git a/.deny.toml b/.deny.toml index 6239fe0fb46..e0ece6eb4e0 100644 --- a/.deny.toml +++ b/.deny.toml @@ -30,6 +30,8 @@ allow = [ "BSD-3-Clause", "OpenSSL", "CC0-1.0", + "Unlicense", + "Zlib", ] confidence-threshold = 0.8 # eg { allow = ["Zlib"], name = "adler32", version = "*" }, diff --git a/Cargo.lock b/Cargo.lock index 5f31b5f396b..b5a25ead20b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,7 +98,7 @@ dependencies = [ "coarsetime", "convert_case", "criterion", - "csv", + "csv 1.1.6", "data-encoding", "difflib", "dirs", @@ -107,6 +107,7 @@ dependencies = [ "fluent", "fluent-bundle", "fnv", + "fsrs-optimizer", "futures", "hex", "htmlescape", @@ -138,7 +139,7 @@ dependencies = [ "serde_tuple", "sha1", "snafu", - "strum", + "strum 0.25.0", "syn 2.0.29", "tempfile", "tokio", @@ -215,7 +216,7 @@ dependencies = [ "prost-types", "serde", "snafu", - "strum", + "strum 0.25.0", ] [[package]] @@ -489,6 +490,15 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +[[package]] +name = "bincode" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -562,6 +572,152 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "burn" +version = "0.9.0" +source = "git+https://github.com/burn-rs/burn.git?rev=36446e8d35694a9158f97e85e44b84544b8c4afb#36446e8d35694a9158f97e85e44b84544b8c4afb" +dependencies = [ + "burn-core", + "burn-train", +] + +[[package]] +name = "burn-autodiff" +version = "0.9.0" +source = "git+https://github.com/burn-rs/burn.git?rev=36446e8d35694a9158f97e85e44b84544b8c4afb#36446e8d35694a9158f97e85e44b84544b8c4afb" +dependencies = [ + "burn-common", + "burn-tensor", + "burn-tensor-testgen", + "derive-new", + "spin 0.9.8", +] + +[[package]] +name = "burn-common" +version = "0.9.0" +source = "git+https://github.com/burn-rs/burn.git?rev=36446e8d35694a9158f97e85e44b84544b8c4afb#36446e8d35694a9158f97e85e44b84544b8c4afb" +dependencies = [ + "const-random", + "rand 0.8.5", + "spin 0.9.8", + "uuid", +] + +[[package]] +name = "burn-core" +version = "0.9.0" +source = "git+https://github.com/burn-rs/burn.git?rev=36446e8d35694a9158f97e85e44b84544b8c4afb#36446e8d35694a9158f97e85e44b84544b8c4afb" +dependencies = [ + "bincode", + "burn-autodiff", + "burn-common", + "burn-dataset", + "burn-derive", + "burn-ndarray", + "burn-tensor", + "derive-new", + "flate2", + "half 2.3.1", + "hashbrown 0.14.0", + "libm", + "log", + "rand 0.8.5", + "rmp-serde", + "serde", + "serde_json", + "spin 0.9.8", +] + +[[package]] +name = "burn-dataset" +version = "0.9.0" +source = "git+https://github.com/burn-rs/burn.git?rev=36446e8d35694a9158f97e85e44b84544b8c4afb#36446e8d35694a9158f97e85e44b84544b8c4afb" +dependencies = [ + "csv 1.2.2", + "derive-new", + "dirs", + "gix-tempfile", + "rand 0.8.5", + "rmp-serde", + "sanitize-filename", + "serde", + "serde_json", + "strum 0.24.1", + "strum_macros 0.24.3", + "tempfile", + "thiserror", +] + +[[package]] +name = "burn-derive" +version = "0.9.0" +source = "git+https://github.com/burn-rs/burn.git?rev=36446e8d35694a9158f97e85e44b84544b8c4afb#36446e8d35694a9158f97e85e44b84544b8c4afb" +dependencies = [ + "derive-new", + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "burn-ndarray" +version = "0.9.0" +source = "git+https://github.com/burn-rs/burn.git?rev=36446e8d35694a9158f97e85e44b84544b8c4afb#36446e8d35694a9158f97e85e44b84544b8c4afb" +dependencies = [ + "burn-autodiff", + "burn-common", + "burn-tensor", + "derive-new", + "libm", + "matrixmultiply", + "ndarray", + "num-traits", + "rand 0.8.5", + "rayon", + "spin 0.9.8", +] + +[[package]] +name = "burn-tensor" +version = "0.9.0" +source = "git+https://github.com/burn-rs/burn.git?rev=36446e8d35694a9158f97e85e44b84544b8c4afb#36446e8d35694a9158f97e85e44b84544b8c4afb" +dependencies = [ + "burn-tensor-testgen", + "derive-new", + "half 2.3.1", + "hashbrown 0.14.0", + "libm", + "num-traits", + "rand 0.8.5", + "rand_distr", + "serde", +] + +[[package]] +name = "burn-tensor-testgen" +version = "0.9.0" +source = "git+https://github.com/burn-rs/burn.git?rev=36446e8d35694a9158f97e85e44b84544b8c4afb#36446e8d35694a9158f97e85e44b84544b8c4afb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "burn-train" +version = "0.9.0" +source = "git+https://github.com/burn-rs/burn.git?rev=36446e8d35694a9158f97e85e44b84544b8c4afb#36446e8d35694a9158f97e85e44b84544b8c4afb" +dependencies = [ + "burn-core", + "derive-new", + "log", + "serde", + "tracing-appender", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -647,7 +803,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", - "half", + "half 1.8.2", ] [[package]] @@ -769,6 +925,28 @@ dependencies = [ "ninja_gen", ] +[[package]] +name = "const-random" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" +dependencies = [ + "const-random-macro", + "proc-macro-hack", +] + +[[package]] +name = "const-random-macro" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" +dependencies = [ + "getrandom 0.2.10", + "once_cell", + "proc-macro-hack", + "tiny-keccak", +] + [[package]] name = "constant_time_eq" version = "0.3.0" @@ -897,6 +1075,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -913,12 +1097,33 @@ version = "1.1.6" source = "git+https://github.com/ankitects/rust-csv.git?rev=1c9d3aab6f79a7d815c69f925a46a4590c115f90#1c9d3aab6f79a7d815c69f925a46a4590c115f90" dependencies = [ "bstr 0.2.17", - "csv-core", + "csv-core 0.1.10 (git+https://github.com/ankitects/rust-csv.git?rev=1c9d3aab6f79a7d815c69f925a46a4590c115f90)", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086" +dependencies = [ + "csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "itoa", "ryu", "serde", ] +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + [[package]] name = "csv-core" version = "0.1.10" @@ -927,6 +1132,19 @@ dependencies = [ "memchr", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.0", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.4.0" @@ -958,6 +1176,17 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +[[package]] +name = "derive-new" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "des" version = "0.8.1" @@ -1134,6 +1363,15 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "faster-hex" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9042d281a5eec0f2387f8c3ea6c4514e2cf2732c90a85aaf383b761ee3b290d" +dependencies = [ + "serde", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1291,6 +1529,22 @@ dependencies = [ "libc", ] +[[package]] +name = "fsrs-optimizer" +version = "0.1.0" +source = "git+https://github.com/open-spaced-repetition/fsrs-optimizer-burn?rev=e0b15cce555a94de6fdaa4bf1e096d19704a397d#e0b15cce555a94de6fdaa4bf1e096d19704a397d" +dependencies = [ + "burn", + "itertools 0.11.0", + "log", + "ndarray", + "ndarray-rand", + "rand 0.8.5", + "serde", + "snafu", + "strum 0.25.0", +] + [[package]] name = "ftl" version = "0.0.0" @@ -1476,6 +1730,58 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "gix-features" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f77decb545f63a52852578ef5f66ecd71017ffc1983d551d5fa2328d6d9817f" +dependencies = [ + "gix-hash", + "gix-trace", + "libc", +] + +[[package]] +name = "gix-fs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d5089f3338647776733a75a800a664ab046f56f21c515fa4722e395f877ef8" +dependencies = [ + "gix-features", +] + +[[package]] +name = "gix-hash" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d4796bac3aaf0c2f8bea152ca924ae3bdc5f135caefe6431116bcd67e98eab9" +dependencies = [ + "faster-hex", + "thiserror", +] + +[[package]] +name = "gix-tempfile" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea558d3daf3b1d0001052b12218c66c8f84788852791333b633d7eeb6999db1" +dependencies = [ + "dashmap", + "gix-fs", + "libc", + "once_cell", + "parking_lot", + "signal-hook", + "signal-hook-registry", + "tempfile", +] + +[[package]] +name = "gix-trace" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b6d623a1152c3facb79067d6e2ecdae48130030cf27d6eb21109f13bd7b836" + [[package]] name = "glob" version = "0.3.1" @@ -1520,6 +1826,18 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "half" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "serde", +] + [[package]] name = "handlebars" version = "4.3.7" @@ -1548,6 +1866,7 @@ checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ "ahash", "allocator-api2", + "serde", ] [[package]] @@ -2016,6 +2335,12 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + [[package]] name = "libsqlite3-sys" version = "0.26.0" @@ -2071,7 +2396,7 @@ dependencies = [ "linkcheck", "regex", "reqwest", - "strum", + "strum 0.25.0", "tokio", ] @@ -2209,6 +2534,19 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" +[[package]] +name = "matrixmultiply" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" +dependencies = [ + "autocfg", + "num_cpus", + "once_cell", + "rawpointer", + "thread-tree", +] + [[package]] name = "mdbook" version = "0.4.34" @@ -2356,6 +2694,31 @@ 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", + "rayon", +] + +[[package]] +name = "ndarray-rand" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65608f937acc725f5b164dcf40f4f0bc5d67dc268ab8a649d3002606718c4588" +dependencies = [ + "ndarray", + "rand 0.8.5", + "rand_distr", +] + [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -2442,6 +2805,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + [[package]] name = "num-format" version = "0.4.4" @@ -2452,6 +2824,16 @@ dependencies = [ "itoa", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.16" @@ -2459,6 +2841,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2636,6 +3019,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "pem" version = "1.1.1" @@ -3156,6 +3545,16 @@ dependencies = [ "getrandom 0.2.10", ] +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -3165,6 +3564,12 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.7.0" @@ -3349,6 +3754,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "rmp" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "rsbridge" version = "0.0.0" @@ -3502,6 +3929,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "sanitize-filename" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "schannel" version = "0.1.22" @@ -3570,9 +4007,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.185" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] @@ -3590,9 +4027,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.185" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", @@ -3722,6 +4159,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -3818,6 +4265,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "string_cache" @@ -3851,13 +4301,32 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + [[package]] name = "strum" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" dependencies = [ - "strum_macros", + "strum_macros 0.25.2", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", ] [[package]] @@ -3987,6 +4456,15 @@ dependencies = [ "syn 2.0.29", ] +[[package]] +name = "thread-tree" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbd370cb847953a25954d9f63e14824a36113f8c72eecf6eccef5dc4b45d630" +dependencies = [ + "crossbeam-channel", +] + [[package]] name = "thread_local" version = "1.1.7" @@ -4025,6 +4503,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.7.1" @@ -4581,6 +5068,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 9284bb17a84..aeb44a125f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,9 @@ anki_process = { path = "rslib/process" } anki_proto_gen = { path = "rslib/proto_gen" } ninja_gen = { "path" = "build/ninja_gen" } +fsrs-optimizer = { git = "https://github.com/open-spaced-repetition/fsrs-optimizer-burn", rev = "e0b15cce555a94de6fdaa4bf1e096d19704a397d" } +# fsrs-optimizer.path = "../../../fsrs-optimizer-burn" + # forked csv = { git = "https://github.com/ankitects/rust-csv.git", rev = "1c9d3aab6f79a7d815c69f925a46a4590c115f90" } percent-encoding-iri = { git = "https://github.com/ankitects/rust-url.git", rev = "bb930b8d089f4d30d7d19c12e54e66191de47b88" } diff --git a/cargo/licenses.json b/cargo/licenses.json index a5308194a7b..c9ef69734eb 100644 --- a/cargo/licenses.json +++ b/cargo/licenses.json @@ -269,6 +269,15 @@ "license_file": null, "description": "encodes and decodes base64 as bytes or utf8" }, + { + "name": "bincode", + "version": "2.0.0-rc.3", + "authors": "Ty Overby |Zoey Riordan |Victor Koenders ", + "repository": "https://github.com/bincode-org/bincode", + "license": "MIT", + "license_file": null, + "description": "A binary serialization / deserialization strategy for transforming structs into bytes and vice versa!" + }, { "name": "bitflags", "version": "1.3.2", @@ -323,6 +332,96 @@ "license_file": null, "description": "A fast bump allocation arena for Rust." }, + { + "name": "burn", + "version": "0.9.0", + "authors": "nathanielsimard ", + "repository": "https://github.com/burn-rs/burn", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Flexible and Comprehensive Deep Learning Framework in Rust" + }, + { + "name": "burn-autodiff", + "version": "0.9.0", + "authors": "nathanielsimard ", + "repository": "https://github.com/burn-rs/burn/tree/main/burn-autodiff", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Automatic differentiation backend for the Burn framework" + }, + { + "name": "burn-common", + "version": "0.9.0", + "authors": "Dilshod Tadjibaev (@antimora)", + "repository": "https://github.com/burn-rs/burn/tree/main/burn-common", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Common crate for the Burn framework" + }, + { + "name": "burn-core", + "version": "0.9.0", + "authors": "nathanielsimard ", + "repository": "https://github.com/burn-rs/burn/tree/main/burn-core", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Flexible and Comprehensive Deep Learning Framework in Rust" + }, + { + "name": "burn-dataset", + "version": "0.9.0", + "authors": "nathanielsimard ", + "repository": "https://github.com/burn-rs/burn/tree/main/burn-dataset", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Library with simple dataset APIs for creating ML data pipelines" + }, + { + "name": "burn-derive", + "version": "0.9.0", + "authors": "nathanielsimard ", + "repository": "https://github.com/burn-rs/burn/tree/main/burn-derive", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Derive crate for the Burn framework" + }, + { + "name": "burn-ndarray", + "version": "0.9.0", + "authors": "nathanielsimard ", + "repository": "https://github.com/burn-rs/burn/tree/main/burn-ndarray", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Ndarray backend for the Burn framework" + }, + { + "name": "burn-tensor", + "version": "0.9.0", + "authors": "nathanielsimard ", + "repository": "https://github.com/burn-rs/burn/tree/main/burn-tensor", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Tensor library with user-friendly APIs and automatic differentiation support" + }, + { + "name": "burn-tensor-testgen", + "version": "0.9.0", + "authors": "nathanielsimard ", + "repository": "https://github.com/burn-rs/burn/tree/main/burn-tensor-testgen", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Test generation crate for burn-tensor" + }, + { + "name": "burn-train", + "version": "0.9.0", + "authors": "nathanielsimard ", + "repository": "https://github.com/burn-rs/burn/tree/main/burn-train", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Training crate for the Burn framework" + }, { "name": "byteorder", "version": "1.4.3", @@ -395,6 +494,24 @@ "license_file": null, "description": "Concurrent multi-producer multi-consumer queue" }, + { + "name": "const-random", + "version": "0.1.15", + "authors": "Tom Kaitchuck ", + "repository": "https://github.com/tkaitchuck/constrandom", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Provides compile time random number generation." + }, + { + "name": "const-random-macro", + "version": "0.1.15", + "authors": "Tom Kaitchuck ", + "repository": "https://github.com/tkaitchuck/constrandom", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Provides the procedural macro used by const-random" + }, { "name": "constant_time_eq", "version": "0.3.0", @@ -458,6 +575,24 @@ "license_file": null, "description": "Multi-producer multi-consumer channels for message passing" }, + { + "name": "crossbeam-deque", + "version": "0.8.3", + "authors": null, + "repository": "https://github.com/crossbeam-rs/crossbeam", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Concurrent work-stealing deque" + }, + { + "name": "crossbeam-epoch", + "version": "0.9.15", + "authors": null, + "repository": "https://github.com/crossbeam-rs/crossbeam", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Epoch-based garbage collection" + }, { "name": "crossbeam-utils", "version": "0.8.16", @@ -467,6 +602,15 @@ "license_file": null, "description": "Utilities for concurrent programming" }, + { + "name": "crunchy", + "version": "0.2.2", + "authors": "Vurich ", + "repository": null, + "license": "MIT", + "license_file": null, + "description": "Crunchy unroller: deterministically unroll constant loops" + }, { "name": "crypto-common", "version": "0.1.6", @@ -485,6 +629,15 @@ "license_file": null, "description": "Fast CSV parsing with support for serde." }, + { + "name": "csv", + "version": "1.2.2", + "authors": "Andrew Gallant ", + "repository": "https://github.com/BurntSushi/rust-csv", + "license": "MIT OR Unlicense", + "license_file": null, + "description": "Fast CSV parsing with support for serde." + }, { "name": "csv-core", "version": "0.1.10", @@ -494,6 +647,24 @@ "license_file": null, "description": "Bare bones CSV parsing with no_std support." }, + { + "name": "csv-core", + "version": "0.1.10", + "authors": "Andrew Gallant ", + "repository": "https://github.com/BurntSushi/rust-csv", + "license": "MIT OR Unlicense", + "license_file": null, + "description": "Bare bones CSV parsing with no_std support." + }, + { + "name": "dashmap", + "version": "5.5.3", + "authors": "Acrimon ", + "repository": "https://github.com/xacrimon/dashmap", + "license": "MIT", + "license_file": null, + "description": "Blazing fast concurrent HashMap for Rust." + }, { "name": "data-encoding", "version": "2.4.0", @@ -530,6 +701,15 @@ "license_file": null, "description": "Ranged integers" }, + { + "name": "derive-new", + "version": "0.5.9", + "authors": "Nick Cameron ", + "repository": "https://github.com/nrc/derive-new", + "license": "MIT", + "license_file": null, + "description": "`#[derive(new)]` implements simple constructor functions for structs and enums." + }, { "name": "difflib", "version": "0.4.0", @@ -665,6 +845,15 @@ "license_file": null, "description": "Fallible streaming iteration" }, + { + "name": "faster-hex", + "version": "0.8.0", + "authors": "zhangsoledad <787953403@qq.com>", + "repository": "https://github.com/NervosFoundation/faster-hex", + "license": "MIT", + "license_file": null, + "description": "Fast hex encoding." + }, { "name": "fastrand", "version": "1.9.0", @@ -782,6 +971,15 @@ "license_file": null, "description": "Parser for values from the Forwarded header (RFC 7239)" }, + { + "name": "fsrs-optimizer", + "version": "0.1.0", + "authors": null, + "repository": null, + "license": "BSD-3-Clause", + "license_file": null, + "description": null + }, { "name": "futf", "version": "0.1.5", @@ -935,6 +1133,51 @@ "license_file": null, "description": "A library for reading and writing the DWARF debugging format." }, + { + "name": "gix-features", + "version": "0.33.0", + "authors": "Sebastian Thiel ", + "repository": "https://github.com/Byron/gitoxide", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A crate to integrate various capabilities using compile-time feature flags" + }, + { + "name": "gix-fs", + "version": "0.5.0", + "authors": "Sebastian Thiel ", + "repository": "https://github.com/Byron/gitoxide", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A crate providing file system specific utilities to `gitoxide`" + }, + { + "name": "gix-hash", + "version": "0.12.0", + "authors": "Sebastian Thiel ", + "repository": "https://github.com/Byron/gitoxide", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Borrowed and owned git hash digests used to identify git objects" + }, + { + "name": "gix-tempfile", + "version": "8.0.0", + "authors": "Sebastian Thiel ", + "repository": "https://github.com/Byron/gitoxide", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A tempfile implementation with a global registry to assure cleanup" + }, + { + "name": "gix-trace", + "version": "0.1.3", + "authors": "Sebastian Thiel ", + "repository": "https://github.com/Byron/gitoxide", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A crate to provide minimal `tracing` support that can be turned off to zero cost" + }, { "name": "h2", "version": "0.3.21", @@ -944,6 +1187,15 @@ "license_file": null, "description": "An HTTP/2 client and server" }, + { + "name": "half", + "version": "2.3.1", + "authors": "Kathryn Long ", + "repository": "https://github.com/starkat99/half-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Half-precision floating point f16 and bf16 types for Rust implementing the IEEE 754-2008 standard binary16 and bfloat16 types." + }, { "name": "hashbrown", "version": "0.12.3", @@ -1286,6 +1538,15 @@ "license_file": null, "description": "Raw FFI bindings to platform libraries like libc." }, + { + "name": "libm", + "version": "0.2.7", + "authors": "Jorge Aparicio ", + "repository": "https://github.com/rust-lang/libm", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "libm in pure Rust" + }, { "name": "libsqlite3-sys", "version": "0.26.0", @@ -1376,6 +1637,15 @@ "license_file": null, "description": "A blazing fast URL router." }, + { + "name": "matrixmultiply", + "version": "0.3.7", + "authors": "bluss|R. Janis Goldschmidt", + "repository": "https://github.com/bluss/matrixmultiply/", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "General matrix multiplication for f32 and f64 matrices. Operates on matrices with general layout (they can use arbitrary row and column stride). Detects and uses AVX or SSE2 on x86 platforms transparently for higher performance. Uses a microkernel strategy, so that the implementation is easy to parallelize and optimize. Supports multithreading." + }, { "name": "memchr", "version": "2.5.0", @@ -1385,6 +1655,15 @@ "license_file": null, "description": "Safe interface to memchr." }, + { + "name": "memoffset", + "version": "0.9.0", + "authors": "Gilad Naaman ", + "repository": "https://github.com/Gilnaa/memoffset", + "license": "MIT", + "license_file": null, + "description": "offset_of functionality for Rust structs." + }, { "name": "mime", "version": "0.3.17", @@ -1457,6 +1736,24 @@ "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-rand", + "version": "0.14.0", + "authors": "bluss", + "repository": "https://github.com/rust-ndarray/ndarray", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Constructors for randomized arrays. `rand` integration for `ndarray`." + }, { "name": "new_debug_unreachable", "version": "1.0.4", @@ -1493,6 +1790,15 @@ "license_file": null, "description": "Library for ANSI terminal colors and styles (bold, underline)" }, + { + "name": "num-complex", + "version": "0.4.4", + "authors": "The Rust Project Developers", + "repository": "https://github.com/rust-num/num-complex", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Complex numbers implementation for Rust" + }, { "name": "num-format", "version": "0.4.4", @@ -1502,6 +1808,15 @@ "license_file": null, "description": "A Rust crate for producing string-representations of numbers, formatted according to international standards" }, + { + "name": "num-integer", + "version": "0.1.45", + "authors": "The Rust Project Developers", + "repository": "https://github.com/rust-num/num-integer", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Integer traits and functions" + }, { "name": "num-traits", "version": "0.2.16", @@ -1637,6 +1952,15 @@ "license_file": null, "description": "An advanced API for creating custom synchronization primitives." }, + { + "name": "paste", + "version": "1.0.14", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/paste", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Macros for all your token pasting needs" + }, { "name": "percent-encoding", "version": "2.3.0", @@ -1961,6 +2285,15 @@ "license_file": null, "description": "Core random number generator traits and tools for implementation." }, + { + "name": "rand_distr", + "version": "0.4.3", + "authors": "The Rand Project Developers", + "repository": "https://github.com/rust-random/rand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Sampling from random number distributions" + }, { "name": "rand_hc", "version": "0.2.0", @@ -1970,6 +2303,33 @@ "license_file": null, "description": "HC128 random number generator" }, + { + "name": "rawpointer", + "version": "0.2.1", + "authors": "bluss", + "repository": "https://github.com/bluss/rawpointer/", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Extra methods for raw pointers and `NonNull`. For example `.post_inc()` and `.pre_dec()` (c.f. `ptr++` and `--ptr`), `offset` and `add` for `NonNull`, and the function `ptrdistance`." + }, + { + "name": "rayon", + "version": "1.7.0", + "authors": "Niko Matsakis |Josh Stone ", + "repository": "https://github.com/rayon-rs/rayon", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Simple work-stealing parallelism for Rust" + }, + { + "name": "rayon-core", + "version": "1.11.0", + "authors": "Niko Matsakis |Josh Stone ", + "repository": "https://github.com/rayon-rs/rayon", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Core APIs for Rayon" + }, { "name": "redox_syscall", "version": "0.2.16", @@ -2069,6 +2429,24 @@ "license_file": "LICENSE", "description": "Safe, fast, small crypto using Rust." }, + { + "name": "rmp", + "version": "0.8.12", + "authors": "Evgeny Safronov ", + "repository": "https://github.com/3Hren/msgpack-rust", + "license": "MIT", + "license_file": null, + "description": "Pure Rust MessagePack serialization implementation" + }, + { + "name": "rmp-serde", + "version": "1.1.2", + "authors": "Evgeny Safronov ", + "repository": "https://github.com/3Hren/msgpack-rust", + "license": "MIT", + "license_file": null, + "description": "Serde bindings for RMP" + }, { "name": "rusqlite", "version": "0.29.0", @@ -2168,6 +2546,15 @@ "license_file": null, "description": "A simple crate for determining whether two file paths point to the same file." }, + { + "name": "sanitize-filename", + "version": "0.5.0", + "authors": "Jacob Brown ", + "repository": "https://github.com/kardeiz/sanitize-filename", + "license": "MIT", + "license_file": null, + "description": "A simple filename sanitizer, based on Node's sanitize-filename" + }, { "name": "schannel", "version": "0.1.22", @@ -2224,7 +2611,7 @@ }, { "name": "serde", - "version": "1.0.185", + "version": "1.0.188", "authors": "Erick Tryzelaar |David Tolnay ", "repository": "https://github.com/serde-rs/serde", "license": "Apache-2.0 OR MIT", @@ -2242,7 +2629,7 @@ }, { "name": "serde_derive", - "version": "1.0.185", + "version": "1.0.188", "authors": "Erick Tryzelaar |David Tolnay ", "repository": "https://github.com/serde-rs/serde", "license": "Apache-2.0 OR MIT", @@ -2330,6 +2717,15 @@ "license_file": null, "description": "A lock-free concurrent slab." }, + { + "name": "signal-hook", + "version": "0.3.17", + "authors": "Michal 'vorner' Vaner |Thomas Himmelstoss ", + "repository": "https://github.com/vorner/signal-hook", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Unix signal handling" + }, { "name": "signal-hook-registry", "version": "1.4.1", @@ -2447,6 +2843,15 @@ "license_file": null, "description": "A codegen library for string-cache, developed as part of the Servo project." }, + { + "name": "strum", + "version": "0.24.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", "version": "0.25.0", @@ -2456,6 +2861,15 @@ "license_file": null, "description": "Helpful macros for working with enums and strings" }, + { + "name": "strum_macros", + "version": "0.24.3", + "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.25.2", @@ -2537,6 +2951,15 @@ "license_file": null, "description": "Implementation detail of the `thiserror` crate" }, + { + "name": "thread-tree", + "version": "0.3.3", + "authors": "bluss <>", + "repository": "https://github.com/bluss/thread-tree", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A tree-structured thread pool for splitting jobs hierarchically on worker threads. The tree structure means that there is no contention between workers when delivering jobs." + }, { "name": "thread_local", "version": "1.1.7", @@ -2573,6 +2996,15 @@ "license_file": null, "description": "Procedural macros for the time crate. This crate is an implementation detail and should not be relied upon directly." }, + { + "name": "tiny-keccak", + "version": "2.0.2", + "authors": "debris ", + "repository": null, + "license": "CC0-1.0", + "license_file": null, + "description": "An implementation of Keccak derived functions." + }, { "name": "tinystr", "version": "0.7.1", @@ -2960,6 +3392,15 @@ "license_file": null, "description": "A missing utime function for Rust." }, + { + "name": "uuid", + "version": "1.4.1", + "authors": "Ashley Mannix|Christopher Armstrong|Dylan DPC|Hunar Roop Kahlon", + "repository": "https://github.com/uuid-rs/uuid", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A library to generate and parse UUIDs." + }, { "name": "valuable", "version": "0.1.0", diff --git a/proto/anki/collection.proto b/proto/anki/collection.proto index ec97f83c2ab..b0928acc13f 100644 --- a/proto/anki/collection.proto +++ b/proto/anki/collection.proto @@ -126,6 +126,17 @@ message Progress { uint32 stage_current = 3; } + message ComputeWeights { + uint32 current = 1; + uint32 total = 2; + uint32 revlog_entries = 3; + } + + message ComputeRetention { + uint32 current = 1; + uint32 total = 2; + } + oneof value { generic.Empty none = 1; MediaSync media_sync = 2; @@ -135,6 +146,8 @@ message Progress { DatabaseCheck database_check = 6; string importing = 7; string exporting = 8; + ComputeWeights compute_weights = 9; + ComputeRetention compute_retention = 10; } } diff --git a/proto/anki/deck_config.proto b/proto/anki/deck_config.proto index d1ca4a0be4a..10b081aedc6 100644 --- a/proto/anki/deck_config.proto +++ b/proto/anki/deck_config.proto @@ -92,7 +92,9 @@ message DeckConfig { repeated float learn_steps = 1; repeated float relearn_steps = 2; - reserved 3 to 8; + repeated float fsrs_weights = 3; + + reserved 4 to 8; uint32 new_per_day = 9; uint32 reviews_per_day = 10; @@ -133,6 +135,9 @@ message DeckConfig { bool bury_reviews = 28; bool bury_interday_learning = 29; + bool fsrs_enabled = 36; + float desired_retention = 37; // for fsrs + bytes other = 255; } diff --git a/proto/anki/scheduler.proto b/proto/anki/scheduler.proto index 10580aacd95..e279d2dd11a 100644 --- a/proto/anki/scheduler.proto +++ b/proto/anki/scheduler.proto @@ -45,6 +45,11 @@ service SchedulerService { rpc CustomStudyDefaults(CustomStudyDefaultsRequest) returns (CustomStudyDefaultsResponse); rpc RepositionDefaults(generic.Empty) returns (RepositionDefaultsResponse); + rpc ComputeFsrsWeights(ComputeFsrsWeightsRequest) + returns (ComputeFsrsWeightsResponse); + rpc ComputeOptimalRetention(ComputeOptimalRetentionRequest) + returns (ComputeOptimalRetentionResponse); + rpc EvaluateWeights(EvaluateWeightsRequest) returns (EvaluateWeightsResponse); } // Implicitly includes any of the above methods that are not listed in the @@ -317,3 +322,37 @@ message RepositionDefaultsResponse { bool random = 1; bool shift = 2; } + +message ComputeFsrsWeightsRequest { + /// The search used to gather cards for training + string search = 1; +} + +message ComputeFsrsWeightsResponse { + repeated float weights = 1; +} + +message ComputeOptimalRetentionRequest { + repeated float weights = 1; + uint32 deck_size = 2; + uint32 days_to_simulate = 3; + uint32 max_seconds_of_study_per_day = 4; + uint32 max_interval = 5; + uint32 recall_secs = 6; + uint32 forget_secs = 7; + uint32 learn_secs = 8; +} + +message ComputeOptimalRetentionResponse { + float optimal_retention = 1; +} + +message EvaluateWeightsRequest { + repeated float weights = 1; + string search = 2; +} + +message EvaluateWeightsResponse { + float log_loss = 1; + float rmse = 2; +} diff --git a/qt/aqt/deckoptions.py b/qt/aqt/deckoptions.py index 2e8d2d4a476..fb4428f9ea3 100644 --- a/qt/aqt/deckoptions.py +++ b/qt/aqt/deckoptions.py @@ -60,6 +60,7 @@ def _setup_ui(self) -> None: gui_hooks.deck_options_did_load(self) def reject(self) -> None: + self.mw.col.set_wants_abort() self.web.cleanup() self.web = None saveGeom(self, self.TITLE) diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index c207773dad0..b169023ecbf 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -535,6 +535,11 @@ def handle_on_main() -> None: "add_image_occlusion_note", "get_image_occlusion_note", "update_image_occlusion_note", + # SchedulerService + "compute_fsrs_weights", + "compute_optimal_retention", + "set_wants_abort", + "evaluate_weights", ] diff --git a/rslib/Cargo.toml b/rslib/Cargo.toml index 03e8fbe718c..8e90fd980a4 100644 --- a/rslib/Cargo.toml +++ b/rslib/Cargo.toml @@ -60,6 +60,7 @@ flate2.workspace = true fluent.workspace = true fluent-bundle.workspace = true fnv.workspace = true +fsrs-optimizer.workspace = true futures.workspace = true hex.workspace = true htmlescape.workspace = true diff --git a/rslib/src/deckconfig/mod.rs b/rslib/src/deckconfig/mod.rs index d1515d61b5f..de099652ddc 100644 --- a/rslib/src/deckconfig/mod.rs +++ b/rslib/src/deckconfig/mod.rs @@ -66,6 +66,9 @@ const DEFAULT_DECK_CONFIG_INNER: DeckConfigInner = DeckConfigInner { bury_new: false, bury_reviews: false, bury_interday_learning: false, + fsrs_enabled: false, + fsrs_weights: vec![], + desired_retention: 0.9, other: Vec::new(), }; diff --git a/rslib/src/deckconfig/schema11.rs b/rslib/src/deckconfig/schema11.rs index d9b5f876f1a..d8185ce105e 100644 --- a/rslib/src/deckconfig/schema11.rs +++ b/rslib/src/deckconfig/schema11.rs @@ -65,6 +65,13 @@ pub struct DeckConfSchema11 { #[serde(default)] bury_interday_learning: bool, + #[serde(default)] + fsrs_weights: Vec, + #[serde(default)] + fsrs_enabled: bool, + #[serde(default)] + desired_retention: f32, + #[serde(flatten)] other: HashMap, } @@ -250,6 +257,9 @@ impl Default for DeckConfSchema11 { new_sort_order: 0, new_gather_priority: 0, bury_interday_learning: false, + fsrs_weights: vec![], + fsrs_enabled: false, + desired_retention: 0.9, } } } @@ -318,6 +328,9 @@ impl From for DeckConfig { bury_new: c.new.bury, bury_reviews: c.rev.bury, bury_interday_learning: c.bury_interday_learning, + fsrs_weights: c.fsrs_weights, + fsrs_enabled: c.fsrs_enabled, + desired_retention: c.desired_retention, other: other_bytes, }, } @@ -409,6 +422,9 @@ impl From for DeckConfSchema11 { new_sort_order: i.new_card_sort_order, new_gather_priority: i.new_card_gather_priority, bury_interday_learning: i.bury_interday_learning, + fsrs_weights: i.fsrs_weights, + fsrs_enabled: i.fsrs_enabled, + desired_retention: i.desired_retention, } } } @@ -429,7 +445,10 @@ static RESERVED_DECKCONF_KEYS: Set<&'static str> = phf_set! { "timer", "name", "interdayLearningMix", - "newGatherPriority" + "newGatherPriority", + "fsrsWeights", + "desiredRetention", + "fsrsEnabled", }; static RESERVED_DECKCONF_NEW_KEYS: Set<&'static str> = phf_set! { diff --git a/rslib/src/deckconfig/service.rs b/rslib/src/deckconfig/service.rs index 451187afcad..8d49f3506d9 100644 --- a/rslib/src/deckconfig/service.rs +++ b/rslib/src/deckconfig/service.rs @@ -7,13 +7,13 @@ use crate::deckconfig::DeckConfSchema11; use crate::deckconfig::DeckConfig; use crate::deckconfig::DeckConfigId; use crate::deckconfig::UpdateDeckConfigsRequest; -use crate::error; +use crate::error::Result; impl crate::services::DeckConfigService for Collection { fn add_or_update_deck_config_legacy( &mut self, input: generic::Json, - ) -> error::Result { + ) -> Result { let conf: DeckConfSchema11 = serde_json::from_slice(&input.json)?; let mut conf: DeckConfig = conf.into(); @@ -24,7 +24,7 @@ impl crate::services::DeckConfigService for Collection { .map(Into::into) } - fn all_deck_config_legacy(&mut self) -> error::Result { + fn all_deck_config_legacy(&mut self) -> Result { let conf: Vec = self .storage .all_deck_config()? @@ -39,7 +39,7 @@ impl crate::services::DeckConfigService for Collection { fn get_deck_config( &mut self, input: anki_proto::deck_config::DeckConfigId, - ) -> error::Result { + ) -> Result { Ok(Collection::get_deck_config(self, input.into(), true)? .unwrap() .into()) @@ -48,22 +48,19 @@ impl crate::services::DeckConfigService for Collection { fn get_deck_config_legacy( &mut self, input: anki_proto::deck_config::DeckConfigId, - ) -> error::Result { + ) -> Result { let conf = Collection::get_deck_config(self, input.into(), true)?.unwrap(); let conf: DeckConfSchema11 = conf.into(); Ok(serde_json::to_vec(&conf)?).map(Into::into) } - fn new_deck_config_legacy(&mut self) -> error::Result { + fn new_deck_config_legacy(&mut self) -> Result { serde_json::to_vec(&DeckConfSchema11::default()) .map_err(Into::into) .map(Into::into) } - fn remove_deck_config( - &mut self, - input: anki_proto::deck_config::DeckConfigId, - ) -> error::Result<()> { + fn remove_deck_config(&mut self, input: anki_proto::deck_config::DeckConfigId) -> Result<()> { self.transact_no_undo(|col| col.remove_deck_config_inner(input.into())) .map(Into::into) } @@ -71,14 +68,14 @@ impl crate::services::DeckConfigService for Collection { fn get_deck_configs_for_update( &mut self, input: anki_proto::decks::DeckId, - ) -> error::Result { + ) -> Result { self.get_deck_configs_for_update(input.did.into()) } fn update_deck_configs( &mut self, input: anki_proto::deck_config::UpdateDeckConfigsRequest, - ) -> error::Result { + ) -> Result { self.update_deck_configs(input.into()).map(Into::into) } } diff --git a/rslib/src/progress.rs b/rslib/src/progress.rs index 3224bcd8b31..25e115d29d3 100644 --- a/rslib/src/progress.rs +++ b/rslib/src/progress.rs @@ -6,6 +6,8 @@ use std::sync::Arc; use std::sync::Mutex; use anki_i18n::I18n; +use anki_proto::collection::progress::ComputeRetention; +use anki_proto::collection::progress::ComputeWeights; use futures::future::AbortHandle; use crate::dbcheck::DatabaseCheckProgress; @@ -14,6 +16,8 @@ use crate::error::Result; use crate::import_export::ExportProgress; use crate::import_export::ImportProgress; use crate::prelude::Collection; +use crate::scheduler::fsrs::retention::ComputeRetentionProgress; +use crate::scheduler::fsrs::weights::ComputeWeightsProgress; use crate::sync::collection::normal::NormalSyncProgress; use crate::sync::collection::progress::FullSyncProgress; use crate::sync::collection::progress::SyncStage; @@ -131,6 +135,8 @@ pub enum Progress { DatabaseCheck(DatabaseCheckProgress), Import(ImportProgress), Export(ExportProgress), + ComputeWeights(ComputeWeightsProgress), + ComputeRetention(ComputeRetentionProgress), } pub(crate) fn progress_to_proto( @@ -216,6 +222,19 @@ pub(crate) fn progress_to_proto( } .into(), ), + Progress::ComputeWeights(progress) => { + anki_proto::collection::progress::Value::ComputeWeights(ComputeWeights { + current: progress.current, + total: progress.total, + revlog_entries: progress.revlog_entries, + }) + } + Progress::ComputeRetention(progress) => { + anki_proto::collection::progress::Value::ComputeRetention(ComputeRetention { + current: progress.current, + total: progress.total, + }) + } } } else { anki_proto::collection::progress::Value::None(anki_proto::generic::Empty {}) @@ -282,6 +301,18 @@ impl From for Progress { } } +impl From for Progress { + fn from(p: ComputeWeightsProgress) -> Self { + Progress::ComputeWeights(p) + } +} + +impl From for Progress { + fn from(p: ComputeRetentionProgress) -> Self { + Progress::ComputeRetention(p) + } +} + impl Collection { pub fn new_progress_handler + Default + Clone>( &self, diff --git a/rslib/src/scheduler/fsrs/error.rs b/rslib/src/scheduler/fsrs/error.rs new file mode 100644 index 00000000000..e21ab902a4a --- /dev/null +++ b/rslib/src/scheduler/fsrs/error.rs @@ -0,0 +1,20 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +use fsrs_optimizer::FSRSError; + +use crate::error::AnkiError; +use crate::error::InvalidInputError; + +impl From for AnkiError { + fn from(err: FSRSError) -> Self { + match err { + FSRSError::NotEnoughData => InvalidInputError { + message: "Not enough data available".to_string(), + source: None, + backtrace: None, + } + .into(), + FSRSError::Interrupted => AnkiError::Interrupted, + } + } +} diff --git a/rslib/src/scheduler/fsrs/mod.rs b/rslib/src/scheduler/fsrs/mod.rs new file mode 100644 index 00000000000..436c7c5a4a5 --- /dev/null +++ b/rslib/src/scheduler/fsrs/mod.rs @@ -0,0 +1,5 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +mod error; +pub mod retention; +pub mod weights; diff --git a/rslib/src/scheduler/fsrs/retention.rs b/rslib/src/scheduler/fsrs/retention.rs new file mode 100644 index 00000000000..d46337dedc7 --- /dev/null +++ b/rslib/src/scheduler/fsrs/retention.rs @@ -0,0 +1,50 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +use anki_proto::scheduler::ComputeOptimalRetentionRequest; +use fsrs_optimizer::find_optimal_retention; +use fsrs_optimizer::SimulatorConfig; +use itertools::Itertools; + +use crate::prelude::*; + +#[derive(Default, Clone, Copy, Debug)] +pub struct ComputeRetentionProgress { + pub current: u32, + pub total: u32, +} + +impl Collection { + pub fn compute_optimal_retention( + &mut self, + req: ComputeOptimalRetentionRequest, + ) -> Result { + let mut anki_progress = self.new_progress_handler::(); + if req.weights.len() != 17 { + invalid_input!("must have 17 weights"); + } + let mut weights = [0f64; 17]; + weights + .iter_mut() + .set_from(req.weights.into_iter().map(|v| v as f64)); + Ok(find_optimal_retention( + &SimulatorConfig { + w: weights, + deck_size: req.deck_size as usize, + learn_span: req.days_to_simulate as usize, + max_cost_perday: req.max_seconds_of_study_per_day as f64, + max_ivl: req.max_interval as f64, + recall_cost: req.recall_secs as f64, + forget_cost: req.forget_secs as f64, + learn_cost: req.learn_secs as f64, + }, + |ip| { + anki_progress + .update(false, |p| { + p.total = ip.total as u32; + p.current = ip.current as u32; + }) + .is_ok() + }, + )? as f32) + } +} diff --git a/rslib/src/scheduler/fsrs/weights.rs b/rslib/src/scheduler/fsrs/weights.rs new file mode 100644 index 00000000000..b2b87b96d86 --- /dev/null +++ b/rslib/src/scheduler/fsrs/weights.rs @@ -0,0 +1,285 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +use std::iter; +use std::thread; +use std::time::Duration; + +use fsrs_optimizer::compute_weights; +use fsrs_optimizer::evaluate; +use fsrs_optimizer::FSRSItem; +use fsrs_optimizer::FSRSReview; +use fsrs_optimizer::ProgressState; +use itertools::Itertools; + +use crate::prelude::*; +use crate::revlog::RevlogEntry; +use crate::revlog::RevlogReviewKind; +use crate::search::SortMode; + +impl Collection { + pub fn compute_weights(&mut self, search: &str) -> Result> { + let timing = self.timing_today()?; + let mut anki_progress = self.new_progress_handler::(); + let guard = self.search_cards_into_table(search, SortMode::NoOrder)?; + let revlogs = guard + .col + .storage + .get_revlog_entries_for_searched_cards_in_order()?; + anki_progress.state.revlog_entries = revlogs.len() as u32; + let items = anki_to_fsrs(revlogs, timing.next_day_at); + // adapt the progress handler to our built-in progress handling + let progress = ProgressState::new_shared(); + let progress2 = progress.clone(); + thread::spawn(move || { + let mut finished = false; + while !finished { + thread::sleep(Duration::from_millis(100)); + let mut guard = progress.lock().unwrap(); + if let Err(_err) = anki_progress.update(false, |s| { + s.total = guard.total() as u32; + s.current = guard.current() as u32; + finished = s.total > 0 && s.total == s.current; + }) { + guard.want_abort = true; + return; + } + } + }); + compute_weights(items, Some(progress2)).map_err(Into::into) + } + + pub fn evaluate_weights(&mut self, weights: &[f32], search: &str) -> Result<(f32, f32)> { + let timing = self.timing_today()?; + if weights.len() != 17 { + invalid_input!("must have 17 weights"); + } + let mut weights_arr = [0f32; 17]; + weights_arr.iter_mut().set_from(weights.iter().cloned()); + let mut anki_progress = self.new_progress_handler::(); + let guard = self.search_cards_into_table(search, SortMode::NoOrder)?; + let revlogs = guard + .col + .storage + .get_revlog_entries_for_searched_cards_in_order()?; + anki_progress.state.revlog_entries = revlogs.len() as u32; + let items = anki_to_fsrs(revlogs, timing.next_day_at); + + Ok(evaluate(weights_arr, items, |ip| { + anki_progress + .update(false, |p| { + p.total = ip.total as u32; + p.current = ip.current as u32; + }) + .is_ok() + })?) + } +} + +#[derive(Default, Clone, Copy, Debug)] +pub struct ComputeWeightsProgress { + pub current: u32, + pub total: u32, + pub revlog_entries: u32, +} + +/// Convert a series of revlog entries sorted by card id into FSRS items. +fn anki_to_fsrs(revlogs: Vec, next_day_at: TimestampSecs) -> Vec { + let mut revlogs = revlogs + .into_iter() + .group_by(|r| r.cid) + .into_iter() + .filter_map(|(_cid, entries)| single_card_revlog_to_items(entries.collect(), next_day_at)) + .flatten() + .collect_vec(); + revlogs.sort_by_cached_key(|r| r.reviews.len()); + revlogs +} + +fn single_card_revlog_to_items( + mut entries: Vec, + next_day_at: TimestampSecs, +) -> Option> { + // Find the index of the first learn entry in the last continuous group + let mut index_to_keep = 0; + let mut i = entries.len(); + + while i > 0 { + i -= 1; + if entries[i].review_kind == RevlogReviewKind::Learning { + index_to_keep = i; + } else if index_to_keep != 0 { + // Found a continuous group + break; + } + } + + // Remove all entries before this one + entries.drain(..index_to_keep); + + // we ignore cards that don't start in the learning state + if let Some(entry) = entries.first() { + if entry.review_kind != RevlogReviewKind::Learning { + return None; + } + } else { + // no revlog entries + return None; + } + + // Keep only the first review when multiple reviews done on one day + let mut unique_dates = std::collections::HashSet::new(); + entries.retain(|entry| unique_dates.insert(entry.days_elapsed(next_day_at))); + + // Old versions of Anki did not record Manual entries in the review log when + // cards were manually rescheduled. So we look for times when the card has + // gone from Review to Learning, indicating it has been reset, and remove + // entries after. + for (i, (a, b)) in entries.iter().tuple_windows().enumerate() { + if let ( + RevlogReviewKind::Review | RevlogReviewKind::Relearning, + RevlogReviewKind::Learning, + ) = (a.review_kind, b.review_kind) + { + // Remove entry and all following + entries.truncate(i + 1); + break; + } + } + + // Compute delta_t for each entry + let delta_ts = iter::once(0) + .chain(entries.iter().tuple_windows().map(|(previous, current)| { + previous.days_elapsed(next_day_at) - current.days_elapsed(next_day_at) + })) + .collect_vec(); + + // Skip the first learning step, then convert the remaining entries into + // separate FSRSItems, where each item contains all reviews done until then. + Some( + entries + .iter() + .enumerate() + .skip(1) + .map(|(outer_idx, _)| { + let reviews = entries + .iter() + .take(outer_idx + 1) + .enumerate() + .map(|(inner_idx, r)| FSRSReview { + rating: r.button_chosen as i32, + delta_t: delta_ts[inner_idx] as i32, + }) + .collect(); + FSRSItem { reviews } + }) + .collect(), + ) +} + +impl RevlogEntry { + fn days_elapsed(&self, next_day_at: TimestampSecs) -> u32 { + (next_day_at.elapsed_secs_since(self.id.as_secs()) / 86_400).max(0) as u32 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const NEXT_DAY_AT: TimestampSecs = TimestampSecs(86400 * 100); + + fn revlog(review_kind: RevlogReviewKind, days_ago: i64) -> RevlogEntry { + RevlogEntry { + review_kind, + id: ((NEXT_DAY_AT.0 - days_ago * 86400) * 1000).into(), + ..Default::default() + } + } + + #[test] + fn delta_t_is_correct() -> Result<()> { + assert_eq!( + single_card_revlog_to_items( + vec![ + revlog(RevlogReviewKind::Learning, 1), + revlog(RevlogReviewKind::Review, 0) + ], + NEXT_DAY_AT + ), + Some(vec![FSRSItem { + reviews: vec![ + FSRSReview { + rating: 0, + delta_t: 0 + }, + FSRSReview { + rating: 0, + delta_t: 1 + } + ] + }]) + ); + assert_eq!( + single_card_revlog_to_items( + vec![ + revlog(RevlogReviewKind::Learning, 15), + revlog(RevlogReviewKind::Learning, 13), + revlog(RevlogReviewKind::Review, 10), + revlog(RevlogReviewKind::Review, 5) + ], + NEXT_DAY_AT, + ), + Some(vec![ + FSRSItem { + reviews: vec![ + FSRSReview { + rating: 0, + delta_t: 0 + }, + FSRSReview { + rating: 0, + delta_t: 2 + } + ] + }, + FSRSItem { + reviews: vec![ + FSRSReview { + rating: 0, + delta_t: 0 + }, + FSRSReview { + rating: 0, + delta_t: 2 + }, + FSRSReview { + rating: 0, + delta_t: 3 + } + ] + }, + FSRSItem { + reviews: vec![ + FSRSReview { + rating: 0, + delta_t: 0 + }, + FSRSReview { + rating: 0, + delta_t: 2 + }, + FSRSReview { + rating: 0, + delta_t: 3 + }, + FSRSReview { + rating: 0, + delta_t: 5 + } + ] + } + ]) + ); + Ok(()) + } +} diff --git a/rslib/src/scheduler/mod.rs b/rslib/src/scheduler/mod.rs index e24053c8dc7..0e70a89a224 100644 --- a/rslib/src/scheduler/mod.rs +++ b/rslib/src/scheduler/mod.rs @@ -10,6 +10,7 @@ pub mod answering; pub mod bury_and_suspend; pub(crate) mod congrats; pub(crate) mod filtered; +pub mod fsrs; mod learning; pub mod new; pub(crate) mod queue; diff --git a/rslib/src/scheduler/service/mod.rs b/rslib/src/scheduler/service/mod.rs index 9e862c34d2b..c0fb042462e 100644 --- a/rslib/src/scheduler/service/mod.rs +++ b/rslib/src/scheduler/service/mod.rs @@ -6,6 +6,8 @@ mod states; use anki_proto::generic; use anki_proto::scheduler; +use anki_proto::scheduler::ComputeOptimalRetentionRequest; +use anki_proto::scheduler::ComputeOptimalRetentionResponse; use crate::prelude::*; use crate::scheduler::new::NewCardDueOrder; @@ -237,4 +239,33 @@ impl crate::services::SchedulerService for Collection { ) -> Result { self.custom_study_defaults(input.deck_id.into()) } + + fn compute_fsrs_weights( + &mut self, + input: scheduler::ComputeFsrsWeightsRequest, + ) -> Result { + Ok(scheduler::ComputeFsrsWeightsResponse { + weights: self.compute_weights(&input.search)?, + }) + } + + fn compute_optimal_retention( + &mut self, + input: ComputeOptimalRetentionRequest, + ) -> Result { + Ok(ComputeOptimalRetentionResponse { + optimal_retention: self.compute_optimal_retention(input)?, + }) + } + + fn evaluate_weights( + &mut self, + input: scheduler::EvaluateWeightsRequest, + ) -> Result { + let ret = self.evaluate_weights(&input.weights, &input.search)?; + Ok(scheduler::EvaluateWeightsResponse { + log_loss: ret.0, + rmse: ret.1, + }) + } } diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index 2ea62e62a65..4a411c05ec5 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -93,6 +93,7 @@ pub enum SearchNode { NoCombining(String), WordBoundary(String), CustomData(String), + Preset(String), } #[derive(Debug, PartialEq, Clone)] diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index f20fc89b1c8..d13374fa78b 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -181,6 +181,7 @@ impl SqlWriter<'_> { SearchNode::Property { operator, kind } => self.write_prop(operator, kind)?, SearchNode::CustomData(key) => self.write_custom_data(key)?, SearchNode::WholeCollection => write!(self.sql, "true").unwrap(), + SearchNode::Preset(name) => self.write_deck_preset(name)?, }; Ok(()) } @@ -824,6 +825,25 @@ impl SqlWriter<'_> { self.col.get_config_bool(BoolKey::IgnoreAccentsInSearch), ) } + fn write_deck_preset(&mut self, name: &str) -> Result<()> { + let dcid = self.col.storage.get_deck_config_id_by_name(name)?; + let mut str_ids = String::new(); + let deck_ids = self + .col + .storage + .get_all_decks()? + .into_iter() + .filter_map(|d| { + if d.config_id() == dcid { + Some(d.id) + } else { + None + } + }); + ids_to_string(&mut str_ids, deck_ids); + write!(self.sql, "c.did in {str_ids}").unwrap(); + Ok(()) + } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -958,6 +978,7 @@ impl SearchNode { SearchNode::WholeCollection => RequiredTable::CardsOrNotes, SearchNode::CardTemplate(_) => RequiredTable::CardsAndNotes, + SearchNode::Preset(_) => RequiredTable::Cards, } } } diff --git a/rslib/src/search/writer.rs b/rslib/src/search/writer.rs index 3937c6a3811..37813e57109 100644 --- a/rslib/src/search/writer.rs +++ b/rslib/src/search/writer.rs @@ -87,6 +87,7 @@ fn write_search_node(node: &SearchNode) -> String { NoCombining(s) => maybe_quote(&format!("nc:{}", s)), WordBoundary(s) => maybe_quote(&format!("w:{}", s)), CustomData(k) => maybe_quote(&format!("has-cd:{}", k)), + Preset(s) => maybe_quote(&format!("preset:{}", s)), } } diff --git a/rslib/src/storage/deckconfig/mod.rs b/rslib/src/storage/deckconfig/mod.rs index 1c95234d61f..896b0fa1370 100644 --- a/rslib/src/storage/deckconfig/mod.rs +++ b/rslib/src/storage/deckconfig/mod.rs @@ -62,6 +62,15 @@ impl SqliteStorage { .transpose() } + pub(crate) fn get_deck_config_id_by_name(&self, name: &str) -> Result> { + self.db + .prepare_cached("select id from deck_config where WHERE name = ?")? + .query_and_then([name], |row| Ok::<_, AnkiError>(DeckConfigId(row.get(0)?)))? + .next() + .transpose() + .map_err(Into::into) + } + pub(crate) fn add_deck_conf(&self, conf: &mut DeckConfig) -> Result<()> { let mut conf_bytes = vec![]; conf.inner.encode(&mut conf_bytes)?; diff --git a/rslib/src/storage/revlog/mod.rs b/rslib/src/storage/revlog/mod.rs index f9d38eb4061..5c6d62d6de1 100644 --- a/rslib/src/storage/revlog/mod.rs +++ b/rslib/src/storage/revlog/mod.rs @@ -132,6 +132,18 @@ impl SqliteStorage { .collect() } + pub(crate) fn get_revlog_entries_for_searched_cards_in_order( + &self, + ) -> Result> { + self.db + .prepare_cached(concat!( + include_str!("get.sql"), + " where cid in (select cid from search_cids) order by cid" + ))? + .query_and_then([], row_to_revlog_entry)? + .collect() + } + pub(crate) fn get_all_revlog_entries(&self, after: TimestampSecs) -> Result> { self.db .prepare_cached(concat!(include_str!("get.sql"), " where id >= ?"))? diff --git a/ts/deck-options/AdvancedOptions.svelte b/ts/deck-options/AdvancedOptions.svelte index 8cfc99539b1..7135b4e99db 100644 --- a/ts/deck-options/AdvancedOptions.svelte +++ b/ts/deck-options/AdvancedOptions.svelte @@ -17,6 +17,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import SettingTitle from "./SettingTitle.svelte"; import SpinBoxFloatRow from "./SpinBoxFloatRow.svelte"; import SpinBoxRow from "./SpinBoxRow.svelte"; + import SwitchRow from "./SwitchRow.svelte"; import type { DeckOption } from "./types"; export let state: DeckOptionsState; @@ -183,6 +184,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + {#if state.v3Scheduler} + + + FSRS optimizer + + + {/if} + {#if state.v3Scheduler} > { return state.currentAuxData; @@ -112,11 +114,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - - - - - + {#if $addons.length} + + + + + + {/if} + + {#if state.v3Scheduler && $config.fsrsEnabled} + + + + + + {/if} diff --git a/ts/deck-options/FsrsOptions.svelte b/ts/deck-options/FsrsOptions.svelte new file mode 100644 index 00000000000..12cdfb01135 --- /dev/null +++ b/ts/deck-options/FsrsOptions.svelte @@ -0,0 +1,289 @@ + + + + + + Weights + +
Optimal retention
+ + + + + + +
+ +
+ Optimize weights +
+ + + +
{computeWeightsProgressString}
+
+ +
+ Calculate optimal retention +
+ + Deck size: +
+ +
+ + Days to simulate +
+ +
+ + Max seconds of study per day: +
+ +
+ + Maximum interval: +
+ +
+ + Seconds to recall a card: +
+ +
+ + Seconds to forget a card: +
+ +
+ + Seconds to learn a card: +
+ +
+ + +
{computeRetentionProgressString}
+
+ + + diff --git a/ts/deck-options/WeightsInput.svelte b/ts/deck-options/WeightsInput.svelte new file mode 100644 index 00000000000..229f6cecebe --- /dev/null +++ b/ts/deck-options/WeightsInput.svelte @@ -0,0 +1,16 @@ + + + +