diff --git a/README.md b/README.md index cf3e589..3a29eb0 100644 --- a/README.md +++ b/README.md @@ -25,37 +25,32 @@ The latest prototype is in the folder `app-proto`. It includes both a user inter ### Install the prerequisites 1. Install [`rustup`](https://rust-lang.github.io/rustup/): the officially recommended Rust toolchain manager - - It's available on Ubuntu as a [Snap](https://snapcraft.io/rustup) + * It's available on Ubuntu as a [Snap](https://snapcraft.io/rustup) 2. Call `rustup default stable` to "download the latest stable release of Rust and set it as your default toolchain" - - If you forget, the `rustup` [help system](https://github.com/rust-lang/rustup/blob/d9b3601c3feb2e88cf3f8ca4f7ab4fdad71441fd/src/errors.rs#L109-L112) will remind you + * If you forget, the `rustup` [help system](https://github.com/rust-lang/rustup/blob/d9b3601c3feb2e88cf3f8ca4f7ab4fdad71441fd/src/errors.rs#L109-L112) will remind you 3. Call `rustup target add wasm32-unknown-unknown` to add the [most generic 32-bit WebAssembly target](https://doc.rust-lang.org/nightly/rustc/platform-support/wasm32-unknown-unknown.html) 4. Call `cargo install wasm-pack` to install the [WebAssembly toolchain](https://rustwasm.github.io/docs/wasm-pack/) 5. Call `cargo install trunk` to install the [Trunk](https://trunkrs.dev/) web-build tool 6. Add the `.cargo/bin` folder in your home directory to your executable search path - - This lets you call Trunk, and other tools installed by Cargo, without specifying their paths - - On POSIX systems, the search path is stored in the `PATH` environment variable + * This lets you call Trunk, and other tools installed by Cargo, without specifying their paths + * On POSIX systems, the search path is stored in the `PATH` environment variable ### Play with the prototype 1. From the `app-proto` folder, call `trunk serve --release` to build and serve the prototype - - The crates the prototype depends on will be downloaded and served automatically - - For a faster build, at the expense of a much slower prototype, you can call `trunk serve` without the `--release` flag - - If you want to stay in the top-level folder, you can call `trunk serve --config app-proto [--release]` from there instead. + * *The crates the prototype depends on will be downloaded and served automatically* + * *For a faster build, at the expense of a much slower prototype, you can call `trunk serve` without the `--release` flag* + * *If you want to stay in the top-level folder, you can call `trunk serve --config app-proto [--release]`* from there instead. 3. In a web browser, visit one of the URLs listed under the message `INFO 📡 server listening at:` - - Touching any file in the `app-proto` folder will make Trunk rebuild and live-reload the prototype + * *Touching any file in the `app-proto` folder will make Trunk rebuild and live-reload the prototype* 4. Press *ctrl+C* in the shell where Trunk is running to stop serving the prototype ### Run the engine on some example problems -1. Use `sh` to run the script `tools/run-examples.sh` - - The script is location-independent, so you can do this from anywhere in the dyna3 repository - - The call from the top level of the repository is: - - ```bash - sh tools/run-examples.sh - ``` - - For each example problem, the engine will print the value of the loss function at each optimization step - - The first example that prints is the same as the Irisawa hexlet example from the Julia version of the engine prototype. If you go into `engine-proto/gram-test`, launch Julia, and then +1. Go into the `app-proto` folder +2. Call `./run-examples` + * *For each example problem, the engine will print the value of the loss function at each optimization step* + * *The first example that prints is the same as the Irisawa hexlet example from the Julia version of the engine prototype. If you go into `engine-proto/gram-test`, launch Julia, and then* ```julia include("irisawa-hexlet.jl") @@ -64,24 +59,9 @@ The latest prototype is in the folder `app-proto`. It includes both a user inter end ``` - you should see that it prints basically the same loss history until the last few steps, when the lower default precision of the Rust engine really starts to show + *you should see that it prints basically the same loss history until the last few steps, when the lower default precision of the Rust engine really starts to show* ### Run the automated tests 1. Go into the `app-proto` folder 2. Call `cargo test` - -### Deploy the prototype - -1. From the `app-proto` folder, call `trunk build --release` - - Building in [release mode](https://doc.rust-lang.org/cargo/reference/profiles.html#release) produces an executable which is smaller and often much faster, but harder to debug and more time-consuming to build - - If you want to stay in the top-level folder, you can call `trunk build --config app-proto --release` from there instead -2. Use `sh` to run the packaging script `tools/package-for-deployment.sh`. - - The script is location-independent, so you can do this from anywhere in the dyna3 repository - - The call from the top level of the repository is: - ```bash - sh tools/package-for-deployment.sh - ``` - - This will overwrite or replace the files in `deploy/dyna3` -3. Put the contents of `deploy/dyna3` in the folder on your server that the prototype will be served from. - - To simplify uploading, you might want to combine these files into an archive called `deploy/dyna3.zip`. Git has been set to ignore this path \ No newline at end of file diff --git a/app-proto/Trunk.toml b/app-proto/Trunk.toml deleted file mode 100644 index 017deba..0000000 --- a/app-proto/Trunk.toml +++ /dev/null @@ -1,2 +0,0 @@ -[build] -public_url = "./" \ No newline at end of file diff --git a/app-proto/examples/kaleidocycle.rs b/app-proto/examples/kaleidocycle.rs index ae4eb07..7ca1f97 100644 --- a/app-proto/examples/kaleidocycle.rs +++ b/app-proto/examples/kaleidocycle.rs @@ -23,7 +23,7 @@ fn main() { let twist_motion: DMatrix<_> = (0..N_POINTS).step_by(4).flat_map( |n| [ tangent.proj(&up.as_view(), n), - tangent.proj(&down.as_view(), n+1), + tangent.proj(&down.as_view(), n+1) ] ).sum(); let normalization = 5.0 / twist_motion[(2, 0)]; diff --git a/app-proto/examples/point-on-sphere.rs b/app-proto/examples/point-on-sphere.rs index a73490e..89dee76 100644 --- a/app-proto/examples/point-on-sphere.rs +++ b/app-proto/examples/point-on-sphere.rs @@ -6,7 +6,7 @@ use dyna3::engine::{ realize_gram, sphere, ConfigNeighborhood, - ConstraintProblem, + ConstraintProblem }; fn main() { @@ -25,7 +25,7 @@ fn main() { ); print::title("Point on a sphere"); print::realization_diagnostics(&realization); - if let Ok(ConfigNeighborhood { config, .. }) = realization.result { + if let Ok(ConfigNeighborhood{ config, .. }) = realization.result { print::gram_matrix(&config); print::config(&config); } diff --git a/app-proto/examples/three-spheres.rs b/app-proto/examples/three-spheres.rs index 7901e31..aa5a105 100644 --- a/app-proto/examples/three-spheres.rs +++ b/app-proto/examples/three-spheres.rs @@ -5,7 +5,7 @@ use dyna3::engine::{ realize_gram, sphere, ConfigNeighborhood, - ConstraintProblem, + ConstraintProblem }; fn main() { @@ -14,7 +14,7 @@ fn main() { &[ 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), + sphere(-0.5, -a, 0.0, 1.0) ] }); for j in 0..3 { @@ -27,7 +27,7 @@ fn main() { ); print::title("Three spheres"); print::realization_diagnostics(&realization); - if let Ok(ConfigNeighborhood { config, .. }) = realization.result { + if let Ok(ConfigNeighborhood{ config, .. }) = realization.result { print::gram_matrix(&config); } print::loss_history(&realization.history); diff --git a/tools/run-examples.sh b/app-proto/run-examples.sh similarity index 89% rename from tools/run-examples.sh rename to app-proto/run-examples.sh index 0946d92..861addf 100644 --- a/tools/run-examples.sh +++ b/app-proto/run-examples.sh @@ -8,7 +8,7 @@ # the application prototype # find the manifest file for the application prototype -MANIFEST="$(dirname -- $0)/../app-proto/Cargo.toml" +MANIFEST="$(dirname -- $0)/Cargo.toml" # set up the command that runs each example RUN_EXAMPLE="cargo run --manifest-path $MANIFEST --example" diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs new file mode 100644 index 0000000..d737c79 --- /dev/null +++ b/app-proto/src/add_remove.rs @@ -0,0 +1,259 @@ +use std::{f64::consts::FRAC_1_SQRT_2, rc::Rc}; +use sycamore::prelude::*; +use web_sys::{console, wasm_bindgen::JsValue}; + +use crate::{ + AppState, + engine, + engine::DescentHistory, + assembly::{Assembly, InversiveDistanceRegulator, Point, Sphere} +}; + +/* DEBUG */ +// load an example assembly for testing. this code will be removed once we've +// built a more formal test assembly system +fn load_gen_assemb(assembly: &Assembly) { + let _ = assembly.try_insert_element( + Sphere::new( + String::from("gemini_a"), + String::from("Castor"), + [1.00_f32, 0.25_f32, 0.00_f32], + engine::sphere(0.5, 0.5, 0.0, 1.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("gemini_b"), + String::from("Pollux"), + [0.00_f32, 0.25_f32, 1.00_f32], + engine::sphere(-0.5, -0.5, 0.0, 1.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("ursa_major"), + String::from("Ursa major"), + [0.25_f32, 0.00_f32, 1.00_f32], + engine::sphere(-0.5, 0.5, 0.0, 0.75) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("ursa_minor"), + String::from("Ursa minor"), + [0.25_f32, 1.00_f32, 0.00_f32], + engine::sphere(0.5, -0.5, 0.0, 0.5) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("moon_deimos"), + String::from("Deimos"), + [0.75_f32, 0.75_f32, 0.00_f32], + engine::sphere(0.0, 0.15, 1.0, 0.25) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("moon_phobos"), + String::from("Phobos"), + [0.00_f32, 0.75_f32, 0.50_f32], + engine::sphere(0.0, -0.15, -1.0, 0.25) + ) + ); +} + +/* DEBUG */ +// load an example assembly for testing. this code will be removed once we've +// built a more formal test assembly system +fn load_low_curv_assemb(assembly: &Assembly) { + let a = 0.75_f64.sqrt(); + let _ = assembly.try_insert_element( + Sphere::new( + "central".to_string(), + "Central".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "assemb_plane".to_string(), + "Assembly plane".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "side1".to_string(), + "Side 1".to_string(), + [1.00_f32, 0.00_f32, 0.25_f32], + engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "side2".to_string(), + "Side 2".to_string(), + [0.25_f32, 1.00_f32, 0.00_f32], + engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "side3".to_string(), + "Side 3".to_string(), + [0.00_f32, 0.25_f32, 1.00_f32], + engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "corner1".to_string(), + "Corner 1".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "corner2".to_string(), + "Corner 2".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("corner3"), + String::from("Corner 3"), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0) + ) + ); +} + +fn load_pointed_assemb(assembly: &Assembly) { + let _ = assembly.try_insert_element( + Point::new( + format!("point_front"), + format!("Front point"), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::point(0.0, 0.0, FRAC_1_SQRT_2) + ) + ); + let _ = assembly.try_insert_element( + Point::new( + format!("point_back"), + format!("Back point"), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::point(0.0, 0.0, -FRAC_1_SQRT_2) + ) + ); + for index_x in 0..=1 { + for index_y in 0..=1 { + let x = index_x as f64 - 0.5; + let y = index_y as f64 - 0.5; + + let _ = assembly.try_insert_element( + Sphere::new( + format!("sphere{index_x}{index_y}"), + format!("Sphere {index_x}{index_y}"), + [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], + engine::sphere(x, y, 0.0, 1.0) + ) + ); + + let _ = assembly.try_insert_element( + Point::new( + format!("point{index_x}{index_y}"), + format!("Point {index_x}{index_y}"), + [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], + engine::point(x, y, 0.0) + ) + ); + } + } +} + +#[component] +pub fn AddRemove() -> View { + /* DEBUG */ + let assembly_name = create_signal("general".to_string()); + create_effect(move || { + // get name of chosen assembly + let name = assembly_name.get_clone(); + console::log_1( + &JsValue::from(format!("Showing assembly \"{}\"", name.clone())) + ); + + batch(|| { + let state = use_context::(); + let assembly = &state.assembly; + + // clear state + assembly.regulators.update(|regs| regs.clear()); + assembly.elements.update(|elts| elts.clear()); + assembly.elements_by_id.update(|elts_by_id| elts_by_id.clear()); + assembly.descent_history.set(DescentHistory::new()); + state.selection.update(|sel| sel.clear()); + + // load assembly + match name.as_str() { + "general" => load_gen_assemb(assembly), + "low-curv" => load_low_curv_assemb(assembly), + "pointed" => load_pointed_assemb(assembly), + _ => () + }; + }); + }); + + view! { + div(id="add-remove") { + button( + on:click=|_| { + let state = use_context::(); + state.assembly.insert_element_default::(); + } + ) { "Add sphere" } + button( + on:click=|_| { + let state = use_context::(); + state.assembly.insert_element_default::(); + } + ) { "Add point" } + button( + class="emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button + disabled={ + let state = use_context::(); + state.selection.with(|sel| sel.len() != 2) + }, + on:click=|_| { + let state = use_context::(); + 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_regulator( + Rc::new(InversiveDistanceRegulator::new(subjects)) + ); + state.selection.update(|sel| sel.clear()); + } + ) { "🔗" } + select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser + option(value="general") { "General" } + option(value="low-curv") { "Low-curvature" } + option(value="pointed") { "Pointed" } + option(value="empty") { "Empty" } + } + } + } +} \ No newline at end of file diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 94e7b3c..c3b0c6b 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -1,21 +1,22 @@ use nalgebra::{DMatrix, DVector, DVectorView}; use std::{ cell::Cell, - cmp::Ordering, collections::{BTreeMap, BTreeSet}, + cmp::Ordering, fmt, fmt::{Debug, Formatter}, hash::{Hash, Hasher}, rc::Rc, - sync::{atomic, atomic::AtomicU64}, + sync::{atomic, atomic::AtomicU64} }; use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ use crate::{ - components::{display::DisplayItem, outline::OutlineItem}, + display::DisplayItem, engine::{ Q, + change_half_curvature, local_unif_to_std, point, project_point_to_normalized, @@ -26,9 +27,10 @@ use crate::{ ConfigSubspace, ConstraintProblem, DescentHistory, - Realization, + Realization }, - specified::SpecifiedValue, + outline::OutlineItem, + specified::SpecifiedValue }; pub type ElementColor = [f32; 3]; @@ -164,7 +166,7 @@ pub struct Sphere { pub ghost: Signal, pub regulators: Signal>>, serial: u64, - column_index: Cell>, + column_index: Cell> } impl Sphere { @@ -174,17 +176,17 @@ impl Sphere { id: String, label: String, color: ElementColor, - representation: DVector, - ) -> Self { - Self { - id, - label, - color, + representation: DVector + ) -> Sphere { + Sphere { + id: id, + label: label, + color: color, representation: create_signal(representation), ghost: create_signal(false), regulators: create_signal(BTreeSet::new()), serial: Self::next_serial(), - column_index: None.into(), + column_index: None.into() } } } @@ -194,12 +196,12 @@ impl Element for Sphere { "sphere".to_string() } - fn default(id: String, id_num: u64) -> Self { - Self::new( + fn default(id: String, id_num: u64) -> Sphere { + Sphere::new( id, format!("Sphere {id_num}"), [0.75_f32, 0.75_f32, 0.75_f32], - sphere(0.0, 0.0, 0.0, 1.0), + sphere(0.0, 0.0, 0.0, 1.0) ) } @@ -264,7 +266,7 @@ pub struct Point { pub ghost: Signal, pub regulators: Signal>>, serial: u64, - column_index: Cell>, + column_index: Cell> } impl Point { @@ -274,9 +276,9 @@ impl Point { id: String, label: String, color: ElementColor, - representation: DVector, - ) -> Self { - Self { + representation: DVector + ) -> Point { + Point { id, label, color, @@ -284,7 +286,7 @@ impl Point { ghost: create_signal(false), regulators: create_signal(BTreeSet::new()), serial: Self::next_serial(), - column_index: None.into(), + column_index: None.into() } } } @@ -294,12 +296,12 @@ impl Element for Point { "point".to_string() } - fn default(id: String, id_num: u64) -> Self { - Self::new( + fn default(id: String, id_num: u64) -> Point { + Point::new( id, format!("Point {id_num}"), [0.75_f32, 0.75_f32, 0.75_f32], - point(0.0, 0.0, 0.0), + point(0.0, 0.0, 0.0) ) } @@ -348,7 +350,7 @@ impl ProblemPoser for Point { format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str() ); problem.gram.push_sym(index, index, 0.0); - problem.frozen.push(Self::WEIGHT_COMPONENT, index, 0.5); + problem.frozen.push(Point::WEIGHT_COMPONENT, index, 0.5); problem.guess.set_column(index, &self.representation.get_clone_untracked()); } } @@ -357,6 +359,16 @@ pub trait Regulator: Serial + ProblemPoser + OutlineItem { fn subjects(&self) -> Vec>; fn measurement(&self) -> ReadSignal; fn set_point(&self) -> Signal; + + // this method is used to responsively precondition the assembly for + // realization when the regulator becomes a constraint, or is edited while + // acting as a constraint. it should track the set point, do any desired + // preconditioning when the set point is present, and use its return value + // to report whether the set is present. the default implementation does no + // preconditioning + fn try_activate(&self) -> bool { + self.set_point().with(|set_pt| set_pt.is_present()) + } } impl Hash for dyn Regulator { @@ -389,11 +401,11 @@ pub struct InversiveDistanceRegulator { pub subjects: [Rc; 2], pub measurement: ReadSignal, pub set_point: Signal, - serial: u64, + serial: u64 } impl InversiveDistanceRegulator { - pub fn new(subjects: [Rc; 2]) -> Self { + pub fn new(subjects: [Rc; 2]) -> InversiveDistanceRegulator { let representations = subjects.each_ref().map(|subj| subj.representation()); let measurement = create_memo(move || { representations[0].with(|rep_0| @@ -406,7 +418,7 @@ impl InversiveDistanceRegulator { let set_point = create_signal(SpecifiedValue::from_empty_spec()); let serial = Self::next_serial(); - Self { subjects, measurement, set_point, serial } + InversiveDistanceRegulator { subjects, measurement, set_point, serial } } } @@ -449,11 +461,11 @@ pub struct HalfCurvatureRegulator { pub subject: Rc, pub measurement: ReadSignal, pub set_point: Signal, - serial: u64, + serial: u64 } impl HalfCurvatureRegulator { - pub fn new(subject: Rc) -> Self { + pub fn new(subject: Rc) -> HalfCurvatureRegulator { let measurement = subject.representation().map( |rep| rep[Sphere::CURVATURE_COMPONENT] ); @@ -461,7 +473,7 @@ impl HalfCurvatureRegulator { let set_point = create_signal(SpecifiedValue::from_empty_spec()); let serial = Self::next_serial(); - Self { subject, measurement, set_point, serial } + HalfCurvatureRegulator { subject, measurement, set_point, serial } } } @@ -477,6 +489,18 @@ impl Regulator for HalfCurvatureRegulator { fn set_point(&self) -> Signal { self.set_point } + + fn try_activate(&self) -> bool { + match self.set_point.with(|set_pt| set_pt.value) { + Some(half_curv) => { + self.subject.representation().update( + |rep| change_half_curvature(rep, half_curv) + ); + true + } + None => false + } + } } impl Serial for HalfCurvatureRegulator { @@ -501,7 +525,7 @@ impl ProblemPoser for HalfCurvatureRegulator { // the velocity is expressed in uniform coordinates pub struct ElementMotion<'a> { pub element: Rc, - pub velocity: DVectorView<'a, f64>, + pub velocity: DVectorView<'a, f64> } type AssemblyMotion<'a> = Vec>; @@ -528,42 +552,21 @@ pub struct Assembly { // indexing pub elements_by_id: Signal>>, - // realization control - pub realization_trigger: Signal<()>, - // realization diagnostics pub realization_status: Signal>, - pub descent_history: Signal, + pub descent_history: Signal } impl Assembly { pub fn new() -> Assembly { - // create an assembly - let assembly = Assembly { + Assembly { elements: create_signal(BTreeSet::new()), regulators: create_signal(BTreeSet::new()), tangent: create_signal(ConfigSubspace::zero(0)), elements_by_id: create_signal(BTreeMap::default()), - realization_trigger: create_signal(()), realization_status: create_signal(Ok(())), - descent_history: create_signal(DescentHistory::new()), - }; - - // realize the assembly whenever the element list, the regulator list, - // a regulator's set point, or the realization trigger is updated - let assembly_for_effect = assembly.clone(); - create_effect(move || { - assembly_for_effect.elements.track(); - assembly_for_effect.regulators.with( - |regs| for reg in regs { - reg.set_point().track(); - } - ); - assembly_for_effect.realization_trigger.track(); - assembly_for_effect.realize(); - }); - - assembly + descent_history: create_signal(DescentHistory::new()) + } } // --- inserting elements and regulators --- @@ -624,6 +627,19 @@ impl Assembly { regulators.update(|regs| regs.insert(regulator.clone())); } + // update the realization when the regulator becomes a constraint, or is + // edited while acting as a constraint + let self_for_effect = self.clone(); + create_effect(move || { + /* DEBUG */ + // log the regulator update + console_log!("Updated regulator with subjects {:?}", regulator.subjects()); + + if regulator.try_activate() { + self_for_effect.realize(); + } + }); + /* DEBUG */ // print an updated list of regulators console_log!("Regulators:"); @@ -691,10 +707,8 @@ impl Assembly { } else { console_log!("✅️ Target accuracy achieved!"); } - if history.scaled_loss.len() > 0 { - console_log!("Steps: {}", history.scaled_loss.len() - 1); - console_log!("Loss: {}", history.scaled_loss.last().unwrap()); - } + console_log!("Steps: {}", history.scaled_loss.len() - 1); + console_log!("Loss: {}", history.scaled_loss.last().unwrap()); // report the loss history self.descent_history.set(history); @@ -724,7 +738,7 @@ impl Assembly { // `Err(message)` we received from the match: we're changing the // `Ok` type from `Realization` to `()` self.realization_status.set(Err(message)) - }, + } } } @@ -807,15 +821,15 @@ impl Assembly { }, None => { console_log!("No velocity to unpack for fresh element \"{}\"", elt.id()) - }, + } }; }); } - // trigger a realization to bring the configuration back onto the - // solution variety. this also gets the elements' column indices and the - // saved tangent space back in sync - self.realization_trigger.set(()); + // bring the configuration back onto the solution variety. this also + // gets the elements' column indices and the saved tangent space back in + // sync + self.realize(); } } @@ -867,7 +881,7 @@ mod tests { String::from(sphere_id), String::from("Sphere 0"), [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, INITIAL_RADIUS), + engine::sphere(0.0, 0.0, 0.0, INITIAL_RADIUS) ) ); @@ -881,7 +895,7 @@ mod tests { vec![ ElementMotion { element: sphere.clone(), - velocity: velocity.as_view(), + velocity: velocity.as_view() } ] ); diff --git a/app-proto/src/components.rs b/app-proto/src/components.rs deleted file mode 100644 index 7387d58..0000000 --- a/app-proto/src/components.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod add_remove; -pub mod diagnostics; -pub mod display; -pub mod outline; -pub mod test_assembly_chooser; \ No newline at end of file diff --git a/app-proto/src/components/add_remove.rs b/app-proto/src/components/add_remove.rs deleted file mode 100644 index 4196640..0000000 --- a/app-proto/src/components/add_remove.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::rc::Rc; -use sycamore::prelude::*; - -use super::test_assembly_chooser::TestAssemblyChooser; -use crate::{ - AppState, - assembly::{InversiveDistanceRegulator, Point, Sphere}, -}; - -#[component] -pub fn AddRemove() -> View { - view! { - div(id = "add-remove") { - button( - on:click = |_| { - let state = use_context::(); - batch(|| { - // this call is batched to avoid redundant realizations. - // it updates the element list and the regulator list, - // which are both tracked by the realization effect - /* TO DO */ - // it would make more to do the batching inside - // `insert_element_default`, but that will have to wait - // until Sycamore handles nested batches correctly. - // - // https://github.com/sycamore-rs/sycamore/issues/802 - // - // the nested batch issue is relevant here because the - // assembly loaders in the test assembly chooser use - // `insert_element_default` within larger batches - state.assembly.insert_element_default::(); - }); - } - ) { "Add sphere" } - button( - on:click = |_| { - let state = use_context::(); - state.assembly.insert_element_default::(); - } - ) { "Add point" } - button( - class = "emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button - disabled = { - let state = use_context::(); - state.selection.with(|sel| sel.len() != 2) - }, - on:click = |_| { - let state = use_context::(); - 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_regulator( - Rc::new(InversiveDistanceRegulator::new(subjects)) - ); - state.selection.update(|sel| sel.clear()); - } - ) { "🔗" } - TestAssemblyChooser {} - } - } -} \ No newline at end of file diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs deleted file mode 100644 index 0d387d3..0000000 --- a/app-proto/src/components/test_assembly_chooser.rs +++ /dev/null @@ -1,941 +0,0 @@ -use itertools::izip; -use std::{f64::consts::{FRAC_1_SQRT_2, PI}, rc::Rc}; -use nalgebra::Vector3; -use sycamore::prelude::*; -use web_sys::{console, wasm_bindgen::JsValue}; - -use crate::{ - AppState, - assembly::{ - Assembly, - Element, - ElementColor, - InversiveDistanceRegulator, - Point, - Sphere, - }, - engine, - engine::DescentHistory, - specified::SpecifiedValue, -}; - -// --- loaders --- - -/* DEBUG */ -// each of these functions loads an example assembly for testing. once we've -// done more work on saving and loading assemblies, we should come back to this -// code to see if it can be simplified - -fn load_general(assembly: &Assembly) { - let _ = assembly.try_insert_element( - Sphere::new( - String::from("gemini_a"), - String::from("Castor"), - [1.00_f32, 0.25_f32, 0.00_f32], - engine::sphere(0.5, 0.5, 0.0, 1.0), - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("gemini_b"), - String::from("Pollux"), - [0.00_f32, 0.25_f32, 1.00_f32], - engine::sphere(-0.5, -0.5, 0.0, 1.0), - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("ursa_major"), - String::from("Ursa major"), - [0.25_f32, 0.00_f32, 1.00_f32], - engine::sphere(-0.5, 0.5, 0.0, 0.75), - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("ursa_minor"), - String::from("Ursa minor"), - [0.25_f32, 1.00_f32, 0.00_f32], - engine::sphere(0.5, -0.5, 0.0, 0.5), - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("moon_deimos"), - String::from("Deimos"), - [0.75_f32, 0.75_f32, 0.00_f32], - engine::sphere(0.0, 0.15, 1.0, 0.25), - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("moon_phobos"), - String::from("Phobos"), - [0.00_f32, 0.75_f32, 0.50_f32], - engine::sphere(0.0, -0.15, -1.0, 0.25), - ) - ); -} - -fn load_low_curvature(assembly: &Assembly) { - // create the spheres - let a = 0.75_f64.sqrt(); - let _ = assembly.try_insert_element( - Sphere::new( - "central".to_string(), - "Central".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, 1.0), - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "assemb_plane".to_string(), - "Assembly plane".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0), - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "side1".to_string(), - "Side 1".to_string(), - [1.00_f32, 0.00_f32, 0.25_f32], - engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0), - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "side2".to_string(), - "Side 2".to_string(), - [0.25_f32, 1.00_f32, 0.00_f32], - engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0), - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "side3".to_string(), - "Side 3".to_string(), - [0.00_f32, 0.25_f32, 1.00_f32], - engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0), - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "corner1".to_string(), - "Corner 1".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0), - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "corner2".to_string(), - "Corner 2".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0), - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("corner3"), - String::from("Corner 3"), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0), - ) - ); - - // impose the desired tangencies and make the sides planar - let index_range = 1..=3; - let [central, assemb_plane] = ["central", "assemb_plane"].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[id].clone() - ) - ); - let sides = index_range.clone().map( - |k| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("side{k}")].clone() - ) - ); - let corners = index_range.map( - |k| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("corner{k}")].clone() - ) - ); - for plane in [assemb_plane.clone()].into_iter().chain(sides.clone()) { - // fix the curvature of each plane - let curvature = plane.regulators().with_untracked( - |regs| regs.first().unwrap().clone() - ); - curvature.set_point().set(SpecifiedValue::try_from("0".to_string()).unwrap()); - } - let all_perpendicular = [central.clone()].into_iter() - .chain(sides.clone()) - .chain(corners.clone()); - for sphere in all_perpendicular { - // make each side and packed sphere perpendicular to the assembly plane - let right_angle = InversiveDistanceRegulator::new([sphere, assemb_plane.clone()]); - right_angle.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(right_angle)); - } - for sphere in sides.clone().chain(corners.clone()) { - // make each side and corner sphere tangent to the central sphere - let tangency = InversiveDistanceRegulator::new([sphere.clone(), central.clone()]); - tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(tangency)); - } - for (side_index, side) in sides.enumerate() { - // make each side tangent to the two adjacent corner spheres - for (corner_index, corner) in corners.clone().enumerate() { - if side_index != corner_index { - let tangency = InversiveDistanceRegulator::new([side.clone(), corner]); - tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(tangency)); - } - } - } -} - -fn load_pointed(assembly: &Assembly) { - let _ = assembly.try_insert_element( - Point::new( - format!("point_front"), - format!("Front point"), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::point(0.0, 0.0, FRAC_1_SQRT_2), - ) - ); - let _ = assembly.try_insert_element( - Point::new( - format!("point_back"), - format!("Back point"), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::point(0.0, 0.0, -FRAC_1_SQRT_2), - ) - ); - for index_x in 0..=1 { - for index_y in 0..=1 { - let x = index_x as f64 - 0.5; - let y = index_y as f64 - 0.5; - - let _ = assembly.try_insert_element( - Sphere::new( - format!("sphere{index_x}{index_y}"), - format!("Sphere {index_x}{index_y}"), - [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], - engine::sphere(x, y, 0.0, 1.0), - ) - ); - - let _ = assembly.try_insert_element( - Point::new( - format!("point{index_x}{index_y}"), - format!("Point {index_x}{index_y}"), - [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], - engine::point(x, y, 0.0), - ) - ); - } - } -} - -// to finish describing the tridiminished icosahedron, set the inversive -// distance regulators as follows: -// A-A -0.25 -// A-B " -// B-C " -// C-C " -// A-C -0.25 * φ^2 = -0.6545084971874737 -fn load_tridiminished_icosahedron(assembly: &Assembly) { - // create the vertices - const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.25_f32]; - const COLOR_B: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; - const COLOR_C: ElementColor = [0.25_f32, 0.50_f32, 1.00_f32]; - let vertices = [ - Point::new( - "a1".to_string(), - "A₁".to_string(), - COLOR_A, - engine::point(0.25, 0.75, 0.75), - ), - Point::new( - "a2".to_string(), - "A₂".to_string(), - COLOR_A, - engine::point(0.75, 0.25, 0.75), - ), - Point::new( - "a3".to_string(), - "A₃".to_string(), - COLOR_A, - engine::point(0.75, 0.75, 0.25), - ), - Point::new( - "b1".to_string(), - "B₁".to_string(), - COLOR_B, - engine::point(0.75, -0.25, -0.25), - ), - Point::new( - "b2".to_string(), - "B₂".to_string(), - COLOR_B, - engine::point(-0.25, 0.75, -0.25), - ), - Point::new( - "b3".to_string(), - "B₃".to_string(), - COLOR_B, - engine::point(-0.25, -0.25, 0.75), - ), - Point::new( - "c1".to_string(), - "C₁".to_string(), - COLOR_C, - engine::point(0.0, -1.0, -1.0), - ), - Point::new( - "c2".to_string(), - "C₂".to_string(), - COLOR_C, - engine::point(-1.0, 0.0, -1.0), - ), - Point::new( - "c3".to_string(), - "C₃".to_string(), - COLOR_C, - engine::point(-1.0, -1.0, 0.0), - ), - ]; - for vertex in vertices { - let _ = assembly.try_insert_element(vertex); - } - - // create the faces - const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; - let frac_1_sqrt_6 = 1.0 / 6.0_f64.sqrt(); - let frac_2_sqrt_6 = 2.0 * frac_1_sqrt_6; - let faces = [ - Sphere::new( - "face1".to_string(), - "Face 1".to_string(), - COLOR_FACE, - engine::sphere_with_offset(frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0), - ), - Sphere::new( - "face2".to_string(), - "Face 2".to_string(), - COLOR_FACE, - engine::sphere_with_offset(-frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0), - ), - Sphere::new( - "face3".to_string(), - "Face 3".to_string(), - COLOR_FACE, - engine::sphere_with_offset(-frac_1_sqrt_6, -frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, 0.0), - ), - ]; - for face in faces { - face.ghost().set(true); - let _ = assembly.try_insert_element(face); - } - - let index_range = 1..=3; - for j in index_range.clone() { - // make each face planar - let face = assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("face{j}")].clone() - ); - let curvature_regulator = face.regulators().with_untracked( - |regs| regs.first().unwrap().clone() - ); - curvature_regulator.set_point().set( - SpecifiedValue::try_from("0".to_string()).unwrap() - ); - - // put each A vertex on the face it belongs to - let vertex_a = assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("a{j}")].clone() - ); - let incidence_a = InversiveDistanceRegulator::new([face.clone(), vertex_a.clone()]); - incidence_a.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(incidence_a)); - - // regulate the B-C vertex distances - let vertices_bc = ["b", "c"].map( - |series| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("{series}{j}")].clone() - ) - ); - assembly.insert_regulator( - Rc::new(InversiveDistanceRegulator::new(vertices_bc)) - ); - - // get the pair of indices adjacent to `j` - let adjacent_indices = [j % 3 + 1, (j + 1) % 3 + 1]; - - for k in adjacent_indices.clone() { - for series in ["b", "c"] { - // put each B and C vertex on the faces it belongs to - let vertex = assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("{series}{k}")].clone() - ); - let incidence = InversiveDistanceRegulator::new([face.clone(), vertex.clone()]); - incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(incidence)); - - // regulate the A-B and A-C vertex distances - assembly.insert_regulator( - Rc::new(InversiveDistanceRegulator::new([vertex_a.clone(), vertex])) - ); - } - } - - // regulate the A-A and C-C vertex distances - let adjacent_pairs = ["a", "c"].map( - |series| adjacent_indices.map( - |index| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("{series}{index}")].clone() - ) - ) - ); - for pair in adjacent_pairs { - assembly.insert_regulator( - Rc::new(InversiveDistanceRegulator::new(pair)) - ); - } - } -} - -// to finish describing the dodecahedral circle packing, set the inversive -// distance regulators to -1. some of the regulators have already been set -fn load_dodecahedral_packing(assembly: &Assembly) { - // add the substrate - let _ = assembly.try_insert_element( - Sphere::new( - "substrate".to_string(), - "Substrate".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, 1.0), - ) - ); - let substrate = assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id["substrate"].clone() - ); - - // fix the substrate's curvature - substrate.regulators().with_untracked( - |regs| regs.first().unwrap().clone() - ).set_point().set( - SpecifiedValue::try_from("0.5".to_string()).unwrap() - ); - - // add the circles to be packed - const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.00_f32]; - const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32]; - const COLOR_C: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; - let phi = 0.5 + 1.25_f64.sqrt(); /* TO DO */ // replace with std::f64::consts::PHI when that gets stabilized - let phi_inv = 1.0 / phi; - let coord_scale = (phi + 2.0).sqrt(); - let face_scales = [phi_inv, (13.0 / 12.0) / coord_scale]; - let face_radii = [phi_inv, 5.0 / 12.0]; - let mut faces = Vec::>::new(); - let subscripts = ["₀", "₁"]; - for j in 0..2 { - for k in 0..2 { - let small_coord = face_scales[k] * (2.0*(j as f64) - 1.0); - let big_coord = face_scales[k] * (2.0*(k as f64) - 1.0) * phi; - - let id_num = format!("{j}{k}"); - let label_sub = format!("{}{}", subscripts[j], subscripts[k]); - - // add the A face - let id_a = format!("a{id_num}"); - let _ = assembly.try_insert_element( - Sphere::new( - id_a.clone(), - format!("A{label_sub}"), - COLOR_A, - engine::sphere(0.0, small_coord, big_coord, face_radii[k]), - ) - ); - faces.push( - assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&id_a].clone() - ) - ); - - // add the B face - let id_b = format!("b{id_num}"); - let _ = assembly.try_insert_element( - Sphere::new( - id_b.clone(), - format!("B{label_sub}"), - COLOR_B, - engine::sphere(small_coord, big_coord, 0.0, face_radii[k]), - ) - ); - faces.push( - assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&id_b].clone() - ) - ); - - // add the C face - let id_c = format!("c{id_num}"); - let _ = assembly.try_insert_element( - Sphere::new( - id_c.clone(), - format!("C{label_sub}"), - COLOR_C, - engine::sphere(big_coord, 0.0, small_coord, face_radii[k]), - ) - ); - faces.push( - assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&id_c].clone() - ) - ); - } - } - - // make each face sphere perpendicular to the substrate - for face in faces { - let right_angle = InversiveDistanceRegulator::new([face, substrate.clone()]); - right_angle.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(right_angle)); - } - - // set up the tangencies that define the packing - for [long_edge_plane, short_edge_plane] in [["a", "b"], ["b", "c"], ["c", "a"]] { - for k in 0..2 { - let long_edge_ids = [ - format!("{long_edge_plane}{k}0"), - format!("{long_edge_plane}{k}1") - ]; - let short_edge_ids = [ - format!("{short_edge_plane}0{k}"), - format!("{short_edge_plane}1{k}") - ]; - let [long_edge, short_edge] = [long_edge_ids, short_edge_ids].map( - |edge_ids| edge_ids.map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&id].clone() - ) - ) - ); - - // set up the short-edge tangency - let short_tangency = InversiveDistanceRegulator::new(short_edge.clone()); - if k == 0 { - short_tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); - } - assembly.insert_regulator(Rc::new(short_tangency)); - - // set up the side tangencies - for i in 0..2 { - for j in 0..2 { - let side_tangency = InversiveDistanceRegulator::new( - [long_edge[i].clone(), short_edge[j].clone()] - ); - if i == 0 && k == 0 { - side_tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); - } - assembly.insert_regulator(Rc::new(side_tangency)); - } - } - } - } -} - -// the initial configuration of this test assembly deliberately violates the -// constraints, so loading the assembly will trigger a non-trivial realization -fn load_balanced(assembly: &Assembly) { - // create the spheres - const R_OUTER: f64 = 10.0; - const R_INNER: f64 = 4.0; - let spheres = [ - Sphere::new( - "outer".to_string(), - "Outer".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, R_OUTER), - ), - Sphere::new( - "a".to_string(), - "A".to_string(), - [1.00_f32, 0.00_f32, 0.25_f32], - engine::sphere(0.0, 4.0, 0.0, R_INNER), - ), - Sphere::new( - "b".to_string(), - "B".to_string(), - [0.00_f32, 0.25_f32, 1.00_f32], - engine::sphere(0.0, -4.0, 0.0, R_INNER), - ), - ]; - for sphere in spheres { - let _ = assembly.try_insert_element(sphere); - } - - // get references to the spheres - let [outer, a, b] = ["outer", "a", "b"].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[id].clone() - ) - ); - - // fix the diameters of the outer, sun, and moon spheres - for (sphere, radius) in [ - (outer.clone(), R_OUTER), - (a.clone(), R_INNER), - (b.clone(), R_INNER), - ] { - let curvature_regulator = sphere.regulators().with_untracked( - |regs| regs.first().unwrap().clone() - ); - let curvature = 0.5 / radius; - curvature_regulator.set_point().set( - SpecifiedValue::try_from(curvature.to_string()).unwrap() - ); - } - - // set the inversive distances between the spheres. as described above, the - // initial configuration deliberately violates these constraints - for inner in [a, b] { - let tangency = InversiveDistanceRegulator::new([outer.clone(), inner]); - tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(tangency)); - } -} - -// the initial configuration of this test assembly deliberately violates the -// constraints, so loading the assembly will trigger a non-trivial realization -fn load_off_center(assembly: &Assembly) { - // create a point almost at the origin and a sphere centered on the origin - let _ = assembly.try_insert_element( - Point::new( - "point".to_string(), - "Point".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::point(1e-9, 0.0, 0.0), - ), - ); - let _ = assembly.try_insert_element( - Sphere::new( - "sphere".to_string(), - "Sphere".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, 1.0), - ), - ); - - // get references to the elements - let point_and_sphere = ["point", "sphere"].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[id].clone() - ) - ); - - // put the point on the sphere - let incidence = InversiveDistanceRegulator::new(point_and_sphere); - incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(incidence)); -} - -// setting the inversive distances between the vertices to -2 gives a regular -// tetrahedron with side length 1, whose insphere and circumsphere have radii -// sqrt(1/6) and sqrt(3/2), respectively. to measure those radii, set an -// inversive distance of -1 between the insphere and each face, and then set an -// inversive distance of 0 between the circumsphere and each vertex -fn load_radius_ratio(assembly: &Assembly) { - let index_range = 1..=4; - - // create the spheres - const GRAY: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; - let spheres = [ - Sphere::new( - "sphere_faces".to_string(), - "Insphere".to_string(), - GRAY, - engine::sphere(0.0, 0.0, 0.0, 0.5), - ), - Sphere::new( - "sphere_vertices".to_string(), - "Circumsphere".to_string(), - GRAY, - engine::sphere(0.0, 0.0, 0.0, 0.25), - ), - ]; - for sphere in spheres { - let _ = assembly.try_insert_element(sphere); - } - - // create the vertices - let vertices = izip!( - index_range.clone(), - [ - [1.00_f32, 0.50_f32, 0.75_f32], - [1.00_f32, 0.75_f32, 0.50_f32], - [1.00_f32, 1.00_f32, 0.50_f32], - [0.75_f32, 0.50_f32, 1.00_f32], - ].into_iter(), - [ - engine::point(-0.6, -0.8, -0.6), - engine::point(-0.6, 0.8, 0.6), - engine::point(0.6, -0.8, 0.6), - engine::point(0.6, 0.8, -0.6), - ].into_iter() - ).map( - |(k, color, representation)| { - Point::new( - format!("v{k}"), - format!("Vertex {k}"), - color, - representation, - ) - } - ); - for vertex in vertices { - let _ = assembly.try_insert_element(vertex); - } - - // create the faces - let base_dir = Vector3::new(1.0, 0.75, 1.0).normalize(); - let offset = base_dir.dot(&Vector3::new(-0.6, 0.8, 0.6)); - let faces = izip!( - index_range.clone(), - [ - [1.00_f32, 0.00_f32, 0.25_f32], - [1.00_f32, 0.25_f32, 0.00_f32], - [0.75_f32, 0.75_f32, 0.00_f32], - [0.25_f32, 0.00_f32, 1.00_f32], - ].into_iter(), - [ - engine::sphere_with_offset(base_dir[0], base_dir[1], base_dir[2], offset, 0.0), - engine::sphere_with_offset(base_dir[0], -base_dir[1], -base_dir[2], offset, 0.0), - engine::sphere_with_offset(-base_dir[0], base_dir[1], -base_dir[2], offset, 0.0), - engine::sphere_with_offset(-base_dir[0], -base_dir[1], base_dir[2], offset, 0.0), - ].into_iter() - ).map( - |(k, color, representation)| { - Sphere::new( - format!("f{k}"), - format!("Face {k}"), - color, - representation, - ) - } - ); - for face in faces { - face.ghost().set(true); - let _ = assembly.try_insert_element(face); - } - - // impose the constraints - for j in index_range.clone() { - let [face_j, vertex_j] = [ - format!("f{j}"), - format!("v{j}"), - ].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&id].clone() - ) - ); - - // make the faces planar - let curvature_regulator = face_j.regulators().with_untracked( - |regs| regs.first().unwrap().clone() - ); - curvature_regulator.set_point().set( - SpecifiedValue::try_from("0".to_string()).unwrap() - ); - - for k in index_range.clone().filter(|&index| index != j) { - let vertex_k = assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("v{k}")].clone() - ); - - // fix the distances between the vertices - if j < k { - let distance_regulator = InversiveDistanceRegulator::new( - [vertex_j.clone(), vertex_k.clone()] - ); - assembly.insert_regulator(Rc::new(distance_regulator)); - } - - // put the vertices on the faces - let incidence_regulator = InversiveDistanceRegulator::new([face_j.clone(), vertex_k.clone()]); - incidence_regulator.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(incidence_regulator)); - } - } -} - -// to finish setting up the problem, fix the following curvatures: -// sun 1 -// moon 5/3 = 1.666666666666666... -// chain1 2 -// a tiny `x` or `z` nudge of the outer sphere reliably prevents realization -// failures before they happen, or resolves them after they happen. the result -// depends sensitively on the translation direction, suggesting that realization -// is failing because the engine is having trouble breaking a symmetry -// /* TO DO */ -// the engine's performance on this problem is scale-dependent! with the current -// initial conditions, realization fails for any order of imposing the remaining -// curvature constraints. scaling everything up by a factor of ten, as done in -// the original problem, makes realization succeed reliably. one potentially -// relevant difference is that a lot of the numbers in the current initial -// conditions are exactly representable as floats, unlike the analogous numbers -// in the scaled-up problem. the inexact representations might break the -// symmetry that's getting the engine stuck -fn load_irisawa_hexlet(assembly: &Assembly) { - let index_range = 1..=6; - let colors = [ - [1.00_f32, 0.00_f32, 0.25_f32], - [1.00_f32, 0.25_f32, 0.00_f32], - [0.75_f32, 0.75_f32, 0.00_f32], - [0.25_f32, 1.00_f32, 0.00_f32], - [0.00_f32, 0.25_f32, 1.00_f32], - [0.25_f32, 0.00_f32, 1.00_f32], - ].into_iter(); - - // create the spheres - let spheres = [ - Sphere::new( - "outer".to_string(), - "Outer".to_string(), - [0.5_f32, 0.5_f32, 0.5_f32], - engine::sphere(0.0, 0.0, 0.0, 1.5), - ), - Sphere::new( - "sun".to_string(), - "Sun".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, -0.75, 0.0, 0.75), - ), - Sphere::new( - "moon".to_string(), - "Moon".to_string(), - [0.25_f32, 0.25_f32, 0.25_f32], - engine::sphere(0.0, 0.75, 0.0, 0.75), - ), - ].into_iter().chain( - index_range.clone().zip(colors).map( - |(k, color)| { - let ang = (k as f64) * PI/3.0; - Sphere::new( - format!("chain{k}"), - format!("Chain {k}"), - color, - engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5), - ) - } - ) - ); - for sphere in spheres { - let _ = assembly.try_insert_element(sphere); - } - - // put the outer sphere in ghost mode and fix its curvature - let outer = assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id["outer"].clone() - ); - outer.ghost().set(true); - let outer_curvature_regulator = outer.regulators().with_untracked( - |regs| regs.first().unwrap().clone() - ); - outer_curvature_regulator.set_point().set( - SpecifiedValue::try_from((1.0 / 3.0).to_string()).unwrap() - ); - - // impose the desired tangencies - let [outer, sun, moon] = ["outer", "sun", "moon"].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[id].clone() - ) - ); - let chain = index_range.map( - |k| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("chain{k}")].clone() - ) - ); - for (chain_sphere, chain_sphere_next) in chain.clone().zip(chain.cycle().skip(1)) { - for (other_sphere, inversive_distance) in [ - (outer.clone(), "1"), - (sun.clone(), "-1"), - (moon.clone(), "-1"), - (chain_sphere_next.clone(), "-1"), - ] { - let tangency = InversiveDistanceRegulator::new([chain_sphere.clone(), other_sphere]); - tangency.set_point.set(SpecifiedValue::try_from(inversive_distance.to_string()).unwrap()); - assembly.insert_regulator(Rc::new(tangency)); - } - } - - let outer_sun_tangency = InversiveDistanceRegulator::new([outer.clone(), sun]); - outer_sun_tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(outer_sun_tangency)); - - let outer_moon_tangency = InversiveDistanceRegulator::new([outer.clone(), moon]); - outer_moon_tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(outer_moon_tangency)); -} - -// --- chooser --- - -/* DEBUG */ -#[component] -pub fn TestAssemblyChooser() -> View { - // create an effect that loads the selected test assembly - let assembly_name = create_signal("general".to_string()); - create_effect(move || { - // get name of chosen assembly - let name = assembly_name.get_clone(); - console::log_1( - &JsValue::from(format!("Showing assembly \"{}\"", name.clone())) - ); - - batch(|| { - let state = use_context::(); - let assembly = &state.assembly; - - // clear state - assembly.regulators.update(|regs| regs.clear()); - assembly.elements.update(|elts| elts.clear()); - assembly.elements_by_id.update(|elts_by_id| elts_by_id.clear()); - assembly.descent_history.set(DescentHistory::new()); - state.selection.update(|sel| sel.clear()); - - // load assembly - match name.as_str() { - "general" => load_general(assembly), - "low-curvature" => load_low_curvature(assembly), - "pointed" => load_pointed(assembly), - "tridiminished-icosahedron" => load_tridiminished_icosahedron(assembly), - "dodecahedral-packing" => load_dodecahedral_packing(assembly), - "balanced" => load_balanced(assembly), - "off-center" => load_off_center(assembly), - "radius-ratio" => load_radius_ratio(assembly), - "irisawa-hexlet" => load_irisawa_hexlet(assembly), - _ => (), - }; - }); - }); - - // build the chooser - view! { - select(bind:value = assembly_name) { - option(value = "general") { "General" } - option(value = "low-curvature") { "Low-curvature" } - option(value = "pointed") { "Pointed" } - option(value = "tridiminished-icosahedron") { "Tridiminished icosahedron" } - option(value = "dodecahedral-packing") { "Dodecahedral packing" } - option(value = "balanced") { "Balanced" } - option(value = "off-center") { "Off-center" } - option(value = "radius-ratio") { "Radius ratio" } - option(value = "irisawa-hexlet") { "Irisawa hexlet" } - option(value = "empty") { "Empty" } - } - } -} \ No newline at end of file diff --git a/app-proto/src/components/diagnostics.rs b/app-proto/src/diagnostics.rs similarity index 88% rename from app-proto/src/components/diagnostics.rs rename to app-proto/src/diagnostics.rs index e265982..a2f090a 100644 --- a/app-proto/src/components/diagnostics.rs +++ b/app-proto/src/diagnostics.rs @@ -11,12 +11,14 @@ use crate::AppState; #[derive(Clone)] struct DiagnosticsState { - active_tab: Signal, + active_tab: Signal } impl DiagnosticsState { - fn new(initial_tab: String) -> Self { - Self { active_tab: create_signal(initial_tab) } + fn new(initial_tab: String) -> DiagnosticsState { + DiagnosticsState { + active_tab: create_signal(initial_tab) + } } } @@ -27,20 +29,20 @@ fn RealizationStatus() -> View { let realization_status = state.assembly.realization_status; view! { div( - id = "realization-status", - class = realization_status.with( + id="realization-status", + class=realization_status.with( |status| match status { Ok(_) => "", - Err(_) => "invalid", + Err(_) => "invalid" } ) ) { - div(class = "status") + div(class="status") div { (realization_status.with( |status| match status { Ok(_) => "Target accuracy achieved".to_string(), - Err(message) => message.clone(), + Err(message) => message.clone() } )) } @@ -51,7 +53,7 @@ fn RealizationStatus() -> View { fn into_log10_time_point((step, value): (usize, f64)) -> Vec> { vec![ Some(step as f64), - if value == 0.0 { None } else { Some(value.abs().log10()) }, + if value == 0.0 { None } else { Some(value.abs().log10()) } ] } @@ -103,7 +105,7 @@ fn LossHistory() -> View { }); view! { - div(id = CONTAINER_ID, class = "diagnostics-chart") + div(id=CONTAINER_ID, class="diagnostics-chart") } } @@ -120,7 +122,7 @@ fn SpectrumHistory() -> View { // positive, negative, and strictly-zero parts let ( hess_eigvals_zero, - hess_eigvals_nonzero, + hess_eigvals_nonzero ): (Vec<_>, Vec<_>) = state.assembly.descent_history.with( |history| history.hess_eigvals .iter() @@ -141,7 +143,7 @@ fn SpectrumHistory() -> View { .unwrap_or(1.0); let ( hess_eigvals_pos, - hess_eigvals_neg, + hess_eigvals_neg ): (Vec<_>, Vec<_>) = hess_eigvals_nonzero .into_iter() .partition(|&(_, val)| val > 0.0); @@ -209,7 +211,7 @@ fn SpectrumHistory() -> View { }); view! { - div(id = CONTAINER_ID, class = "diagnostics-chart") + div(id=CONTAINER_ID, class="diagnostics-chart") } } @@ -218,8 +220,8 @@ fn DiagnosticsPanel(name: &'static str, children: Children) -> View { let diagnostics_state = use_context::(); view! { div( - class = "diagnostics-panel", - "hidden" = diagnostics_state.active_tab.with( + class="diagnostics-panel", + "hidden"=diagnostics_state.active_tab.with( |active_tab| { if active_tab == name { None @@ -241,16 +243,16 @@ pub fn Diagnostics() -> View { provide_context(diagnostics_state); view! { - div(id = "diagnostics") { - div(id = "diagnostics-bar") { + div(id="diagnostics") { + div(id="diagnostics-bar") { RealizationStatus {} - select(bind:value = active_tab) { - option(value = "loss") { "Loss" } - option(value = "spectrum") { "Spectrum" } + select(bind:value=active_tab) { + option(value="loss") { "Loss" } + option(value="spectrum") { "Spectrum" } } } - DiagnosticsPanel(name = "loss") { LossHistory {} } - DiagnosticsPanel(name = "spectrum") { SpectrumHistory {} } + DiagnosticsPanel(name="loss") { LossHistory {} } + DiagnosticsPanel(name="spectrum") { SpectrumHistory {} } } } } \ No newline at end of file diff --git a/app-proto/src/components/display.rs b/app-proto/src/display.rs similarity index 93% rename from app-proto/src/components/display.rs rename to app-proto/src/display.rs index da921dd..1646c4e 100644 --- a/app-proto/src/components/display.rs +++ b/app-proto/src/display.rs @@ -12,12 +12,12 @@ use web_sys::{ WebGlProgram, WebGlShader, WebGlUniformLocation, - wasm_bindgen::{JsCast, JsValue}, + wasm_bindgen::{JsCast, JsValue} }; use crate::{ AppState, - assembly::{Element, ElementColor, ElementMotion, Point, Sphere}, + assembly::{Element, ElementColor, ElementMotion, Point, Sphere} }; // --- color --- @@ -37,15 +37,15 @@ fn combine_channels(color: ElementColor, opacity: f32) -> ColorWithOpacity { struct SceneSpheres { representations: Vec>, colors_with_opacity: Vec, - highlights: Vec, + highlights: Vec } impl SceneSpheres { - fn new() -> Self { - Self { + fn new() -> SceneSpheres{ + SceneSpheres { representations: Vec::new(), colors_with_opacity: Vec::new(), - highlights: Vec::new(), + highlights: Vec::new() } } @@ -53,10 +53,7 @@ impl SceneSpheres { self.representations.len().try_into().expect("Number of spheres must fit in a 32-bit integer") } - fn push( - &mut self, representation: DVector, - color: ElementColor, opacity: f32, highlight: f32, - ) { + fn push(&mut self, representation: DVector, color: ElementColor, opacity: f32, highlight: f32) { self.representations.push(representation); self.colors_with_opacity.push(combine_channels(color, opacity)); self.highlights.push(highlight); @@ -67,23 +64,20 @@ struct ScenePoints { representations: Vec>, colors_with_opacity: Vec, highlights: Vec, - selections: Vec, + selections: Vec } impl ScenePoints { - fn new() -> Self { - Self { + fn new() -> ScenePoints { + ScenePoints { representations: Vec::new(), colors_with_opacity: Vec::new(), highlights: Vec::new(), - selections: Vec::new(), + selections: Vec::new() } } - fn push( - &mut self, representation: DVector, - color: ElementColor, opacity: f32, highlight: f32, selected: bool, - ) { + fn push(&mut self, representation: DVector, color: ElementColor, opacity: f32, highlight: f32, selected: bool) { self.representations.push(representation); self.colors_with_opacity.push(combine_channels(color, opacity)); self.highlights.push(highlight); @@ -93,14 +87,14 @@ impl ScenePoints { pub struct Scene { spheres: SceneSpheres, - points: ScenePoints, + points: ScenePoints } impl Scene { - fn new() -> Self { - Self { + fn new() -> Scene { + Scene { spheres: SceneSpheres::new(), - points: ScenePoints::new(), + points: ScenePoints::new() } } } @@ -111,12 +105,7 @@ pub trait DisplayItem { // the smallest positive depth, represented as a multiple of `dir`, where // the line generated by `dir` hits the element. returns `None` if the line // misses the element - fn cast( - &self, - dir: Vector3, - assembly_to_world: &DMatrix, - pixel_size: f64, - ) -> Option; + fn cast(&self, dir: Vector3, assembly_to_world: &DMatrix, pixel_size: f64) -> Option; } impl DisplayItem for Sphere { @@ -135,12 +124,7 @@ impl DisplayItem for Sphere { // this method should be kept synchronized with `sphere_cast` in // `spheres.frag`, which does essentially the same thing on the GPU side - fn cast( - &self, - dir: Vector3, - assembly_to_world: &DMatrix, - _pixel_size: f64, - ) -> Option { + fn cast(&self, dir: Vector3, assembly_to_world: &DMatrix, _pixel_size: f64) -> Option { // if `a/b` is less than this threshold, we approximate // `a*u^2 + b*u + c` by the linear function `b*u + c` const DEG_THRESHOLD: f64 = 1e-9; @@ -193,12 +177,7 @@ impl DisplayItem for Point { } /* SCAFFOLDING */ - fn cast( - &self, - dir: Vector3, - assembly_to_world: &DMatrix, - pixel_size: f64, - ) -> Option { + fn cast(&self, dir: Vector3, assembly_to_world: &DMatrix, pixel_size: f64) -> Option { let rep = self.representation.with_untracked(|rep| assembly_to_world * rep); if rep[2] < 0.0 { // this constant should be kept synchronized with `point.frag` @@ -241,7 +220,7 @@ fn compile_shader( fn set_up_program( context: &WebGl2RenderingContext, vertex_shader_source: &str, - fragment_shader_source: &str, + fragment_shader_source: &str ) -> WebGlProgram { // compile the shaders let vertex_shader = compile_shader( @@ -281,12 +260,12 @@ fn get_uniform_array_locations( context: &WebGl2RenderingContext, program: &WebGlProgram, var_name: &str, - member_name_opt: Option<&str>, + member_name_opt: Option<&str> ) -> [Option; N] { array::from_fn(|n| { let name = match member_name_opt { Some(member_name) => format!("{var_name}[{n}].{member_name}"), - None => format!("{var_name}[{n}]"), + None => format!("{var_name}[{n}]") }; context.get_uniform_location(&program, name.as_str()) }) @@ -297,7 +276,7 @@ fn bind_to_attribute( context: &WebGl2RenderingContext, attr_index: u32, attr_size: i32, - buffer: &Option, + buffer: &Option ) { context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, buffer.as_ref()); context.vertex_attrib_pointer_with_i32( @@ -313,7 +292,7 @@ fn bind_to_attribute( // load the given data into a new vertex buffer object fn load_new_buffer( context: &WebGl2RenderingContext, - data: &[f32], + data: &[f32] ) -> Option { // create a buffer and bind it to ARRAY_BUFFER let buffer = context.create_buffer(); @@ -340,7 +319,7 @@ fn bind_new_buffer_to_attribute( context: &WebGl2RenderingContext, attr_index: u32, attr_size: i32, - data: &[f32], + data: &[f32] ) { let buffer = load_new_buffer(context, data); bind_to_attribute(context, attr_index, attr_size, &buffer); @@ -362,9 +341,9 @@ fn event_dir(event: &MouseEvent) -> (Vector3, f64) { Vector3::new( FOCAL_SLOPE * (2.0*(f64::from(event.client_x()) - rect.left()) - width) / shortdim, FOCAL_SLOPE * (2.0*(rect.bottom() - f64::from(event.client_y())) - height) / shortdim, - -1.0, + -1.0 ), - FOCAL_SLOPE * 2.0 / shortdim, + FOCAL_SLOPE * 2.0 / shortdim ) } @@ -464,14 +443,14 @@ pub fn Display() -> View { let sphere_program = set_up_program( &ctx, include_str!("identity.vert"), - include_str!("spheres.frag"), + include_str!("spheres.frag") ); // set up the point rendering program let point_program = set_up_program( &ctx, include_str!("point.vert"), - include_str!("point.frag"), + include_str!("point.frag") ); /* DEBUG */ @@ -488,7 +467,7 @@ pub fn Display() -> View { // capped at 1024 elements console::log_2( &ctx.get_parameter(WebGl2RenderingContext::MAX_FRAGMENT_UNIFORM_VECTORS).unwrap(), - &JsValue::from("uniform vectors available"), + &JsValue::from("uniform vectors available") ); // find the sphere program's vertex attribute @@ -524,7 +503,7 @@ pub fn Display() -> View { // southeast triangle -1.0, -1.0, 0.0, 1.0, 1.0, 0.0, - 1.0, -1.0, 0.0, + 1.0, -1.0, 0.0 ]; let viewport_position_buffer = load_new_buffer(&ctx, &viewport_positions); @@ -617,7 +596,7 @@ pub fn Display() -> View { vec![ ElementMotion { element: sel, - velocity: elt_motion.as_view(), + velocity: elt_motion.as_view() } ] ); @@ -650,7 +629,7 @@ pub fn Display() -> View { 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, u, 0.0, 0.0, 2.0*u, 1.0, u*u, - 0.0, 0.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0, 0.0, 1.0 ]) }; let asm_to_world = &location * &orientation; @@ -689,19 +668,19 @@ pub fn Display() -> View { let v = &sphere_reps_world[n]; ctx.uniform3fv_with_f32_array( sphere_sp_locs[n].as_ref(), - v.rows(0, 3).as_slice(), + v.rows(0, 3).as_slice() ); ctx.uniform2fv_with_f32_array( sphere_lt_locs[n].as_ref(), - v.rows(3, 2).as_slice(), + v.rows(3, 2).as_slice() ); ctx.uniform4fv_with_f32_array( sphere_color_locs[n].as_ref(), - &scene.spheres.colors_with_opacity[n], + &scene.spheres.colors_with_opacity[n] ); ctx.uniform1f( sphere_highlight_locs[n].as_ref(), - scene.spheres.highlights[n], + scene.spheres.highlights[n] ); } @@ -794,7 +773,7 @@ pub fn Display() -> View { "ArrowLeft" if shift => roll_ccw.set(value), "ArrowRight" => yaw_right.set(value), "ArrowLeft" => yaw_left.set(value), - _ => navigating = false, + _ => navigating = false }; if navigating { scene_changed.set(true); @@ -814,7 +793,7 @@ pub fn Display() -> View { "s" | "S" => translate_neg_y.set(value), "]" | "}" => shrink_neg.set(value), "[" | "{" => shrink_pos.set(value), - _ => manipulating = false, + _ => manipulating = false }; if manipulating { event.prevent_default(); @@ -826,12 +805,12 @@ pub fn Display() -> View { // switch back to integer-valued parameters when that becomes possible // again canvas( - ref = display, - id = "display", - width = "600", - height = "600", - tabindex = "0", - on:keydown = move |event: KeyboardEvent| { + ref=display, + id="display", + width="600", + height="600", + tabindex="0", + on:keydown=move |event: KeyboardEvent| { if event.key() == "Shift" { // swap navigation inputs roll_cw.set(yaw_right.get()); @@ -857,7 +836,7 @@ pub fn Display() -> View { set_manip_signal(&event, 1.0); } }, - on:keyup = move |event: KeyboardEvent| { + on:keyup=move |event: KeyboardEvent| { if event.key() == "Shift" { // swap navigation inputs yaw_right.set(roll_cw.get()); @@ -879,7 +858,7 @@ pub fn Display() -> View { set_manip_signal(&event, 0.0); } }, - on:blur = move |_| { + on:blur=move |_| { pitch_up.set(0.0); pitch_down.set(0.0); yaw_right.set(0.0); @@ -887,7 +866,7 @@ pub fn Display() -> View { roll_ccw.set(0.0); roll_cw.set(0.0); }, - on:click = move |event: MouseEvent| { + on:click=move |event: MouseEvent| { // find the nearest element along the pointer direction let (dir, pixel_size) = event_dir(&event); console::log_1(&JsValue::from(dir.to_string())); @@ -904,18 +883,18 @@ pub fn Display() -> View { clicked = Some((elt, depth)) } }, - None => clicked = Some((elt, depth)), - }, - None => (), + None => clicked = Some((elt, depth)) + } + None => () }; } // if we clicked something, select it match clicked { Some((elt, _)) => state.select(&elt, event.shift_key()), - None => state.selection.update(|sel| sel.clear()), + None => state.selection.update(|sel| sel.clear()) }; - }, + } ) } } \ No newline at end of file diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index d033c01..e6ffa25 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -1,6 +1,7 @@ use lazy_static::lazy_static; use nalgebra::{Const, DMatrix, DVector, DVectorView, Dyn, SymmetricEigen}; use std::fmt::{Display, Error, Formatter}; +use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ // --- elements --- @@ -16,7 +17,7 @@ pub fn sphere(center_x: f64, center_y: f64, center_z: f64, radius: f64) -> DVect center_y / radius, center_z / radius, 0.5 / radius, - 0.5 * (center_norm_sq / radius - radius), + 0.5 * (center_norm_sq / radius - radius) ]) } @@ -30,7 +31,7 @@ pub fn sphere_with_offset(dir_x: f64, dir_y: f64, dir_z: f64, off: f64, curv: f6 norm_sp * dir_y, norm_sp * dir_z, 0.5 * curv, - off * (1.0 + 0.5 * off * curv), + off * (1.0 + 0.5 * off * curv) ]) } @@ -49,23 +50,57 @@ pub fn project_point_to_normalized(rep: &mut DVector) { rep.scale_mut(0.5 / rep[3]); } +// given a sphere's representation vector, change the sphere's half-curvature to +// `half-curv` and then restore normalization by contracting the representation +// vector toward the curvature axis +pub fn change_half_curvature(rep: &mut DVector, half_curv: f64) { + // 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)) + ) + )); +} + // --- partial matrices --- pub struct MatrixEntry { index: (usize, usize), - value: f64, + value: f64 } pub struct PartialMatrix(Vec); impl PartialMatrix { - pub fn new() -> Self { - Self(Vec::::new()) + pub fn new() -> PartialMatrix { + PartialMatrix(Vec::::new()) } pub fn push(&mut self, row: usize, col: usize, value: f64) { - let Self(entries) = self; - entries.push(MatrixEntry { index: (row, col), value }); + let PartialMatrix(entries) = self; + entries.push(MatrixEntry { index: (row, col), value: value }); } pub fn push_sym(&mut self, row: usize, col: usize, value: f64) { @@ -114,7 +149,7 @@ impl IntoIterator for PartialMatrix { type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { - let Self(entries) = self; + let PartialMatrix(entries) = self; entries.into_iter() } } @@ -135,26 +170,22 @@ impl<'a> IntoIterator for &'a PartialMatrix { pub struct ConfigSubspace { assembly_dim: usize, basis_std: Vec>, - basis_proj: Vec>, + basis_proj: Vec> } impl ConfigSubspace { - pub fn zero(assembly_dim: usize) -> Self { - Self { - assembly_dim, + pub fn zero(assembly_dim: usize) -> ConfigSubspace { + ConfigSubspace { + assembly_dim: assembly_dim, basis_proj: Vec::new(), - basis_std: Vec::new(), + basis_std: Vec::new() } } // approximate the kernel of a symmetric endomorphism of the configuration // space for `assembly_dim` elements. we consider an eigenvector to be part // of the kernel if its eigenvalue is smaller than the constant `THRESHOLD` - fn symmetric_kernel( - a: DMatrix, - proj_to_std: DMatrix, - assembly_dim: usize, - ) -> Self { + fn symmetric_kernel(a: DMatrix, proj_to_std: DMatrix, assembly_dim: usize) -> ConfigSubspace { // find a basis for the kernel. the basis is expressed in the projection // coordinates, and it's orthonormal with respect to the projection // inner product @@ -168,13 +199,20 @@ impl ConfigSubspace { ).collect::>().as_slice() ); + /* DEBUG */ + // print the eigenvalues + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + console::log_1(&JsValue::from( + format!("Eigenvalues used to find kernel:{}", eig.eigenvalues) + )); + // express the basis in the standard coordinates let basis_std = proj_to_std * &basis_proj; const ELEMENT_DIM: usize = 5; const UNIFORM_DIM: usize = 4; - Self { - assembly_dim, + ConfigSubspace { + assembly_dim: assembly_dim, basis_std: basis_std.column_iter().map( |v| Into::>::into( v.reshape_generic(Dyn(ELEMENT_DIM), Dyn(assembly_dim)) @@ -184,7 +222,7 @@ impl ConfigSubspace { |v| Into::>::into( v.reshape_generic(Dyn(UNIFORM_DIM), Dyn(assembly_dim)) ) - ).collect(), + ).collect() } } @@ -218,14 +256,14 @@ pub struct DescentHistory { pub config: Vec>, pub scaled_loss: Vec, pub neg_grad: Vec>, - pub hess_eigvals: Vec>, + pub hess_eigvals: Vec::>, pub base_step: Vec>, - pub backoff_steps: Vec, + pub backoff_steps: Vec } impl DescentHistory { - pub fn new() -> Self { - Self { + pub fn new() -> DescentHistory { + DescentHistory { config: Vec::>::new(), scaled_loss: Vec::::new(), neg_grad: Vec::>::new(), @@ -245,21 +283,21 @@ pub struct ConstraintProblem { } impl ConstraintProblem { - pub fn new(element_count: usize) -> Self { + pub fn new(element_count: usize) -> ConstraintProblem { const ELEMENT_DIM: usize = 5; - Self { + ConstraintProblem { gram: PartialMatrix::new(), frozen: PartialMatrix::new(), - guess: DMatrix::::zeros(ELEMENT_DIM, element_count), + guess: DMatrix::::zeros(ELEMENT_DIM, element_count) } } #[cfg(feature = "dev")] - pub fn from_guess(guess_columns: &[DVector]) -> Self { - Self { + pub fn from_guess(guess_columns: &[DVector]) -> ConstraintProblem { + ConstraintProblem { gram: PartialMatrix::new(), frozen: PartialMatrix::new(), - guess: DMatrix::from_columns(guess_columns), + guess: DMatrix::from_columns(guess_columns) } } } @@ -273,21 +311,25 @@ lazy_static! { 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.0, - 0.0, 0.0, 0.0, -2.0, 0.0, + 0.0, 0.0, 0.0, -2.0, 0.0 ]); } struct SearchState { config: DMatrix, err_proj: DMatrix, - loss: f64, + loss: f64 } impl SearchState { - fn from_config(gram: &PartialMatrix, config: DMatrix) -> Self { + fn from_config(gram: &PartialMatrix, config: DMatrix) -> SearchState { let err_proj = gram.sub_proj(&(config.tr_mul(&*Q) * &config)); let loss = err_proj.norm_squared(); - Self { config, err_proj, loss } + SearchState { + config: config, + err_proj: err_proj, + loss: loss + } } } @@ -314,7 +356,7 @@ pub fn local_unif_to_std(v: DVectorView) -> DMatrix { curv, 0.0, 0.0, 0.0, v[0], 0.0, curv, 0.0, 0.0, v[1], 0.0, 0.0, curv, 0.0, v[2], - 0.0, 0.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0, 0.0, 1.0 ]) } else { // `v` represents a sphere. the normalization condition says that the @@ -323,7 +365,7 @@ pub fn local_unif_to_std(v: DVectorView) -> DMatrix { curv, 0.0, 0.0, 0.0, v[0], 0.0, curv, 0.0, 0.0, v[1], 0.0, 0.0, curv, 0.0, v[2], - curv*v[0], curv*v[1], curv*v[2], curv*v[3], curv*v[4] + 1.0, + curv*v[0], curv*v[1], curv*v[2], curv*v[3], curv*v[4] + 1.0 ]) } } @@ -336,7 +378,7 @@ fn seek_better_config( base_target_improvement: f64, min_efficiency: f64, backoff: f64, - max_backoff_steps: i32, + max_backoff_steps: i32 ) -> Option<(SearchState, i32)> { let mut rate = 1.0; for backoff_steps in 0..max_backoff_steps { @@ -354,12 +396,12 @@ fn seek_better_config( // a first-order neighborhood of a configuration pub struct ConfigNeighborhood { pub config: DMatrix, - pub nbhd: ConfigSubspace, + pub nbhd: ConfigSubspace } pub struct Realization { pub result: Result, - pub history: DescentHistory, + pub history: DescentHistory } // seek a matrix `config` that matches the partial matrix `problem.frozen` and @@ -373,30 +415,19 @@ pub fn realize_gram( backoff: f64, reg_scale: f64, max_descent_steps: i32, - max_backoff_steps: i32, + max_backoff_steps: i32 ) -> Realization { // destructure the problem data - let ConstraintProblem { gram, guess, frozen } = problem; + let ConstraintProblem { + gram, guess, frozen + } = problem; // start the descent history let mut history = DescentHistory::new(); - // handle the case where the assembly is empty. our general realization - // routine can't handle this case because it builds the Hessian using - // `DMatrix::from_columns`, which panics when the list of columns is empty - let assembly_dim = guess.ncols(); - if assembly_dim == 0 { - let result = Ok( - ConfigNeighborhood { - config: guess.clone(), - nbhd: ConfigSubspace::zero(0), - } - ); - return Realization { result, history }; - } - // find the dimension of the search space let element_dim = guess.nrows(); + let assembly_dim = guess.ncols(); let total_dim = element_dim * assembly_dim; // scale the tolerance @@ -473,8 +504,8 @@ pub fn realize_gram( Some(cholesky) => cholesky, None => return Realization { result: Err("Cholesky decomposition failed".to_string()), - history, - }, + history + } }; let base_step_stacked = hess_cholesky.solve(&neg_grad_stacked); let base_step = base_step_stacked.reshape_generic(Dyn(element_dim), Dyn(assembly_dim)); @@ -483,16 +514,16 @@ pub fn realize_gram( // use backtracking line search to find a better configuration if let Some((better_state, backoff_steps)) = seek_better_config( gram, &state, &base_step, neg_grad.dot(&base_step), - min_efficiency, backoff, max_backoff_steps, + min_efficiency, backoff, max_backoff_steps ) { state = better_state; history.backoff_steps.push(backoff_steps); } else { return Realization { result: Err("Line search failed".to_string()), - history, - }; - } + history + } + }; } let result = if state.loss < tol { // express the uniform basis in the standard basis @@ -537,7 +568,7 @@ pub mod examples { [ sphere(0.0, 0.0, 0.0, 15.0), sphere(0.0, 0.0, -9.0, 5.0), - sphere(0.0, 0.0, 11.0, 3.0), + sphere(0.0, 0.0, 11.0, 3.0) ].into_iter().chain( (1..=6).map( |k| { @@ -596,7 +627,7 @@ pub mod examples { point(0.0, 0.0, 0.0), point(ang_hor.cos(), ang_hor.sin(), 0.0), point(x_vert, y_vert, -0.5), - point(x_vert, y_vert, 0.5), + point(x_vert, y_vert, 0.5) ] } ).collect::>().as_slice() @@ -639,15 +670,15 @@ mod tests { 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 }, + 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, + 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, + 4.0, 42.0, 49.0 ]); assert_eq!(frozen.freeze(&config), expected_result); } @@ -658,15 +689,15 @@ mod tests { MatrixEntry { index: (0, 0), value: 19.0 }, MatrixEntry { index: (0, 2), value: 39.0 }, MatrixEntry { index: (1, 1), value: 59.0 }, - MatrixEntry { index: (1, 2), value: 69.0 }, + MatrixEntry { index: (1, 2), value: 69.0 } ]); let attempt = DMatrix::::from_row_slice(2, 3, &[ 1.0, 2.0, 3.0, - 4.0, 5.0, 6.0, + 4.0, 5.0, 6.0 ]); let expected_result = DMatrix::::from_row_slice(2, 3, &[ 18.0, 0.0, 36.0, - 0.0, 54.0, 63.0, + 0.0, 54.0, 63.0 ]); assert_eq!(target.sub_proj(&attempt), expected_result); } @@ -684,7 +715,7 @@ mod tests { DMatrix::from_columns(&[ sphere(1.0, 0.0, 0.0, a), sphere(-0.5, a, 0.0, a), - sphere(-0.5, -a, 0.0, a), + sphere(-0.5, -a, 0.0, a) ]) }; let state = SearchState::from_config(&gram, config); @@ -698,7 +729,7 @@ mod tests { fn frozen_entry_test() { let mut problem = ConstraintProblem::from_guess(&[ point(0.0, 0.0, 2.0), - sphere(0.0, 0.0, 0.0, 0.95), + sphere(0.0, 0.0, 0.0, 0.95) ]); for j in 0..2 { for k in j..2 { @@ -742,7 +773,7 @@ mod tests { 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), + sphere(0.0, 0.0, -1.0, 1.0) ]); for j in 0..3 { for k in j..3 { @@ -772,8 +803,8 @@ mod tests { DMatrix::::from_column_slice(UNIFORM_DIM, assembly_dim, &[ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, -0.5, - 0.0, 0.0, -0.5, 0.5, - ]), + 0.0, 0.0, -0.5, 0.5 + ]) ]; let tangent_motions_std = vec![ basis_matrix((0, 1), element_dim, assembly_dim), @@ -783,8 +814,8 @@ mod tests { DMatrix::::from_column_slice(element_dim, assembly_dim, &[ 0.0, 0.0, 0.0, 0.00, 0.0, 0.0, 0.0, -1.0, -0.25, -1.0, - 0.0, 0.0, -1.0, 0.25, 1.0, - ]), + 0.0, 0.0, -1.0, 0.25, 1.0 + ]) ]; // confirm that the dimension of the tangent space is no greater than @@ -860,10 +891,10 @@ mod tests { DVector::from_column_slice(&[0.0, 0.0, 5.0, 0.0]), DVector::from_column_slice(&[0.0, 0.0, 1.0, 0.0]), DVector::from_column_slice(&[-vel_vert_x, -vel_vert_y, -3.0, 0.0]), - DVector::from_column_slice(&[vel_vert_x, vel_vert_y, -3.0, 0.0]), + DVector::from_column_slice(&[vel_vert_x, vel_vert_y, -3.0, 0.0]) ] } - ).collect::>(), + ).collect::>() ]; let tangent_motions_std = tangent_motions_unif.iter().map( |motion| DMatrix::from_columns( @@ -896,7 +927,7 @@ mod tests { 0.0, 1.0, 0.0, 0.0, dis[1], 0.0, 0.0, 1.0, 0.0, dis[2], 2.0*dis[0], 2.0*dis[1], 2.0*dis[2], 1.0, dis.norm_squared(), - 0.0, 0.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0, 0.0, 1.0 ]) } @@ -908,7 +939,7 @@ mod tests { const SCALED_TOL: f64 = 1.0e-12; 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), + 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); @@ -926,13 +957,13 @@ mod tests { let a = 0.5 * FRAC_1_SQRT_2; DMatrix::from_columns(&[ sphere(a, 0.0, 7.0 + a, 1.0), - sphere(-a, 0.0, 7.0 - a, 1.0), + sphere(-a, 0.0, 7.0 - a, 1.0) ]) }; let problem_tfm = ConstraintProblem { gram: problem_orig.gram, - frozen: problem_orig.frozen, guess: guess_tfm, + frozen: problem_orig.frozen }; let Realization { result: result_tfm, history: history_tfm } = realize_gram( &problem_tfm, SCALED_TOL, 0.5, 0.9, 1.1, 200, 110 @@ -960,7 +991,7 @@ mod tests { 0.0, 1.0, 0.0, 0.0, 0.0, FRAC_1_SQRT_2, 0.0, FRAC_1_SQRT_2, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0, 0.0, 1.0 ]); let transl = translation(Vector3::new(0.0, 0.0, 7.0)); let motion_proj_tfm = transl * rot * motion_orig_proj; diff --git a/app-proto/src/components/identity.vert b/app-proto/src/identity.vert similarity index 100% rename from app-proto/src/components/identity.vert rename to app-proto/src/identity.vert diff --git a/app-proto/src/main.rs b/app-proto/src/main.rs index a03b026..f905c46 100644 --- a/app-proto/src/main.rs +++ b/app-proto/src/main.rs @@ -1,6 +1,9 @@ +mod add_remove; mod assembly; -mod components; +mod diagnostics; +mod display; mod engine; +mod outline; mod specified; #[cfg(test)] @@ -9,25 +12,23 @@ mod tests; use std::{collections::BTreeSet, rc::Rc}; use sycamore::prelude::*; +use add_remove::AddRemove; use assembly::{Assembly, Element}; -use components::{ - add_remove::AddRemove, - diagnostics::Diagnostics, - display::Display, - outline::Outline, -}; +use diagnostics::Diagnostics; +use display::Display; +use outline::Outline; #[derive(Clone)] struct AppState { assembly: Assembly, - selection: Signal>>, + selection: Signal>> } impl AppState { - fn new() -> Self { - Self { + fn new() -> AppState { + AppState { assembly: Assembly::new(), - selection: create_signal(BTreeSet::default()), + selection: create_signal(BTreeSet::default()) } } @@ -58,7 +59,7 @@ fn main() { provide_context(AppState::new()); view! { - div(id = "sidebar") { + div(id="sidebar") { AddRemove {} Outline {} Diagnostics {} diff --git a/app-proto/src/components/outline.rs b/app-proto/src/outline.rs similarity index 76% rename from app-proto/src/components/outline.rs rename to app-proto/src/outline.rs index 5355042..77d8575 100644 --- a/app-proto/src/components/outline.rs +++ b/app-proto/src/outline.rs @@ -1,7 +1,11 @@ use itertools::Itertools; use std::rc::Rc; use sycamore::prelude::*; -use web_sys::{KeyboardEvent, MouseEvent, wasm_bindgen::JsCast}; +use web_sys::{ + KeyboardEvent, + MouseEvent, + wasm_bindgen::JsCast +}; use crate::{ AppState, @@ -9,7 +13,7 @@ use crate::{ Element, HalfCurvatureRegulator, InversiveDistanceRegulator, - Regulator, + Regulator }, specified::SpecifiedValue }; @@ -45,8 +49,8 @@ fn RegulatorInput(regulator: Rc) -> View { view! { input( - r#type = "text", - class = move || { + r#type="text", + class=move || { if valid.get() { set_point.with(|set_pt| { if set_pt.is_present() { @@ -59,27 +63,27 @@ fn RegulatorInput(regulator: Rc) -> View { "regulator-input invalid" } }, - placeholder = measurement.with(|result| result.to_string()), - bind:value = value, - on:change = move |_| { + 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) => { set_point.set(set_pt); true - }, - Err(_) => false, + } + Err(_) => false } ) }, - on:keydown = { + on:keydown={ move |event: KeyboardEvent| { match event.key().as_str() { "Escape" => reset_value(), - _ => (), + _ => () } } - }, + } ) } } @@ -96,11 +100,11 @@ impl OutlineItem for InversiveDistanceRegulator { self.subjects[0].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") + li(class="regulator") { + div(class="regulator-label") { (other_subject_label) } + div(class="regulator-type") { "Inversive distance" } + RegulatorInput(regulator=self) + div(class="status") } } } @@ -109,11 +113,11 @@ impl OutlineItem for InversiveDistanceRegulator { impl OutlineItem for HalfCurvatureRegulator { fn outline_item(self: Rc, _element: &Rc) -> View { view! { - li(class = "regulator") { - div(class = "regulator-label") // for spacing - div(class = "regulator-type") { "Half-curvature" } - RegulatorInput(regulator = self) - div(class = "status") + li(class="regulator") { + div(class="regulator-label") // for spacing + div(class="regulator-type") { "Half-curvature" } + RegulatorInput(regulator=self) + div(class="status") } } } @@ -152,10 +156,10 @@ fn ElementOutlineItem(element: Rc) -> View { let details_node = create_node_ref(); view! { li { - details(ref = details_node) { + details(ref=details_node) { summary( - class = class.get(), - on:keydown = { + class=class.get(), + on:keydown={ let element_for_handler = element.clone(); move |event: KeyboardEvent| { match event.key().as_str() { @@ -175,18 +179,18 @@ fn ElementOutlineItem(element: Rc) -> View { .unchecked_into::() .remove_attribute("open"); }, - _ => (), + _ => () } } } ) { div( - class = "element-switch", - on:click = |event: MouseEvent| event.stop_propagation() + class="element-switch", + on:click=|event: MouseEvent| event.stop_propagation() ) div( - class = "element", - on:click = { + class="element", + on:click={ let state_for_handler = state.clone(); let element_for_handler = element.clone(); move |event: MouseEvent| { @@ -196,20 +200,20 @@ fn ElementOutlineItem(element: Rc) -> View { } } ) { - div(class = "element-label") { (label) } - div(class = "element-representation") { (rep_components) } + div(class="element-label") { (label) } + div(class="element-representation") { (rep_components) } input( - r#type = "checkbox", - bind:checked = element.ghost(), - on:click = |event: MouseEvent| event.stop_propagation() + r#type="checkbox", + bind:checked=element.ghost(), + on:click=|event: MouseEvent| event.stop_propagation() ) } } - ul(class = "regulators") { + ul(class="regulators") { Keyed( - list = regulator_list, - view = move |reg| reg.outline_item(&element), - key = |reg| reg.serial() + list=regulator_list, + view=move |reg| reg.outline_item(&element), + key=|reg| reg.serial() ) } } @@ -242,18 +246,18 @@ pub fn Outline() -> View { view! { ul( - id = "outline", - on:click = { + id="outline", + on:click={ let state = use_context::(); move |_| state.selection.update(|sel| sel.clear()) } ) { Keyed( - list = element_list, - view = |elt| view! { - ElementOutlineItem(element = elt) + list=element_list, + view=|elt| view! { + ElementOutlineItem(element=elt) }, - key = |elt| elt.serial() + key=|elt| elt.serial() ) } } diff --git a/app-proto/src/components/point.frag b/app-proto/src/point.frag similarity index 100% rename from app-proto/src/components/point.frag rename to app-proto/src/point.frag diff --git a/app-proto/src/components/point.vert b/app-proto/src/point.vert similarity index 100% rename from app-proto/src/components/point.vert rename to app-proto/src/point.vert diff --git a/app-proto/src/specified.rs b/app-proto/src/specified.rs index 788460b..cfe7fc3 100644 --- a/app-proto/src/specified.rs +++ b/app-proto/src/specified.rs @@ -13,12 +13,12 @@ use std::num::ParseFloatError; #[readonly::make] pub struct SpecifiedValue { pub spec: String, - pub value: Option, + pub value: Option } impl SpecifiedValue { - pub fn from_empty_spec() -> Self { - Self { spec: String::new(), value: None } + pub fn from_empty_spec() -> SpecifiedValue { + SpecifiedValue { spec: String::new(), value: None } } pub fn is_present(&self) -> bool { @@ -34,10 +34,10 @@ impl TryFrom for SpecifiedValue { fn try_from(spec: String) -> Result { if spec.is_empty() { - Ok(Self::from_empty_spec()) + Ok(SpecifiedValue::from_empty_spec()) } else { spec.parse::().map( - |value| Self { spec, value: Some(value) } + |value| SpecifiedValue { spec: spec, value: Some(value) } ) } } diff --git a/app-proto/src/components/spheres.frag b/app-proto/src/spheres.frag similarity index 100% rename from app-proto/src/components/spheres.frag rename to app-proto/src/spheres.frag diff --git a/deploy/.gitignore b/deploy/.gitignore deleted file mode 100644 index 192f529..0000000 --- a/deploy/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/dyna3.zip -/dyna3/index.html -/dyna3/dyna3-*.js -/dyna3/dyna3-*.wasm -/dyna3/main-*.css \ No newline at end of file diff --git a/tools/package-for-deployment.sh b/tools/package-for-deployment.sh deleted file mode 100644 index fdda434..0000000 --- a/tools/package-for-deployment.sh +++ /dev/null @@ -1,16 +0,0 @@ -# set paths. this technique for getting the script location comes from -# `mklement0` on Stack Overflow -# -# https://stackoverflow.com/a/24114056 -# -TOOLS=$(dirname -- $0) -SRC="$TOOLS/../app-proto/dist" -DEST="$TOOLS/../deploy/dyna3" - -# remove the old hash-named files -[ -e "$DEST"/dyna3-*.js ] && rm "$DEST"/dyna3-*.js -[ -e "$DEST"/dyna3-*.wasm ] && rm "$DEST"/dyna3-*.wasm -[ -e "$DEST"/main-*.css ] && rm "$DEST"/main-*.css - -# copy the distribution -cp -r "$SRC/." "$DEST"