Compare commits

...

12 Commits

  1. 142
      Cargo.lock
  2. 69
      src/entity.rs
  3. 65
      src/main.rs
  4. 490
      src/math/eqn.rs
  5. 460
      src/math/expr.rs
  6. 230
      src/math/mod.rs
  7. 197
      src/math/ops.rs
  8. 437
      src/math/region.rs
  9. 47
      src/math/unknown.rs
  10. 303
      src/math/vec.rs
  11. 128
      src/relation.rs

142
Cargo.lock generated

@ -1,9 +1,11 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.6.9" version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -11,18 +13,18 @@ name = "alga"
version = "0.7.2" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"approx 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libm 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "libm 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "approx" name = "approx"
version = "0.3.1" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -30,11 +32,16 @@ name = "atty"
version = "0.2.11" version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "autocfg"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.0.4" version = "1.0.4"
@ -44,8 +51,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "cad_rs" name = "cad_rs"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"approx 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"nalgebra 0.16.13 (registry+https://github.com/rust-lang/crates.io-index)", "nalgebra 0.16.13 (registry+https://github.com/rust-lang/crates.io-index)",
@ -53,7 +60,7 @@ dependencies = [
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "0.1.6" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -66,24 +73,24 @@ dependencies = [
[[package]] [[package]]
name = "either" name = "either"
version = "1.5.0" version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.6.0" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "fuchsia-cprng" name = "fuchsia-cprng"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -107,22 +114,22 @@ name = "itertools"
version = "0.8.0" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.2.0" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.48" version = "0.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "libm" name = "libm"
version = "0.1.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -130,7 +137,7 @@ name = "log"
version = "0.4.6" version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -143,12 +150,8 @@ dependencies = [
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.1.3" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "nalgebra" name = "nalgebra"
@ -156,11 +159,11 @@ version = "0.16.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"alga 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "alga 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"approx 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "generic-array 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
"matrixmultiply 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", "matrixmultiply 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -170,12 +173,20 @@ name = "num-complex"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.6" version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -189,10 +200,10 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-cprng 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -215,7 +226,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.1.51" version = "0.1.54"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -223,24 +234,24 @@ name = "redox_termios"
version = "0.1.1" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.1.0" version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.6.5" version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -256,11 +267,12 @@ dependencies = [
[[package]] [[package]]
name = "termion" name = "termion"
version = "1.5.1" version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -269,7 +281,7 @@ name = "thread_local"
version = "0.3.6" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -289,7 +301,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.6" version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -306,7 +318,7 @@ name = "winapi-util"
version = "0.1.2" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -319,49 +331,51 @@ name = "wincolor"
version = "1.0.1" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[metadata] [metadata]
"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e" "checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c"
"checksum alga 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24bb00eeca59f2986c747b8c2f271d52310ce446be27428fc34705138b155778" "checksum alga 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24bb00eeca59f2986c747b8c2f271d52310ce446be27428fc34705138b155778"
"checksum approx 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3c57ff1a5b00753647aebbbcf4ea67fa1e711a65ea7a30eb90dbf07de2485aee" "checksum approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3"
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
"checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf"
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b"
"checksum env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afb070faf94c85d17d50ca44f6ad076bce18ae92f0037d350947240a36e9d42e" "checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a"
"checksum fuchsia-cprng 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "81f7f8eb465745ea9b02e2704612a9946a59fa40572086c6fd49d6ddcf30bf31" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
"checksum generic-array 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8107dafa78c80c848b71b60133954b4a58609a3a1a5f9af037ecc7f67280f369" "checksum generic-array 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8107dafa78c80c848b71b60133954b4a58609a3a1a5f9af037ecc7f67280f369"
"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358"
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
"checksum libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)" = "e962c7641008ac010fa60a7dfdc1712449f29c44ef2d4702394aea943ee75047" "checksum libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "42914d39aad277d9e176efbdad68acb1d5443ab65afe0e0e4f0d49352a950880"
"checksum libm 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "03c0bb6d5ce1b5cc6fd0578ec1cbc18c9d88b5b591a5c7c1d6c6175e266a0819" "checksum libm 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cfcefbd3a8aad4c0552fa150c28291becc386b1b056988493517875e987e99e3"
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
"checksum matrixmultiply 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "dcad67dcec2d58ff56f6292582377e6921afdf3bfbd533e26fb8900ae575e002" "checksum matrixmultiply 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "dcad67dcec2d58ff56f6292582377e6921afdf3bfbd533e26fb8900ae575e002"
"checksum memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e1dd4eaac298c32ce07eb6ed9242eda7d82955b9170b7d6db59b2e02cc63fcb8" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
"checksum nalgebra 0.16.13 (registry+https://github.com/rust-lang/crates.io-index)" = "8e0799b53947b9c9048a1537f024f22f54701bbb75274f65955d081a87c0b739" "checksum nalgebra 0.16.13 (registry+https://github.com/rust-lang/crates.io-index)" = "8e0799b53947b9c9048a1537f024f22f54701bbb75274f65955d081a87c0b739"
"checksum num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "107b9be86cd2481930688277b675b0114578227f034674726605b8a482d8baf8" "checksum num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "107b9be86cd2481930688277b675b0114578227f034674726605b8a482d8baf8"
"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" "checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" "checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
"checksum rawpointer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ebac11a9d2e11f2af219b8b8d833b76b1ea0e054aa0e8d8e9e4cbde353bdf019" "checksum rawpointer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ebac11a9d2e11f2af219b8b8d833b76b1ea0e054aa0e8d8e9e4cbde353bdf019"
"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" "checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f" "checksum regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f0a0bcab2fd7d1d7c54fa9eae6f43eddeb9ce2e7352f8518a814a4f65d60c58"
"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861" "checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96"
"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" "checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dde0593aeb8d47accea5392b39350015b5eccb12c0d98044d856983d89548dea"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

69
src/entity.rs

@ -1,17 +1,34 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use crate::math::eqn::Eqns;
use crate::math::{Point2, Region, Region1, Region2, Scalar}; use crate::math::{Point2, Region, Region1, Region2, Scalar};
use std::fmt;
type EntityId = i64;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Var<T: Clone, TRegion: Region<T>> { pub struct Constrainable<T: Clone, TRegion: Region<T>> {
id: EntityId,
value: T, value: T,
constraints: TRegion, constraints: TRegion,
} }
impl<T: Clone, TRegion: Region<T>> Var<T, TRegion> { impl<T: Clone + fmt::Display, TRegion: Region<T> + fmt::Display> fmt::Display
for Constrainable<T, TRegion>
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{{ {} ∈ {} }}", self.value, self.constraints)
}
}
impl<T: Clone, TRegion: Region<T> + Clone> Constrainable<T, TRegion> {
pub fn new(value: T, constraints: TRegion) -> Self { pub fn new(value: T, constraints: TRegion) -> Self {
Self { value, constraints } Self {
id: 0,
value,
constraints,
}
} }
pub fn new_full(value: T) -> Self { pub fn new_full(value: T) -> Self {
@ -22,6 +39,30 @@ impl<T: Clone, TRegion: Region<T>> Var<T, TRegion> {
Self::new(value.clone(), TRegion::singleton(value)) Self::new(value.clone(), TRegion::singleton(value))
} }
pub fn val(&self) -> &T {
&self.value
}
pub fn resolve_with(&mut self, eqns: &Eqns) -> bool {
let resolved_constraints = self.constraints.clone().evaluate_with(eqns).simplify();
if let Some(n) = resolved_constraints.nearest(&self.value) {
self.value = n;
true
} else {
false
}
}
pub fn evaluate_with(&mut self, eqns: &Eqns) -> bool {
self.constraints = self.constraints.clone().evaluate_with(eqns).simplify();
if let Some(n) = self.constraints.nearest(&self.value) {
self.value = n;
true
} else {
false
}
}
pub fn constraints(&self) -> &TRegion { pub fn constraints(&self) -> &TRegion {
&self.constraints &self.constraints
} }
@ -37,28 +78,22 @@ impl<T: Clone, TRegion: Region<T>> Var<T, TRegion> {
} }
} }
type ScalarVar = Var<Scalar, Region1>; pub type CScalar = Constrainable<Scalar, Region1>;
type PointVar = Var<Point2, Region2>; pub type CPoint = Constrainable<Point2<Scalar>, Region2>;
#[derive(Debug)]
pub struct Point {
// pub id: i64,
pub pos: PointVar,
}
pub type PointRef = Rc<RefCell<Point>>; pub type PointRef = Rc<RefCell<CPoint>>;
impl Point { impl CPoint {
pub fn new_ref(pos: PointVar) -> PointRef { pub fn into_ref(self) -> PointRef {
Rc::new(RefCell::new(Point { pos })) Rc::new(RefCell::new(self))
} }
} }
struct Line { struct Line {
p1: PointRef, p1: PointRef,
p2: PointRef, p2: PointRef,
len: ScalarVar, len: CScalar,
dir: ScalarVar, dir: CScalar,
} }
// struct System { // struct System {

65
src/main.rs

@ -17,34 +17,51 @@ mod math;
mod relation; mod relation;
fn main() { fn main() {
use entity::{Point, PointRef, Var}; use entity::{CPoint, PointRef};
use math::Point2; use math::{Eqn, Eqns, Expr, Point2, Region2, Rot2};
use relation::{Relation, ResolveResult}; use relation::{Relation, ResolveResult};
env_logger::init(); env_logger::init();
println!("Hello, world!"); println!("Hello, world!");
let origin = Point::new_ref(Var::new_single(Point2::new(0., 0.))); let u1 = math::Unknown(1);
let p1 = Point::new_ref(Var::new_full(Point2::new(1., 1.))); let u2 = math::Unknown(2);
let p2 = Point::new_ref(Var::new_full(Point2::new(4., 4.))); // let u1 = eqn::Expr::from(1.);
let p3 = Point::new_ref(Var::new_full(Point2::new(2., 2.))); // let u2 = eqn::Expr::from(1.);
let origin = CPoint::new_single(Point2::new(0., 0.)).into_ref();
// let p1 = Point::new_ref(Var::new_full(Point2::new((1.).into(), (1.).into())));
let p1 = CPoint::new(
Point2::new(0., 0.),
Region2::Singleton(Point2::new((u1).into(), (u2).into())),
)
.into_ref();
let p2 = CPoint::new_full(Point2::new(4., 4.)).into_ref();
let p3 = CPoint::new_full(Point2::new(2., 2.)).into_ref();
let mut points: Vec<PointRef> = vec![origin.clone(), p1.clone(), p2.clone(), p3.clone()]; let mut points: Vec<PointRef> = vec![origin.clone(), p1.clone(), p2.clone(), p3.clone()];
let print_points = |points: &Vec<PointRef>| { let print_points = |points: &Vec<PointRef>| {
println!( println!(
"origin, p1, p2, p3:\n {:?}\n {:?}\n {:?}\n {:?}", "origin: {}\np1: {}\np2: {}\np3: {}",
points[0], points[1], points[2], points[3], points[0].borrow(),
points[1].borrow(),
points[2].borrow(),
points[3].borrow(),
); );
}; };
print_points(&points); print_points(&points);
// let c1 = relation::Coincident { let c1 = relation::Coincident {
// p1: origin.clone(), p1: origin.clone(),
// p2: p1.clone(), p2: p1.clone(),
// }; };
let c2 = relation::PointAngle::new_vertical(p1.clone(), p2.clone()); let c4 = relation::PointAngle::new_vertical(p1.clone(), p2.clone());
let c3 = relation::PointAngle::new_horizontal(p2.clone(), p3.clone()); let c3 = relation::PointAngle::new_horizontal(p2.clone(), p3.clone());
let c4 = relation::AlignedDistance::new_vertical(p1.clone(), p2.clone(), 12.); let c2 = relation::AlignedDistance::new_vertical(p1.clone(), p2.clone(), 12.);
let mut relations: Vec<Box<dyn Relation>> = let c5 = relation::PointAngle::new(p1.clone(), p3.clone(), Rot2::from_angle_deg(0.572938698));
vec![/*Box::new(c1),*/ Box::new(c2), Box::new(c3), Box::new(c4)]; let mut relations: Vec<Box<dyn Relation>> = vec![
/*Box::new(c1),*/ Box::new(c2),
Box::new(c3),
Box::new(c4),
Box::new(c5),
];
let mut constrained: Vec<Box<dyn Relation>> = Vec::new(); let mut constrained: Vec<Box<dyn Relation>> = Vec::new();
let mut any_underconstrained = true; let mut any_underconstrained = true;
let mut any_constrained = true; let mut any_constrained = true;
@ -55,10 +72,11 @@ fn main() {
let newly_constrained = relations.drain_filter(|r| { let newly_constrained = relations.drain_filter(|r| {
let rr = r.resolve(); let rr = r.resolve();
println!("resolve result: {:?}", rr); println!("resolve result: {:?}", rr);
println!( print_points(&points);
"origin, p1, p2, p3:\n {:?}\n {:?}\n {:?}\n {:?}", let mut pos = p2.borrow().val().clone();
origin, p1, p2, p3 println!("p2 pos: {}", pos);
); let mut pos = p3.borrow().val().clone();
println!("p3 pos: {}", pos);
match rr { match rr {
ResolveResult::Underconstrained => { ResolveResult::Underconstrained => {
any_underconstrained = true; any_underconstrained = true;
@ -83,4 +101,11 @@ fn main() {
} else { } else {
println!("All constraints have been solved") println!("All constraints have been solved")
} }
let e1 = Eqn::new(Expr::Unkn(u1), Expr::Const(1.));
let e2 = Eqn::new(Expr::Unkn(u2), Expr::Const(1.));
let eqns = Eqns(vec![e1, e2]);
for p in &mut points {
p.borrow_mut().resolve_with(&eqns);
}
print_points(&points);
} }

490
src/math/eqn.rs

@ -1,450 +1,12 @@
use std::collections::{BTreeMap, BTreeSet};
use std::fmt; use std::fmt;
use std::iter::FromIterator;
use crate::math::Scalar; use super::expr::Expr;
use super::unknown::*;
// an unknown variable with an id use super::Scalar;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] use crate::math::expr::Expr::*;
pub struct Unknown(i64);
pub type UnknownSet = BTreeSet<Unknown>;
pub trait Unknowns {
fn unknowns(&self) -> UnknownSet;
fn has_unknowns(&self) -> bool;
fn has_unknown(&self, u: Unknown) -> bool;
}
impl Unknowns for Scalar {
fn unknowns(&self) -> UnknownSet {
UnknownSet::new()
}
fn has_unknowns(&self) -> bool {
false
}
fn has_unknown(&self, _: Unknown) -> bool {
false
}
}
impl Unknowns for Unknown {
fn unknowns(&self) -> UnknownSet {
FromIterator::from_iter(Some(*self))
}
fn has_unknowns(&self) -> bool {
true
}
fn has_unknown(&self, u: Unknown) -> bool {
*self == u
}
}
impl fmt::Display for Unknown {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "u{}", self.0)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Expr {
Unkn(Unknown),
Const(Scalar),
Sum(Exprs),
Neg(Box<Expr>),
Product(Exprs),
Div(Box<Expr>, Box<Expr>),
}
pub type Exprs = Vec<Expr>;
impl Unknowns for Exprs {
fn unknowns(&self) -> UnknownSet {
self.iter().flat_map(|e: &Expr| e.unknowns()).collect()
}
fn has_unknowns(&self) -> bool {
self.iter().any(|e: &Expr| e.has_unknowns())
}
fn has_unknown(&self, u: Unknown) -> bool {
self.iter().any(|e: &Expr| e.has_unknown(u))
}
}
fn write_separated_exprs(es: &Exprs, f: &mut fmt::Formatter, sep: &str) -> fmt::Result {
let mut is_first = true;
for e in es {
if is_first {
is_first = false;
} else {
write!(f, "{}", sep)?
}
write!(f, "({})", e)?
}
Ok(())
}
fn remove_common_terms(l: &mut Vec<Expr>, r: &mut Vec<Expr>) -> Vec<Expr> {
let common: Vec<_> = l.drain_filter(|e| r.contains(e)).collect();
common.iter().for_each(|e| {
r.remove_item(e);
});
common
}
fn remove_term(terms: &mut Vec<Expr>, term: &Expr) -> Option<Expr> {
terms.remove_item(term)
}
fn sum_fold(l: Expr, r: Expr) -> Expr {
use itertools::Itertools;
use Expr::*;
match (l, r) {
(Const(lc), Const(rc)) => Const(lc + rc),
(Const(c), o) | (o, Const(c)) if relative_eq!(c, 0.) => o,
(Product(mut l), Product(mut r)) => {
let comm = remove_common_terms(&mut l, &mut r);
Expr::new_product(Sum(comm), Expr::new_sum(Product(l), Product(r))).simplify()
}
(Product(mut l), r) | (r, Product(mut l)) => {
let comm = remove_term(&mut l, &r);
match comm {
Some(_) => {
Expr::new_product(r, Expr::new_sum(Product(l), Const(1.))).simplify()
}
None => Expr::new_sum(Product(l), r),
}
}
(l, r) => Expr::new_sum(l, r),
}
}
fn group_sum(es: Exprs) -> Exprs {
use Expr::*;
let mut common: BTreeMap<UnknownSet, Expr> = BTreeMap::new();
for e in es {
let unkns = e.unknowns();
match common.get_mut(&unkns) {
None => {
match e {
Const(c) if relative_eq!(c, 0.) => (),
e => {
common.insert(unkns, e);
}
};
}
Some(existing) => {
match existing {
Sum(ref mut es) => {
// already failed at merging, so just add it to the list
es.push(e);
}
other => {
*other = sum_fold(other.clone(), e);
}
};
}
};
}
common.into_iter().map(|(_, v)| v).collect()
}
fn product_fold(l: Expr, r: Expr) -> Expr {
use itertools::Itertools;
use Expr::*;
match (l, r) {
(Const(lc), Const(rc)) => Const(lc * rc),
(Const(c), o) | (o, Const(c)) if relative_eq!(c, 1.) => o,
(Div(num, den), mul) | (mul, Div(num, den)) => {
if mul == *den {
*num
} else {
Expr::Div(Box::new(Expr::Product(vec![*num, mul])), den).simplify()
}
}
(l, r) => Expr::new_product(l, r),
}
}
fn group_product(es: Exprs) -> Exprs {
use Expr::*;
let mut common: BTreeMap<UnknownSet, Expr> = BTreeMap::new();
for e in es {
let unkns = e.unknowns();
match common.get_mut(&unkns) {
None => {
match e {
Const(c) if relative_eq!(c, 1.) => (),
e => {
common.insert(unkns, e);
}
};
}
Some(existing) => {
match existing {
Sum(ref mut es) => {
// already failed at merging, so just add it to the list
es.push(e);
}
other => *other = product_fold(other.clone(), e),
};
}
};
}
common.into_iter().map(|(_, v)| v).collect()
}
fn distribute_product_sums(mut es: Exprs) -> Expr {
trace!("distribute_product_sums: {}", Product(es.clone()));
use itertools::Itertools;
use Expr::*;
let sums = es
.drain_filter(|e| match e {
Sum(_) => true,
_ => false,
})
.map(|e| {
trace!("sum in product: {}", e);
match e {
Sum(es) => es,
_ => unreachable!(),
}
});
let products: Vec<_> = sums.multi_cartesian_product().collect();
if products.is_empty() {
trace!("no sums to distribute");
return Product(es);
}
let sums = products
.into_iter()
.map(|mut prod| {
prod.extend(es.clone());
trace!("prod: {}", Product(prod.clone()));
Product(prod)
})
.collect();
Sum(sums)
}
impl Unknowns for Expr {
fn unknowns(&self) -> UnknownSet {
use Expr::*;
match self {
Unkn(u) => u.unknowns(),
Const(_) => UnknownSet::default(),
Sum(es) | Product(es) => es.unknowns(),
Div(l, r) => l.unknowns().union(&r.unknowns()).cloned().collect(),
Neg(e) => e.unknowns(),
}
}
fn has_unknowns(&self) -> bool {
use Expr::*;
match self {
Unkn(u) => u.has_unknowns(),
Const(_) => false,
Sum(es) | Product(es) => es.has_unknowns(),
Div(l, r) => l.has_unknowns() || r.has_unknowns(),
Neg(e) => e.has_unknowns(),
}
}
fn has_unknown(&self, u: Unknown) -> bool {
use Expr::*;
match self {
Unkn(u1) => u1.has_unknown(u),
Const(_) => false,
Sum(es) | Product(es) => es.has_unknown(u),
Div(l, r) => l.has_unknown(u) || r.has_unknown(u),
Neg(e) => e.has_unknown(u),
}
}
}
impl Expr {
pub fn new_sum(e1: Expr, e2: Expr) -> Expr {
Expr::Sum(vec![e1, e2])
}
pub fn new_product(e1: Expr, e2: Expr) -> Expr {
Expr::Product(vec![e1, e2])
}
pub fn new_neg(e1: Expr) -> Expr {
Expr::Neg(Box::new(e1))
}
pub fn new_div(num: Expr, den: Expr) -> Expr {
Expr::Div(Box::new(num), Box::new(den))
}
pub fn new_minus(e1: Expr, e2: Expr) -> Expr {
Expr::Sum(vec![e1, Expr::new_neg(e2)])
}
pub fn new_inv(den: Expr) -> Expr {
Expr::new_div(Expr::Const(1.), den)
}
pub fn is_zero(&self) -> bool {
use Expr::*;
match self {
Const(c) => relative_eq!(*c, 0.),
_ => false,
}
}
pub fn is_one(&self) -> bool {
use Expr::*;
match self {
Const(c) => relative_eq!(*c, 1.),
_ => false,
}
}
pub fn simplify(self) -> Expr {
use Expr::*;
match self {
Sum(es) => {
let mut new_es: Vec<_> = es
.into_iter()
.map(|e| e.simplify())
.flat_map(|e| match e {
Sum(more_es) => more_es,
other => vec![other],
})
.collect();
let pre_new_es = new_es.clone();
new_es = group_sum(new_es);
trace!(
"simplify sum {} => {}",
Sum(pre_new_es),
Sum(new_es.clone())
);
match new_es.len() {
0 => Const(0.), // none
1 => new_es.into_iter().next().unwrap(), // one
_ => Sum(new_es), // many
}
}
Product(es) => {
let new_es: Vec<_> = es
.into_iter()
.map(|e| e.simplify())
.flat_map(|e| match e {
Product(more_es) => more_es,
other => vec![other],
})
.collect();
let pre_new_es = new_es.clone();
let new_es = group_product(new_es);
trace!(
"simplify product {} => {}",
Product(pre_new_es),
Product(new_es.clone())
);
match new_es.len() {
0 => Const(1.), // none
1 => new_es.into_iter().next().unwrap(), // one
_ => Product(new_es), // many
}
}
Neg(mut v) => {
*v = v.simplify();
trace!("simplify neg {}", Neg(v.clone()));
match v {
box Const(c) => Const(-c),
box Neg(v) => *v,
e => Product(vec![Const(-1.), *e]),
}
}
Div(mut num, mut den) => {
*num = num.simplify();
*den = den.simplify();
trace!("simplify div {}", Div(num.clone(), den.clone()));
match (num, den) {
(box Const(num), box Const(den)) => Const(num / den),
(num, box Const(den)) => {
if relative_eq!(den, 1.) {
*num
} else {
Expr::new_product(*num, Const(1. / den))
}
}
(num, box Div(dennum, denden)) => {
Div(Box::new(Product(vec![*num, *denden])), dennum).simplify()
}
(box Product(mut es), box den) => match es.remove_item(&den) {
Some(_) => Product(es),
None => Expr::new_div(Product(es), den),
},
(num, den) => {
if num == den {
Expr::Const(1.)
} else {
Div(num, den)
}
}
}
}
e => e,
}
}
pub fn distribute(self) -> Expr {
use Expr::*;
trace!("distribute {}", self);
match self {
Sum(mut es) => {
for e in &mut es {
*e = e.clone().distribute();
}
Sum(es)
}
Product(es) => distribute_product_sums(es),
Div(mut num, mut den) => {
*num = num.distribute();
*den = den.distribute();
match (num, den) {
(box Sum(es), box den) => Sum(es
.into_iter()
.map(|e| Expr::new_div(e, den.clone()))
.collect()),
(mut num, mut den) => Div(num, den),
}
}
Neg(v) => match v {
// box Sum(mut l, mut r) => {
// *l = Neg(l.clone()).distribute();
// *r = Neg(r.clone()).distribute();
// Sum(l, r)
// }
// box Product(mut l, r) => {
// *l = Neg(l.clone()).distribute();
// Product(l, r)
// }
box Neg(v) => v.distribute(),
box Div(mut num, mut den) => {
*num = Neg(num.clone()).distribute();
*den = Neg(den.clone()).distribute();
Div(num, den)
}
e => Neg(e),
},
e => e,
}
}
}
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Expr::*;
match self {
Unkn(u) => write!(f, "{}", u),
Const(c) => write!(f, "{}", c),
Sum(es) => write_separated_exprs(es, f, " + "),
Product(es) => write_separated_exprs(es, f, " * "),
Div(num, den) => write!(f, "({}) / ({})", num, den),
Neg(e) => write!(f, "-({})", e),
}
}
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Eqn(Expr, Expr); pub struct Eqn(pub Expr, pub Expr);
impl Unknowns for Eqn { impl Unknowns for Eqn {
fn unknowns(&self) -> UnknownSet { fn unknowns(&self) -> UnknownSet {
@ -486,17 +48,18 @@ impl Eqn {
if !self.has_unknown(for_u) { if !self.has_unknown(for_u) {
return None; return None;
} }
trace!("solve: {}", self);
let (l, r) = ( let (l, r) = (
self.0 self.0
.clone() /*.distribute()*/ .clone().distribute()
.simplify(), .simplify(),
self.1 self.1
.clone() /*.distribute()*/ .clone().distribute()
.simplify(), .simplify(),
); );
let (mut l, mut r) = ord_by_unkn(l, r, for_u)?; let (mut l, mut r) = ord_by_unkn(l, r, for_u)?;
loop { loop {
trace!("solve: {} == {}", l, r); trace!("solve iter: {} == {}", l, r);
let (new_l, new_r): (Expr, Expr) = match l { let (new_l, new_r): (Expr, Expr) = match l {
Unkn(u) => return if u == for_u { Some(r.simplify()) } else { None }, Unkn(u) => return if u == for_u { Some(r.simplify()) } else { None },
Sum(es) => { Sum(es) => {
@ -546,7 +109,7 @@ impl fmt::Display for Eqn {
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Eqns(Vec<Eqn>); pub struct Eqns(pub Vec<Eqn>);
impl Unknowns for Eqns { impl Unknowns for Eqns {
fn unknowns(&self) -> UnknownSet { fn unknowns(&self) -> UnknownSet {
@ -604,14 +167,14 @@ mod tests {
let eqn = Eqn(e2.clone(), e1.clone()); let eqn = Eqn(e2.clone(), e1.clone());
assert_eq!(eqn.solve(u1), Some(Const(1.))); assert_eq!(eqn.solve(u1), Some(Const(1.)));
let e3 = Expr::new_sum(Const(1.), Expr::new_sum(Const(1.), Const(2.))); let e3: Expr = Expr::from(1.) + 1. + 2.;
let eqn = Eqn(e1.clone(), e3.clone()); let eqn = Eqn(e1.clone(), e3.clone());
assert_eq!(eqn.solve(u1), Some(Const(4.))); assert_eq!(eqn.solve(u1), Some(Const(4.)));
let e3 = Expr::new_minus(Const(1.), Const(1.)); let e3 = Expr::from(1.) - 1.;
let eqn = Eqn(e1.clone(), e3.clone()); let eqn = Eqn(e1.clone(), e3.clone());
assert_eq!(eqn.solve(u1), Some(Const(0.))); assert_eq!(eqn.solve(u1), Some(Const(0.)));
let e1 = Expr::new_div(Const(2.), Expr::new_minus(Const(1.), Const(4.))); let e1 = Expr::from(2.) / (Expr::from(1.) - 4.);
let e2 = Expr::new_minus(Const(1.), Unkn(u1)); let e2 = Expr::new_minus(Const(1.), Unkn(u1));
let eqn = Eqn(e1, e2); let eqn = Eqn(e1, e2);
info!("eqn: {} => {}", eqn, eqn.clone().simplify()); info!("eqn: {} => {}", eqn, eqn.clone().simplify());
@ -619,8 +182,8 @@ mod tests {
assert!(const_expr(e.clone()).is_some()); assert!(const_expr(e.clone()).is_some());
assert!(relative_eq!(const_expr(e.clone()).unwrap(), 5. / 3.)); assert!(relative_eq!(const_expr(e.clone()).unwrap(), 5. / 3.));
let e1 = Expr::new_product(Const(2.), Expr::new_minus(Const(1.), Const(4.))); let e1 = Expr::from(2.) * (Expr::from(1.) - 4.);
let e2 = Expr::new_minus(Expr::new_product(Unkn(u1), Const(2.)), Unkn(u1)); let e2 = (u1 * 2.) - u1;
info!( info!(
"e1==e2: {}=={} => {}=={}", "e1==e2: {}=={} => {}=={}",
e1, e1,
@ -633,14 +196,8 @@ mod tests {
assert!(const_expr(e.clone()).is_some()); assert!(const_expr(e.clone()).is_some());
assert!(relative_eq!(const_expr(e.clone()).unwrap(), -6.)); assert!(relative_eq!(const_expr(e.clone()).unwrap(), -6.));
let e1 = Expr::new_product(Const(2.), Expr::new_minus(Const(1.), Const(4.))); let e1 = Expr::from(2.) * (Expr::from(1.) - 4.);
let e2 = Expr::new_div( let e2 = ((u1 * 2.) + (u1 * u1)) / u1;
Expr::new_sum(
Expr::new_product(Unkn(u1), Const(2.)),
Expr::new_product(Unkn(u1), Unkn(u1)),
),
Unkn(u1),
);
info!( info!(
"{}=={} distrib=> {}=={}", "{}=={} distrib=> {}=={}",
e1, e1,
@ -660,17 +217,8 @@ mod tests {
assert!(const_expr(e.clone()).is_some()); assert!(const_expr(e.clone()).is_some());
assert!(relative_eq!(const_expr(e.clone()).unwrap(), -8.)); assert!(relative_eq!(const_expr(e.clone()).unwrap(), -8.));
let e1 = Expr::new_product(Const(2.), Expr::new_minus(Const(1.), Const(4.))); let e1 = Expr::from(2.) * (Expr::from(1.) - (4.));
let e2 = Expr::new_div( let e2 = ((u1 * 2.) + (((u1 * u1) + u1) + (Expr::from(2.) - (1. + 1.)))) / u1;
Expr::new_sum(
Expr::new_product(Unkn(u1), Const(2.)),
Expr::new_sum(
Expr::new_sum(Expr::new_product(Unkn(u1), Unkn(u1)), Unkn(u1)),
Expr::new_minus(Const(2.), Const(1. + 1.)),
),
),
Unkn(u1),
);
info!( info!(
"e1==e2: {}=={} => {}=={}", "e1==e2: {}=={} => {}=={}",
e1, e1,

460
src/math/expr.rs

@ -0,0 +1,460 @@
use std::collections::BTreeMap;
use std::fmt;
use super::eqn::Eqns;
use super::unknown::{Unknown, UnknownSet, Unknowns};
use super::Scalar;
#[derive(Clone, Debug, PartialEq)]
pub enum Expr {
Unkn(Unknown),
Const(Scalar),
Sum(Exprs),
Neg(Box<Expr>),
Product(Exprs),
Div(Box<Expr>, Box<Expr>),
}
pub type Exprs = Vec<Expr>;
impl Unknowns for Exprs {
fn unknowns(&self) -> UnknownSet {
self.iter().flat_map(|e: &Expr| e.unknowns()).collect()
}
fn has_unknowns(&self) -> bool {
self.iter().any(|e: &Expr| e.has_unknowns())
}
fn has_unknown(&self, u: Unknown) -> bool {
self.iter().any(|e: &Expr| e.has_unknown(u))
}
}
fn write_separated_exprs(es: &Exprs, f: &mut fmt::Formatter, sep: &str) -> fmt::Result {
let mut is_first = true;
write!(f, "(")?;
for e in es {
if is_first {
is_first = false;
} else {
write!(f, "{}", sep)?
}
write!(f, "{}", e)?
}
write!(f, ")")?;
Ok(())
}
fn remove_common_terms(l: &mut Vec<Expr>, r: &mut Vec<Expr>) -> Vec<Expr> {
let common: Vec<_> = l.drain_filter(|e| r.contains(e)).collect();
common.iter().for_each(|e| {
r.remove_item(e);
});
common
}
fn remove_term(terms: &mut Vec<Expr>, term: &Expr) -> Option<Expr> {
terms.remove_item(term)
}
fn sum_fold(l: Expr, r: Expr) -> Expr {
use Expr::*;
match (l, r) {
(Const(lc), Const(rc)) => Const(lc + rc),
(Const(c), o) | (o, Const(c)) if relative_eq!(c, 0.) => o,
(Product(mut l), Product(mut r)) => {
let comm = remove_common_terms(&mut l, &mut r);
if comm.is_empty() {
Expr::new_sum(Product(l), Product(r))
} else {
Expr::new_product(Product(comm), Expr::new_sum(Product(l), Product(r)))
}
}
(Product(mut l), r) | (r, Product(mut l)) => {
let comm = remove_term(&mut l, &r);
match comm {
Some(_) => Expr::new_product(r, Expr::new_sum(Product(l), Const(1.))),
None => Expr::new_sum(Product(l), r),
}
}
(l, r) => Expr::new_sum(l, r),
}
}
fn group_sum(es: Exprs) -> Exprs {
use Expr::*;
let mut common: BTreeMap<UnknownSet, Expr> = BTreeMap::new();
for e in es {
let unkns = e.unknowns();
match common.get_mut(&unkns) {
None => {
match e {
Const(c) if relative_eq!(c, 0.) => (),
e => {
common.insert(unkns, e);
}
};
}
Some(existing) => {
match existing {
Sum(ref mut es) => {
// already failed at merging, so just add it to the list
es.push(e);
}
other => {
*other = sum_fold(other.clone(), e);
}
};
}
};
}
for c in common.values() {
trace!("group sum value: {}", c);
}
common.into_iter().map(|(_, v)| v).collect()
}
fn product_fold(l: Expr, r: Expr) -> Expr {
use Expr::*;
match (l, r) {
(Const(lc), Const(rc)) => Const(lc * rc),
(Const(c), o) | (o, Const(c)) if relative_eq!(c, 1.) => o,
(Const(c), _) | (_, Const(c)) if relative_eq!(c, 0.) => Const(0.),
(Div(num, den), mul) | (mul, Div(num, den)) => {
if mul == *den {
*num
} else {
Expr::Div(Box::new(Expr::Product(vec![*num, mul])), den).simplify()
}
}
(Product(mut ls), Product(mut rs)) => {
ls.append(&mut rs);
Product(ls)
}
(Product(mut ps), o) | (o, Product(mut ps)) => {
ps.push(o);
Product(ps)
}
(l, r) => Expr::new_product(l, r),
}
}
fn group_product(es: Exprs) -> Exprs {
use Expr::*;
let es2 = es.clone();
let mut consts: Option<Scalar> = None;
let mut other = Exprs::new();
for e in es {
let unkns = e.unknowns();
match e {
Const(c) => match consts {
None => consts = Some(c),
Some(cs) => consts = Some(c * cs),
},
e => other.push(e),
}
}
if let Some(cs) = consts {
if relative_eq!(cs, 0.0) {
other.clear();
other.push(Const(0.0))
} else if relative_ne!(cs, 1.0) {
other.push(Const(cs))
}
};
trace!("group product: {:?} => {:?}", es2, other);
other
}
fn distribute_product_sums(mut es: Exprs) -> Expr {
let es_pre = es.clone();
use itertools::Itertools;
use Expr::*;
for e in &mut es {
*e = e.clone().distribute();
}
let sums = es
.drain_filter(|e| match e {
Sum(_) => true,
_ => false,
})
.map(|e| {
trace!("sum in product: {}", e);
match e.simplify() {
Sum(es) => es,
o => vec![o],
}
});
let products: Vec<_> = sums.multi_cartesian_product().collect();
if products.is_empty() {
trace!("distribute_product_sums: no sums to distribute");
return Product(es);
}
let sums = products
.into_iter()
.map(|mut prod| {
prod.extend(es.clone());
trace!("prod: {}", Product(prod.clone()));
Product(prod)
})
.collect();
let res = Sum(sums);
trace!("distribute_product_sums: {} => {}", Product(es_pre), res);
res
}
impl Unknowns for Expr {
fn unknowns(&self) -> UnknownSet {
use Expr::*;
match self {
Unkn(u) => u.unknowns(),
Const(_) => UnknownSet::default(),
Sum(es) | Product(es) => es.unknowns(),
Div(l, r) => l.unknowns().union(&r.unknowns()).cloned().collect(),
Neg(e) => e.unknowns(),
}
}
fn has_unknowns(&self) -> bool {
use Expr::*;
match self {
Unkn(u) => u.has_unknowns(),
Const(_) => false,
Sum(es) | Product(es) => es.has_unknowns(),
Div(l, r) => l.has_unknowns() || r.has_unknowns(),
Neg(e) => e.has_unknowns(),
}
}
fn has_unknown(&self, u: Unknown) -> bool {
use Expr::*;
match self {
Unkn(u1) => u1.has_unknown(u),
Const(_) => false,
Sum(es) | Product(es) => es.has_unknown(u),
Div(l, r) => l.has_unknown(u) || r.has_unknown(u),
Neg(e) => e.has_unknown(u),
}
}
}
impl Expr {
pub fn new_sum(e1: Expr, e2: Expr) -> Expr {
Expr::Sum(vec![e1, e2])
}
pub fn new_product(e1: Expr, e2: Expr) -> Expr {
Expr::Product(vec![e1, e2])
}
pub fn new_neg(e1: Expr) -> Expr {
Expr::Neg(Box::new(e1))
}
pub fn new_div(num: Expr, den: Expr) -> Expr {
Expr::Div(Box::new(num), Box::new(den))
}
pub fn new_minus(e1: Expr, e2: Expr) -> Expr {
Expr::Sum(vec![e1, Expr::new_neg(e2)])
}
pub fn new_inv(den: Expr) -> Expr {
Expr::new_div(Expr::Const(1.), den)
}
pub fn is_zero(self) -> bool {
use Expr::*;
match self.simplify() {
Const(c) => relative_eq!(c, 0.),
_ => false,
}
}
pub fn is_one(self) -> bool {
use Expr::*;
match self.simplify() {
Const(c) => relative_eq!(c, 1.),
_ => false,
}
}
pub fn evaluate_with(self, eqns: &Eqns) -> Expr {
use Expr::*;
for eqn in &eqns.0 {
if self == eqn.0 {
return eqn.1.clone();
}
}
match self {
Sum(mut es) => {
for e in &mut es {
*e = e.clone().evaluate_with(eqns);
}
Sum(es)
}
Product(mut es) => {
for e in &mut es {
*e = e.clone().evaluate_with(eqns);
}
Product(es)
}
Neg(mut e) => {
*e = e.evaluate_with(eqns);
Neg(e)
}
Div(mut num, mut den) => {
*num = num.evaluate_with(eqns);
*den = den.evaluate_with(eqns);
Div(num, den)
}
other => other,
}
}
pub fn simplify(self) -> Expr {
use Expr::*;
match self {
Sum(es) => {
let pre_new_es = es.clone();
let mut new_es: Vec<_> = es
.into_iter()
.map(|e| e.simplify())
.flat_map(|e| match e {
Sum(more_es) => more_es,
other => vec![other],
})
.collect();
new_es = group_sum(new_es);
trace!(
"simplify sum {} => {}",
Sum(pre_new_es),
Sum(new_es.clone())
);
match new_es.len() {
0 => Const(0.), // none
1 => new_es.into_iter().next().unwrap(), // one
_ => Sum(new_es), // many
}
}
Product(es) => {
let pre_new_es = es.clone();
let new_es: Vec<_> = es
.into_iter()
.map(|e| e.simplify())
.flat_map(|e| match e {
Product(more_es) => more_es,
other => vec![other],
})
.collect();
let new_es = group_product(new_es);
trace!(
"simplify product {} => {}",
Product(pre_new_es),
Product(new_es.clone())
);
match new_es.len() {
0 => Const(1.), // none
1 => new_es.into_iter().next().unwrap(), // one
_ => Product(new_es), // many
}
}
Neg(mut v) => {
*v = v.simplify();
trace!("simplify neg {}", Neg(v.clone()));
match v {
box Const(c) => Const(-c),
box Neg(v) => *v,
box Product(mut es) => {
es.push(Const(-1.));
Product(es).simplify()
}
e => Product(vec![Const(-1.), *e]),
}
}
Div(mut num, mut den) => {
*num = num.simplify();
*den = den.simplify();
trace!("simplify div {}", Div(num.clone(), den.clone()));
match (num, den) {
(box Const(num), box Const(den)) => Const(num / den),
(num, box Const(den)) => {
if relative_eq!(den, 1.) {
*num
} else {
Expr::new_product(*num, Const(1. / den))
}
}
(num, box Div(dennum, denden)) => {
Div(Box::new(Product(vec![*num, *denden])), dennum).simplify()
}
(box Product(mut es), box den) => match es.remove_item(&den) {
Some(_) => Product(es),
None => Expr::new_div(Product(es), den),
},
(num, den) => {
if num == den {
Expr::Const(1.)
} else {
Div(num, den)
}
}
}
}
e => e,
}
}
pub fn distribute(self) -> Expr {
use Expr::*;
match self {
Sum(mut es) => {
let es_pre = es.clone();
for e in &mut es {
*e = e.clone().distribute();
}
let res = Sum(es);
trace!("distribute sum {} => {}", Sum(es_pre), res);
res
}
Product(es) => distribute_product_sums(es),
Div(mut num, mut den) => {
*num = num.distribute();
*den = den.distribute();
match (num, den) {
(box Sum(es), box den) => Sum(es
.into_iter()
.map(|e| Expr::new_div(e, den.clone()))
.collect()),
(mut num, mut den) => Div(num, den),
}
}
Neg(v) => match v {
// box Sum(mut l, mut r) => {
// *l = Neg(l.clone()).distribute();
// *r = Neg(r.clone()).distribute();
// Sum(l, r)
// }
// box Product(mut l, r) => {
// *l = Neg(l.clone()).distribute();
// Product(l, r)
// }
box Neg(v) => v.distribute(),
box Div(mut num, mut den) => {
*num = Neg(num.clone()).distribute();
*den = Neg(den.clone()).distribute();
Div(num, den)
}
e => Neg(e),
},
e => e,
}
}
}
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Expr::*;
match self {
Unkn(u) => write!(f, "{}", u),
Const(c) => write!(f, "{}", c),
Sum(es) => write_separated_exprs(es, f, " + "),
Product(es) => write_separated_exprs(es, f, " * "),
Div(num, den) => write!(f, "({} / {})", num, den),
Neg(e) => write!(f, "-({})", e),
}
}
}

230
src/math/mod.rs

@ -1,217 +1,23 @@
pub mod eqn; pub mod eqn;
pub mod expr;
pub mod ops;
pub mod region;
pub mod unknown;
pub mod vec;
pub use eqn::{Eqn, Eqns};
pub use expr::{Expr, Exprs};
pub use ops::*;
pub use region::{GenericRegion, Line2, Region, Region1, Region2};
pub use unknown::{Unknown, UnknownSet, Unknowns};
pub use vec::*;
pub type Scalar = f64; pub type Scalar = f64;
pub type Vec2 = nalgebra::Vector2<Scalar>; // #[derive(Clone, Copy, PartialEq, Debug)]
pub type Point2 = nalgebra::Point2<Scalar>; // pub enum Value {
// Known(Scalar),
// Unkn(Unknown),
// }
pub type Rot2 = nalgebra::UnitComplex<Scalar>; pub type Value = Expr;
pub trait Region<T> {
fn full() -> Self;
fn singleton(value: T) -> Self;
fn nearest(&self, value: &T) -> Option<T>;
fn contains(&self, value: &T) -> bool;
}
#[derive(Clone, Debug)]
pub enum Region1 {
Empty,
Singleton(Scalar),
Range(Scalar, Scalar),
Union(Box<Region1>, Box<Region1>),
Full,
}
impl Region<Scalar> for Region1 {
fn full() -> Self {
Region1::Full
}
fn singleton(value: Scalar) -> Self {
Region1::Singleton(value)
}
fn contains(&self, n: &Scalar) -> bool {
use Region1::*;
match self {
Empty => false,
Singleton(n1) => relative_eq!(n1, n),
Range(l, u) => *l <= *n && *n <= *u,
Union(r1, r2) => r1.contains(n) || r2.contains(n),
Full => true,
}
}
fn nearest(&self, s: &Scalar) -> Option<Scalar> {
use Region1::*;
match self {
Empty => None,
Full => Some(*s),
Singleton(n) => Some(*n),
Range(l, u) => match (l < s, s < u) {
(true, true) => Some(*s),
(true, false) => Some(*u),
(false, true) => Some(*l),
_ => None,
},
Union(r1, r2) => {
let distance = |a: Scalar, b: Scalar| (a - b).abs();
match (r1.nearest(s), r2.nearest(s)) {
(None, None) => None,
(Some(n), None) | (None, Some(n)) => Some(n),
(Some(n1), Some(n2)) => Some({
if distance(*s, n1) <= distance(*s, n2) {
n1
} else {
n2
}
}),
}
}
}
}
}
// line starting at start, point at angle dir, with range extent
// ie. start + (cos dir, sin dir) * t for t in extent
#[derive(Clone, Debug)]
pub struct Line2 {
start: Point2,
dir: Rot2,
extent: Region1,
}
impl Line2 {
pub fn new(start: Point2, dir: Rot2, extent: Region1) -> Self {
Self { start, dir, extent }
}
pub fn evaluate(&self, t: Scalar) -> Point2 {
self.start + self.dir * Vec2::new(t, 0.)
}
pub fn nearest(&self, p: &Point2) -> Point2 {
// rotate angle 90 degrees
let perp_dir = self.dir * Rot2::from_cos_sin_unchecked(0., 1.);
let perp = Line2::new(*p, perp_dir, Region1::Full);
if let Region2::Singleton(np) = self.intersect(&perp) {
np
} else {
panic!("Line2::nearest not found!");
}
}
pub fn intersect(&self, other: &Line2) -> Region2 {
// if the two lines are parallel...
let dirs = self.dir / other.dir;
if relative_eq!(dirs.sin_angle(), 0.) {
let starts = self.dir.to_rotation_matrix().inverse() * (other.start - self.start);
return if relative_eq!(starts.y, 0.) {
// and they are colinear
Region2::Line(self.clone())
} else {
// they are parallel and never intersect
Region2::Empty
};
}
// TODO: respect extent
let (a, b) = (self, other);
let (a_0, a_v, b_0, b_v) = (a.start, a.dir, b.start, b.dir);
let (a_c, a_s, b_c, b_s) = (
a_v.cos_angle(),
a_v.sin_angle(),
b_v.cos_angle(),
b_v.sin_angle(),
);
let t_b = (a_0.x * a_s - a_0.y * a_c + a_0.x * a_s + b_0.y * a_c) / (a_s * b_c - a_c * b_s);
Region2::Singleton(b.evaluate(t_b))
}
}
#[derive(Clone, Debug)]
pub enum Region2 {
Empty,
// single point at 0
Singleton(Point2),
Line(Line2),
#[allow(dead_code)]
Union(Box<Region2>, Box<Region2>),
Full,
}
impl Region<Point2> for Region2 {
fn full() -> Self {
Region2::Full
}
fn singleton(value: Point2) -> Self {
Region2::Singleton(value)
}
fn contains(&self, p: &Point2) -> bool {
self.nearest(p).map_or(false, |n| relative_eq!(n, p))
}
fn nearest(&self, p: &Point2) -> Option<Point2> {
use Region2::*;
match self {
Empty => None,
Full => Some(*p),
Singleton(n) => Some(*n),
Line(line) => Some(line.nearest(p)),
Union(r1, r2) => {
use nalgebra::distance;
match (r1.nearest(p), r2.nearest(p)) {
(None, None) => None,
(Some(n), None) | (None, Some(n)) => Some(n),
(Some(n1), Some(n2)) => Some({
if distance(p, &n1) <= distance(p, &n2) {
n1
} else {
n2
}
}),
}
}
}
}
}
impl Region2 {
pub fn union(r1: Region2, r2: Region2) -> Region2 {
use Region2::*;
match (r1, r2) {
(Empty, r) | (r, Empty) => r,
(Full, _) | (_, Full) => Full,
(r1, r2) => Union(Box::new(r1), Box::new(r2)),
}
}
pub fn intersect(&self, other: &Region2) -> Region2 {
use Region2::*;
match (self, other) {
(Empty, _) | (_, Empty) => Empty,
(Full, r) | (r, Full) => r.clone(),
(Singleton(n1), Singleton(n2)) => {
if n1 == n2 {
Singleton(*n1)
} else {
Empty
}
}
(Singleton(n), o) | (o, Singleton(n)) => {
if o.contains(n) {
Singleton(*n)
} else {
Empty
}
}
(Line(l1), Line(l2)) => l1.intersect(l2),
(Union(un1, un2), o) | (o, Union(un1, un2)) => {
Self::union(un1.intersect(o), un2.intersect(o))
}
}
}
}

197
src/math/ops.rs

@ -0,0 +1,197 @@
use std::ops;
use super::{Expr, Scalar, Unknown};
impl From<Scalar> for Expr {
fn from(c: Scalar) -> Expr {
Expr::Const(c)
}
}
impl From<Unknown> for Expr {
fn from(u: Unknown) -> Expr {
Expr::Unkn(u)
}
}
impl ops::Add<Expr> for Expr {
type Output = Expr;
fn add(self, rhs: Expr) -> Expr {
Expr::new_sum(self, rhs)
}
}
impl ops::Add<Scalar> for Expr {
type Output = Expr;
fn add(self, rhs: Scalar) -> Expr {
Expr::new_sum(self, rhs.into())
}
}
impl ops::Add<Unknown> for Expr {
type Output = Expr;
fn add(self, rhs: Unknown) -> Expr {
Expr::new_sum(self, rhs.into())
}
}
impl ops::Sub<Expr> for Expr {
type Output = Expr;
fn sub(self, rhs: Expr) -> Expr {
Expr::new_minus(self, rhs)
}
}
impl ops::Sub<Scalar> for Expr {
type Output = Expr;
fn sub(self, rhs: Scalar) -> Expr {
Expr::new_minus(self, rhs.into())
}
}
impl ops::Sub<Unknown> for Expr {
type Output = Expr;
fn sub(self, rhs: Unknown) -> Expr {
Expr::new_minus(self, rhs.into())
}
}
impl ops::Mul<Expr> for Expr {
type Output = Expr;
fn mul(self, rhs: Expr) -> Expr {
Expr::new_product(self, rhs)
}
}
impl ops::Mul<Scalar> for Expr {
type Output = Expr;
fn mul(self, rhs: Scalar) -> Expr {
Expr::new_product(self, rhs.into())
}
}
impl ops::Mul<Unknown> for Expr {
type Output = Expr;
fn mul(self, rhs: Unknown) -> Expr {
Expr::new_product(self, rhs.into())
}
}
impl ops::Div<Expr> for Expr {
type Output = Expr;
fn div(self, rhs: Expr) -> Expr {
Expr::new_div(self, rhs)
}
}
impl ops::Div<Scalar> for Expr {
type Output = Expr;
fn div(self, rhs: Scalar) -> Expr {
Expr::new_div(self, rhs.into())
}
}
impl ops::Div<Unknown> for Expr {
type Output = Expr;
fn div(self, rhs: Unknown) -> Expr {
Expr::new_div(self, rhs.into())
}
}
impl ops::Neg for Expr {
type Output = Expr;
fn neg(self) -> Expr {
Expr::new_neg(self)
}
}
impl ops::Add<Expr> for Unknown {
type Output = Expr;
fn add(self, rhs: Expr) -> Expr {
Expr::new_sum(self.into(), rhs)
}
}
impl ops::Add<Scalar> for Unknown {
type Output = Expr;
fn add(self, rhs: Scalar) -> Expr {
Expr::new_sum(self.into(), rhs.into())
}
}
impl ops::Add<Unknown> for Unknown {
type Output = Expr;
fn add(self, rhs: Unknown) -> Expr {
Expr::new_sum(self.into(), rhs.into())
}
}
impl ops::Sub<Expr> for Unknown {
type Output = Expr;
fn sub(self, rhs: Expr) -> Expr {
Expr::new_minus(self.into(), rhs)
}
}
impl ops::Sub<Scalar> for Unknown {
type Output = Expr;
fn sub(self, rhs: Scalar) -> Expr {
Expr::new_minus(self.into(), rhs.into())
}
}
impl ops::Sub<Unknown> for Unknown {
type Output = Expr;
fn sub(self, rhs: Unknown) -> Expr {
Expr::new_minus(self.into(), rhs.into())
}
}
impl ops::Mul<Expr> for Unknown {
type Output = Expr;
fn mul(self, rhs: Expr) -> Expr {
Expr::new_product(self.into(), rhs)
}
}
impl ops::Mul<Scalar> for Unknown {
type Output = Expr;
fn mul(self, rhs: Scalar) -> Expr {
Expr::new_product(self.into(), rhs.into())
}
}
impl ops::Mul<Unknown> for Unknown {
type Output = Expr;
fn mul(self, rhs: Unknown) -> Expr {
Expr::new_product(self.into(), rhs.into())
}
}
impl ops::Div<Expr> for Unknown {
type Output = Expr;
fn div(self, rhs: Expr) -> Expr {
Expr::new_div(self.into(), rhs)
}
}
impl ops::Div<Scalar> for Unknown {
type Output = Expr;
fn div(self, rhs: Scalar) -> Expr {
Expr::new_div(self.into(), rhs.into())
}
}
impl ops::Div<Unknown> for Unknown {
type Output = Expr;
fn div(self, rhs: Unknown) -> Expr {
Expr::new_div(self.into(), rhs.into())
}
}
impl ops::Neg for Unknown {
type Output = Expr;
fn neg(self) -> Expr {
Expr::new_neg(self.into())
}
}

437
src/math/region.rs

@ -0,0 +1,437 @@
use std::fmt;
use super::{eqn, Expr, Point2, Rot2, Scalar, Value};
// pub type Vec2 = nalgebra::Vector2<Value>;
// pub type Point2 = nalgebra::Point2<Value>;
// pub type Rot2 = nalgebra::UnitComplex<Value>;
pub trait GenericRegion {
fn full() -> Self;
fn intersection(self, other: Self) -> Self;
fn simplify(self) -> Self;
fn evaluate_with(self, eqns: &eqn::Eqns) -> Self;
}
pub trait Region<T>: GenericRegion {
fn singleton(value: T) -> Self;
fn nearest(&self, value: &T) -> Option<T>;
fn contains(&self, value: &T) -> Option<bool>;
}
#[derive(Clone, Debug)]
pub enum Region1 {
Empty,
Singleton(Value),
Range(Value, Value),
Intersection(Box<Region1>, Box<Region1>),
// Union(Box<Region1>, Box<Region1>),
Full,
}
impl fmt::Display for Region1 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Region1::*;
match self {
Empty => write!(f, "Ø"),
Singleton(v) => write!(f, "{{ {} }}", v),
Range(l, u) => write!(f, "[ {}, {} ]", l, u),
Intersection(r1, r2) => write!(f, "{} ∩ {}", r1, r2),
Full => write!(f, "ℝ"),
}
}
}
impl GenericRegion for Region1 {
fn intersection(self, other: Region1) -> Self {
Region1::Intersection(Box::new(self), Box::new(other))
}
fn full() -> Self {
Region1::Full
}
fn simplify(self) -> Self {
use Region1::*;
match self {
Singleton(n) => Singleton(n.simplify()),
Range(l, u) => Range(l.simplify(), u.simplify()),
Intersection(r1, r2) => r1.simplify().intersection(r2.simplify()),
other => other,
}
}
fn evaluate_with(self, eqns: &eqn::Eqns) -> Self {
use Region1::*;
match self {
Singleton(n) => Singleton(n.evaluate_with(eqns)),
Range(l, u) => Range(l.evaluate_with(eqns), u.evaluate_with(eqns)),
Intersection(r1, r2) => r1.evaluate_with(eqns).intersection(r2.evaluate_with(eqns)),
other => other,
}
}
}
impl Region<Scalar> for Region1 {
fn singleton(value: Scalar) -> Self {
Region1::Singleton(value.into())
}
fn contains(&self, n: &Scalar) -> Option<bool> {
use Expr::Const;
use Region1::*;
match self {
Empty => Some(false),
Singleton(n1) => match n1 {
Const(c) => Some(relative_eq!(c, n)),
_ => None,
},
Range(l, u) => match (l, u) {
(Const(cl), Const(cu)) => Some(*cl <= *n && *n <= *cu),
_ => None,
},
Intersection(r1, r2) => r1
.contains(n)
.and_then(|c1| r2.contains(n).map(|c2| c1 && c2)),
// Union(r1, r2) => r1.contains(n) || r2.contains(n),
Full => Some(true),
}
}
fn nearest(&self, s: &Scalar) -> Option<Scalar> {
use Expr::Const;
use Region1::*;
match self {
Empty => None,
Full => Some(*s),
Singleton(n) => match n {
Const(c) => Some(*c),
_ => None,
},
Range(l, u) => match (l, u) {
(Const(cl), Const(cu)) => match (cl < s, s < cu) {
(true, true) => Some(*s),
(true, false) => Some(*cu),
(false, true) => Some(*cl),
_ => None,
},
_ => None,
},
Intersection(r1, r2) => unimplemented!(), /*Union(r1, r2) => {
let distance = |a: Scalar, b: Scalar| (a - b).abs();
match (r1.nearest(s), r2.nearest(s)) {
(None, None) => None,
(Some(n), None) | (None, Some(n)) => Some(n),
(Some(n1), Some(n2)) => Some({
if distance(*s, n1) <= distance(*s, n2) {
n1
} else {
n2
}
}),
}
}*/
}
}
}
// line starting at start, point at angle dir, with range extent
// ie. start + (cos dir, sin dir) * t for t in extent
#[derive(Clone, Debug)]
pub struct Line2 {
start: Point2<Value>,
dir: Rot2,
extent: Region1,
}
impl fmt::Display for Line2 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{{ (x, y, t): ⟨x, y⟩ = {} + t{} & t ∈ {} }}",
self.start, self.dir, self.extent
)
}
}
impl Line2 {
pub fn new(start: Point2<Value>, dir: Rot2, extent: Region1) -> Self {
Self { start, dir, extent }
}
pub fn evaluate(&self, t: Value) -> Point2<Value> {
self.start.clone() + self.dir.clone() * t
}
pub fn evaluate_extent(&self) -> Option<Point2<Value>> {
match &self.extent {
Region1::Singleton(t) => Some(self.evaluate(t.clone())),
_ => None,
}
}
pub fn with_extent(self, new_extent: Region1) -> Line2 {
Line2 {
start: self.start,
dir: self.dir,
extent: new_extent,
}
}
pub fn nearest(&self, p: &Point2<Value>) -> Point2<Value> {
// rotate angle 90 degrees
let perp_dir = self.dir.clone() + Rot2::cardinal(1);
let perp = Line2::new(p.clone(), perp_dir, Region1::Full);
match self.intersect(&perp) {
Region2::Singleton(np) => np,
Region2::Line(l) => l.evaluate_extent().expect("Line2::nearest not found"),
_ => panic!("Line2::nearest not found!"),
}
}
pub fn intersect(&self, other: &Line2) -> Region2 {
// if the two lines are parallel...
let dirs = self.dir.clone() - other.dir.clone();
if relative_eq!(dirs.sin(), 0.) {
let starts = self.dir.conj() * (other.start.clone() - self.start.clone());
return if starts.y.simplify().is_zero() {
// and they are colinear
Region2::Line(self.clone())
} else {
// they are parallel and never intersect
Region2::Empty
};
}
// TODO: respect extent
let (a, b) = (self, other);
let (a_0, a_v, b_0, b_v) = (
a.start.clone(),
a.dir.clone(),
b.start.clone(),
b.dir.clone(),
);
let (a_c, a_s, b_c, b_s) = (a_v.cos(), a_v.sin(), b_v.cos(), b_v.sin());
let t_b = (a_0.x.clone() * a_s.clone()
- a_0.y.clone() * a_c.clone()
- b_0.x.clone() * a_s.clone()
+ b_0.y.clone() * a_c.clone())
/ (a_s.clone() * b_c.clone() - a_c.clone() * b_s.clone());
// Region2::Singleton(b.evaluate(t_b))
trace!("intersect a: {}, b: {}, t_b = {}", a, b, t_b);
let res = Region2::Line(b.clone().with_extent(Region1::Singleton(t_b.simplify())));
trace!("intersect a: {}, b: {} = {}", a, b, res);
res
}
pub fn simplify(self) -> Region2 {
let new_l = Line2 {
start: self.start.simplify(),
dir: self.dir,
extent: self.extent.simplify(),
};
trace!(
"line {}: simplify evaluate extent: {:?}",
new_l,
new_l.evaluate_extent()
);
if let Some(p) = new_l.evaluate_extent() {
return Region2::Singleton(p.simplify());
}
Region2::Line(new_l)
}
pub fn evaluate_with(self, eqns: &eqn::Eqns) -> Self {
Line2 {
start: self.start.evaluate_with(eqns),
dir: self.dir,
extent: self.extent.evaluate_with(eqns),
}
}
}
#[derive(Clone, Debug)]
pub enum Region2 {
Empty,
// single point at 0
Singleton(Point2<Value>),
Line(Line2),
// #[allow(dead_code)]
// Union(Box<Region2>, Box<Region2>),
Intersection(Box<Region2>, Box<Region2>),
Full,
}
impl fmt::Display for Region2 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Region2::*;
match self {
Empty => write!(f, "ز"),
Singleton(v) => write!(f, "{{ {} }}", v),
Line(l) => l.fmt(f),
Intersection(r1, r2) => write!(f, "{} ∩ {}", r1, r2),
Full => write!(f, "ℝ²"),
}
}
}
impl GenericRegion for Region2 {
fn full() -> Self {
Region2::Full
}
fn intersection(self, other: Self) -> Self {
use Region2::*;
match (self, other) {
(Empty, _) | (_, Empty) => Empty,
(Full, r) | (r, Full) => r,
(r1, r2) => Intersection(Box::new(r1), Box::new(r2)),
}
}
fn simplify(self) -> Region2 {
use Region2::*;
match self {
Singleton(n) => Singleton(n.simplify()),
Line(l) => l.simplify(),
Intersection(r1, r2) => r1.simplify().intersect(r2.simplify()),
other => other,
}
}
fn evaluate_with(self, eqns: &eqn::Eqns) -> Self {
use Region2::*;
match self {
Singleton(n) => Singleton(n.evaluate_with(eqns)),
Line(l) => Line(l.evaluate_with(eqns)),
Intersection(r1, r2) => r1.evaluate_with(eqns).intersection(r2.evaluate_with(eqns)),
other => other,
}
}
}
impl Region<Point2<Scalar>> for Region2 {
fn singleton(value: Point2<Scalar>) -> Self {
Region2::Singleton(value.into())
}
fn contains(&self, p: &Point2<Scalar>) -> Option<bool> {
self.nearest(p).map(|n| n == *p)
}
fn nearest(&self, p: &Point2<Scalar>) -> Option<Point2<Scalar>> {
use Expr::Const;
use Region2::*;
match self {
Empty => None,
Full => Some(p.clone()),
Singleton(n) => match (&n.x, &n.y) {
(Const(cx), Const(cy)) => Some(Point2::new(*cx, *cy)),
_ => None,
},
Line(line) => {
let pv: Point2<Value> = p.clone().into();
let n = line.nearest(&pv).simplify();
trace!("line {} nearest to {}: {}", line, pv, n);
match (n.x, n.y) {
(Const(cx), Const(cy)) => Some(Point2::new(cx, cy)),
_ => None,
}
}
Intersection(r1, r2) => {
None
// r1.clone().intersect((**r2).clone()).nearest(p)
} /*Union(r1, r2) => {
use nalgebra::distance;
match (r1.nearest(p), r2.nearest(p)) {
(None, None) => None,
(Some(n), None) | (None, Some(n)) => Some(n),
(Some(n1), Some(n2)) => Some({
if distance(p, &n1) <= distance(p, &n2) {
n1
} else {
n2
}
}),
}
}*/
}
}
}
impl Region<Point2<Value>> for Region2 {
fn singleton(value: Point2<Value>) -> Self {
Region2::Singleton(value)
}
fn contains(&self, p: &Point2<Value>) -> Option<bool> {
self.nearest(p)
.map(|n| n.simplify() == p.clone().simplify())
}
fn nearest(&self, p: &Point2<Value>) -> Option<Point2<Value>> {
use Region2::*;
match self {
Empty => None,
Full => Some(p.clone()),
Singleton(n) => Some(n.clone()),
Line(line) => Some(line.nearest(p)),
Intersection(r1, r2) => r1.clone().intersect((**r2).clone()).nearest(p), /*Union(r1, r2) => {
use nalgebra::distance;
match (r1.nearest(p), r2.nearest(p)) {
(None, None) => None,
(Some(n), None) | (None, Some(n)) => Some(n),
(Some(n1), Some(n2)) => Some({
if distance(p, &n1) <= distance(p, &n2) {
n1
} else {
n2
}
}),
}
}*/
}
}
}
impl Region2 {
/*
pub fn union(r1: Region2, r2: Region2) -> Region2 {
use Region2::*;
match (r1, r2) {
(Empty, r) | (r, Empty) => r,
(Full, _) | (_, Full) => Full,
(r1, r2) => Union(Box::new(r1), Box::new(r2)),
}
}
*/
pub fn intersect(self, other: Region2) -> Region2 {
use Region2::*;
match (self, other) {
(Empty, _) | (_, Empty) => Empty,
(Full, r) | (r, Full) => r.clone(),
(Singleton(n1), Singleton(n2)) => {
if n1 == n2 {
Singleton(n1)
} else {
Region2::intersection(Singleton(n1), Singleton(n2))
}
}
(Singleton(n), o) | (o, Singleton(n)) => {
if o.contains(&n).unwrap_or(false) {
Singleton(n)
} else {
Region2::intersection(Singleton(n), o)
}
}
(Intersection(r1, r2), o) | (o, Intersection(r1, r2)) => r1.intersect(*r2).intersect(o),
(Line(l1), Line(l2)) => l1.intersect(&l2).simplify(),
/*(Union(un1, un2), o) | (o, Union(un1, un2)) => {
Self::union(un1.intersect(o), un2.intersect(o))
}*/
(r1, r2) => Intersection(Box::new(r1), Box::new(r2)),
}
}
}

47
src/math/unknown.rs

@ -0,0 +1,47 @@
use std::collections::BTreeSet;
use std::fmt;
use std::iter::FromIterator;
use super::Scalar;
// an unknown variable with an id
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Unknown(pub i64);
pub type UnknownSet = BTreeSet<Unknown>;
pub trait Unknowns {
fn unknowns(&self) -> UnknownSet;
fn has_unknowns(&self) -> bool;
fn has_unknown(&self, u: Unknown) -> bool;
}
impl Unknowns for Scalar {
fn unknowns(&self) -> UnknownSet {
UnknownSet::new()
}
fn has_unknowns(&self) -> bool {
false
}
fn has_unknown(&self, _: Unknown) -> bool {
false
}
}
impl Unknowns for Unknown {
fn unknowns(&self) -> UnknownSet {
FromIterator::from_iter(Some(*self))
}
fn has_unknowns(&self) -> bool {
true
}
fn has_unknown(&self, u: Unknown) -> bool {
*self == u
}
}
impl fmt::Display for Unknown {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "u{}", self.0)
}
}

303
src/math/vec.rs

@ -0,0 +1,303 @@
use super::eqn::Eqns;
use super::{Scalar, Value};
use std::ops;
#[derive(Clone, PartialEq, Debug)]
pub struct Vec2<T> {
pub x: T,
pub y: T,
}
impl<T> Vec2<T> {
pub fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl Vec2<Scalar> {
pub fn normal2(self) -> Scalar {
self.x * self.x + self.y * self.y
}
pub fn normal(self) -> Scalar {
self.normal2().sqrt()
}
pub fn normalize(self) -> Vec2<Scalar> {
self.clone() / self.normal()
}
}
impl Vec2<Value> {
pub fn simplify(self) -> Self {
Self {
x: self.x.simplify(),
y: self.y.simplify(),
}
}
}
impl<T: ops::Add<U>, U> ops::Add<Vec2<U>> for Vec2<T> {
type Output = Vec2<T::Output>;
fn add(self, rhs: Vec2<U>) -> Self::Output {
Self::Output {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl<T: ops::Sub<U>, U> ops::Sub<Vec2<U>> for Vec2<T> {
type Output = Vec2<T::Output>;
fn sub(self, rhs: Vec2<U>) -> Self::Output {
Self::Output {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
impl<T: ops::Mul<U>, U: Clone> ops::Mul<U> for Vec2<T> {
type Output = Vec2<T::Output>;
fn mul(self, rhs: U) -> Self::Output {
Self::Output {
x: self.x * rhs.clone(),
y: self.y * rhs,
}
}
}
impl<T: ops::Div<U>, U: Clone> ops::Div<U> for Vec2<T> {
type Output = Vec2<T::Output>;
fn div(self, rhs: U) -> Self::Output {
Self::Output {
x: self.x / rhs.clone(),
y: self.y / rhs,
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct Point2<T> {
pub x: T,
pub y: T,
}
impl<T> Point2<T> {
pub fn new(x: T, y: T) -> Point2<T> {
Point2 { x, y }
}
}
impl Point2<Value> {
pub fn simplify(self) -> Self {
Self {
x: self.x.distribute().simplify(),
y: self.y.distribute().simplify(),
}
}
pub fn evaluate_with(self, eqns: &Eqns) -> Self {
Self {
x: self.x.evaluate_with(eqns),
y: self.y.evaluate_with(eqns),
}
}
}
impl From<Point2<Scalar>> for Point2<Value> {
fn from(sp: Point2<Scalar>) -> Self {
Self {
x: sp.x.into(),
y: sp.y.into(),
}
}
}
impl<T: ops::Add<U>, U> ops::Add<Vec2<U>> for Point2<T> {
type Output = Point2<T::Output>;
fn add(self, rhs: Vec2<U>) -> Self::Output {
Point2 {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl<T: ops::Sub<U>, U> ops::Sub<Vec2<U>> for Point2<T> {
type Output = Point2<T::Output>;
fn sub(self, rhs: Vec2<U>) -> Self::Output {
Point2 {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
impl<T: ops::Sub<U>, U> ops::Sub<Point2<U>> for Point2<T> {
type Output = Vec2<T::Output>;
fn sub(self, rhs: Point2<U>) -> Self::Output {
Vec2 {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
use std::fmt;
impl<T: fmt::Display> fmt::Display for Point2<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "⟨{}, {}⟩", self.x, self.y)
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Rot2 {
cos: Scalar,
sin: Scalar,
}
impl fmt::Display for Rot2 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "⟨{}, {}⟩", self.cos, self.sin)
}
}
impl Rot2 {
pub fn from_cos_sin_unchecked(cos: Scalar, sin: Scalar) -> Self {
Self { cos, sin }
}
pub fn up() -> Self {
Self { cos: 0., sin: 1. }
}
pub fn right() -> Self {
Self { cos: 1., sin: 0. }
}
pub fn from_cos_sin(cos: Scalar, sin: Scalar) -> Self {
Vec2 { x: cos, y: sin }.into()
}
pub fn from_angle(angle: Scalar) -> Self {
Self {
cos: angle.cos().into(),
sin: angle.sin().into(),
}
}
pub fn from_angle_deg(angle_deg: Scalar) -> Self {
Self::from_angle(angle_deg * std::f64::consts::PI / 180.)
}
pub fn cardinal(index: i64) -> Self {
match index % 4 {
0 => Rot2 {
cos: (1.).into(),
sin: (0.).into(),
},
1 => Rot2 {
cos: (0.).into(),
sin: (1.).into(),
},
2 => Rot2 {
cos: (-1.).into(),
sin: (0.).into(),
},
3 => Rot2 {
cos: (0.).into(),
sin: (-1.).into(),
},
_ => unreachable!(),
}
}
pub fn cos(&self) -> Scalar {
self.cos
}
pub fn sin(&self) -> Scalar {
self.sin
}
pub fn conj(self) -> Self {
Self {
cos: self.cos,
sin: -self.sin,
}
}
pub fn dot(self, v: Vec2<Value>) -> Value {
v.x * self.cos + v.y * self.sin
}
}
impl From<Vec2<Scalar>> for Rot2 {
fn from(v: Vec2<Scalar>) -> Rot2 {
let v = v.normalize();
Rot2 { cos: v.x, sin: v.y }
}
}
impl ops::Mul<Scalar> for Rot2 {
type Output = Vec2<Scalar>;
fn mul(self, rhs: Scalar) -> Vec2<Scalar> {
Vec2 {
x: self.cos * rhs,
y: self.sin * rhs,
}
}
}
impl ops::Mul<Value> for Rot2 {
type Output = Vec2<Value>;
fn mul(self, rhs: Value) -> Vec2<Value> {
Vec2 {
x: rhs.clone() * self.cos,
y: rhs * self.sin,
}
}
}
impl ops::Add<Rot2> for Rot2 {
type Output = Rot2;
fn add(self, rhs: Rot2) -> Rot2 {
Rot2 {
cos: self.cos.clone() * rhs.cos.clone() - self.sin.clone() * rhs.sin.clone(),
sin: self.cos * rhs.sin + self.sin * rhs.cos,
}
}
}
impl ops::Sub<Rot2> for Rot2 {
type Output = Rot2;
fn sub(self, rhs: Rot2) -> Rot2 {
Rot2 {
cos: self.cos.clone() * rhs.cos.clone() + self.sin.clone() * rhs.sin.clone(),
sin: self.sin * rhs.cos - self.cos * rhs.sin,
}
}
}
impl ops::Mul<Vec2<Scalar>> for Rot2 {
type Output = Vec2<Scalar>;
fn mul(self, rhs: Vec2<Scalar>) -> Vec2<Scalar> {
Vec2 {
x: self.cos * rhs.x - self.sin * rhs.y,
y: self.cos * rhs.y + self.sin * rhs.x,
}
}
}
impl ops::Mul<Vec2<Value>> for Rot2 {
type Output = Vec2<Value>;
fn mul(self, rhs: Vec2<Value>) -> Vec2<Value> {
Vec2 {
x: rhs.x.clone() * self.cos - rhs.y.clone() * self.sin,
y: rhs.y * self.cos + rhs.x * self.sin,
}
}
}

128
src/relation.rs

@ -1,5 +1,5 @@
use crate::entity::{Point as PointEntity, PointRef}; use crate::entity::{CPoint as PointEntity, PointRef};
use crate::math::{Line2, Vec2, Point2, Region1, Region2, Rot2, Scalar}; use crate::math::{GenericRegion, Line2, Point2, Region1, Region2, Rot2, Scalar, Value, Vec2};
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum ResolveResult { pub enum ResolveResult {
@ -31,9 +31,14 @@ pub struct Coincident {
impl Relation for Coincident { impl Relation for Coincident {
fn resolve(&self) -> ResolveResult { fn resolve(&self) -> ResolveResult {
let (mut p1, mut p2) = (self.p1.borrow_mut(), self.p2.borrow_mut()); let (mut p1, mut p2) = (self.p1.borrow_mut(), self.p2.borrow_mut());
let r = { p1.pos.constraints().intersect(p2.pos.constraints()) }; let r = {
p1.pos.reconstrain(r.clone()); p1.constraints()
p2.pos.reconstrain(r.clone()); .clone()
.intersect(p2.constraints().clone())
.simplify()
};
p1.reconstrain(r.clone());
p2.reconstrain(r.clone());
ResolveResult::from_r2(&r) ResolveResult::from_r2(&r)
} }
} }
@ -50,11 +55,19 @@ impl PointAngle {
} }
pub fn new_horizontal(p1: PointRef, p2: PointRef) -> Self { pub fn new_horizontal(p1: PointRef, p2: PointRef) -> Self {
Self::new(p1, p2, Rot2::from_cos_sin_unchecked(1., 0.)) Self::new(
p1,
p2,
Rot2::from_cos_sin_unchecked((1.).into(), (0.).into()),
)
} }
pub fn new_vertical(p1: PointRef, p2: PointRef) -> Self { pub fn new_vertical(p1: PointRef, p2: PointRef) -> Self {
Self::new(p1, p2, Rot2::from_cos_sin_unchecked(0., 1.)) Self::new(
p1,
p2,
Rot2::from_cos_sin_unchecked((0.).into(), (1.).into()),
)
} }
} }
@ -62,23 +75,30 @@ impl Relation for PointAngle {
fn resolve(&self) -> ResolveResult { fn resolve(&self) -> ResolveResult {
use Region2::*; use Region2::*;
let (mut p1, mut p2) = (self.p1.borrow_mut(), self.p2.borrow_mut()); let (mut p1, mut p2) = (self.p1.borrow_mut(), self.p2.borrow_mut());
let constrain_line = |p1: &Point2, p2: &mut PointEntity| { let constrain_line = |p1: &Point2<Value>, p2: &mut PointEntity| {
let line = Region2::Line(Line2::new(*p1, self.angle, Region1::Full)); let line = Region2::Line(Line2::new(p1.clone(), self.angle.clone(), Region1::Full));
let new_constraint = p2.pos.constraints().intersect(&line); trace!(
p2.pos.reconstrain(new_constraint); "PointAngle line: {}, p2 constraint: {}",
ResolveResult::from_r2(p2.pos.constraints()) line,
p2.constraints()
);
let new_constraint = p2.constraints().clone().intersection(line).simplify();
trace!("PointAngle new_constraint: {}", new_constraint);
p2.reconstrain(new_constraint);
ResolveResult::from_r2(p2.constraints())
}; };
match (&mut p1.pos.constraints(), &mut p2.pos.constraints()) { match (&mut p1.constraints(), &mut p2.constraints()) {
(Empty, _) | (_, Empty) => ResolveResult::Overconstrained, (Empty, _) | (_, Empty) => ResolveResult::Overconstrained,
(Singleton(p1), Singleton(p2)) => { (Singleton(p1), Singleton(p2)) => {
// if the angle p1 and p2 form is parallel to self.angle, the result // if the angle p1 and p2 form is parallel to self.angle, the result
// will have a y component of 0 // will have a y component of 0
let r = self.angle.to_rotation_matrix().inverse() * (p2 - p1); let r = self.angle.clone().conj() * (p2.clone() - p1.clone());
if relative_eq!(r.y, 0.) { trace!("angle.cos: {}", r.x);
ResolveResult::Constrained // if relative_eq!(r.y, 0.) {
} else { ResolveResult::Constrained
ResolveResult::Overconstrained // } else {
} // ResolveResult::Overconstrained
// }
} }
(Singleton(p), _) => constrain_line(p, &mut *p2), (Singleton(p), _) => constrain_line(p, &mut *p2),
(_, Singleton(p)) => constrain_line(p, &mut *p1), (_, Singleton(p)) => constrain_line(p, &mut *p1),
@ -87,34 +107,29 @@ impl Relation for PointAngle {
} }
} }
pub enum Axis {
Vertical,
Horizontal,
}
pub struct AlignedDistance { pub struct AlignedDistance {
pub p1: PointRef, pub p1: PointRef,
pub p2: PointRef, pub p2: PointRef,
pub axis: Axis, pub angle: Rot2,
pub distance: Scalar, pub distance: Scalar,
} }
impl AlignedDistance { impl AlignedDistance {
pub fn new(p1: PointRef, p2: PointRef, axis: Axis, distance: Scalar) -> Self { pub fn new(p1: PointRef, p2: PointRef, angle: Rot2, distance: Scalar) -> Self {
Self { Self {
p1, p1,
p2, p2,
axis, angle,
distance, distance,
} }
} }
pub fn new_vertical(p1: PointRef, p2: PointRef, distance: Scalar) -> Self { pub fn new_vertical(p1: PointRef, p2: PointRef, distance: Scalar) -> Self {
Self::new(p1, p2, Axis::Vertical, distance) Self::new(p1, p2, Rot2::up(), distance)
} }
pub fn new_horizontal(p1: PointRef, p2: PointRef, distance: Scalar) -> Self { pub fn new_horizontal(p1: PointRef, p2: PointRef, distance: Scalar) -> Self {
Self::new(p1, p2, Axis::Horizontal, distance) Self::new(p1, p2, Rot2::right(), distance)
} }
} }
@ -122,37 +137,40 @@ impl Relation for AlignedDistance {
fn resolve(&self) -> ResolveResult { fn resolve(&self) -> ResolveResult {
use Region2::*; use Region2::*;
let (mut p1, mut p2) = (self.p1.borrow_mut(), self.p2.borrow_mut()); let (mut p1, mut p2) = (self.p1.borrow_mut(), self.p2.borrow_mut());
let constrain_line = |p1: Point2, p2: &mut PointEntity| { let constrain_line = |p1: Point2<Value>, p2: &mut PointEntity| {
let angle = match self.axis { let angle = self.angle + Rot2::up();
Axis::Horizontal => Rot2::from_cos_sin_unchecked(0., 1.), let line = Region2::Line(Line2::new(p1.clone(), angle, Region1::Full)).simplify();
Axis::Vertical => Rot2::from_cos_sin_unchecked(1., 0.), trace!(
}; "AlignedDistance line: {}, p2 constraint: {}",
let line = Region2::Line(Line2::new(p1, angle, Region1::Full)); line,
let new_constraint = p2.pos.constraints().intersect(&line); p2.constraints()
p2.pos.reconstrain(new_constraint); );
ResolveResult::from_r2(p2.pos.constraints()) let new_constraint = p2.constraints().clone().intersection(line).simplify();
}; trace!("AlignedDistance new_constraint: {}", new_constraint);
let offset = match self.axis { p2.reconstrain(new_constraint);
Axis::Horizontal => Vec2::new(self.distance, 0.), ResolveResult::from_r2(p2.constraints())
Axis::Vertical => Vec2::new(0., self.distance),
}; };
match (&mut p1.pos.constraints(), &mut p2.pos.constraints()) { let offset: Vec2<Scalar> = self.angle * self.distance;
match (&mut p1.constraints(), &mut p2.constraints()) {
(Empty, _) | (_, Empty) => ResolveResult::Overconstrained, (Empty, _) | (_, Empty) => ResolveResult::Overconstrained,
(Singleton(p1), Singleton(p2)) => { (Singleton(p1), Singleton(p2)) => {
let r = p2 - p1; let r = p2.clone() - p1.clone();
let d = match self.axis { let d = self.angle.dot(r);
Axis::Horizontal => r.x, // if relative_eq!(d, self.distance) {
Axis::Vertical => r.y, ResolveResult::Constrained
}; // } else {
if relative_eq!(d, self.distance) { // ResolveResult::Overconstrained
ResolveResult::Constrained // }
} else {
ResolveResult::Overconstrained
}
} }
(Singleton(pos), _) => constrain_line(pos + offset, &mut *p2), (Singleton(pos), _) => constrain_line(pos.clone() + offset, &mut *p2),
(_, Singleton(pos)) => constrain_line(pos - offset, &mut *p1), (_, Singleton(pos)) => constrain_line(pos.clone() - offset, &mut *p1),
_ => ResolveResult::Underconstrained, _ => ResolveResult::Underconstrained,
} }
} }
} }
pub struct Midpoint {
pub p1: PointRef,
pub p2: PointRef,
pub mid: PointRef,
}

Loading…
Cancel
Save