From d243f19e25c08fe1dd127e88938bd9658948ff0f Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 11 Mar 2025 14:51:16 -0700 Subject: [PATCH 1/8] Start tracking `Cargo.lock` --- app-proto/Cargo.lock | 788 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 788 insertions(+) create mode 100644 app-proto/Cargo.lock diff --git a/app-proto/Cargo.lock b/app-proto/Cargo.lock new file mode 100644 index 0000000..9738589 --- /dev/null +++ b/app-proto/Cargo.lock @@ -0,0 +1,788 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "dyna3" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "dyna3", + "itertools", + "js-sys", + "lazy_static", + "nalgebra", + "readonly", + "rustc-hash", + "slab", + "sycamore", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "minicov" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71e683cd655513b99affab7d317deb690528255a0d5f717f1024093c12b169" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "nalgebra" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c4b5f057b303842cf3262c27e465f4c303572e7f6b0648f60e16248ac3397f4" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "readonly" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2a62d85ed81ca5305dc544bd42c8804c5060b78ffa5ad3c64b0fb6a8c13d062" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + +[[package]] +name = "safe_arch" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simba" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "sycamore" +version = "0.9.0-beta.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dedaf7237c05913604a5b0b2536b613f6c8510c6b213d2583b1294869755cabd" +dependencies = [ + "hashbrown", + "indexmap", + "paste", + "sycamore-core", + "sycamore-macro", + "sycamore-reactive", + "sycamore-web", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "sycamore-core" +version = "0.9.0-beta.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ddddc3d1bcb38c04ad55d2d1ab4f6a358e4daaeae0a0436892f1fade9fb31a" +dependencies = [ + "hashbrown", + "paste", + "sycamore-reactive", +] + +[[package]] +name = "sycamore-macro" +version = "0.9.0-beta.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77181c27cb753e86065308901871ccc7456fb19527b6a4ffacad3b63175ed014" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "rand", + "sycamore-view-parser", + "syn", +] + +[[package]] +name = "sycamore-reactive" +version = "0.9.0-beta.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aa6870203507c07e850687c0ccf528eb0f04240e3596bac9137007ffb6c50b1" +dependencies = [ + "paste", + "slotmap", + "smallvec", +] + +[[package]] +name = "sycamore-view-parser" +version = "0.9.0-beta.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6144640af2eafffc68a92f3aacbbfaa21f7fd31906e2336fe304fd100fe226b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sycamore-web" +version = "0.9.0-beta.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca93dcf1b1830bf1aac93508ed51babcda92c1d32d96067ab416d94e4b7c475" +dependencies = [ + "html-escape", + "js-sys", + "once_cell", + "paste", + "smallvec", + "sycamore-core", + "sycamore-macro", + "sycamore-reactive", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68497a05fb21143a08a7d24fc81763384a3072ee43c44e86aad1744d6adef9d9" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "minicov", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wide" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] From c6e6e7be9fd4d011a379c28f9489928d406fa877 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 24 Mar 2025 23:21:55 -0400 Subject: [PATCH 2/8] Encapsulate the constraint problem data This will make it easier for elements and regulators to write themselves into the constraint problem. --- app-proto/examples/point-on-sphere.rs | 25 +-- app-proto/examples/three-spheres.rs | 29 ++- app-proto/src/assembly.rs | 36 ++-- app-proto/src/engine.rs | 249 ++++++++++++++------------ 4 files changed, 172 insertions(+), 167 deletions(-) diff --git a/app-proto/examples/point-on-sphere.rs b/app-proto/examples/point-on-sphere.rs index 13040e5..2820793 100644 --- a/app-proto/examples/point-on-sphere.rs +++ b/app-proto/examples/point-on-sphere.rs @@ -1,26 +1,19 @@ -use nalgebra::DMatrix; - -use dyna3::engine::{Q, point, realize_gram, sphere, PartialMatrix}; +use dyna3::engine::{Q, point, realize_gram, sphere, ConstraintProblem}; fn main() { - let gram = { - let mut gram_to_be = PartialMatrix::new(); - for j in 0..2 { - for k in j..2 { - gram_to_be.push_sym(j, k, if (j, k) == (1, 1) { 1.0 } else { 0.0 }); - } - } - gram_to_be - }; - let guess = DMatrix::from_columns(&[ + let mut problem = ConstraintProblem::from_guess(&[ point(0.0, 0.0, 2.0), sphere(0.0, 0.0, 0.0, 1.0) ]); - let frozen = [(3, 0)]; + for j in 0..2 { + for k in j..2 { + problem.gram.push_sym(j, k, if (j, k) == (1, 1) { 1.0 } else { 0.0 }); + } + } + problem.frozen.push((3, 0)); println!(); let (config, _, success, history) = realize_gram( - &gram, guess, &frozen, - 1.0e-12, 0.5, 0.9, 1.1, 200, 110 + &problem, 1.0e-12, 0.5, 0.9, 1.1, 200, 110 ); print!("\nCompleted Gram matrix:{}", config.tr_mul(&*Q) * &config); print!("Configuration:{}", config); diff --git a/app-proto/examples/three-spheres.rs b/app-proto/examples/three-spheres.rs index 19acfd1..3f3cc44 100644 --- a/app-proto/examples/three-spheres.rs +++ b/app-proto/examples/three-spheres.rs @@ -1,29 +1,22 @@ -use nalgebra::DMatrix; - -use dyna3::engine::{Q, realize_gram, sphere, PartialMatrix}; +use dyna3::engine::{Q, realize_gram, sphere, ConstraintProblem}; fn main() { - let gram = { - let mut gram_to_be = PartialMatrix::new(); - for j in 0..3 { - for k in j..3 { - gram_to_be.push_sym(j, k, if j == k { 1.0 } else { -1.0 }); - } - } - gram_to_be - }; - let guess = { + let mut problem = ConstraintProblem::from_guess({ let a: f64 = 0.75_f64.sqrt(); - DMatrix::from_columns(&[ + &[ sphere(1.0, 0.0, 0.0, 1.0), sphere(-0.5, a, 0.0, 1.0), sphere(-0.5, -a, 0.0, 1.0) - ]) - }; + ] + }); + for j in 0..3 { + for k in j..3 { + problem.gram.push_sym(j, k, if j == k { 1.0 } else { -1.0 }); + } + } println!(); let (config, _, success, history) = realize_gram( - &gram, guess, &[], - 1.0e-12, 0.5, 0.9, 1.1, 200, 110 + &problem, 1.0e-12, 0.5, 0.9, 1.1, 200, 110 ); print!("\nCompleted Gram matrix:{}", config.tr_mul(&*Q) * &config); if success { diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 18176df..289b271 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -6,7 +6,13 @@ use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ use crate::{ - engine::{Q, local_unif_to_std, realize_gram, ConfigSubspace, PartialMatrix}, + engine::{ + Q, + local_unif_to_std, + realize_gram, + ConfigSubspace, + ConstraintProblem + }, specified::SpecifiedValue }; @@ -268,6 +274,11 @@ impl Assembly { // --- realization --- pub fn realize(&self) { + // create a blank constraint problem + let mut problem = ConstraintProblem::new( + self.elements.with_untracked(|elts| elts.len()) + ); + // index the elements self.elements.update_silent(|elts| { for (index, (_, elt)) in elts.into_iter().enumerate() { @@ -276,9 +287,8 @@ impl Assembly { }); // set up the Gram matrix and the initial configuration matrix - let (gram, guess) = self.elements.with_untracked(|elts| { + self.elements.with_untracked(|elts| { // set up the off-diagonal part of the Gram matrix - let mut gram_to_be = PartialMatrix::new(); self.regulators.with_untracked(|regs| { for (_, reg) in regs { reg.set_point.with_untracked(|set_pt| { @@ -286,7 +296,7 @@ impl Assembly { let subjects = reg.subjects; let row = elts[subjects.0].column_index.unwrap(); let col = elts[subjects.1].column_index.unwrap(); - gram_to_be.push_sym(row, col, val); + problem.gram.push_sym(row, col, val); } }); } @@ -294,36 +304,32 @@ impl Assembly { // set up the initial configuration matrix and the diagonal of the // Gram matrix - let mut guess_to_be = DMatrix::::zeros(5, elts.len()); for (_, elt) in elts { let index = elt.column_index.unwrap(); - gram_to_be.push_sym(index, index, 1.0); - guess_to_be.set_column(index, &elt.representation.get_clone_untracked()); + problem.gram.push_sym(index, index, 1.0); + problem.guess.set_column(index, &elt.representation.get_clone_untracked()); } - - (gram_to_be, guess_to_be) }); /* DEBUG */ // log the Gram matrix console::log_1(&JsValue::from("Gram matrix:")); - gram.log_to_console(); + problem.gram.log_to_console(); /* DEBUG */ // log the initial configuration matrix console::log_1(&JsValue::from("Old configuration:")); - for j in 0..guess.nrows() { + for j in 0..problem.guess.nrows() { let mut row_str = String::new(); - for k in 0..guess.ncols() { - row_str.push_str(format!(" {:>8.3}", guess[(j, k)]).as_str()); + for k in 0..problem.guess.ncols() { + row_str.push_str(format!(" {:>8.3}", problem.guess[(j, k)]).as_str()); } console::log_1(&JsValue::from(row_str)); } // look for a configuration with the given Gram matrix let (config, tangent, success, history) = realize_gram( - &gram, guess, &[], - 1.0e-12, 0.5, 0.9, 1.1, 200, 110 + &problem, 1.0e-12, 0.5, 0.9, 1.1, 200, 110 ); /* DEBUG */ diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index 35f898c..deb88bd 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -195,6 +195,34 @@ impl DescentHistory { } } +// --- constraint problems --- + +pub struct ConstraintProblem { + pub gram: PartialMatrix, + pub guess: DMatrix, + pub frozen: Vec<(usize, usize)> +} + +impl ConstraintProblem { + pub fn new(element_count: usize) -> ConstraintProblem { + const ELEMENT_DIM: usize = 5; + ConstraintProblem { + gram: PartialMatrix::new(), + guess: DMatrix::::zeros(ELEMENT_DIM, element_count), + frozen: Vec::new() + } + } + + #[cfg(feature = "dev")] + pub fn from_guess(guess_columns: &[DVector]) -> ConstraintProblem { + ConstraintProblem { + gram: PartialMatrix::new(), + guess: DMatrix::from_columns(guess_columns), + frozen: Vec::new() + } + } +} + // --- gram matrix realization --- // the Lorentz form @@ -289,9 +317,7 @@ fn seek_better_config( // seek a matrix `config` for which `config' * Q * config` matches the partial // matrix `gram`. use gradient descent starting from `guess` pub fn realize_gram( - gram: &PartialMatrix, - guess: DMatrix, - frozen: &[(usize, usize)], + problem: &ConstraintProblem, scaled_tol: f64, min_efficiency: f64, backoff: f64, @@ -299,6 +325,11 @@ pub fn realize_gram( max_descent_steps: i32, max_backoff_steps: i32 ) -> (DMatrix, ConfigSubspace, bool, DescentHistory) { + // destructure the problem data + let ConstraintProblem { + gram, guess, frozen + } = problem; + // start the descent history let mut history = DescentHistory::new(); @@ -317,7 +348,7 @@ pub fn realize_gram( ).collect(); // use Newton's method with backtracking and gradient descent backup - let mut state = SearchState::from_config(gram, guess); + let mut state = SearchState::from_config(gram, guess.clone()); let mut hess = DMatrix::zeros(element_dim, assembly_dim); for _ in 0..max_descent_steps { // find the negative gradient of the loss function @@ -415,7 +446,7 @@ pub fn realize_gram( #[cfg(feature = "dev")] pub mod examples { - use std::{array, f64::consts::PI}; + use std::f64::consts::PI; use super::*; @@ -428,35 +459,7 @@ pub mod examples { // https://www.nippon.com/en/japan-topics/c12801/ // pub fn realize_irisawa_hexlet(scaled_tol: f64) -> (DMatrix, ConfigSubspace, bool, DescentHistory) { - let gram = { - let mut gram_to_be = PartialMatrix::new(); - for s in 0..9 { - // each sphere is represented by a spacelike vector - gram_to_be.push_sym(s, s, 1.0); - - // the circumscribing sphere is tangent to all of the other - // spheres, with matching orientation - if s > 0 { - gram_to_be.push_sym(0, s, 1.0); - } - - if s > 2 { - // each chain sphere is tangent to the "sun" and "moon" - // spheres, with opposing orientation - for n in 1..3 { - gram_to_be.push_sym(s, n, -1.0); - } - - // each chain sphere is tangent to the next chain sphere, - // with opposing orientation - let s_next = 3 + (s-2) % 6; - gram_to_be.push_sym(s, s_next, -1.0); - } - } - gram_to_be - }; - - let guess = DMatrix::from_columns( + let mut problem = ConstraintProblem::from_guess( [ sphere(0.0, 0.0, 0.0, 15.0), sphere(0.0, 0.0, -9.0, 5.0), @@ -471,42 +474,45 @@ pub mod examples { ).collect::>().as_slice() ); + for s in 0..9 { + // each sphere is represented by a spacelike vector + problem.gram.push_sym(s, s, 1.0); + + // the circumscribing sphere is tangent to all of the other + // spheres, with matching orientation + if s > 0 { + problem.gram.push_sym(0, s, 1.0); + } + + if s > 2 { + // each chain sphere is tangent to the "sun" and "moon" + // spheres, with opposing orientation + for n in 1..3 { + problem.gram.push_sym(s, n, -1.0); + } + + // each chain sphere is tangent to the next chain sphere, + // with opposing orientation + let s_next = 3 + (s-2) % 6; + problem.gram.push_sym(s, s_next, -1.0); + } + } + // the frozen entries fix the radii of the circumscribing sphere, the // "sun" and "moon" spheres, and one of the chain spheres - let frozen: [(usize, usize); 4] = array::from_fn(|k| (3, k)); + for k in 0..4 { + problem.frozen.push((3, k)) + } - realize_gram( - &gram, guess, &frozen, - scaled_tol, 0.5, 0.9, 1.1, 200, 110 - ) + realize_gram(&problem, scaled_tol, 0.5, 0.9, 1.1, 200, 110) } // set up a kaleidocycle, made of points with fixed distances between them, // and find its tangent space pub fn realize_kaleidocycle(scaled_tol: f64) -> (DMatrix, ConfigSubspace, bool, DescentHistory) { - const N_POINTS: usize = 12; - let gram = { - let mut gram_to_be = PartialMatrix::new(); - for block in (0..N_POINTS).step_by(2) { - let block_next = (block + 2) % N_POINTS; - for j in 0..2 { - // diagonal and hinge edges - for k in j..2 { - gram_to_be.push_sym(block + j, block + k, if j == k { 0.0 } else { -0.5 }); - } - - // non-hinge edges - for k in 0..2 { - gram_to_be.push_sym(block + j, block_next + k, -0.625); - } - } - } - gram_to_be - }; - - let guess = { - const N_HINGES: usize = 6; - let guess_elts = (0..N_HINGES).step_by(2).flat_map( + const N_HINGES: usize = 6; + let mut problem = ConstraintProblem::from_guess( + (0..N_HINGES).step_by(2).flat_map( |n| { let ang_hor = (n as f64) * PI/3.0; let ang_vert = ((n + 1) as f64) * PI/3.0; @@ -519,16 +525,30 @@ pub mod examples { point(x_vert, y_vert, 0.5) ] } - ).collect::>(); - DMatrix::from_columns(&guess_elts) - }; + ).collect::>().as_slice() + ); - let frozen: [_; N_POINTS] = array::from_fn(|k| (3, k)); + const N_POINTS: usize = 2 * N_HINGES; + for block in (0..N_POINTS).step_by(2) { + let block_next = (block + 2) % N_POINTS; + for j in 0..2 { + // diagonal and hinge edges + for k in j..2 { + problem.gram.push_sym(block + j, block + k, if j == k { 0.0 } else { -0.5 }); + } + + // non-hinge edges + for k in 0..2 { + problem.gram.push_sym(block + j, block_next + k, -0.625); + } + } + } - realize_gram( - &gram, guess, &frozen, - scaled_tol, 0.5, 0.9, 1.1, 200, 110 - ) + for k in 0..N_POINTS { + problem.frozen.push((3, k)) + } + + realize_gram(&problem, scaled_tol, 0.5, 0.9, 1.1, 200, 110) } } @@ -588,33 +608,29 @@ mod tests { // and the realized configuration should match the initial guess #[test] fn frozen_entry_test() { - let gram = { - let mut gram_to_be = PartialMatrix::new(); - for j in 0..2 { - for k in j..2 { - gram_to_be.push_sym(j, k, if (j, k) == (1, 1) { 1.0 } else { 0.0 }); - } - } - gram_to_be - }; - let guess = DMatrix::from_columns(&[ + let mut problem = ConstraintProblem::from_guess(&[ point(0.0, 0.0, 2.0), sphere(0.0, 0.0, 0.0, 1.0) ]); - let frozen = [(3, 0), (3, 1)]; - println!(); + for j in 0..2 { + for k in j..2 { + problem.gram.push_sym(j, k, if (j, k) == (1, 1) { 1.0 } else { 0.0 }); + } + } + for k in 0..2 { + problem.frozen.push((3, k)); + } let (config, _, success, history) = realize_gram( - &gram, guess.clone(), &frozen, - 1.0e-12, 0.5, 0.9, 1.1, 200, 110 + &problem, 1.0e-12, 0.5, 0.9, 1.1, 200, 110 ); assert_eq!(success, true); for base_step in history.base_step.into_iter() { - for index in frozen { + for &index in &problem.frozen { assert_eq!(base_step[index], 0.0); } } - for index in frozen { - assert_eq!(config[index], guess[index]); + for index in problem.frozen { + assert_eq!(config[index], problem.guess[index]); } } @@ -635,34 +651,32 @@ mod tests { #[test] fn tangent_test_three_spheres() { const SCALED_TOL: f64 = 1.0e-12; - let gram = { - let mut gram_to_be = PartialMatrix::new(); - for j in 0..3 { - for k in j..3 { - gram_to_be.push_sym(j, k, if j == k { 1.0 } else { -1.0 }); - } - } - gram_to_be - }; - let guess = DMatrix::from_columns(&[ + const ELEMENT_DIM: usize = 5; + let mut problem = ConstraintProblem::from_guess(&[ sphere(0.0, 0.0, 0.0, -2.0), sphere(0.0, 0.0, 1.0, 1.0), sphere(0.0, 0.0, -1.0, 1.0) ]); - let frozen: [_; 5] = std::array::from_fn(|k| (k, 0)); + for j in 0..3 { + for k in j..3 { + problem.gram.push_sym(j, k, if j == k { 1.0 } else { -1.0 }); + } + } + for n in 0..ELEMENT_DIM { + problem.frozen.push((n, 0)); + } let (config, tangent, success, history) = realize_gram( - &gram, guess.clone(), &frozen, - SCALED_TOL, 0.5, 0.9, 1.1, 200, 110 + &problem, SCALED_TOL, 0.5, 0.9, 1.1, 200, 110 ); - assert_eq!(config, guess); + assert_eq!(config, problem.guess); assert_eq!(success, true); assert_eq!(history.scaled_loss.len(), 1); // list some motions that should form a basis for the tangent space of // the solution variety const UNIFORM_DIM: usize = 4; - let element_dim = guess.nrows(); - let assembly_dim = guess.ncols(); + let element_dim = problem.guess.nrows(); + let assembly_dim = problem.guess.ncols(); let tangent_motions_unif = vec![ basis_matrix((0, 1), UNIFORM_DIM, assembly_dim), basis_matrix((1, 1), UNIFORM_DIM, assembly_dim), @@ -805,22 +819,17 @@ mod tests { fn proj_equivar_test() { // find a pair of spheres that meet at 120° const SCALED_TOL: f64 = 1.0e-12; - let gram = { - let mut gram_to_be = PartialMatrix::new(); - gram_to_be.push_sym(0, 0, 1.0); - gram_to_be.push_sym(1, 1, 1.0); - gram_to_be.push_sym(0, 1, 0.5); - gram_to_be - }; - let guess_orig = DMatrix::from_columns(&[ + let mut problem_orig = ConstraintProblem::from_guess(&[ sphere(0.0, 0.0, 0.5, 1.0), sphere(0.0, 0.0, -0.5, 1.0) ]); + problem_orig.gram.push_sym(0, 0, 1.0); + problem_orig.gram.push_sym(1, 1, 1.0); + problem_orig.gram.push_sym(0, 1, 0.5); let (config_orig, tangent_orig, success_orig, history_orig) = realize_gram( - &gram, guess_orig.clone(), &[], - SCALED_TOL, 0.5, 0.9, 1.1, 200, 110 + &problem_orig, SCALED_TOL, 0.5, 0.9, 1.1, 200, 110 ); - assert_eq!(config_orig, guess_orig); + assert_eq!(config_orig, problem_orig.guess); assert_eq!(success_orig, true); assert_eq!(history_orig.scaled_loss.len(), 1); @@ -833,11 +842,15 @@ mod tests { sphere(-a, 0.0, 7.0 - a, 1.0) ]) }; + let problem_tfm = ConstraintProblem { + gram: problem_orig.gram, + guess: guess_tfm, + frozen: problem_orig.frozen + }; let (config_tfm, tangent_tfm, success_tfm, history_tfm) = realize_gram( - &gram, guess_tfm.clone(), &[], - SCALED_TOL, 0.5, 0.9, 1.1, 200, 110 + &problem_tfm, SCALED_TOL, 0.5, 0.9, 1.1, 200, 110 ); - assert_eq!(config_tfm, guess_tfm); + assert_eq!(config_tfm, problem_tfm.guess); assert_eq!(success_tfm, true); assert_eq!(history_tfm.scaled_loss.len(), 1); @@ -869,7 +882,7 @@ mod tests { // the comparison tolerance because the transformation seems to // introduce some numerical error const SCALED_TOL_TFM: f64 = 1.0e-9; - let tol_sq = ((guess_orig.nrows() * guess_orig.ncols()) as f64) * SCALED_TOL_TFM * SCALED_TOL_TFM; + let tol_sq = ((problem_orig.guess.nrows() * problem_orig.guess.ncols()) as f64) * SCALED_TOL_TFM * SCALED_TOL_TFM; assert!((motion_proj_tfm - motion_tfm_proj).norm_squared() < tol_sq); } } \ No newline at end of file From 677d7707381821e5eaf6c91de363ddbcf3a25522 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 25 Mar 2025 02:15:03 -0700 Subject: [PATCH 3/8] Let the elements and regulators write the problem When we realize an assembly, each element and regulator now writes its own data into the constraint problem. --- app-proto/src/assembly.rs | 122 +++++++++++++++++++++++++++++--------- app-proto/src/outline.rs | 4 +- 2 files changed, 95 insertions(+), 31 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 289b271..293bf73 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -121,15 +121,43 @@ impl Element { None } } + + fn write_to_problem(&self, problem: &mut ConstraintProblem) { + if let Some(index) = self.column_index { + problem.gram.push_sym(index, index, 1.0); + problem.guess.set_column(index, &self.representation.get_clone_untracked()); + } else { + panic!("Tried to write problem data from an unindexed element: \"{}\"", self.id); + } + } } #[derive(Clone, Copy)] -pub struct Regulator { +pub struct ProductRegulator { pub subjects: (ElementKey, ElementKey), pub measurement: ReadSignal, pub set_point: Signal } +impl ProductRegulator { + fn write_to_problem(&self, problem: &mut ConstraintProblem, elts: &Slab) { + self.set_point.with_untracked(|set_pt| { + if let Some(val) = set_pt.value { + let subjects = self.subjects; + let subject_column_indices = ( + elts[subjects.0].column_index, + elts[subjects.1].column_index + ); + if let (Some(row), Some(col)) = subject_column_indices { + problem.gram.push_sym(row, col, val); + } else { + panic!("Tried to write problem data from a regulator with an unindexed subject"); + } + } + }); + } +} + // the velocity is expressed in uniform coordinates pub struct ElementMotion<'a> { pub key: ElementKey, @@ -143,7 +171,7 @@ type AssemblyMotion<'a> = Vec>; pub struct Assembly { // elements and regulators pub elements: Signal>, - pub regulators: Signal>, + pub regulators: Signal>, // solution variety tangent space. the basis vectors are stored in // configuration matrix format, ordered according to the elements' column @@ -214,7 +242,7 @@ impl Assembly { ); } - fn insert_regulator(&self, regulator: Regulator) { + fn insert_regulator(&self, regulator: ProductRegulator) { let subjects = regulator.subjects; let key = self.regulators.update(|regs| regs.insert(regulator)); let subject_regulators = self.elements.with( @@ -236,7 +264,7 @@ impl Assembly { } ); let set_point = create_signal(SpecifiedValue::from_empty_spec()); - self.insert_regulator(Regulator { + self.insert_regulator(ProductRegulator { subjects: subjects, measurement: measurement, set_point: set_point @@ -263,7 +291,7 @@ impl Assembly { // edited while acting as a constraint create_effect(move || { console::log_1(&JsValue::from( - format!("Updated constraint with subjects ({}, {})", subjects.0, subjects.1) + format!("Updated regulator with subjects {:?}", subjects) )); if set_point.with(|set_pt| set_pt.is_present()) { self.realize(); @@ -274,11 +302,6 @@ impl Assembly { // --- realization --- pub fn realize(&self) { - // create a blank constraint problem - let mut problem = ConstraintProblem::new( - self.elements.with_untracked(|elts| elts.len()) - ); - // index the elements self.elements.update_silent(|elts| { for (index, (_, elt)) in elts.into_iter().enumerate() { @@ -286,29 +309,18 @@ impl Assembly { } }); - // set up the Gram matrix and the initial configuration matrix - self.elements.with_untracked(|elts| { - // set up the off-diagonal part of the Gram matrix + // set up the constraint problem + let problem = self.elements.with_untracked(|elts| { + let mut problem_to_be = ConstraintProblem::new(elts.len()); + for (_, elt) in elts { + elt.write_to_problem(&mut problem_to_be); + } self.regulators.with_untracked(|regs| { for (_, reg) in regs { - reg.set_point.with_untracked(|set_pt| { - if let Some(val) = set_pt.value { - let subjects = reg.subjects; - let row = elts[subjects.0].column_index.unwrap(); - let col = elts[subjects.1].column_index.unwrap(); - problem.gram.push_sym(row, col, val); - } - }); + reg.write_to_problem(&mut problem_to_be, elts); } }); - - // set up the initial configuration matrix and the diagonal of the - // Gram matrix - for (_, elt) in elts { - let index = elt.column_index.unwrap(); - problem.gram.push_sym(index, index, 1.0); - problem.guess.set_column(index, &elt.representation.get_clone_untracked()); - } + problem_to_be }); /* DEBUG */ @@ -464,4 +476,56 @@ impl Assembly { // sync self.realize(); } +} + +#[cfg(test)] +mod tests { + use crate::engine; + + use super::*; + + #[test] + #[should_panic(expected = "Tried to write problem data from an unindexed element: \"sphere\"")] + fn unindexed_element_test() { + let _ = create_root(|| { + Element::new( + "sphere".to_string(), + "Sphere".to_string(), + [1.0_f32, 1.0_f32, 1.0_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ).write_to_problem(&mut ConstraintProblem::new(1)); + }); + } + + #[test] + #[should_panic(expected = "Tried to write problem data from a regulator with an unindexed subject")] + fn unindexed_subject_test() { + let _ = create_root(|| { + let mut elts = Slab::new(); + let subjects = ( + elts.insert( + Element::new( + "sphere0".to_string(), + "Sphere 0".to_string(), + [1.0_f32, 1.0_f32, 1.0_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ) + ), + elts.insert( + Element::new( + "sphere1".to_string(), + "Sphere 1".to_string(), + [1.0_f32, 1.0_f32, 1.0_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ) + ) + ); + elts[subjects.0].column_index = Some(0); + ProductRegulator { + subjects: subjects, + measurement: create_memo(|| 0.0), + set_point: create_signal(SpecifiedValue::try_from("0.0".to_string()).unwrap()) + }.write_to_problem(&mut ConstraintProblem::new(2), &elts); + }); + } } \ No newline at end of file diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index 002baea..deede23 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -9,13 +9,13 @@ use web_sys::{ use crate::{ AppState, assembly, - assembly::{ElementKey, Regulator, RegulatorKey}, + assembly::{ElementKey, ProductRegulator, RegulatorKey}, specified::SpecifiedValue }; // an editable view of a regulator #[component(inline_props)] -fn RegulatorInput(regulator: Regulator) -> View { +fn RegulatorInput(regulator: ProductRegulator) -> View { let valid = create_signal(true); let value = create_signal( regulator.set_point.with_untracked(|set_pt| set_pt.spec.clone()) From f1f87e97be07f871930f16ce877dd14859c9bb61 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 26 Mar 2025 23:50:40 -0700 Subject: [PATCH 4/8] Store a product regulator's subjects in an array This lets us iterate over subjects. Based on commit 257ce33, with a few updates from 4a9e777. --- app-proto/src/add_remove.rs | 15 +++++++---- app-proto/src/assembly.rs | 52 ++++++++++++++++--------------------- app-proto/src/outline.rs | 6 ++--- 3 files changed, 35 insertions(+), 38 deletions(-) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index 5fed411..cd8b4f9 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -188,11 +188,16 @@ pub fn AddRemove() -> View { }, on:click=|_| { let state = use_context::(); - let subjects = state.selection.with( - |sel| { - let subject_vec: Vec<_> = sel.into_iter().collect(); - (subject_vec[0].clone(), subject_vec[1].clone()) - } + let subjects: [_; 2] = state.selection.with( + // the button is only enabled when two elements are + // selected, so we know the cast to a two-element array + // will succeed + |sel| sel + .clone() + .into_iter() + .collect::>() + .try_into() + .unwrap() ); state.assembly.insert_new_regulator(subjects); state.selection.update(|sel| sel.clear()); diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 293bf73..d006b7b 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -134,7 +134,7 @@ impl Element { #[derive(Clone, Copy)] pub struct ProductRegulator { - pub subjects: (ElementKey, ElementKey), + pub subjects: [ElementKey; 2], pub measurement: ReadSignal, pub set_point: Signal } @@ -143,12 +143,10 @@ impl ProductRegulator { fn write_to_problem(&self, problem: &mut ConstraintProblem, elts: &Slab) { self.set_point.with_untracked(|set_pt| { if let Some(val) = set_pt.value { - let subjects = self.subjects; - let subject_column_indices = ( - elts[subjects.0].column_index, - elts[subjects.1].column_index + let subject_column_indices = self.subjects.map( + |subj| elts[subj].column_index ); - if let (Some(row), Some(col)) = subject_column_indices { + if let [Some(row), Some(col)] = subject_column_indices { problem.gram.push_sym(row, col, val); } else { panic!("Tried to write problem data from a regulator with an unindexed subject"); @@ -246,21 +244,23 @@ impl Assembly { let subjects = regulator.subjects; let key = self.regulators.update(|regs| regs.insert(regulator)); let subject_regulators = self.elements.with( - |elts| (elts[subjects.0].regulators, elts[subjects.1].regulators) + |elts| subjects.map(|subj| elts[subj].regulators) ); - subject_regulators.0.update(|regs| regs.insert(key)); - subject_regulators.1.update(|regs| regs.insert(key)); + for regulators in subject_regulators { + regulators.update(|regs| regs.insert(key)); + } } - pub fn insert_new_regulator(self, subjects: (ElementKey, ElementKey)) { + pub fn insert_new_regulator(self, subjects: [ElementKey; 2]) { // create and insert a new regulator let measurement = self.elements.map( move |elts| { - let reps = ( - elts[subjects.0].representation.get_clone(), - elts[subjects.1].representation.get_clone() - ); - reps.0.dot(&(&*Q * reps.1)) + let representations = subjects.map(|subj| elts[subj].representation); + representations[0].with(|rep_0| + representations[1].with(|rep_1| + rep_0.dot(&(&*Q * rep_1)) + ) + ) } ); let set_point = create_signal(SpecifiedValue::from_empty_spec()); @@ -277,8 +277,8 @@ impl Assembly { for (_, reg) in regs.into_iter() { console::log_5( &JsValue::from(" "), - &JsValue::from(reg.subjects.0), - &JsValue::from(reg.subjects.1), + &JsValue::from(reg.subjects[0]), + &JsValue::from(reg.subjects[1]), &JsValue::from(":"), ®.set_point.with_untracked( |set_pt| JsValue::from(set_pt.spec.as_str()) @@ -502,25 +502,17 @@ mod tests { fn unindexed_subject_test() { let _ = create_root(|| { let mut elts = Slab::new(); - let subjects = ( + let subjects = [0, 1].map(|k| { elts.insert( Element::new( - "sphere0".to_string(), - "Sphere 0".to_string(), - [1.0_f32, 1.0_f32, 1.0_f32], - engine::sphere(0.0, 0.0, 0.0, 1.0) - ) - ), - elts.insert( - Element::new( - "sphere1".to_string(), - "Sphere 1".to_string(), + "sphere{k}".to_string(), + "Sphere {k}".to_string(), [1.0_f32, 1.0_f32, 1.0_f32], engine::sphere(0.0, 0.0, 0.0, 1.0) ) ) - ); - elts[subjects.0].column_index = Some(0); + }); + elts[subjects[0]].column_index = Some(0); ProductRegulator { subjects: subjects, measurement: create_memo(|| 0.0), diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index deede23..2951e69 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -81,10 +81,10 @@ fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) -> let state = use_context::(); let assembly = &state.assembly; let regulator = assembly.regulators.with(|regs| regs[regulator_key]); - let other_subject = if regulator.subjects.0 == element_key { - regulator.subjects.1 + let other_subject = if regulator.subjects[0] == element_key { + regulator.subjects[1] } else { - regulator.subjects.0 + regulator.subjects[0] }; let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone()); view! { From 25f446499b9b43d822d10a348241a0059a31a969 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Thu, 27 Mar 2025 00:29:27 -0700 Subject: [PATCH 5/8] Introduce a regulator trait This will provide a common interface for Lorentz product regulators, curvature regulators, and hopefully all the other regulators too. --- app-proto/src/add_remove.rs | 2 +- app-proto/src/assembly.rs | 72 ++++++++++++++++++++++++++----------- app-proto/src/outline.rs | 36 ++++++++++++------- 3 files changed, 75 insertions(+), 35 deletions(-) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index cd8b4f9..8bf6bb1 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -199,7 +199,7 @@ pub fn AddRemove() -> View { .try_into() .unwrap() ); - state.assembly.insert_new_regulator(subjects); + state.assembly.insert_new_product_regulator(subjects); state.selection.update(|sel| sel.clear()); } ) { "🔗" } diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index d006b7b..fe08c91 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -1,7 +1,7 @@ use nalgebra::{DMatrix, DVector, DVectorView, Vector3}; use rustc_hash::FxHashMap; use slab::Slab; -use std::{collections::BTreeSet, sync::atomic::{AtomicU64, Ordering}}; +use std::{collections::BTreeSet, rc::Rc, sync::atomic::{AtomicU64, Ordering}}; use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ @@ -132,14 +132,35 @@ impl Element { } } -#[derive(Clone, Copy)] +pub trait Regulator { + // get information + fn subjects(&self) -> Vec; + fn measurement(&self) -> ReadSignal; + fn set_point(&self) -> Signal; + + // write problem data + fn write_to_problem(&self, problem: &mut ConstraintProblem, elts: &Slab); +} + pub struct ProductRegulator { pub subjects: [ElementKey; 2], pub measurement: ReadSignal, pub set_point: Signal } -impl ProductRegulator { +impl Regulator for ProductRegulator { + fn subjects(&self) -> Vec { + self.subjects.into() + } + + fn measurement(&self) -> ReadSignal { + self.measurement + } + + fn set_point(&self) -> Signal { + self.set_point + } + fn write_to_problem(&self, problem: &mut ConstraintProblem, elts: &Slab) { self.set_point.with_untracked(|set_pt| { if let Some(val) = set_pt.value { @@ -169,7 +190,7 @@ type AssemblyMotion<'a> = Vec>; pub struct Assembly { // elements and regulators pub elements: Signal>, - pub regulators: Signal>, + pub regulators: Signal>>, // solution variety tangent space. the basis vectors are stored in // configuration matrix format, ordered according to the elements' column @@ -240,19 +261,23 @@ impl Assembly { ); } - fn insert_regulator(&self, regulator: ProductRegulator) { - let subjects = regulator.subjects; - let key = self.regulators.update(|regs| regs.insert(regulator)); - let subject_regulators = self.elements.with( - |elts| subjects.map(|subj| elts[subj].regulators) + fn insert_regulator(&self, regulator: Rc) { + let subjects = regulator.subjects(); + let key = self.regulators.update( + |regs| regs.insert(regulator) + ); + let subject_regulators: Vec<_> = self.elements.with( + |elts| subjects.into_iter().map( + |subj| elts[subj].regulators + ).collect() ); for regulators in subject_regulators { regulators.update(|regs| regs.insert(key)); } } - pub fn insert_new_regulator(self, subjects: [ElementKey; 2]) { - // create and insert a new regulator + pub fn insert_new_product_regulator(self, subjects: [ElementKey; 2]) { + // create and insert a new product regulator let measurement = self.elements.map( move |elts| { let representations = subjects.map(|subj| elts[subj].representation); @@ -264,26 +289,31 @@ impl Assembly { } ); let set_point = create_signal(SpecifiedValue::from_empty_spec()); - self.insert_regulator(ProductRegulator { + self.insert_regulator(Rc::new(ProductRegulator { subjects: subjects, measurement: measurement, set_point: set_point - }); + })); /* DEBUG */ // print an updated list of regulators console::log_1(&JsValue::from("Regulators:")); self.regulators.with(|regs| { for (_, reg) in regs.into_iter() { - console::log_5( - &JsValue::from(" "), - &JsValue::from(reg.subjects[0]), - &JsValue::from(reg.subjects[1]), - &JsValue::from(":"), - ®.set_point.with_untracked( - |set_pt| JsValue::from(set_pt.spec.as_str()) + console::log_1(&JsValue::from(format!( + " {:?}: {}", + reg.subjects(), + reg.set_point().with_untracked( + |set_pt| { + let spec = &set_pt.spec; + if spec.is_empty() { + "__".to_string() + } else { + spec.clone() + } + } ) - ); + ))); } }); diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index 2951e69..497677d 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -1,4 +1,5 @@ use itertools::Itertools; +use std::rc::Rc; use sycamore::prelude::*; use web_sys::{ KeyboardEvent, @@ -9,24 +10,32 @@ use web_sys::{ use crate::{ AppState, assembly, - assembly::{ElementKey, ProductRegulator, RegulatorKey}, + assembly::{ElementKey, Regulator, RegulatorKey}, specified::SpecifiedValue }; // an editable view of a regulator #[component(inline_props)] -fn RegulatorInput(regulator: ProductRegulator) -> View { +fn RegulatorInput(regulator: Rc) -> View { + // get the regulator's measurement and set point signals + let measurement = regulator.measurement(); + let set_point = regulator.set_point(); + + // the `valid` signal tracks whether the last entered value is a valid set + // point specification let valid = create_signal(true); + + // the `value` signal holds the current set point specification let value = create_signal( - regulator.set_point.with_untracked(|set_pt| set_pt.spec.clone()) + set_point.with_untracked(|set_pt| set_pt.spec.clone()) ); - // this closure resets the input value to the regulator's set point - // specification + // this `reset_value` closure resets the input value to the regulator's set + // point specification let reset_value = move || { batch(|| { valid.set(true); - value.set(regulator.set_point.with(|set_pt| set_pt.spec.clone())); + value.set(set_point.with(|set_pt| set_pt.spec.clone())); }) }; @@ -39,7 +48,7 @@ fn RegulatorInput(regulator: ProductRegulator) -> View { r#type="text", class=move || { if valid.get() { - regulator.set_point.with(|set_pt| { + set_point.with(|set_pt| { if set_pt.is_present() { "regulator-input constraint" } else { @@ -50,13 +59,13 @@ fn RegulatorInput(regulator: ProductRegulator) -> View { "regulator-input invalid" } }, - placeholder=regulator.measurement.with(|result| result.to_string()), + placeholder=measurement.with(|result| result.to_string()), bind:value=value, on:change=move |_| { valid.set( match SpecifiedValue::try_from(value.get_clone_untracked()) { Ok(set_pt) => { - regulator.set_point.set(set_pt); + set_point.set(set_pt); true } Err(_) => false @@ -80,11 +89,12 @@ fn RegulatorInput(regulator: ProductRegulator) -> View { fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) -> View { let state = use_context::(); let assembly = &state.assembly; - let regulator = assembly.regulators.with(|regs| regs[regulator_key]); - let other_subject = if regulator.subjects[0] == element_key { - regulator.subjects[1] + let regulator = assembly.regulators.with(|regs| regs[regulator_key].clone()); + let subjects = regulator.subjects(); + let other_subject = if subjects[0] == element_key { + subjects[1] } else { - regulator.subjects[0] + subjects[0] }; let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone()); view! { From ca9de34427b4224b3a0b839b1d8a9b636692dc22 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Thu, 27 Mar 2025 02:56:46 -0700 Subject: [PATCH 6/8] Interpolate sphere ID and label, as intended Thanks, Clippy! --- app-proto/src/assembly.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index fe08c91..dc0bf91 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -535,8 +535,8 @@ mod tests { let subjects = [0, 1].map(|k| { elts.insert( Element::new( - "sphere{k}".to_string(), - "Sphere {k}".to_string(), + format!("sphere{k}"), + format!("Sphere {k}"), [1.0_f32, 1.0_f32, 1.0_f32], engine::sphere(0.0, 0.0, 0.0, 1.0) ) From b117c00992209bad400e0b764ef09660c9ae1574 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Thu, 27 Mar 2025 13:57:46 -0700 Subject: [PATCH 7/8] Specify the values of the frozen entries Before, a `ConstraintProblem` only specified the indices of the frozen entries. During realization, the frozen entries kept whatever values they had in the initial guess. This commit adds the values of the frozen entries to the `frozen` field of `ConstraintProblem`. The frozen entries of the guess are set to the desired values at the beginning of realization. This commit also improves the `PartialMatrix` structure, which is used to specify the indices and values of the frozen entries. --- app-proto/examples/point-on-sphere.rs | 2 +- app-proto/src/engine.rs | 135 +++++++++++++++++--------- 2 files changed, 91 insertions(+), 46 deletions(-) diff --git a/app-proto/examples/point-on-sphere.rs b/app-proto/examples/point-on-sphere.rs index 2820793..880d7b0 100644 --- a/app-proto/examples/point-on-sphere.rs +++ b/app-proto/examples/point-on-sphere.rs @@ -10,7 +10,7 @@ fn main() { problem.gram.push_sym(j, k, if (j, k) == (1, 1) { 1.0 } else { 0.0 }); } } - problem.frozen.push((3, 0)); + problem.frozen.push(3, 0, problem.guess[(3, 0)]); println!(); let (config, _, success, history) = realize_gram( &problem, 1.0e-12, 0.5, 0.9, 1.1, 200, 110 diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index deb88bd..44f44e0 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -37,7 +37,7 @@ pub fn sphere_with_offset(dir_x: f64, dir_y: f64, dir_z: f64, off: f64, curv: f6 // --- partial matrices --- -struct MatrixEntry { +pub struct MatrixEntry { index: (usize, usize), value: f64 } @@ -49,42 +49,72 @@ impl PartialMatrix { PartialMatrix(Vec::::new()) } - pub fn push_sym(&mut self, row: usize, col: usize, value: f64) { + pub fn push(&mut self, row: usize, col: usize, value: f64) { let PartialMatrix(entries) = self; entries.push(MatrixEntry { index: (row, col), value: value }); + } + + pub fn push_sym(&mut self, row: usize, col: usize, value: f64) { + self.push(row, col, value); if row != col { - entries.push(MatrixEntry { index: (col, row), value: value }); + self.push(col, row, value); } } /* DEBUG */ pub fn log_to_console(&self) { - let PartialMatrix(entries) = self; - for ent in entries { - let ent_str = format!(" {} {} {}", ent.index.0, ent.index.1, ent.value); - console::log_1(&JsValue::from(ent_str.as_str())); + for &MatrixEntry { index: (row, col), value } in self { + console::log_1(&JsValue::from( + format!(" {} {} {}", row, col, value) + )); } } + fn freeze(&self, a: &DMatrix) -> DMatrix { + let mut result = a.clone(); + for &MatrixEntry { index, value } in self { + result[index] = value; + } + result + } + fn proj(&self, a: &DMatrix) -> DMatrix { let mut result = DMatrix::::zeros(a.nrows(), a.ncols()); - let PartialMatrix(entries) = self; - for ent in entries { - result[ent.index] = a[ent.index]; + for &MatrixEntry { index, .. } in self { + result[index] = a[index]; } result } fn sub_proj(&self, rhs: &DMatrix) -> DMatrix { let mut result = DMatrix::::zeros(rhs.nrows(), rhs.ncols()); - let PartialMatrix(entries) = self; - for ent in entries { - result[ent.index] = ent.value - rhs[ent.index]; + for &MatrixEntry { index, value } in self { + result[index] = value - rhs[index]; } result } } +impl IntoIterator for PartialMatrix { + type Item = MatrixEntry; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + let PartialMatrix(entries) = self; + entries.into_iter() + } +} + +impl<'a> IntoIterator for &'a PartialMatrix { + type Item = &'a MatrixEntry; + type IntoIter = std::slice::Iter<'a, MatrixEntry>; + + fn into_iter(self) -> Self::IntoIter { + let PartialMatrix(entries) = self; + entries.into_iter() + } +} + // --- configuration subspaces --- #[derive(Clone)] @@ -199,8 +229,8 @@ impl DescentHistory { pub struct ConstraintProblem { pub gram: PartialMatrix, + pub frozen: PartialMatrix, pub guess: DMatrix, - pub frozen: Vec<(usize, usize)> } impl ConstraintProblem { @@ -208,8 +238,8 @@ impl ConstraintProblem { const ELEMENT_DIM: usize = 5; ConstraintProblem { gram: PartialMatrix::new(), - guess: DMatrix::::zeros(ELEMENT_DIM, element_count), - frozen: Vec::new() + frozen: PartialMatrix::new(), + guess: DMatrix::::zeros(ELEMENT_DIM, element_count) } } @@ -217,8 +247,8 @@ impl ConstraintProblem { pub fn from_guess(guess_columns: &[DVector]) -> ConstraintProblem { ConstraintProblem { gram: PartialMatrix::new(), - guess: DMatrix::from_columns(guess_columns), - frozen: Vec::new() + frozen: PartialMatrix::new(), + guess: DMatrix::from_columns(guess_columns) } } } @@ -314,8 +344,10 @@ fn seek_better_config( None } -// seek a matrix `config` for which `config' * Q * config` matches the partial -// matrix `gram`. use gradient descent starting from `guess` +// seek a matrix `config` that matches the partial matrix `problem.frozen` and +// has `config' * Q * config` matching the partial matrix `problem.gram`. start +// at `problem.guess`, set the frozen entries to their desired values, and then +// use a regularized Newton's method to seek the desired Gram matrix pub fn realize_gram( problem: &ConstraintProblem, scaled_tol: f64, @@ -344,11 +376,11 @@ pub fn realize_gram( // convert the frozen indices to stacked format let frozen_stacked: Vec = frozen.into_iter().map( - |index| index.1*element_dim + index.0 + |MatrixEntry { index: (row, col), .. }| col*element_dim + row ).collect(); - // use Newton's method with backtracking and gradient descent backup - let mut state = SearchState::from_config(gram, guess.clone()); + // use a regularized Newton's method with backtracking + let mut state = SearchState::from_config(gram, frozen.freeze(guess)); let mut hess = DMatrix::zeros(element_dim, assembly_dim); for _ in 0..max_descent_steps { // find the negative gradient of the loss function @@ -501,7 +533,7 @@ pub mod examples { // the frozen entries fix the radii of the circumscribing sphere, the // "sun" and "moon" spheres, and one of the chain spheres for k in 0..4 { - problem.frozen.push((3, k)) + problem.frozen.push(3, k, problem.guess[(3, k)]); } realize_gram(&problem, scaled_tol, 0.5, 0.9, 1.1, 200, 110) @@ -545,7 +577,7 @@ pub mod examples { } for k in 0..N_POINTS { - problem.frozen.push((3, k)) + problem.frozen.push(3, k, problem.guess[(3, k)]) } realize_gram(&problem, scaled_tol, 0.5, 0.9, 1.1, 200, 110) @@ -559,6 +591,25 @@ mod tests { use super::{*, examples::*}; + #[test] + fn freeze_test() { + let frozen = PartialMatrix(vec![ + MatrixEntry { index: (0, 0), value: 14.0 }, + MatrixEntry { index: (0, 2), value: 28.0 }, + MatrixEntry { index: (1, 1), value: 42.0 }, + MatrixEntry { index: (1, 2), value: 49.0 } + ]); + let config = DMatrix::::from_row_slice(2, 3, &[ + 1.0, 2.0, 3.0, + 4.0, 5.0, 6.0 + ]); + let expected_result = DMatrix::::from_row_slice(2, 3, &[ + 14.0, 2.0, 28.0, + 4.0, 42.0, 49.0 + ]); + assert_eq!(frozen.freeze(&config), expected_result); + } + #[test] fn sub_proj_test() { let target = PartialMatrix(vec![ @@ -580,18 +631,12 @@ mod tests { #[test] fn zero_loss_test() { - let gram = PartialMatrix({ - let mut entries = Vec::::new(); - for j in 0..3 { - for k in 0..3 { - entries.push(MatrixEntry { - index: (j, k), - value: if j == k { 1.0 } else { -1.0 } - }); - } + let mut gram = PartialMatrix::new(); + for j in 0..3 { + for k in 0..3 { + gram.push(j, k, if j == k { 1.0 } else { -1.0 }); } - entries - }); + } let config = { let a = 0.75_f64.sqrt(); DMatrix::from_columns(&[ @@ -604,33 +649,33 @@ mod tests { assert!(state.loss.abs() < f64::EPSILON); } + /* TO DO */ // at the frozen indices, the optimization steps should have exact zeros, - // and the realized configuration should match the initial guess + // and the realized configuration should have the desired values #[test] fn frozen_entry_test() { let mut problem = ConstraintProblem::from_guess(&[ point(0.0, 0.0, 2.0), - sphere(0.0, 0.0, 0.0, 1.0) + sphere(0.0, 0.0, 0.0, 0.95) ]); for j in 0..2 { for k in j..2 { problem.gram.push_sym(j, k, if (j, k) == (1, 1) { 1.0 } else { 0.0 }); } } - for k in 0..2 { - problem.frozen.push((3, k)); - } + problem.frozen.push(3, 0, problem.guess[(3, 0)]); + problem.frozen.push(3, 1, 0.5); let (config, _, success, history) = realize_gram( &problem, 1.0e-12, 0.5, 0.9, 1.1, 200, 110 ); assert_eq!(success, true); for base_step in history.base_step.into_iter() { - for &index in &problem.frozen { + for &MatrixEntry { index, .. } in &problem.frozen { assert_eq!(base_step[index], 0.0); } } - for index in problem.frozen { - assert_eq!(config[index], problem.guess[index]); + for MatrixEntry { index, value } in problem.frozen { + assert_eq!(config[index], value); } } @@ -663,7 +708,7 @@ mod tests { } } for n in 0..ELEMENT_DIM { - problem.frozen.push((n, 0)); + problem.frozen.push(n, 0, problem.guess[(n, 0)]); } let (config, tangent, success, history) = realize_gram( &problem, SCALED_TOL, 0.5, 0.9, 1.1, 200, 110 From 47cc559cd6c00437b31a10dd20f349478fd8e55b Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 1 Apr 2025 22:23:08 -0700 Subject: [PATCH 8/8] Add a half-curvature regulator In the process, add the `OutlineItem` trait so that each regulator can implement its own outline item view. --- app-proto/src/add_remove.rs | 13 +-- app-proto/src/assembly.rs | 160 +++++++++++++++++++++++++++++------- app-proto/src/outline.rs | 78 +++++++++++++----- 3 files changed, 189 insertions(+), 62 deletions(-) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index 8bf6bb1..f44f5c0 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -166,18 +166,7 @@ pub fn AddRemove() -> View { button( on:click=|_| { let state = use_context::(); - state.assembly.insert_new_element(); - - /* DEBUG */ - // print updated list of elements by identifier - console::log_1(&JsValue::from("elements by identifier:")); - for (id, key) in state.assembly.elements_by_id.get_clone().iter() { - console::log_3( - &JsValue::from(" "), - &JsValue::from(id), - &JsValue::from(*key) - ); - } + state.assembly.insert_new_sphere(); } ) { "+" } button( diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index dc0bf91..f41397b 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -10,9 +10,11 @@ use crate::{ Q, local_unif_to_std, realize_gram, + sphere, ConfigSubspace, ConstraintProblem }, + outline::OutlineItem, specified::SpecifiedValue }; @@ -36,8 +38,8 @@ pub struct Element { pub color: ElementColor, pub representation: Signal>, - // All regulators with this element as a subject. The assembly owning - // this element is responsible for keeping this set up to date. + // the regulators this element is subject to. the assembly that owns the + // element is responsible for keeping this set up to date pub regulators: Signal>, // a serial number, assigned by `Element::new`, that uniquely identifies @@ -132,7 +134,7 @@ impl Element { } } -pub trait Regulator { +pub trait Regulator: OutlineItem { // get information fn subjects(&self) -> Vec; fn measurement(&self) -> ReadSignal; @@ -177,6 +179,39 @@ impl Regulator for ProductRegulator { } } +pub struct HalfCurvatureRegulator { + pub subject: ElementKey, + pub measurement: ReadSignal, + pub set_point: Signal +} + +impl Regulator for HalfCurvatureRegulator { + fn subjects(&self) -> Vec { + vec![self.subject] + } + + fn measurement(&self) -> ReadSignal { + self.measurement + } + + fn set_point(&self) -> Signal { + self.set_point + } + + fn write_to_problem(&self, problem: &mut ConstraintProblem, elts: &Slab) { + self.set_point.with_untracked(|set_pt| { + if let Some(val) = set_pt.value { + if let Some(col) = elts[self.subject].column_index { + const CURVATURE_COMPONENT: usize = 3; + problem.frozen.push(CURVATURE_COMPONENT, col, val); + } else { + panic!("Tried to write problem data from a regulator with an unindexed subject"); + } + } + }); + } +} + // the velocity is expressed in uniform coordinates pub struct ElementMotion<'a> { pub key: ElementKey, @@ -223,23 +258,25 @@ impl Assembly { // insert an element into the assembly without checking whether we already // have an element with the same identifier. any element that does have the // same identifier will get kicked out of the `elements_by_id` index - fn insert_element_unchecked(&self, elt: Element) { + fn insert_element_unchecked(&self, elt: Element) -> ElementKey { let id = elt.id.clone(); let key = self.elements.update(|elts| elts.insert(elt)); self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, key)); + key } - pub fn try_insert_element(&self, elt: Element) -> bool { + pub fn try_insert_element(&self, elt: Element) -> Option { let can_insert = self.elements_by_id.with_untracked( |elts_by_id| !elts_by_id.contains_key(&elt.id) ); if can_insert { - self.insert_element_unchecked(elt); + Some(self.insert_element_unchecked(elt)) + } else { + None } - can_insert } - pub fn insert_new_element(&self) { + pub fn insert_new_sphere(self) { // find the next unused identifier in the default sequence let mut id_num = 1; let mut id = format!("sphere{}", id_num); @@ -250,15 +287,18 @@ impl Assembly { id = format!("sphere{}", id_num); } - // create and insert a new element - self.insert_element_unchecked( + // create and insert a sphere + let key = self.insert_element_unchecked( Element::new( id, format!("Sphere {}", id_num), [0.75_f32, 0.75_f32, 0.75_f32], - DVector::::from_column_slice(&[0.0, 0.0, 0.0, 0.5, -0.5]) + sphere(0.0, 0.0, 0.0, 1.0) ) ); + + // create and insert a curvature regulator + self.insert_new_half_curvature_regulator(key); } fn insert_regulator(&self, regulator: Rc) { @@ -274,26 +314,6 @@ impl Assembly { for regulators in subject_regulators { regulators.update(|regs| regs.insert(key)); } - } - - pub fn insert_new_product_regulator(self, subjects: [ElementKey; 2]) { - // create and insert a new product regulator - let measurement = self.elements.map( - move |elts| { - let representations = subjects.map(|subj| elts[subj].representation); - representations[0].with(|rep_0| - representations[1].with(|rep_1| - rep_0.dot(&(&*Q * rep_1)) - ) - ) - } - ); - let set_point = create_signal(SpecifiedValue::from_empty_spec()); - self.insert_regulator(Rc::new(ProductRegulator { - subjects: subjects, - measurement: measurement, - set_point: set_point - })); /* DEBUG */ // print an updated list of regulators @@ -316,6 +336,26 @@ impl Assembly { ))); } }); + } + + pub fn insert_new_product_regulator(self, subjects: [ElementKey; 2]) { + // create and insert a new product regulator + let measurement = self.elements.map( + move |elts| { + let representations = subjects.map(|subj| elts[subj].representation); + representations[0].with(|rep_0| + representations[1].with(|rep_1| + rep_0.dot(&(&*Q * rep_1)) + ) + ) + } + ); + let set_point = create_signal(SpecifiedValue::from_empty_spec()); + self.insert_regulator(Rc::new(ProductRegulator { + subjects: subjects, + measurement: measurement, + set_point: set_point + })); // update the realization when the regulator becomes a constraint, or is // edited while acting as a constraint @@ -329,6 +369,64 @@ impl Assembly { }); } + pub fn insert_new_half_curvature_regulator(self, subject: ElementKey) { + // create and insert a new half-curvature regulator + let measurement = self.elements.map( + move |elts| elts[subject].representation.with(|rep| rep[3]) + ); + let set_point = create_signal(SpecifiedValue::from_empty_spec()); + self.insert_regulator(Rc::new(HalfCurvatureRegulator { + subject: subject, + measurement: measurement, + set_point: set_point + })); + + // update the realization when the regulator becomes a constraint, or is + // edited while acting as a constraint + create_effect(move || { + console::log_1(&JsValue::from( + format!("Updated regulator with subjects [{}]", subject) + )); + if let Some(half_curv) = set_point.with(|set_pt| set_pt.value) { + let representation = self.elements.with( + |elts| elts[subject].representation + ); + representation.update(|rep| { + // set the sphere's half-curvature to the desired value + rep[3] = half_curv; + + // restore normalization by contracting toward the curvature + // axis + const SIZE_THRESHOLD: f64 = 1e-9; + let half_q_lt = -2.0 * half_curv * rep[4]; + let half_q_lt_sq = half_q_lt * half_q_lt; + let mut spatial = rep.fixed_rows_mut::<3>(0); + let q_sp = spatial.norm_squared(); + if q_sp < SIZE_THRESHOLD && half_q_lt_sq < SIZE_THRESHOLD { + spatial.copy_from_slice( + &[0.0, 0.0, (1.0 - 2.0 * half_q_lt).sqrt()] + ); + } else { + let scaling = half_q_lt + (q_sp + half_q_lt_sq).sqrt(); + spatial.scale_mut(1.0 / scaling); + rep[4] /= scaling; + } + + /* DEBUG */ + // verify normalization + let rep_for_debug = rep.clone(); + console::log_1(&JsValue::from( + format!( + "Sphere self-product after curvature change: {}", + rep_for_debug.dot(&(&*Q * &rep_for_debug)) + ) + )); + }); + self.realize(); + } + }); + } + // --- realization --- pub fn realize(&self) { diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index 497677d..32ff2e7 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -10,7 +10,13 @@ use web_sys::{ use crate::{ AppState, assembly, - assembly::{ElementKey, Regulator, RegulatorKey}, + assembly::{ + ElementKey, + HalfCurvatureRegulator, + ProductRegulator, + Regulator, + RegulatorKey + }, specified::SpecifiedValue }; @@ -84,27 +90,53 @@ fn RegulatorInput(regulator: Rc) -> View { } } +pub trait OutlineItem { + fn outline_item(self: Rc, element_key: ElementKey) -> View; +} + +impl OutlineItem for ProductRegulator { + fn outline_item(self: Rc, element_key: ElementKey) -> View { + let state = use_context::(); + let other_subject = if self.subjects[0] == element_key { + self.subjects[1] + } else { + self.subjects[0] + }; + let other_subject_label = state.assembly.elements.with( + |elts| elts[other_subject].label.clone() + ); + view! { + li(class="regulator") { + div(class="regulator-label") { (other_subject_label) } + div(class="regulator-type") { "Inversive distance" } + RegulatorInput(regulator=self) + div(class="status") + } + } + } +} + +impl OutlineItem for HalfCurvatureRegulator { + fn outline_item(self: Rc, _element_key: ElementKey) -> View { + view! { + li(class="regulator") { + div(class="regulator-label") // for spacing + div(class="regulator-type") { "Half-curvature" } + RegulatorInput(regulator=self) + div(class="status") + } + } + } +} + // a list item that shows a regulator in an outline view of an element #[component(inline_props)] fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) -> View { let state = use_context::(); - let assembly = &state.assembly; - let regulator = assembly.regulators.with(|regs| regs[regulator_key].clone()); - let subjects = regulator.subjects(); - let other_subject = if subjects[0] == element_key { - subjects[1] - } else { - subjects[0] - }; - let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone()); - view! { - li(class="regulator") { - div(class="regulator-label") { (other_subject_label) } - div(class="regulator-type") { "Inversive distance" } - RegulatorInput(regulator=regulator) - div(class="status") - } - } + let regulator = state.assembly.regulators.with( + |regs| regs[regulator_key].clone() + ); + regulator.outline_item(element_key) } // a list item that shows an element in an outline view of an assembly @@ -127,7 +159,15 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View { }; let regulated = element.regulators.map(|regs| regs.len() > 0); let regulator_list = element.regulators.map( - |regs| regs.clone().into_iter().collect() + move |elt_reg_keys| elt_reg_keys + .clone() + .into_iter() + .sorted_by_key( + |®_key| state.assembly.regulators.with( + |regs| regs[reg_key].subjects().len() + ) + ) + .collect() ); let details_node = create_node_ref(); view! {