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/assembly.rs b/app-proto/src/assembly.rs index 94e7b3c..68fcd8b 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -1,13 +1,13 @@ 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 */ @@ -16,6 +16,7 @@ use crate::{ components::{display::DisplayItem, outline::OutlineItem}, engine::{ Q, + change_half_curvature, local_unif_to_std, point, project_point_to_normalized, @@ -26,9 +27,9 @@ use crate::{ ConfigSubspace, ConstraintProblem, DescentHistory, - Realization, + Realization }, - specified::SpecifiedValue, + specified::SpecifiedValue }; pub type ElementColor = [f32; 3]; @@ -164,7 +165,7 @@ pub struct Sphere { pub ghost: Signal, pub regulators: Signal>>, serial: u64, - column_index: Cell>, + column_index: Cell> } impl Sphere { @@ -174,17 +175,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 +195,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 +265,7 @@ pub struct Point { pub ghost: Signal, pub regulators: Signal>>, serial: u64, - column_index: Cell>, + column_index: Cell> } impl Point { @@ -274,9 +275,9 @@ impl Point { id: String, label: String, color: ElementColor, - representation: DVector, - ) -> Self { - Self { + representation: DVector + ) -> Point { + Point { id, label, color, @@ -284,7 +285,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 +295,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 +349,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 +358,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 +400,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 +417,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 +460,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 +472,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 +488,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 +524,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>; @@ -529,11 +552,12 @@ pub struct Assembly { pub elements_by_id: Signal>>, // realization control - pub realization_trigger: Signal<()>, + pub keep_realized: Signal, + pub needs_realization: Signal, // realization diagnostics pub realization_status: Signal>, - pub descent_history: Signal, + pub descent_history: Signal } impl Assembly { @@ -544,23 +568,21 @@ impl Assembly { regulators: create_signal(BTreeSet::new()), tangent: create_signal(ConfigSubspace::zero(0)), elements_by_id: create_signal(BTreeMap::default()), - realization_trigger: create_signal(()), + keep_realized: create_signal(true), + needs_realization: create_signal(false), realization_status: create_signal(Ok(())), - descent_history: create_signal(DescentHistory::new()), + 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 + // realize the assembly whenever it becomes simultaneously true that + // we're trying to keep it realized and it needs realization 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(); + let should_realize = assembly_for_effect.keep_realized.get() + && assembly_for_effect.needs_realization.get(); + if should_realize { + assembly_for_effect.realize(); + } }); assembly @@ -624,6 +646,19 @@ impl Assembly { regulators.update(|regs| regs.insert(regulator.clone())); } + // request a 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.needs_realization.set(true); + } + }); + /* DEBUG */ // print an updated list of regulators console_log!("Regulators:"); @@ -691,10 +726,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); @@ -717,6 +750,9 @@ impl Assembly { // save the tangent space self.tangent.set_silent(tangent); + + // clear the realization request flag + self.needs_realization.set(false); }, Err(message) => { // report the realization status. the `Err(message)` we're @@ -724,7 +760,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 +843,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 + // request 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(()); + self.needs_realization.set(true); } } @@ -867,7 +903,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 +917,7 @@ mod tests { vec![ ElementMotion { element: sphere.clone(), - velocity: velocity.as_view(), + velocity: velocity.as_view() } ] ); diff --git a/app-proto/src/components/add_remove.rs b/app-proto/src/components/add_remove.rs index 4196640..3b0f9e0 100644 --- a/app-proto/src/components/add_remove.rs +++ b/app-proto/src/components/add_remove.rs @@ -4,47 +4,32 @@ use sycamore::prelude::*; use super::test_assembly_chooser::TestAssemblyChooser; use crate::{ AppState, - assembly::{InversiveDistanceRegulator, Point, Sphere}, + assembly::{InversiveDistanceRegulator, Point, Sphere} }; #[component] pub fn AddRemove() -> View { view! { - div(id = "add-remove") { + div(id="add-remove") { button( - on:click = |_| { + 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::(); - }); + state.assembly.insert_element_default::(); } ) { "Add sphere" } button( - on:click = |_| { + 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 = { + 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 = |_| { + on:click=|_| { let state = use_context::(); let subjects: [_; 2] = state.selection.with( // the button is only enabled when two elements are diff --git a/app-proto/src/components/diagnostics.rs b/app-proto/src/components/diagnostics.rs index e265982..a2f090a 100644 --- a/app-proto/src/components/diagnostics.rs +++ b/app-proto/src/components/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/components/display.rs index da921dd..1646c4e 100644 --- a/app-proto/src/components/display.rs +++ b/app-proto/src/components/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/components/outline.rs b/app-proto/src/components/outline.rs index 5355042..77d8575 100644 --- a/app-proto/src/components/outline.rs +++ b/app-proto/src/components/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/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 0d387d3..232cda3 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -6,17 +6,17 @@ use web_sys::{console, wasm_bindgen::JsValue}; use crate::{ AppState, + engine, + engine::DescentHistory, assembly::{ Assembly, Element, ElementColor, InversiveDistanceRegulator, Point, - Sphere, + Sphere }, - engine, - engine::DescentHistory, - specified::SpecifiedValue, + specified::SpecifiedValue }; // --- loaders --- @@ -26,13 +26,13 @@ use crate::{ // 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) { +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), + engine::sphere(0.5, 0.5, 0.0, 1.0) ) ); let _ = assembly.try_insert_element( @@ -40,7 +40,7 @@ fn load_general(assembly: &Assembly) { 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), + engine::sphere(-0.5, -0.5, 0.0, 1.0) ) ); let _ = assembly.try_insert_element( @@ -48,7 +48,7 @@ fn load_general(assembly: &Assembly) { 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), + engine::sphere(-0.5, 0.5, 0.0, 0.75) ) ); let _ = assembly.try_insert_element( @@ -56,7 +56,7 @@ fn load_general(assembly: &Assembly) { 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), + engine::sphere(0.5, -0.5, 0.0, 0.5) ) ); let _ = assembly.try_insert_element( @@ -64,7 +64,7 @@ fn load_general(assembly: &Assembly) { 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), + engine::sphere(0.0, 0.15, 1.0, 0.25) ) ); let _ = assembly.try_insert_element( @@ -72,12 +72,12 @@ fn load_general(assembly: &Assembly) { 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), + engine::sphere(0.0, -0.15, -1.0, 0.25) ) ); } -fn load_low_curvature(assembly: &Assembly) { +fn load_low_curv_assemb(assembly: &Assembly) { // create the spheres let a = 0.75_f64.sqrt(); let _ = assembly.try_insert_element( @@ -85,7 +85,7 @@ fn load_low_curvature(assembly: &Assembly) { "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), + engine::sphere(0.0, 0.0, 0.0, 1.0) ) ); let _ = assembly.try_insert_element( @@ -93,7 +93,7 @@ fn load_low_curvature(assembly: &Assembly) { "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), + engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0) ) ); let _ = assembly.try_insert_element( @@ -101,7 +101,7 @@ fn load_low_curvature(assembly: &Assembly) { "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), + engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0) ) ); let _ = assembly.try_insert_element( @@ -109,7 +109,7 @@ fn load_low_curvature(assembly: &Assembly) { "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), + engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0) ) ); let _ = assembly.try_insert_element( @@ -117,7 +117,7 @@ fn load_low_curvature(assembly: &Assembly) { "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), + engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0) ) ); let _ = assembly.try_insert_element( @@ -125,7 +125,7 @@ fn load_low_curvature(assembly: &Assembly) { "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), + engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0) ) ); let _ = assembly.try_insert_element( @@ -133,7 +133,7 @@ fn load_low_curvature(assembly: &Assembly) { "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), + engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0) ) ); let _ = assembly.try_insert_element( @@ -141,7 +141,7 @@ fn load_low_curvature(assembly: &Assembly) { 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), + engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0) ) ); @@ -196,13 +196,13 @@ fn load_low_curvature(assembly: &Assembly) { } } -fn load_pointed(assembly: &Assembly) { +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), + engine::point(0.0, 0.0, FRAC_1_SQRT_2) ) ); let _ = assembly.try_insert_element( @@ -210,7 +210,7 @@ fn load_pointed(assembly: &Assembly) { 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), + engine::point(0.0, 0.0, -FRAC_1_SQRT_2) ) ); for index_x in 0..=1 { @@ -223,7 +223,7 @@ fn load_pointed(assembly: &Assembly) { 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), + engine::sphere(x, y, 0.0, 1.0) ) ); @@ -232,7 +232,7 @@ fn load_pointed(assembly: &Assembly) { 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), + engine::point(x, y, 0.0) ) ); } @@ -246,7 +246,7 @@ fn load_pointed(assembly: &Assembly) { // B-C " // C-C " // A-C -0.25 * φ^2 = -0.6545084971874737 -fn load_tridiminished_icosahedron(assembly: &Assembly) { +fn load_tridim_icosahedron_assemb(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]; @@ -256,56 +256,56 @@ fn load_tridiminished_icosahedron(assembly: &Assembly) { "a1".to_string(), "A₁".to_string(), COLOR_A, - engine::point(0.25, 0.75, 0.75), + 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), + 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), + 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), + 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), + 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), + 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), + 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), + 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), - ), + engine::point(-1.0, -1.0, 0.0) + ) ]; for vertex in vertices { let _ = assembly.try_insert_element(vertex); @@ -320,20 +320,20 @@ fn load_tridiminished_icosahedron(assembly: &Assembly) { "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), + 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), + 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), - ), + 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); @@ -409,14 +409,14 @@ fn load_tridiminished_icosahedron(assembly: &Assembly) { // 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) { +fn load_dodeca_packing_assemb(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), + engine::sphere(0.0, 0.0, 0.0, 1.0) ) ); let substrate = assembly.elements_by_id.with_untracked( @@ -456,7 +456,7 @@ fn load_dodecahedral_packing(assembly: &Assembly) { id_a.clone(), format!("A{label_sub}"), COLOR_A, - engine::sphere(0.0, small_coord, big_coord, face_radii[k]), + engine::sphere(0.0, small_coord, big_coord, face_radii[k]) ) ); faces.push( @@ -472,7 +472,7 @@ fn load_dodecahedral_packing(assembly: &Assembly) { id_b.clone(), format!("B{label_sub}"), COLOR_B, - engine::sphere(small_coord, big_coord, 0.0, face_radii[k]), + engine::sphere(small_coord, big_coord, 0.0, face_radii[k]) ) ); faces.push( @@ -488,7 +488,7 @@ fn load_dodecahedral_packing(assembly: &Assembly) { id_c.clone(), format!("C{label_sub}"), COLOR_C, - engine::sphere(big_coord, 0.0, small_coord, face_radii[k]), + engine::sphere(big_coord, 0.0, small_coord, face_radii[k]) ) ); faces.push( @@ -550,7 +550,7 @@ fn load_dodecahedral_packing(assembly: &Assembly) { // 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) { +fn load_balanced_assemb(assembly: &Assembly) { // create the spheres const R_OUTER: f64 = 10.0; const R_INNER: f64 = 4.0; @@ -559,19 +559,19 @@ fn load_balanced(assembly: &Assembly) { "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), + 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), + 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), + engine::sphere(0.0, -4.0, 0.0, R_INNER) ), ]; for sphere in spheres { @@ -589,7 +589,7 @@ fn load_balanced(assembly: &Assembly) { for (sphere, radius) in [ (outer.clone(), R_OUTER), (a.clone(), R_INNER), - (b.clone(), R_INNER), + (b.clone(), R_INNER) ] { let curvature_regulator = sphere.regulators().with_untracked( |regs| regs.first().unwrap().clone() @@ -611,14 +611,14 @@ fn load_balanced(assembly: &Assembly) { // 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) { +fn load_off_center_assemb(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), + engine::point(1e-9, 0.0, 0.0) ), ); let _ = assembly.try_insert_element( @@ -626,7 +626,7 @@ fn load_off_center(assembly: &Assembly) { "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), + engine::sphere(0.0, 0.0, 0.0, 1.0) ), ); @@ -648,7 +648,7 @@ fn load_off_center(assembly: &Assembly) { // 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) { +fn load_radius_ratio_assemb(assembly: &Assembly) { let index_range = 1..=4; // create the spheres @@ -658,14 +658,14 @@ fn load_radius_ratio(assembly: &Assembly) { "sphere_faces".to_string(), "Insphere".to_string(), GRAY, - engine::sphere(0.0, 0.0, 0.0, 0.5), + 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), - ), + engine::sphere(0.0, 0.0, 0.0, 0.25) + ) ]; for sphere in spheres { let _ = assembly.try_insert_element(sphere); @@ -678,13 +678,13 @@ fn load_radius_ratio(assembly: &Assembly) { [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], + [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), + engine::point(0.6, 0.8, -0.6) ].into_iter() ).map( |(k, color, representation)| { @@ -692,7 +692,7 @@ fn load_radius_ratio(assembly: &Assembly) { format!("v{k}"), format!("Vertex {k}"), color, - representation, + representation ) } ); @@ -709,13 +709,13 @@ fn load_radius_ratio(assembly: &Assembly) { [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], + [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), + engine::sphere_with_offset(-base_dir[0], -base_dir[1], base_dir[2], offset, 0.0) ].into_iter() ).map( |(k, color, representation)| { @@ -723,7 +723,7 @@ fn load_radius_ratio(assembly: &Assembly) { format!("f{k}"), format!("Face {k}"), color, - representation, + representation ) } ); @@ -736,7 +736,7 @@ fn load_radius_ratio(assembly: &Assembly) { for j in index_range.clone() { let [face_j, vertex_j] = [ format!("f{j}"), - format!("v{j}"), + format!("v{j}") ].map( |id| assembly.elements_by_id.with_untracked( |elts_by_id| elts_by_id[&id].clone() @@ -789,7 +789,7 @@ fn load_radius_ratio(assembly: &Assembly) { // 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) { +fn load_irisawa_hexlet_assemb(assembly: &Assembly) { let index_range = 1..=6; let colors = [ [1.00_f32, 0.00_f32, 0.25_f32], @@ -797,7 +797,7 @@ fn load_irisawa_hexlet(assembly: &Assembly) { [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], + [0.25_f32, 0.00_f32, 1.00_f32] ].into_iter(); // create the spheres @@ -806,19 +806,19 @@ fn load_irisawa_hexlet(assembly: &Assembly) { "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), + 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), + 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), + engine::sphere(0.0, 0.75, 0.0, 0.75) ), ].into_iter().chain( index_range.clone().zip(colors).map( @@ -828,7 +828,7 @@ fn load_irisawa_hexlet(assembly: &Assembly) { format!("chain{k}"), format!("Chain {k}"), color, - engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5), + engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5) ) } ) @@ -865,7 +865,7 @@ fn load_irisawa_hexlet(assembly: &Assembly) { (outer.clone(), "1"), (sun.clone(), "-1"), (moon.clone(), "-1"), - (chain_sphere_next.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()); @@ -900,6 +900,9 @@ pub fn TestAssemblyChooser() -> View { let state = use_context::(); let assembly = &state.assembly; + // pause realization + assembly.keep_realized.set(false); + // clear state assembly.regulators.update(|regs| regs.clear()); assembly.elements.update(|elts| elts.clear()); @@ -909,33 +912,36 @@ pub fn TestAssemblyChooser() -> View { // 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), - _ => (), + "general" => load_gen_assemb(assembly), + "low-curv" => load_low_curv_assemb(assembly), + "pointed" => load_pointed_assemb(assembly), + "tridim-icosahedron" => load_tridim_icosahedron_assemb(assembly), + "dodeca-packing" => load_dodeca_packing_assemb(assembly), + "balanced" => load_balanced_assemb(assembly), + "off-center" => load_off_center_assemb(assembly), + "radius-ratio" => load_radius_ratio_assemb(assembly), + "irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly), + _ => () }; + + // resume realization + assembly.keep_realized.set(true); }); }); // 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" } + select(bind:value=assembly_name) { + option(value="general") { "General" } + option(value="low-curv") { "Low-curvature" } + option(value="pointed") { "Pointed" } + option(value="tridim-icosahedron") { "Tridiminished icosahedron" } + option(value="dodeca-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/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/main.rs b/app-proto/src/main.rs index a03b026..152d11c 100644 --- a/app-proto/src/main.rs +++ b/app-proto/src/main.rs @@ -14,20 +14,20 @@ use components::{ add_remove::AddRemove, diagnostics::Diagnostics, display::Display, - outline::Outline, + 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 +58,7 @@ fn main() { provide_context(AppState::new()); view! { - div(id = "sidebar") { + div(id="sidebar") { AddRemove {} Outline {} Diagnostics {} 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/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"