Compare commits
7 Commits
7f9a962ff7
...
abfe5480e5
| Author | SHA1 | Date | |
|---|---|---|---|
| abfe5480e5 | |||
| 31181513d0 | |||
| 7252d37763 | |||
| fe01277f7b | |||
| e96fcdbe99 | |||
| 9d96977785 | |||
| 2378f719a7 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@
|
||||
/Cargo.lock
|
||||
perf.data
|
||||
flamegraph.svg
|
||||
result
|
||||
|
||||
340
Cargo.lock
generated
340
Cargo.lock
generated
@ -128,6 +128,12 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "as-raw-xcb-connection"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
@ -190,6 +196,21 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@ -291,6 +312,18 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop-wayland-source"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
|
||||
dependencies = [
|
||||
"calloop 0.13.0",
|
||||
"rustix 0.38.44",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop-wayland-source"
|
||||
version = "0.4.1"
|
||||
@ -369,7 +402,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -381,6 +414,15 @@ dependencies = [
|
||||
"error-code",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
|
||||
dependencies = [
|
||||
"unicode-width 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
@ -714,6 +756,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"document-features",
|
||||
"egui",
|
||||
"egui-wgpu",
|
||||
"egui-winit",
|
||||
"egui_glow",
|
||||
"glow",
|
||||
@ -756,6 +799,25 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui-wgpu"
|
||||
version = "0.33.2"
|
||||
source = "git+https://github.com/titaniumtown/egui.git#63106bc9faab805197ba88820d6f11bc8c5c4657"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
"document-features",
|
||||
"egui",
|
||||
"epaint",
|
||||
"log",
|
||||
"profiling",
|
||||
"thiserror 2.0.17",
|
||||
"type-map",
|
||||
"web-time",
|
||||
"wgpu",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui-winit"
|
||||
version = "0.33.2"
|
||||
@ -789,6 +851,7 @@ dependencies = [
|
||||
"profiling",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -941,6 +1004,12 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.5.0"
|
||||
@ -1085,6 +1154,7 @@ dependencies = [
|
||||
"cgl",
|
||||
"dispatch2",
|
||||
"glutin_egl_sys",
|
||||
"glutin_glx_sys",
|
||||
"glutin_wgl_sys",
|
||||
"libloading",
|
||||
"objc2 0.6.3",
|
||||
@ -1093,7 +1163,9 @@ dependencies = [
|
||||
"objc2-foundation 0.3.2",
|
||||
"once_cell",
|
||||
"raw-window-handle",
|
||||
"wayland-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"x11-dl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1118,6 +1190,16 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glutin_glx_sys"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185"
|
||||
dependencies = [
|
||||
"gl_generator",
|
||||
"x11-dl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glutin_wgl_sys"
|
||||
version = "0.6.1"
|
||||
@ -1141,6 +1223,7 @@ checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"num-traits",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
@ -1149,6 +1232,9 @@ name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
@ -1165,6 +1251,12 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
|
||||
[[package]]
|
||||
name = "hexf-parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.64"
|
||||
@ -1485,6 +1577,12 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.10"
|
||||
@ -1609,6 +1707,31 @@ dependencies = [
|
||||
"pxfm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "naga"
|
||||
version = "27.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bit-set",
|
||||
"bitflags 2.10.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"codespan-reporting",
|
||||
"half 2.7.1",
|
||||
"hashbrown",
|
||||
"hexf-parse",
|
||||
"indexmap",
|
||||
"libm",
|
||||
"log",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rustc-hash 1.1.0",
|
||||
"thiserror 2.0.17",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
@ -1746,6 +1869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2286,6 +2410,21 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.4"
|
||||
@ -2500,6 +2639,12 @@ version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "renderdoc-sys"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.52"
|
||||
@ -2524,6 +2669,18 @@ version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
@ -2718,6 +2875,31 @@ version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "smithay-client-toolkit"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"calloop 0.13.0",
|
||||
"calloop-wayland-source 0.3.0",
|
||||
"cursor-icon",
|
||||
"libc",
|
||||
"log",
|
||||
"memmap2 0.9.9",
|
||||
"rustix 0.38.44",
|
||||
"thiserror 1.0.69",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-csd-frame",
|
||||
"wayland-cursor",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-wlr",
|
||||
"wayland-scanner",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smithay-client-toolkit"
|
||||
version = "0.20.0"
|
||||
@ -2726,7 +2908,7 @@ checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"calloop 0.14.3",
|
||||
"calloop-wayland-source",
|
||||
"calloop-wayland-source 0.4.1",
|
||||
"cursor-icon",
|
||||
"libc",
|
||||
"log",
|
||||
@ -2752,7 +2934,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"smithay-client-toolkit",
|
||||
"smithay-client-toolkit 0.20.0",
|
||||
"wayland-backend",
|
||||
]
|
||||
|
||||
@ -2856,7 +3038,7 @@ version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3057,6 +3239,15 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "type-map"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90"
|
||||
dependencies = [
|
||||
"rustc-hash 2.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
@ -3087,6 +3278,12 @@ version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
@ -3318,6 +3515,19 @@ dependencies = [
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-plasma"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-wlr"
|
||||
version = "0.3.9"
|
||||
@ -3396,6 +3606,102 @@ version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||
|
||||
[[package]]
|
||||
name = "wgpu"
|
||||
version = "27.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags 2.10.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"document-features",
|
||||
"hashbrown",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"profiling",
|
||||
"raw-window-handle",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"wgpu-core",
|
||||
"wgpu-hal",
|
||||
"wgpu-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu-core"
|
||||
version = "27.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bit-set",
|
||||
"bit-vec",
|
||||
"bitflags 2.10.0",
|
||||
"bytemuck",
|
||||
"cfg_aliases",
|
||||
"document-features",
|
||||
"hashbrown",
|
||||
"indexmap",
|
||||
"log",
|
||||
"naga",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"profiling",
|
||||
"raw-window-handle",
|
||||
"rustc-hash 1.1.0",
|
||||
"smallvec",
|
||||
"thiserror 2.0.17",
|
||||
"wgpu-core-deps-windows-linux-android",
|
||||
"wgpu-hal",
|
||||
"wgpu-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu-core-deps-windows-linux-android"
|
||||
version = "27.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3"
|
||||
dependencies = [
|
||||
"wgpu-hal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu-hal"
|
||||
version = "27.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libloading",
|
||||
"log",
|
||||
"naga",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"raw-window-handle",
|
||||
"renderdoc-sys",
|
||||
"thiserror 2.0.17",
|
||||
"wgpu-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu-types"
|
||||
version = "27.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bytemuck",
|
||||
"js-sys",
|
||||
"log",
|
||||
"thiserror 2.0.17",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@ -3723,10 +4029,12 @@ version = "0.30.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"android-activity",
|
||||
"atomic-waker",
|
||||
"bitflags 2.10.0",
|
||||
"block2",
|
||||
"bytemuck",
|
||||
"calloop 0.13.0",
|
||||
"cfg_aliases",
|
||||
"concurrent-queue",
|
||||
@ -3736,24 +4044,33 @@ dependencies = [
|
||||
"dpi",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"memmap2 0.9.9",
|
||||
"ndk",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-ui-kit",
|
||||
"orbclient",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"raw-window-handle",
|
||||
"redox_syscall 0.4.1",
|
||||
"rustix 0.38.44",
|
||||
"smithay-client-toolkit 0.19.2",
|
||||
"smol_str",
|
||||
"tracing",
|
||||
"unicode-segmentation",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-plasma",
|
||||
"web-sys",
|
||||
"web-time",
|
||||
"windows-sys 0.52.0",
|
||||
"x11-dl",
|
||||
"x11rb",
|
||||
"xkbcommon-dl",
|
||||
]
|
||||
|
||||
@ -3778,13 +4095,28 @@ version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"once_cell",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
||||
dependencies = [
|
||||
"as-raw-xcb-connection",
|
||||
"gethostname",
|
||||
"libc",
|
||||
"libloading",
|
||||
"once_cell",
|
||||
"rustix 1.1.2",
|
||||
"x11rb-protocol",
|
||||
]
|
||||
|
||||
@ -9,6 +9,10 @@ description = "Crossplatform (and web-compatible) graphing calculator"
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
# Enable x11/wayland support for running tests on native targets
|
||||
native-test = ["eframe/x11", "eframe/wayland"]
|
||||
|
||||
[profile.release]
|
||||
debug = false
|
||||
codegen-units = 1
|
||||
|
||||
16
TODO.md
16
TODO.md
@ -1,18 +1,20 @@
|
||||
## TODO:
|
||||
1. Function management
|
||||
- Integrals between functions (too hard to implement, maybe will shelve)
|
||||
- Display intersection between functions (would have to rewrite a lot of the function plotting handling)
|
||||
- [Drag and drop support](https://github.com/emilk/egui/discussions/1530) in the UI to re-order functions
|
||||
- Hide/disable functions
|
||||
- Prevent user from making too many function entries
|
||||
- Display function errors as tooltips or a warning box (not preventing the display of the graph)
|
||||
- Clone functions
|
||||
a. Integrals between functions (too hard to implement, maybe will shelve)
|
||||
b. Display intersection between functions (would have to rewrite a lot of the function plotting handling)
|
||||
c. [Drag and drop support](https://github.com/emilk/egui/discussions/1530) in the UI to re-order functions
|
||||
d. Hide/disable functions
|
||||
e. Prevent user from making too many function entries
|
||||
f. Display function errors as tooltips or a warning box (not preventing the display of the graph)
|
||||
g. Clone functions
|
||||
2. Smart display of graph
|
||||
- Display of intersections between functions
|
||||
3. Allow constants in min/max integral input (like pi or euler's number)
|
||||
4. Sliding values for functions (like a user-interactable slider that adjusts a variable in the function, like desmos)
|
||||
5. Fix integral display
|
||||
6. Better handling of roots and extrema finding
|
||||
a. For instance, persistance, the roots shouldn't be recalculated for each movement of the viewport
|
||||
b. If applicable, the roots/extrema should be expressed in terms of constants such as a root of a number, pi, or something else.
|
||||
7. Add closing animation for function entry
|
||||
8. Fix mobile text input
|
||||
9. Write custom plotter
|
||||
|
||||
36
flake.nix
36
flake.nix
@ -59,8 +59,42 @@
|
||||
buildInputs = with pkgs; [
|
||||
openssl
|
||||
zstd
|
||||
# Required for running tests with native windowing support
|
||||
libxkbcommon
|
||||
libGL
|
||||
wayland
|
||||
xorg.libX11
|
||||
xorg.libXcursor
|
||||
xorg.libXi
|
||||
xorg.libXrandr
|
||||
];
|
||||
|
||||
# Run all tests on native target before building wasm
|
||||
# Note: Tests run without --release because the release profile uses
|
||||
# panic=abort which is incompatible with the test harness.
|
||||
checkPhase =
|
||||
let
|
||||
libPath = pkgs.lib.makeLibraryPath (
|
||||
with pkgs;
|
||||
[
|
||||
libxkbcommon
|
||||
libGL
|
||||
wayland
|
||||
xorg.libX11
|
||||
xorg.libXcursor
|
||||
xorg.libXi
|
||||
xorg.libXrandr
|
||||
]
|
||||
);
|
||||
in
|
||||
''
|
||||
runHook preCheck
|
||||
export HOME=$TMPDIR
|
||||
export LD_LIBRARY_PATH="${libPath}:$LD_LIBRARY_PATH"
|
||||
cargo test --workspace --features native-test
|
||||
runHook postCheck
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
@ -81,7 +115,7 @@
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
doCheck = false;
|
||||
doCheck = true;
|
||||
};
|
||||
|
||||
# Final web package with wasm-bindgen processing
|
||||
|
||||
@ -5,46 +5,46 @@ use std::path::Path;
|
||||
|
||||
/// REMEMBER TO UPDATE THIS IF EXMEX ADDS NEW FUNCTIONS
|
||||
const SUPPORTED_FUNCTIONS: [&str; 22] = [
|
||||
"abs", "signum", "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", "floor",
|
||||
"round", "ceil", "trunc", "fract", "exp", "sqrt", "cbrt", "ln", "log2", "log10",
|
||||
"abs", "signum", "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", "floor",
|
||||
"round", "ceil", "trunc", "fract", "exp", "sqrt", "cbrt", "ln", "log2", "log10",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=src/*");
|
||||
println!("cargo:rerun-if-changed=src/*");
|
||||
|
||||
generate_hashmap();
|
||||
generate_hashmap();
|
||||
}
|
||||
|
||||
fn generate_hashmap() {
|
||||
let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs");
|
||||
let mut file = BufWriter::new(File::create(path).expect("Could not create file"));
|
||||
let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs");
|
||||
let mut file = BufWriter::new(File::create(path).expect("Could not create file"));
|
||||
|
||||
let string_hashmap =
|
||||
compile_hashmap(SUPPORTED_FUNCTIONS.iter().map(|a| a.to_string()).collect());
|
||||
let string_hashmap =
|
||||
compile_hashmap(SUPPORTED_FUNCTIONS.iter().map(|a| a.to_string()).collect());
|
||||
|
||||
let mut hashmap = phf_codegen::Map::new();
|
||||
let mut hashmap = phf_codegen::Map::new();
|
||||
|
||||
for (key, value) in string_hashmap.iter() {
|
||||
hashmap.entry(key, value);
|
||||
}
|
||||
for (key, value) in string_hashmap.iter() {
|
||||
hashmap.entry(key, value);
|
||||
}
|
||||
|
||||
write!(
|
||||
&mut file,
|
||||
"static COMPLETION_HASHMAP: phf::Map<&'static str, Hint> = {};",
|
||||
hashmap.build()
|
||||
)
|
||||
.expect("Could not write to file");
|
||||
write!(
|
||||
&mut file,
|
||||
"static COMPLETION_HASHMAP: phf::Map<&'static str, Hint> = {};",
|
||||
hashmap.build()
|
||||
)
|
||||
.expect("Could not write to file");
|
||||
|
||||
write!(
|
||||
&mut file,
|
||||
"#[allow(dead_code)] pub const SUPPORTED_FUNCTIONS: [&str; {}] = {:?};",
|
||||
SUPPORTED_FUNCTIONS.len(),
|
||||
SUPPORTED_FUNCTIONS.to_vec()
|
||||
)
|
||||
.expect("Could not write to file");
|
||||
write!(
|
||||
&mut file,
|
||||
"#[allow(dead_code)] pub const SUPPORTED_FUNCTIONS: [&str; {}] = {:?};",
|
||||
SUPPORTED_FUNCTIONS.len(),
|
||||
SUPPORTED_FUNCTIONS.to_vec()
|
||||
)
|
||||
.expect("Could not write to file");
|
||||
}
|
||||
|
||||
include!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/src/autocomplete_hashmap.rs"
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/src/autocomplete_hashmap.rs"
|
||||
));
|
||||
|
||||
@ -4,113 +4,113 @@ use crate::{generate_hint, Hint, HINT_EMPTY};
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum Movement {
|
||||
Complete,
|
||||
#[allow(dead_code)]
|
||||
Down,
|
||||
#[allow(dead_code)]
|
||||
Up,
|
||||
None,
|
||||
Complete,
|
||||
#[allow(dead_code)]
|
||||
Down,
|
||||
#[allow(dead_code)]
|
||||
Up,
|
||||
None,
|
||||
}
|
||||
|
||||
impl Movement {
|
||||
pub const fn is_none(&self) -> bool {
|
||||
matches!(&self, &Self::None)
|
||||
}
|
||||
pub const fn is_none(&self) -> bool {
|
||||
matches!(&self, &Self::None)
|
||||
}
|
||||
|
||||
pub const fn is_complete(&self) -> bool {
|
||||
matches!(&self, &Self::Complete)
|
||||
}
|
||||
pub const fn is_complete(&self) -> bool {
|
||||
matches!(&self, &Self::Complete)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Movement {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct AutoComplete<'a> {
|
||||
pub i: usize,
|
||||
pub hint: &'a Hint<'a>,
|
||||
pub string: String,
|
||||
pub i: usize,
|
||||
pub hint: &'a Hint<'a>,
|
||||
pub string: String,
|
||||
}
|
||||
|
||||
impl<'a> Default for AutoComplete<'a> {
|
||||
fn default() -> AutoComplete<'a> {
|
||||
AutoComplete::EMPTY
|
||||
}
|
||||
fn default() -> AutoComplete<'a> {
|
||||
AutoComplete::EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AutoComplete<'a> {
|
||||
pub const EMPTY: AutoComplete<'a> = Self {
|
||||
i: 0,
|
||||
hint: &HINT_EMPTY,
|
||||
string: String::new(),
|
||||
};
|
||||
pub const EMPTY: AutoComplete<'a> = Self {
|
||||
i: 0,
|
||||
hint: &HINT_EMPTY,
|
||||
string: String::new(),
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn update_string(&mut self, string: &str) {
|
||||
if self.string != string {
|
||||
// catch empty strings here to avoid call to `generate_hint` and unnecessary logic
|
||||
if string.is_empty() {
|
||||
*self = Self::EMPTY;
|
||||
} else {
|
||||
self.string = string.to_owned();
|
||||
self.do_update_logic();
|
||||
}
|
||||
}
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn update_string(&mut self, string: &str) {
|
||||
if self.string != string {
|
||||
// catch empty strings here to avoid call to `generate_hint` and unnecessary logic
|
||||
if string.is_empty() {
|
||||
*self = Self::EMPTY;
|
||||
} else {
|
||||
self.string = string.to_owned();
|
||||
self.do_update_logic();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs update logic assuming that a change to `self.string` has been made
|
||||
fn do_update_logic(&mut self) {
|
||||
self.i = 0;
|
||||
self.hint = generate_hint(&self.string);
|
||||
}
|
||||
/// Runs update logic assuming that a change to `self.string` has been made
|
||||
fn do_update_logic(&mut self) {
|
||||
self.i = 0;
|
||||
self.hint = generate_hint(&self.string);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn register_movement(&mut self, movement: &Movement) {
|
||||
if movement.is_none() | self.hint.is_none() {
|
||||
return;
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn register_movement(&mut self, movement: &Movement) {
|
||||
if movement.is_none() | self.hint.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
match self.hint {
|
||||
Hint::Many(hints) => {
|
||||
// Impossible for plural hints to be singular or non-existant
|
||||
debug_assert!(hints.len() > 1); // check on debug
|
||||
match self.hint {
|
||||
Hint::Many(hints) => {
|
||||
// Impossible for plural hints to be singular or non-existant
|
||||
debug_assert!(hints.len() > 1); // check on debug
|
||||
|
||||
match movement {
|
||||
Movement::Up => {
|
||||
// Wrap self.i to maximum `i` value if needed
|
||||
if self.i == 0 {
|
||||
self.i = hints.len() - 1;
|
||||
} else {
|
||||
self.i -= 1;
|
||||
}
|
||||
}
|
||||
Movement::Down => {
|
||||
// Add one, if resulting value is above maximum `i` value, set `i` to 0
|
||||
self.i += 1;
|
||||
if self.i > (hints.len() - 1) {
|
||||
self.i = 0;
|
||||
}
|
||||
}
|
||||
Movement::Complete => {
|
||||
self.apply_hint(unsafe { hints.get_unchecked(self.i) });
|
||||
}
|
||||
_ => unsafe { unreachable_unchecked() },
|
||||
}
|
||||
}
|
||||
Hint::Single(hint) => {
|
||||
if movement.is_complete() {
|
||||
self.apply_hint(hint);
|
||||
}
|
||||
}
|
||||
Hint::None => unsafe { unreachable_unchecked() },
|
||||
}
|
||||
}
|
||||
match movement {
|
||||
Movement::Up => {
|
||||
// Wrap self.i to maximum `i` value if needed
|
||||
if self.i == 0 {
|
||||
self.i = hints.len() - 1;
|
||||
} else {
|
||||
self.i -= 1;
|
||||
}
|
||||
}
|
||||
Movement::Down => {
|
||||
// Add one, if resulting value is above maximum `i` value, set `i` to 0
|
||||
self.i += 1;
|
||||
if self.i > (hints.len() - 1) {
|
||||
self.i = 0;
|
||||
}
|
||||
}
|
||||
Movement::Complete => {
|
||||
self.apply_hint(unsafe { hints.get_unchecked(self.i) });
|
||||
}
|
||||
_ => unsafe { unreachable_unchecked() },
|
||||
}
|
||||
}
|
||||
Hint::Single(hint) => {
|
||||
if movement.is_complete() {
|
||||
self.apply_hint(hint);
|
||||
}
|
||||
}
|
||||
Hint::None => unsafe { unreachable_unchecked() },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_hint(&mut self, hint: &str) {
|
||||
self.string.push_str(hint);
|
||||
self.do_update_logic();
|
||||
}
|
||||
pub fn apply_hint(&mut self, hint: &str) {
|
||||
self.string.push_str(hint);
|
||||
self.do_update_logic();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,81 +3,82 @@ use std::collections::HashSet;
|
||||
|
||||
/// https://www.dotnetperls.com/sort-rust
|
||||
fn compare_len_reverse_alpha(a: &String, b: &String) -> Ordering {
|
||||
match a.len().cmp(&b.len()) {
|
||||
Ordering::Equal => b.cmp(a),
|
||||
order => order,
|
||||
}
|
||||
match a.len().cmp(&b.len()) {
|
||||
Ordering::Equal => b.cmp(a),
|
||||
order => order,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates hashmap (well really a vector of tuple of strings that are then turned into a hashmap by phf)
|
||||
#[allow(dead_code)]
|
||||
pub fn compile_hashmap(data: Vec<String>) -> Vec<(String, String)> {
|
||||
let mut seen = HashSet::new();
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
let tuple_list_1: Vec<(String, String)> = data
|
||||
.iter()
|
||||
.map(|e| e.to_string() + "(")
|
||||
.flat_map(|func| all_possible_splits(func, &mut seen))
|
||||
.collect();
|
||||
let tuple_list_1: Vec<(String, String)> = data
|
||||
.iter()
|
||||
.map(|e| e.to_string() + "(")
|
||||
.flat_map(|func| all_possible_splits(func, &mut seen))
|
||||
.collect();
|
||||
|
||||
let keys: Vec<&String> = tuple_list_1.iter().map(|(a, _)| a).collect();
|
||||
let mut output: Vec<(String, String)> = Vec::new();
|
||||
let mut seen_3: HashSet<String> = HashSet::new();
|
||||
let keys: Vec<&String> = tuple_list_1.iter().map(|(a, _)| a).collect();
|
||||
let mut output: Vec<(String, String)> = Vec::new();
|
||||
let mut seen_3: HashSet<String> = HashSet::new();
|
||||
|
||||
for (key, value) in tuple_list_1.iter() {
|
||||
if seen_3.contains(key) {
|
||||
continue;
|
||||
}
|
||||
for (key, value) in tuple_list_1.iter() {
|
||||
if seen_3.contains(key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
seen_3.insert(key.clone());
|
||||
seen_3.insert(key.clone());
|
||||
|
||||
let count_keys = keys.iter().filter(|a| a == &&key).count();
|
||||
let count_keys = keys.iter().filter(|a| a == &&key).count();
|
||||
|
||||
match count_keys.cmp(&1usize) {
|
||||
Ordering::Less => {
|
||||
panic!("Number of values for {key} is 0!");
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let mut multi_data = tuple_list_1
|
||||
.iter()
|
||||
.filter(|(a, _)| a == key)
|
||||
.map(|(_, b)| b)
|
||||
.collect::<Vec<&String>>();
|
||||
multi_data.sort_unstable_by(|a, b| compare_len_reverse_alpha(a, b));
|
||||
output.push((key.clone(), format!("Hint::Many(&{:?})", multi_data)));
|
||||
}
|
||||
Ordering::Equal => output.push((key.clone(), format!(r#"Hint::Single("{}")"#, value))),
|
||||
}
|
||||
}
|
||||
match count_keys.cmp(&1usize) {
|
||||
Ordering::Less => {
|
||||
panic!("Number of values for {key} is 0!");
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let mut multi_data = tuple_list_1
|
||||
.iter()
|
||||
.filter(|(a, _)| a == key)
|
||||
.map(|(_, b)| b)
|
||||
.collect::<Vec<&String>>();
|
||||
multi_data.sort_unstable_by(|a, b| compare_len_reverse_alpha(a, b));
|
||||
output.push((key.clone(), format!("Hint::Many(&{:?})", multi_data)));
|
||||
}
|
||||
Ordering::Equal => output.push((key.clone(), format!(r#"Hint::Single("{}")"#, value))),
|
||||
}
|
||||
}
|
||||
|
||||
// sort
|
||||
output.sort_unstable_by(|a, b| {
|
||||
let new_a = format!(r#"("{}", {})"#, a.0, a.1);
|
||||
let new_b = format!(r#"("{}", {})"#, b.0, b.1);
|
||||
// sort
|
||||
output.sort_unstable_by(|a, b| {
|
||||
let new_a = format!(r#"("{}", {})"#, a.0, a.1);
|
||||
let new_b = format!(r#"("{}", {})"#, b.0, b.1);
|
||||
|
||||
compare_len_reverse_alpha(&new_b, &new_a)
|
||||
});
|
||||
compare_len_reverse_alpha(&new_b, &new_a)
|
||||
});
|
||||
|
||||
output
|
||||
output
|
||||
}
|
||||
|
||||
/// Returns a vector of all possible splitting combinations of a strings
|
||||
#[allow(dead_code)]
|
||||
fn all_possible_splits(
|
||||
func: String, seen: &mut HashSet<(String, String)>,
|
||||
func: String,
|
||||
seen: &mut HashSet<(String, String)>,
|
||||
) -> Vec<(String, String)> {
|
||||
(1..func.len())
|
||||
.map(|i| {
|
||||
let (first, last) = func.split_at(i);
|
||||
(first.to_string(), last.to_string())
|
||||
})
|
||||
.flat_map(|(first, last)| {
|
||||
if seen.contains(&(first.clone(), last.clone())) {
|
||||
return None;
|
||||
}
|
||||
seen.insert((first.to_string(), last.to_string()));
|
||||
(1..func.len())
|
||||
.map(|i| {
|
||||
let (first, last) = func.split_at(i);
|
||||
(first.to_string(), last.to_string())
|
||||
})
|
||||
.flat_map(|(first, last)| {
|
||||
if seen.contains(&(first.clone(), last.clone())) {
|
||||
return None;
|
||||
}
|
||||
seen.insert((first.to_string(), last.to_string()));
|
||||
|
||||
Some((first, last))
|
||||
})
|
||||
.collect::<Vec<(String, String)>>()
|
||||
Some((first, last))
|
||||
})
|
||||
.collect::<Vec<(String, String)>>()
|
||||
}
|
||||
|
||||
@ -5,9 +5,9 @@ mod splitting;
|
||||
mod suggestions;
|
||||
|
||||
pub use crate::{
|
||||
autocomplete::{AutoComplete, Movement},
|
||||
autocomplete_hashmap::compile_hashmap,
|
||||
parsing::{process_func_str, BackingFunction, FlatExWrapper},
|
||||
splitting::{split_function, split_function_chars, SplitType},
|
||||
suggestions::{generate_hint, get_last_term, Hint, HINT_EMPTY, SUPPORTED_FUNCTIONS},
|
||||
autocomplete::{AutoComplete, Movement},
|
||||
autocomplete_hashmap::compile_hashmap,
|
||||
parsing::{process_func_str, BackingFunction, FlatExWrapper},
|
||||
splitting::{split_function, split_function_chars, SplitType},
|
||||
suggestions::{generate_hint, get_last_term, Hint, HINT_EMPTY, SUPPORTED_FUNCTIONS},
|
||||
};
|
||||
|
||||
@ -3,176 +3,176 @@ use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct FlatExWrapper {
|
||||
func: Option<FlatEx<f64>>,
|
||||
func_str: Option<String>,
|
||||
func: Option<FlatEx<f64>>,
|
||||
func_str: Option<String>,
|
||||
}
|
||||
|
||||
impl FlatExWrapper {
|
||||
const EMPTY: FlatExWrapper = FlatExWrapper {
|
||||
func: None,
|
||||
func_str: None,
|
||||
};
|
||||
const EMPTY: FlatExWrapper = FlatExWrapper {
|
||||
func: None,
|
||||
func_str: None,
|
||||
};
|
||||
|
||||
#[inline]
|
||||
const fn new(f: FlatEx<f64>) -> Self {
|
||||
Self {
|
||||
func: Some(f),
|
||||
func_str: None,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
const fn new(f: FlatEx<f64>) -> Self {
|
||||
Self {
|
||||
func: Some(f),
|
||||
func_str: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn is_none(&self) -> bool {
|
||||
self.func.is_none()
|
||||
}
|
||||
#[inline]
|
||||
const fn is_none(&self) -> bool {
|
||||
self.func.is_none()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn eval(&self, x: &[f64]) -> f64 {
|
||||
self.func
|
||||
.as_ref()
|
||||
.map(|f| f.eval(x).unwrap_or(f64::NAN))
|
||||
.unwrap_or(f64::NAN)
|
||||
}
|
||||
#[inline]
|
||||
pub fn eval(&self, x: &[f64]) -> f64 {
|
||||
self.func
|
||||
.as_ref()
|
||||
.map(|f| f.eval(x).unwrap_or(f64::NAN))
|
||||
.unwrap_or(f64::NAN)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn partial(&self, x: usize) -> Self {
|
||||
self.func
|
||||
.as_ref()
|
||||
.map(|f| f.clone().partial(x).map(Self::new).unwrap_or(Self::EMPTY))
|
||||
.unwrap_or(Self::EMPTY)
|
||||
}
|
||||
#[inline]
|
||||
fn partial(&self, x: usize) -> Self {
|
||||
self.func
|
||||
.as_ref()
|
||||
.map(|f| f.clone().partial(x).map(Self::new).unwrap_or(Self::EMPTY))
|
||||
.unwrap_or(Self::EMPTY)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_string(&mut self) -> String {
|
||||
match self.func_str {
|
||||
Some(ref func_str) => func_str.clone(),
|
||||
None => {
|
||||
let calculated = self.func.as_ref().map(|f| f.unparse()).unwrap_or("");
|
||||
self.func_str = Some(calculated.to_owned());
|
||||
calculated.to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
fn get_string(&mut self) -> String {
|
||||
match self.func_str {
|
||||
Some(ref func_str) => func_str.clone(),
|
||||
None => {
|
||||
let calculated = self.func.as_ref().map(|f| f.unparse()).unwrap_or("");
|
||||
self.func_str = Some(calculated.to_owned());
|
||||
calculated.to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn partial_iter(&self, n: usize) -> Self {
|
||||
self.func
|
||||
.as_ref()
|
||||
.map(|f| {
|
||||
f.clone()
|
||||
.partial_iter((0..=n).map(|_| 0))
|
||||
.map(Self::new)
|
||||
.unwrap_or(Self::EMPTY)
|
||||
})
|
||||
.unwrap_or(Self::EMPTY)
|
||||
}
|
||||
#[inline]
|
||||
fn partial_iter(&self, n: usize) -> Self {
|
||||
self.func
|
||||
.as_ref()
|
||||
.map(|f| {
|
||||
f.clone()
|
||||
.partial_iter((0..n).map(|_| 0))
|
||||
.map(Self::new)
|
||||
.unwrap_or(Self::EMPTY)
|
||||
})
|
||||
.unwrap_or(Self::EMPTY)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FlatExWrapper {
|
||||
fn default() -> FlatExWrapper {
|
||||
FlatExWrapper::EMPTY
|
||||
}
|
||||
fn default() -> FlatExWrapper {
|
||||
FlatExWrapper::EMPTY
|
||||
}
|
||||
}
|
||||
/// Function that includes f(x), f'(x), f'(x)'s string representation, and f''(x)
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct BackingFunction {
|
||||
/// f(x)
|
||||
function: FlatExWrapper,
|
||||
/// f(x)
|
||||
function: FlatExWrapper,
|
||||
|
||||
/// Temporary cache for nth derivative
|
||||
nth_derivative: HashMap<usize, FlatExWrapper>,
|
||||
/// Temporary cache for nth derivative
|
||||
nth_derivative: HashMap<usize, FlatExWrapper>,
|
||||
}
|
||||
|
||||
impl Default for BackingFunction {
|
||||
fn default() -> Self {
|
||||
Self::new("").unwrap()
|
||||
}
|
||||
fn default() -> Self {
|
||||
Self::new("").unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl BackingFunction {
|
||||
pub const fn is_none(&self) -> bool {
|
||||
self.function.is_none()
|
||||
}
|
||||
pub const fn is_none(&self) -> bool {
|
||||
self.function.is_none()
|
||||
}
|
||||
|
||||
/// Create new [`BackingFunction`] instance
|
||||
pub fn new(func_str: &str) -> Result<Self, String> {
|
||||
if func_str.is_empty() {
|
||||
return Ok(Self {
|
||||
function: FlatExWrapper::EMPTY,
|
||||
nth_derivative: HashMap::new(),
|
||||
});
|
||||
}
|
||||
/// Create new [`BackingFunction`] instance
|
||||
pub fn new(func_str: &str) -> Result<Self, String> {
|
||||
if func_str.is_empty() {
|
||||
return Ok(Self {
|
||||
function: FlatExWrapper::EMPTY,
|
||||
nth_derivative: HashMap::new(),
|
||||
});
|
||||
}
|
||||
|
||||
let function = FlatExWrapper::new({
|
||||
let parse_result = exmex::parse::<f64>(func_str);
|
||||
let function = FlatExWrapper::new({
|
||||
let parse_result = exmex::parse::<f64>(func_str);
|
||||
|
||||
match &parse_result {
|
||||
Err(e) => return Err(e.to_string()),
|
||||
Ok(ok_result) => {
|
||||
let var_names = ok_result.var_names().to_vec();
|
||||
match &parse_result {
|
||||
Err(e) => return Err(e.to_string()),
|
||||
Ok(ok_result) => {
|
||||
let var_names = ok_result.var_names().to_vec();
|
||||
|
||||
if var_names != ["x"] {
|
||||
let var_names_not_x: Vec<&String> = var_names
|
||||
.iter()
|
||||
.filter(|ele| ele != &"x")
|
||||
.collect::<Vec<&String>>();
|
||||
if var_names != ["x"] {
|
||||
let var_names_not_x: Vec<&String> = var_names
|
||||
.iter()
|
||||
.filter(|ele| ele != &"x")
|
||||
.collect::<Vec<&String>>();
|
||||
|
||||
return Err(format!(
|
||||
"Error: invalid variable{}",
|
||||
match var_names_not_x.len() {
|
||||
1 => String::from(": ") + var_names_not_x[0].as_str(),
|
||||
_ => format!("s: {:?}", var_names_not_x),
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe { parse_result.unwrap_unchecked() }
|
||||
});
|
||||
return Err(format!(
|
||||
"Error: invalid variable{}",
|
||||
match var_names_not_x.len() {
|
||||
1 => String::from(": ") + var_names_not_x[0].as_str(),
|
||||
_ => format!("s: {:?}", var_names_not_x),
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe { parse_result.unwrap_unchecked() }
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
function,
|
||||
Ok(Self {
|
||||
function,
|
||||
|
||||
nth_derivative: HashMap::new(),
|
||||
})
|
||||
}
|
||||
nth_derivative: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
// TODO rewrite this logic, it's a mess
|
||||
pub fn generate_derivative(&mut self, derivative: usize) {
|
||||
if derivative == 0 {
|
||||
return;
|
||||
}
|
||||
// TODO rewrite this logic, it's a mess
|
||||
pub fn generate_derivative(&mut self, derivative: usize) {
|
||||
if derivative == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.nth_derivative.contains_key(&derivative) {
|
||||
let new_func = self.function.partial_iter(derivative);
|
||||
self.nth_derivative.insert(derivative, new_func.clone());
|
||||
}
|
||||
}
|
||||
if !self.nth_derivative.contains_key(&derivative) {
|
||||
let new_func = self.function.partial_iter(derivative);
|
||||
self.nth_derivative.insert(derivative, new_func.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_function_derivative(&self, derivative: usize) -> &FlatExWrapper {
|
||||
if derivative == 0 {
|
||||
return &self.function;
|
||||
} else {
|
||||
return self
|
||||
.nth_derivative
|
||||
.get(&derivative)
|
||||
.unwrap_or(&FlatExWrapper::EMPTY);
|
||||
}
|
||||
}
|
||||
pub fn get_function_derivative(&self, derivative: usize) -> &FlatExWrapper {
|
||||
if derivative == 0 {
|
||||
return &self.function;
|
||||
} else {
|
||||
return self
|
||||
.nth_derivative
|
||||
.get(&derivative)
|
||||
.unwrap_or(&FlatExWrapper::EMPTY);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&mut self, derivative: usize, x: f64) -> f64 {
|
||||
self.get_function_derivative(derivative).eval(&[x])
|
||||
}
|
||||
pub fn get(&mut self, derivative: usize, x: f64) -> f64 {
|
||||
self.get_function_derivative(derivative).eval(&[x])
|
||||
}
|
||||
}
|
||||
|
||||
fn prettyify_function_str(func: &str) -> String {
|
||||
let new_str = func.replace("{x}", "x");
|
||||
let new_str = func.replace("{x}", "x");
|
||||
|
||||
if &new_str == "0/0" {
|
||||
"Undefined".to_owned()
|
||||
} else {
|
||||
new_str
|
||||
}
|
||||
if &new_str == "0/0" {
|
||||
"Undefined".to_owned()
|
||||
} else {
|
||||
new_str
|
||||
}
|
||||
}
|
||||
|
||||
// pub const VALID_VARIABLES: [char; 3] = ['x', 'e', 'π'];
|
||||
@ -180,15 +180,15 @@ fn prettyify_function_str(func: &str) -> String {
|
||||
/// Case insensitive checks for if `c` is a character used to represent a variable
|
||||
#[inline]
|
||||
pub const fn is_variable(c: &char) -> bool {
|
||||
let c = c.to_ascii_lowercase();
|
||||
(c == 'x') | (c == 'e') | (c == 'π')
|
||||
let c = c.to_ascii_lowercase();
|
||||
(c == 'x') | (c == 'e') | (c == 'π')
|
||||
}
|
||||
|
||||
/// Adds asterisks where needed in a function
|
||||
pub fn process_func_str(function_in: &str) -> String {
|
||||
if function_in.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
if function_in.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
crate::split_function(function_in, crate::SplitType::Multiplication).join("*")
|
||||
crate::split_function(function_in, crate::SplitType::Multiplication).join("*")
|
||||
}
|
||||
|
||||
@ -1,204 +1,208 @@
|
||||
use crate::parsing::is_variable;
|
||||
|
||||
pub fn split_function(input: &str, split: SplitType) -> Vec<String> {
|
||||
split_function_chars(
|
||||
&input
|
||||
.replace("pi", "π") // replace "pi" text with pi symbol
|
||||
.replace("**", "^") // support alternate manner of expressing exponents
|
||||
.replace("exp", "\u{1fc93}") // stop-gap solution to fix the `exp` function
|
||||
.chars()
|
||||
.collect::<Vec<char>>(),
|
||||
split,
|
||||
)
|
||||
.iter()
|
||||
.map(|x| x.replace('\u{1fc93}', "exp")) // Convert back to `exp` text
|
||||
.collect::<Vec<String>>()
|
||||
split_function_chars(
|
||||
&input
|
||||
.replace("pi", "π") // replace "pi" text with pi symbol
|
||||
.replace("**", "^") // support alternate manner of expressing exponents
|
||||
.replace("exp", "\u{1fc93}") // stop-gap solution to fix the `exp` function
|
||||
.chars()
|
||||
.collect::<Vec<char>>(),
|
||||
split,
|
||||
)
|
||||
.iter()
|
||||
.map(|x| x.replace('\u{1fc93}', "exp")) // Convert back to `exp` text
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
// Specifies how to split a function
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub enum SplitType {
|
||||
Multiplication,
|
||||
Term,
|
||||
Multiplication,
|
||||
Term,
|
||||
}
|
||||
|
||||
/// Used to store info about a character
|
||||
struct BoolSlice {
|
||||
closing_parens: bool,
|
||||
open_parens: bool,
|
||||
number: bool,
|
||||
letter: bool,
|
||||
variable: bool,
|
||||
masked_num: bool,
|
||||
masked_var: bool,
|
||||
closing_parens: bool,
|
||||
open_parens: bool,
|
||||
number: bool,
|
||||
letter: bool,
|
||||
variable: bool,
|
||||
masked_num: bool,
|
||||
masked_var: bool,
|
||||
}
|
||||
|
||||
impl BoolSlice {
|
||||
const fn from_char(c: &char, prev_masked_num: bool, prev_masked_var: bool) -> Self {
|
||||
let isnumber = c.is_ascii_digit();
|
||||
let isvariable = is_variable(c);
|
||||
Self {
|
||||
closing_parens: *c == ')',
|
||||
open_parens: *c == '(',
|
||||
number: isnumber,
|
||||
letter: c.is_ascii_alphabetic(),
|
||||
variable: isvariable,
|
||||
masked_num: match isnumber {
|
||||
true => prev_masked_num,
|
||||
false => false,
|
||||
},
|
||||
masked_var: match isvariable {
|
||||
true => prev_masked_var,
|
||||
false => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
const fn from_char(c: &char, prev_masked_num: bool, prev_masked_var: bool) -> Self {
|
||||
let isnumber = c.is_ascii_digit();
|
||||
let isvariable = is_variable(c);
|
||||
Self {
|
||||
closing_parens: *c == ')',
|
||||
open_parens: *c == '(',
|
||||
number: isnumber,
|
||||
letter: c.is_ascii_alphabetic(),
|
||||
variable: isvariable,
|
||||
masked_num: match isnumber {
|
||||
true => prev_masked_num,
|
||||
false => false,
|
||||
},
|
||||
masked_var: match isvariable {
|
||||
true => prev_masked_var,
|
||||
false => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const fn is_unmasked_variable(&self) -> bool { self.variable && !self.masked_var }
|
||||
const fn is_unmasked_variable(&self) -> bool {
|
||||
self.variable && !self.masked_var
|
||||
}
|
||||
|
||||
const fn is_unmasked_number(&self) -> bool { self.number && !self.masked_num }
|
||||
const fn is_unmasked_number(&self) -> bool {
|
||||
self.number && !self.masked_num
|
||||
}
|
||||
|
||||
const fn calculate_mask(&mut self, other: &BoolSlice) {
|
||||
if other.masked_num && self.number {
|
||||
// If previous char was a masked number, and current char is a number, mask current char's variable status
|
||||
self.masked_num = true;
|
||||
} else if other.masked_var && self.variable {
|
||||
// If previous char was a masked variable, and current char is a variable, mask current char's variable status
|
||||
self.masked_var = true;
|
||||
} else if other.letter && !other.is_unmasked_variable() {
|
||||
self.masked_num = self.number;
|
||||
self.masked_var = self.variable;
|
||||
}
|
||||
}
|
||||
const fn calculate_mask(&mut self, other: &BoolSlice) {
|
||||
if other.masked_num && self.number {
|
||||
// If previous char was a masked number, and current char is a number, mask current char's variable status
|
||||
self.masked_num = true;
|
||||
} else if other.masked_var && self.variable {
|
||||
// If previous char was a masked variable, and current char is a variable, mask current char's variable status
|
||||
self.masked_var = true;
|
||||
} else if other.letter && !other.is_unmasked_variable() {
|
||||
self.masked_num = self.number;
|
||||
self.masked_var = self.variable;
|
||||
}
|
||||
}
|
||||
|
||||
const fn splitable(&self, c: &char, other: &BoolSlice, split: &SplitType) -> bool {
|
||||
if (*c == '*') | (matches!(split, &SplitType::Term) && other.open_parens) {
|
||||
true
|
||||
} else if other.closing_parens {
|
||||
// Cases like `)x`, `)2`, and `)(`
|
||||
return (*c == '(')
|
||||
| (self.letter && !self.is_unmasked_variable())
|
||||
| self.is_unmasked_variable()
|
||||
| self.is_unmasked_number();
|
||||
} else if *c == '(' {
|
||||
// Cases like `x(` and `2(`
|
||||
return (other.is_unmasked_variable() | other.is_unmasked_number()) && !other.letter;
|
||||
} else if other.is_unmasked_number() {
|
||||
// Cases like `2x` and `2sin(x)`
|
||||
return self.is_unmasked_variable() | self.letter;
|
||||
} else if self.is_unmasked_variable() | self.letter {
|
||||
// Cases like `e2` and `xx`
|
||||
return other.is_unmasked_number()
|
||||
| (other.is_unmasked_variable() && self.is_unmasked_variable())
|
||||
| other.is_unmasked_variable();
|
||||
} else if (self.is_unmasked_number() | self.letter | self.is_unmasked_variable())
|
||||
&& (other.is_unmasked_number() | other.letter)
|
||||
{
|
||||
return true;
|
||||
} else {
|
||||
return self.is_unmasked_number() && other.is_unmasked_variable();
|
||||
}
|
||||
}
|
||||
const fn splitable(&self, c: &char, other: &BoolSlice, split: &SplitType) -> bool {
|
||||
if (*c == '*') | (matches!(split, &SplitType::Term) && other.open_parens) {
|
||||
true
|
||||
} else if other.closing_parens {
|
||||
// Cases like `)x`, `)2`, and `)(`
|
||||
return (*c == '(')
|
||||
| (self.letter && !self.is_unmasked_variable())
|
||||
| self.is_unmasked_variable()
|
||||
| self.is_unmasked_number();
|
||||
} else if *c == '(' {
|
||||
// Cases like `x(` and `2(`
|
||||
return (other.is_unmasked_variable() | other.is_unmasked_number()) && !other.letter;
|
||||
} else if other.is_unmasked_number() {
|
||||
// Cases like `2x` and `2sin(x)`
|
||||
return self.is_unmasked_variable() | self.letter;
|
||||
} else if self.is_unmasked_variable() | self.letter {
|
||||
// Cases like `e2` and `xx`
|
||||
return other.is_unmasked_number()
|
||||
| (other.is_unmasked_variable() && self.is_unmasked_variable())
|
||||
| other.is_unmasked_variable();
|
||||
} else if (self.is_unmasked_number() | self.letter | self.is_unmasked_variable())
|
||||
&& (other.is_unmasked_number() | other.letter)
|
||||
{
|
||||
return true;
|
||||
} else {
|
||||
return self.is_unmasked_number() && other.is_unmasked_variable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Splits a function (which is represented as an array of characters) based off of the value of SplitType
|
||||
pub fn split_function_chars(chars: &[char], split: SplitType) -> Vec<String> {
|
||||
// Catch some basic cases
|
||||
match chars.len() {
|
||||
0 => return Vec::new(),
|
||||
1 => return vec![chars[0].to_string()],
|
||||
_ => {}
|
||||
}
|
||||
// Catch some basic cases
|
||||
match chars.len() {
|
||||
0 => return Vec::new(),
|
||||
1 => return vec![chars[0].to_string()],
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Resulting split-up data
|
||||
let mut data: Vec<String> = std::vec::from_elem(chars[0].to_string(), 1);
|
||||
// Resulting split-up data
|
||||
let mut data: Vec<String> = std::vec::from_elem(chars[0].to_string(), 1);
|
||||
|
||||
// Setup first char here
|
||||
let mut prev_char: BoolSlice = BoolSlice::from_char(&chars[0], false, false);
|
||||
// Setup first char here
|
||||
let mut prev_char: BoolSlice = BoolSlice::from_char(&chars[0], false, false);
|
||||
|
||||
let mut last = unsafe { data.last_mut().unwrap_unchecked() };
|
||||
let mut last = unsafe { data.last_mut().unwrap_unchecked() };
|
||||
|
||||
// Iterate through all chars excluding the first one
|
||||
for c in chars.iter().skip(1) {
|
||||
// Set data about current character
|
||||
let mut curr_c = BoolSlice::from_char(c, prev_char.masked_num, prev_char.masked_var);
|
||||
// Iterate through all chars excluding the first one
|
||||
for c in chars.iter().skip(1) {
|
||||
// Set data about current character
|
||||
let mut curr_c = BoolSlice::from_char(c, prev_char.masked_num, prev_char.masked_var);
|
||||
|
||||
curr_c.calculate_mask(&prev_char);
|
||||
curr_c.calculate_mask(&prev_char);
|
||||
|
||||
// Append split
|
||||
if curr_c.splitable(c, &prev_char, &split) {
|
||||
// create new buffer
|
||||
data.push(String::new());
|
||||
last = unsafe { data.last_mut().unwrap_unchecked() };
|
||||
}
|
||||
// Append split
|
||||
if curr_c.splitable(c, &prev_char, &split) {
|
||||
// create new buffer
|
||||
data.push(String::new());
|
||||
last = unsafe { data.last_mut().unwrap_unchecked() };
|
||||
}
|
||||
|
||||
// Exclude asterisks
|
||||
if c != &'*' {
|
||||
last.push(*c);
|
||||
}
|
||||
// Exclude asterisks
|
||||
if c != &'*' {
|
||||
last.push(*c);
|
||||
}
|
||||
|
||||
// Move current character data to `prev_char`
|
||||
prev_char = curr_c;
|
||||
}
|
||||
// Move current character data to `prev_char`
|
||||
prev_char = curr_c;
|
||||
}
|
||||
|
||||
data
|
||||
data
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn assert_test(input: &str, expected: &[&str], split: SplitType) {
|
||||
let output = split_function(input, split);
|
||||
let expected_owned = expected
|
||||
.iter()
|
||||
.map(|&x| x.to_owned())
|
||||
.collect::<Vec<String>>();
|
||||
if output != expected_owned {
|
||||
panic!(
|
||||
"split type: {:?} of {} resulted in {:?} not {:?}",
|
||||
split, input, output, expected
|
||||
);
|
||||
}
|
||||
let output = split_function(input, split);
|
||||
let expected_owned = expected
|
||||
.iter()
|
||||
.map(|&x| x.to_owned())
|
||||
.collect::<Vec<String>>();
|
||||
if output != expected_owned {
|
||||
panic!(
|
||||
"split type: {:?} of {} resulted in {:?} not {:?}",
|
||||
split, input, output, expected
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_function_test() {
|
||||
assert_test(
|
||||
"sin(x)cos(x)",
|
||||
&["sin(x)", "cos(x)"],
|
||||
SplitType::Multiplication,
|
||||
);
|
||||
assert_test(
|
||||
"sin(x)cos(x)",
|
||||
&["sin(x)", "cos(x)"],
|
||||
SplitType::Multiplication,
|
||||
);
|
||||
|
||||
assert_test(
|
||||
"tanh(cos(x)xx)cos(x)",
|
||||
&["tanh(cos(x)", "x", "x)", "cos(x)"],
|
||||
SplitType::Multiplication,
|
||||
);
|
||||
assert_test(
|
||||
"tanh(cos(x)xx)cos(x)",
|
||||
&["tanh(cos(x)", "x", "x)", "cos(x)"],
|
||||
SplitType::Multiplication,
|
||||
);
|
||||
|
||||
assert_test(
|
||||
"tanh(sin(cos(x)xsin(x)))",
|
||||
&["tanh(sin(cos(x)", "x", "sin(x)))"],
|
||||
SplitType::Multiplication,
|
||||
);
|
||||
assert_test(
|
||||
"tanh(sin(cos(x)xsin(x)))",
|
||||
&["tanh(sin(cos(x)", "x", "sin(x)))"],
|
||||
SplitType::Multiplication,
|
||||
);
|
||||
|
||||
// Some test cases from https://github.com/GraphiteEditor/Graphite/blob/2515620a77478e57c255cd7d97c13cc7065dd99d/frontend/wasm/src/editor_api.rs#L829-L840
|
||||
assert_test("2pi", &["2", "π"], SplitType::Multiplication);
|
||||
assert_test("sin(2pi)", &["sin(2", "π)"], SplitType::Multiplication);
|
||||
assert_test("2sin(pi)", &["2", "sin(π)"], SplitType::Multiplication);
|
||||
assert_test(
|
||||
"2sin(3(4 + 5))",
|
||||
&["2", "sin(3", "(4 + 5))"],
|
||||
SplitType::Multiplication,
|
||||
);
|
||||
assert_test("3abs(-4)", &["3", "abs(-4)"], SplitType::Multiplication);
|
||||
assert_test("-1(4)", &["-1", "(4)"], SplitType::Multiplication);
|
||||
assert_test("(-1)4", &["(-1)", "4"], SplitType::Multiplication);
|
||||
assert_test(
|
||||
"(((-1)))(4)",
|
||||
&["(((-1)))", "(4)"],
|
||||
SplitType::Multiplication,
|
||||
);
|
||||
assert_test(
|
||||
"2sin(π) + 2cos(tau)",
|
||||
&["2", "sin(π) + 2", "cos(tau)"],
|
||||
SplitType::Multiplication,
|
||||
);
|
||||
// Some test cases from https://github.com/GraphiteEditor/Graphite/blob/2515620a77478e57c255cd7d97c13cc7065dd99d/frontend/wasm/src/editor_api.rs#L829-L840
|
||||
assert_test("2pi", &["2", "π"], SplitType::Multiplication);
|
||||
assert_test("sin(2pi)", &["sin(2", "π)"], SplitType::Multiplication);
|
||||
assert_test("2sin(pi)", &["2", "sin(π)"], SplitType::Multiplication);
|
||||
assert_test(
|
||||
"2sin(3(4 + 5))",
|
||||
&["2", "sin(3", "(4 + 5))"],
|
||||
SplitType::Multiplication,
|
||||
);
|
||||
assert_test("3abs(-4)", &["3", "abs(-4)"], SplitType::Multiplication);
|
||||
assert_test("-1(4)", &["-1", "(4)"], SplitType::Multiplication);
|
||||
assert_test("(-1)4", &["(-1)", "4"], SplitType::Multiplication);
|
||||
assert_test(
|
||||
"(((-1)))(4)",
|
||||
&["(((-1)))", "(4)"],
|
||||
SplitType::Multiplication,
|
||||
);
|
||||
assert_test(
|
||||
"2sin(π) + 2cos(tau)",
|
||||
&["2", "sin(π) + 2", "cos(tau)"],
|
||||
SplitType::Multiplication,
|
||||
);
|
||||
}
|
||||
|
||||
@ -14,106 +14,112 @@ macro_rules! test_print {
|
||||
|
||||
/// Generate a hint based on the input `input`, returns an `Option<String>`
|
||||
pub fn generate_hint<'a>(input: &str) -> &'a Hint<'a> {
|
||||
if input.is_empty() {
|
||||
&HINT_EMPTY
|
||||
} else {
|
||||
let chars: Vec<char> = input.chars().collect::<Vec<char>>();
|
||||
if input.is_empty() {
|
||||
&HINT_EMPTY
|
||||
} else {
|
||||
let chars: Vec<char> = input.chars().collect::<Vec<char>>();
|
||||
|
||||
let key = get_last_term(&chars);
|
||||
match key {
|
||||
Some(key) => {
|
||||
if let Some(hint) = COMPLETION_HASHMAP.get(&key) {
|
||||
return hint;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return &Hint::None;
|
||||
}
|
||||
}
|
||||
let key = get_last_term(&chars);
|
||||
match key {
|
||||
Some(key) => {
|
||||
if let Some(hint) = COMPLETION_HASHMAP.get(&key) {
|
||||
return hint;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return &Hint::None;
|
||||
}
|
||||
}
|
||||
|
||||
let mut open_parens: usize = 0;
|
||||
let mut closed_parens: usize = 0;
|
||||
chars.iter().for_each(|chr| match *chr {
|
||||
'(' => open_parens += 1,
|
||||
')' => closed_parens += 1,
|
||||
_ => {}
|
||||
});
|
||||
let mut open_parens: usize = 0;
|
||||
let mut closed_parens: usize = 0;
|
||||
chars.iter().for_each(|chr| match *chr {
|
||||
'(' => open_parens += 1,
|
||||
')' => closed_parens += 1,
|
||||
_ => {}
|
||||
});
|
||||
|
||||
if open_parens > closed_parens {
|
||||
return &HINT_CLOSED_PARENS;
|
||||
}
|
||||
if open_parens > closed_parens {
|
||||
return &HINT_CLOSED_PARENS;
|
||||
}
|
||||
|
||||
&Hint::None
|
||||
}
|
||||
&Hint::None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_last_term(chars: &[char]) -> Option<String> {
|
||||
if chars.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if chars.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut result = split_function_chars(chars, SplitType::Term);
|
||||
result.pop()
|
||||
let mut result = split_function_chars(chars, SplitType::Term);
|
||||
result.pop()
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum Hint<'a> {
|
||||
Single(&'a str),
|
||||
Many(&'a [&'a str]),
|
||||
None,
|
||||
Single(&'a str),
|
||||
Many(&'a [&'a str]),
|
||||
None,
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Display for Hint<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Hint::Single(single_data) => {
|
||||
write!(f, "{}", single_data)
|
||||
}
|
||||
Hint::Many(multi_data) => {
|
||||
write!(f, "{:?}", multi_data)
|
||||
}
|
||||
Hint::None => {
|
||||
write!(f, "None")
|
||||
}
|
||||
}
|
||||
}
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Hint::Single(single_data) => {
|
||||
write!(f, "{}", single_data)
|
||||
}
|
||||
Hint::Many(multi_data) => {
|
||||
write!(f, "{:?}", multi_data)
|
||||
}
|
||||
Hint::None => {
|
||||
write!(f, "None")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Debug for Hint<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
}
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Hint<'a> {
|
||||
#[inline]
|
||||
pub const fn is_none(&self) -> bool { matches!(&self, &Hint::None) }
|
||||
#[inline]
|
||||
pub const fn is_none(&self) -> bool {
|
||||
matches!(&self, &Hint::None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub const fn is_some(&self) -> bool { !self.is_none() }
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub const fn is_some(&self) -> bool {
|
||||
!self.is_none()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub const fn is_single(&self) -> bool { matches!(&self, &Hint::Single(_)) }
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub const fn is_single(&self) -> bool {
|
||||
matches!(&self, &Hint::Single(_))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub const fn single(&self) -> Option<&str> {
|
||||
match self {
|
||||
Hint::Single(data) => Some(data),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub const fn single(&self) -> Option<&str> {
|
||||
match self {
|
||||
Hint::Single(data) => Some(data),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub const fn many(&self) -> Option<&[&str]> {
|
||||
match self {
|
||||
Hint::Many(data) => Some(data),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub const fn many(&self) -> Option<&[&str]> {
|
||||
match self {
|
||||
Hint::Many(data) => Some(data),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
|
||||
|
||||
@ -21,6 +21,6 @@ fn main() -> eframe::Result<()> {
|
||||
eframe::run_native(
|
||||
"(Yet-to-be-named) Graphing Software",
|
||||
eframe::NativeOptions::default(),
|
||||
Box::new(|cc| Box::new(math_app::MathApp::new(cc))),
|
||||
Box::new(|cc| Ok(Box::new(math_app::MathApp::new(cc)))),
|
||||
)
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ fn do_test(sum: Riemann, area_target: f64) {
|
||||
if !emath::almost_equal(a[i].0 as f32, DERIVATIVE_TARGET[i].0 as f32, f32::EPSILON)
|
||||
| !emath::almost_equal(a[i].1 as f32, DERIVATIVE_TARGET[i].1 as f32, f32::EPSILON)
|
||||
{
|
||||
panic!("Expected: {:?}\nGot: {:?}", a, DERIVATIVE_TARGET);
|
||||
panic!("Expected: {:?}\nGot: {:?}", DERIVATIVE_TARGET, a);
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ fn do_test(sum: Riemann, area_target: f64) {
|
||||
if !emath::almost_equal(a_1[i].0 as f32, BACK_TARGET[i].0 as f32, f32::EPSILON)
|
||||
| !emath::almost_equal(a_1[i].1 as f32, BACK_TARGET[i].1 as f32, f32::EPSILON)
|
||||
{
|
||||
panic!("Expected: {:?}\nGot: {:?}", a_1, BACK_TARGET);
|
||||
panic!("Expected: {:?}\nGot: {:?}", BACK_TARGET, a_1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -131,7 +131,7 @@ fn do_test(sum: Riemann, area_target: f64) {
|
||||
if !emath::almost_equal(a[i].0 as f32, b[i].0 as f32, f32::EPSILON)
|
||||
| !emath::almost_equal(a[i].1 as f32, b[i].1 as f32, f32::EPSILON)
|
||||
{
|
||||
panic!("Expected: {:?}\nGot: {:?}", a, b);
|
||||
panic!("Expected: {:?}\nGot: {:?}", b, a);
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,7 +160,7 @@ fn do_test(sum: Riemann, area_target: f64) {
|
||||
if !emath::almost_equal(a_1[i].0 as f32, b_1[i].0 as f32, f32::EPSILON)
|
||||
| !emath::almost_equal(a_1[i].1 as f32, b_1[i].1 as f32, f32::EPSILON)
|
||||
{
|
||||
panic!("Expected: {:?}\nGot: {:?}", a_1, b_1);
|
||||
panic!("Expected: {:?}\nGot: {:?}", b_1, a_1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -193,7 +193,7 @@ fn do_test(sum: Riemann, area_target: f64) {
|
||||
if !emath::almost_equal(a[i].0 as f32, b[i].0 as f32, f32::EPSILON)
|
||||
| !emath::almost_equal(a[i].1 as f32, b[i].1 as f32, f32::EPSILON)
|
||||
{
|
||||
panic!("Expected: {:?}\nGot: {:?}", a, b);
|
||||
panic!("Expected: {:?}\nGot: {:?}", b, a);
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,7 +222,7 @@ fn do_test(sum: Riemann, area_target: f64) {
|
||||
if !emath::almost_equal(a_1[i].0 as f32, b_1[i].0 as f32, f32::EPSILON)
|
||||
| !emath::almost_equal(a_1[i].1 as f32, b_1[i].1 as f32, f32::EPSILON)
|
||||
{
|
||||
panic!("Expected: {:?}\nGot: {:?}", a_1, b_1);
|
||||
panic!("Expected: {:?}\nGot: {:?}", b_1, a_1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -271,3 +271,252 @@ fn middle_function() {
|
||||
fn right_function() {
|
||||
do_test(Riemann::Right, 0.8800000000000001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extrema() {
|
||||
let mut settings = app_settings_constructor(Riemann::Middle, -2.0, 2.0, 100, 100, -2.0, 2.0);
|
||||
settings.do_extrema = true;
|
||||
|
||||
let mut function = FunctionEntry::default();
|
||||
function.update_string("x^2 - 4"); // Parabola with vertex at (0, -4)
|
||||
function.integral = false;
|
||||
function.derivative = false;
|
||||
|
||||
function.calculate(true, true, false, settings);
|
||||
|
||||
// For f(x) = x^2 - 4, f'(x) = 2x
|
||||
// Extrema occurs where f'(x) = 0, so at x = 0
|
||||
assert!(!function.extrema_data.is_empty());
|
||||
|
||||
// Should have exactly one extremum at x = 0
|
||||
assert_eq!(function.extrema_data.len(), 1);
|
||||
|
||||
let extremum = function.extrema_data[0];
|
||||
assert!(emath::almost_equal(extremum.x as f32, 0.0, f32::EPSILON));
|
||||
assert!(emath::almost_equal(extremum.y as f32, -4.0, f32::EPSILON));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extrema_multiple() {
|
||||
let mut settings = app_settings_constructor(Riemann::Middle, -3.0, 3.0, 200, 200, -3.0, 3.0);
|
||||
settings.do_extrema = true;
|
||||
|
||||
let mut function = FunctionEntry::default();
|
||||
function.update_string("x^3 - 3*x"); // Cubic with local max and min
|
||||
function.integral = false;
|
||||
function.derivative = false;
|
||||
|
||||
function.calculate(true, true, false, settings);
|
||||
|
||||
// For f(x) = x^3 - 3x, f'(x) = 3x^2 - 3
|
||||
// Extrema occur where f'(x) = 0, so at x = ±1
|
||||
assert!(!function.extrema_data.is_empty());
|
||||
|
||||
// Should have exactly two extrema
|
||||
assert_eq!(function.extrema_data.len(), 2);
|
||||
|
||||
// Sort by x coordinate for consistent testing
|
||||
let mut extrema = function.extrema_data.clone();
|
||||
extrema.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap());
|
||||
|
||||
// First extremum at x = -1, f(-1) = -1 + 3 = 2
|
||||
assert!(emath::almost_equal(extrema[0].x as f32, -1.0, 0.01));
|
||||
assert!(emath::almost_equal(extrema[0].y as f32, 2.0, 0.01));
|
||||
|
||||
// Second extremum at x = 1, f(1) = 1 - 3 = -2
|
||||
assert!(emath::almost_equal(extrema[1].x as f32, 1.0, 0.01));
|
||||
assert!(emath::almost_equal(extrema[1].y as f32, -2.0, 0.01));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extrema_disabled() {
|
||||
let mut settings = app_settings_constructor(Riemann::Middle, -2.0, 2.0, 100, 100, -2.0, 2.0);
|
||||
settings.do_extrema = false; // Disable extrema
|
||||
|
||||
let mut function = FunctionEntry::default();
|
||||
function.update_string("x^2 - 4");
|
||||
function.integral = false;
|
||||
function.derivative = false;
|
||||
|
||||
function.calculate(true, true, false, settings);
|
||||
|
||||
// Extrema data should be empty when disabled
|
||||
assert!(function.extrema_data.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roots() {
|
||||
let mut settings = app_settings_constructor(Riemann::Middle, -3.0, 3.0, 200, 200, -3.0, 3.0);
|
||||
settings.do_roots = true;
|
||||
|
||||
let mut function = FunctionEntry::default();
|
||||
function.update_string("x^2 - 4"); // Parabola crossing x-axis at ±2
|
||||
function.integral = false;
|
||||
function.derivative = false;
|
||||
|
||||
function.calculate(true, true, false, settings);
|
||||
|
||||
// For f(x) = x^2 - 4, roots occur where x^2 = 4, so at x = ±2
|
||||
assert!(!function.root_data.is_empty());
|
||||
|
||||
// Should have exactly two roots
|
||||
assert_eq!(function.root_data.len(), 2);
|
||||
|
||||
// Sort by x coordinate for consistent testing
|
||||
let mut roots = function.root_data.clone();
|
||||
roots.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap());
|
||||
|
||||
// First root at x = -2
|
||||
assert!(emath::almost_equal(roots[0].x as f32, -2.0, 0.01));
|
||||
assert!(emath::almost_equal(roots[0].y as f32, 0.0, 0.001));
|
||||
|
||||
// Second root at x = 2
|
||||
assert!(emath::almost_equal(roots[1].x as f32, 2.0, 0.01));
|
||||
assert!(emath::almost_equal(roots[1].y as f32, 0.0, 0.001));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roots_single() {
|
||||
let mut settings = app_settings_constructor(Riemann::Middle, -2.0, 2.0, 100, 100, -2.0, 2.0);
|
||||
settings.do_roots = true;
|
||||
|
||||
let mut function = FunctionEntry::default();
|
||||
function.update_string("x - 1"); // Linear function crossing x-axis at x = 1
|
||||
function.integral = false;
|
||||
function.derivative = false;
|
||||
|
||||
function.calculate(true, true, false, settings);
|
||||
|
||||
// For f(x) = x - 1, root occurs at x = 1
|
||||
assert!(!function.root_data.is_empty());
|
||||
|
||||
// Should have exactly one root
|
||||
assert_eq!(function.root_data.len(), 1);
|
||||
|
||||
let root = function.root_data[0];
|
||||
assert!(emath::almost_equal(root.x as f32, 1.0, 0.01));
|
||||
assert!(emath::almost_equal(root.y as f32, 0.0, f32::EPSILON));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roots_disabled() {
|
||||
let mut settings = app_settings_constructor(Riemann::Middle, -3.0, 3.0, 200, 200, -3.0, 3.0);
|
||||
settings.do_roots = false; // Disable roots
|
||||
|
||||
let mut function = FunctionEntry::default();
|
||||
function.update_string("x^2 - 4");
|
||||
function.integral = false;
|
||||
function.derivative = false;
|
||||
|
||||
function.calculate(true, true, false, settings);
|
||||
|
||||
// Root data should be empty when disabled
|
||||
assert!(function.root_data.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extrema_and_roots_together() {
|
||||
let mut settings = app_settings_constructor(Riemann::Middle, -3.0, 3.0, 200, 200, -3.0, 3.0);
|
||||
settings.do_extrema = true;
|
||||
settings.do_roots = true;
|
||||
|
||||
let mut function = FunctionEntry::default();
|
||||
function.update_string("x^2 - 1"); // Parabola with vertex at (0, -1) and roots at ±1
|
||||
function.integral = false;
|
||||
function.derivative = false;
|
||||
|
||||
function.calculate(true, true, false, settings);
|
||||
|
||||
// Should have one extremum at x = 0
|
||||
assert!(!function.extrema_data.is_empty());
|
||||
assert_eq!(function.extrema_data.len(), 1);
|
||||
let extremum = function.extrema_data[0];
|
||||
assert!(emath::almost_equal(extremum.x as f32, 0.0, 0.01));
|
||||
assert!(emath::almost_equal(extremum.y as f32, -1.0, 0.01));
|
||||
|
||||
// Should have two roots at x = ±1
|
||||
assert!(!function.root_data.is_empty());
|
||||
assert_eq!(function.root_data.len(), 2);
|
||||
|
||||
let mut roots = function.root_data.clone();
|
||||
roots.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap());
|
||||
|
||||
assert!(emath::almost_equal(roots[0].x as f32, -1.0, 0.01));
|
||||
assert!(emath::almost_equal(roots[1].x as f32, 1.0, 0.01));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extrema_no_extrema() {
|
||||
let mut settings = app_settings_constructor(Riemann::Middle, -2.0, 2.0, 100, 100, -2.0, 2.0);
|
||||
settings.do_extrema = true;
|
||||
|
||||
let mut function = FunctionEntry::default();
|
||||
function.update_string("x"); // Linear function has no extrema
|
||||
function.integral = false;
|
||||
function.derivative = false;
|
||||
|
||||
function.calculate(true, true, false, settings);
|
||||
|
||||
// Linear function should have no extrema
|
||||
assert!(function.extrema_data.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roots_no_roots() {
|
||||
let mut settings = app_settings_constructor(Riemann::Middle, -2.0, 2.0, 100, 100, -2.0, 2.0);
|
||||
settings.do_roots = true;
|
||||
|
||||
let mut function = FunctionEntry::default();
|
||||
function.update_string("x^2 + 1"); // Parabola that never crosses x-axis
|
||||
function.integral = false;
|
||||
function.derivative = false;
|
||||
|
||||
function.calculate(true, true, false, settings);
|
||||
|
||||
// Function that never crosses x-axis should have no roots
|
||||
assert!(function.root_data.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extrema_and_roots_with_trig() {
|
||||
let mut settings = app_settings_constructor(Riemann::Middle, -4.0, 4.0, 300, 300, -4.0, 4.0);
|
||||
settings.do_extrema = true;
|
||||
settings.do_roots = true;
|
||||
|
||||
let mut function = FunctionEntry::default();
|
||||
function.update_string("sin(x)"); // Sine function has extrema at odd multiples of π/2
|
||||
function.integral = false;
|
||||
function.derivative = false;
|
||||
|
||||
function.calculate(true, true, false, settings);
|
||||
|
||||
// Sine function should have extrema in the given range
|
||||
assert!(!function.extrema_data.is_empty());
|
||||
|
||||
// Should have multiple extrema (local max/min)
|
||||
assert!(function.extrema_data.len() >= 2);
|
||||
|
||||
// Check that extrema are at approximately the right locations
|
||||
// Local max at π/2 ≈ 1.57, local min at 3π/2 ≈ 4.71 (outside range)
|
||||
// Local min at -π/2 ≈ -1.57, local max at -3π/2 ≈ -4.71 (outside range)
|
||||
let extrema_x: Vec<f32> = function.extrema_data.iter().map(|p| p.x as f32).collect();
|
||||
|
||||
// Should have extrema near ±π/2
|
||||
assert!(extrema_x
|
||||
.iter()
|
||||
.any(|&x| emath::almost_equal(x, std::f32::consts::PI / 2.0, 0.1)));
|
||||
assert!(extrema_x
|
||||
.iter()
|
||||
.any(|&x| emath::almost_equal(x, -std::f32::consts::PI / 2.0, 0.1)));
|
||||
|
||||
let roots_x: Vec<f32> = function.root_data.iter().map(|p| p.x as f32).collect();
|
||||
|
||||
assert!(roots_x
|
||||
.iter()
|
||||
.any(|&x| emath::almost_equal(x, std::f32::consts::PI, 0.1)));
|
||||
assert!(roots_x
|
||||
.iter()
|
||||
.any(|&x| emath::almost_equal(x, -std::f32::consts::PI, 0.1)));
|
||||
|
||||
assert!(roots_x.iter().any(|&x| emath::almost_equal(x, 0.0, 0.1)));
|
||||
}
|
||||
|
||||
@ -152,7 +152,7 @@ fn newtons_method() {
|
||||
|
||||
let data = newtons_method(
|
||||
&get_flatexwrapper("x^2 -1"),
|
||||
&get_flatexwrapper("2x"),
|
||||
&get_flatexwrapper("2*x"),
|
||||
3.0,
|
||||
&(0.0..5.0),
|
||||
f64::EPSILON,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user