diff --git a/README.md b/README.md index ac2771b..3a29eb0 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ Note that currently this is just the barest beginnings of the project, more of a ### Implementation goals -* Provide a comfortable, intuitive UI +* Comfortable, intuitive UI -* Allow execution in browser (so implemented in WASM-compatible language) +* Able to run in browser (so implemented in WASM-compatible language) -* Produce scalable graphics of 3D diagrams, and maybe STL files (or other fabricatable file format) as well +* Produce scalable graphics of 3D diagrams, and maybe STL files (or other fabricatable file format) as well. ## Prototype @@ -24,40 +24,33 @@ 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). -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. -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. - - In the future, `trunk` can be updated with the same command. (You may need the `--locked` flag if your ambient version of `rustc` does not match that required by `trunk`.) +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) +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 +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. - - Alternatively, if you don't want to adjust your `PATH`, you can install `trunk` in another directory `DIR` via `cargo install --root DIR trunk`. + * 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. -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. -4. Press *ctrl+C* in the shell where Trunk is running to stop serving 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. +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* +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 execute +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") @@ -66,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. +1. Go into the `app-proto` folder +2. Call `cargo test` diff --git a/app-proto/Cargo.lock b/app-proto/Cargo.lock index 731dd84..4f75c45 100644 --- a/app-proto/Cargo.lock +++ b/app-proto/Cargo.lock @@ -255,7 +255,6 @@ dependencies = [ "charming", "console_error_panic_hook", "dyna3", - "enum-iterator", "itertools", "js-sys", "lazy_static", @@ -272,26 +271,6 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -[[package]] -name = "enum-iterator" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4549325971814bda7a44061bf3fe7e487d447cba01e4220a4b454d630d7a016" -dependencies = [ - "enum-iterator-derive", -] - -[[package]] -name = "enum-iterator-derive" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "equivalent" version = "1.0.1" diff --git a/app-proto/Cargo.toml b/app-proto/Cargo.toml index d5221a1..1230b47 100644 --- a/app-proto/Cargo.toml +++ b/app-proto/Cargo.toml @@ -10,7 +10,6 @@ default = ["console_error_panic_hook"] dev = [] [dependencies] -enum-iterator = "2.3.0" itertools = "0.13.0" js-sys = "0.3.70" lazy_static = "1.5.0" 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/main.css b/app-proto/main.css index a00d309..7981285 100644 --- a/app-proto/main.css +++ b/app-proto/main.css @@ -184,7 +184,6 @@ details[open]:has(li) .element-switch::after { #diagnostics-bar { display: flex; - gap: 8px; } #realization-status { @@ -208,14 +207,6 @@ details[open]:has(li) .element-switch::after { content: '⚠'; } -#step-input > label { - padding-right: 4px; -} - -#step-input > input { - width: 45px; -} - .diagnostics-panel { margin-top: 10px; min-height: 180px; 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 0264b75..43066fd 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -1,11 +1,10 @@ -use enum_iterator::{all, Sequence}; use nalgebra::{DMatrix, DVector, DVectorView}; use std::{ cell::Cell, cmp::Ordering, collections::{BTreeMap, BTreeSet}, fmt, - fmt::{Debug, Display, Formatter}, + fmt::{Debug, Formatter}, hash::{Hash, Hasher}, rc::Rc, sync::{atomic, atomic::AtomicU64}, @@ -27,7 +26,6 @@ use crate::{ ConfigSubspace, ConstraintProblem, DescentHistory, - MatrixEntry, Realization, }, specified::SpecifiedValue, @@ -86,14 +84,6 @@ impl Ord for dyn Serial { } } -// Small helper function to generate consistent errors when there -// are indexing issues in a ProblemPoser -fn indexing_error(item: &str, name: &str, actor: &str) -> String { - format!( - "{item} \"{name}\" must be indexed before {actor} writes problem data" - ) -} - pub trait ProblemPoser { fn pose(&self, problem: &mut ConstraintProblem); } @@ -135,8 +125,8 @@ pub trait Element: Serial + ProblemPoser + DisplayItem { } impl Debug for dyn Element { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - Debug::fmt(&self.id(), f) + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + self.id().fmt(f) } } @@ -185,8 +175,8 @@ impl Sphere { label: String, color: ElementColor, representation: DVector, - ) -> Self { - Self { + ) -> Sphere { + Sphere { id, label, color, @@ -204,8 +194,8 @@ 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], @@ -259,7 +249,8 @@ impl Serial for Sphere { impl ProblemPoser for Sphere { fn pose(&self, problem: &mut ConstraintProblem) { let index = self.column_index().expect( - indexing_error("Sphere", &self.id, "it").as_str()); + format!("Sphere \"{}\" should be indexed before writing problem data", self.id).as_str() + ); problem.gram.push_sym(index, index, 1.0); problem.guess.set_column(index, &self.representation.get_clone_untracked()); } @@ -278,15 +269,14 @@ pub struct Point { impl Point { const WEIGHT_COMPONENT: usize = 3; - const NORM_COMPONENT: usize = 4; pub fn new( id: String, label: String, color: ElementColor, representation: DVector, - ) -> Self { - Self { + ) -> Point { + Point { id, label, color, @@ -304,23 +294,14 @@ 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), ) } - - fn default_regulators(self: Rc) -> Vec> { - all::() - .map(|axis| { - Rc::new(PointCoordinateRegulator::new(self.clone(), axis)) - as Rc:: - }) - .collect() - } fn id(&self) -> &String { &self.id @@ -364,9 +345,10 @@ impl Serial for Point { impl ProblemPoser for Point { fn pose(&self, problem: &mut ConstraintProblem) { let index = self.column_index().expect( - indexing_error("Point", &self.id, "it").as_str()); + 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()); } } @@ -411,7 +393,7 @@ pub struct InversiveDistanceRegulator { } 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| @@ -424,7 +406,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 } } } @@ -454,8 +436,8 @@ impl ProblemPoser for InversiveDistanceRegulator { if let Some(val) = set_pt.value { let [row, col] = self.subjects.each_ref().map( |subj| subj.column_index().expect( - indexing_error("Subject", subj.id(), - "inversive distance regulator").as_str()) + "Subjects should be indexed before inversive distance regulator writes problem data" + ) ); problem.gram.push_sym(row, col, val); } @@ -464,14 +446,14 @@ impl ProblemPoser for InversiveDistanceRegulator { } pub struct HalfCurvatureRegulator { - pub subject: Rc, + pub subject: Rc, pub measurement: ReadSignal, pub set_point: Signal, 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] ); @@ -479,7 +461,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 } } } @@ -508,85 +490,14 @@ impl ProblemPoser for HalfCurvatureRegulator { self.set_point.with_untracked(|set_pt| { if let Some(val) = set_pt.value { let col = self.subject.column_index().expect( - indexing_error("Subject", &self.subject.id, - "half-curvature regulator").as_str()); + "Subject should be indexed before half-curvature regulator writes problem data" + ); problem.frozen.push(Sphere::CURVATURE_COMPONENT, col, val); } }); } } -#[derive(Clone, Copy, Sequence)] -pub enum Axis { X = 0, Y = 1, Z = 2 } - -impl Axis { - fn name(&self) -> &'static str { - match self { Axis::X => "X", Axis::Y => "Y", Axis::Z => "Z" } - } -} - -impl Display for Axis { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.name()) - } -} - -pub struct PointCoordinateRegulator { - pub subject: Rc, - pub axis: Axis, - pub measurement: ReadSignal, - pub set_point: Signal, - serial: u64 -} - -impl PointCoordinateRegulator { - pub fn new(subject: Rc, axis: Axis) -> Self { - let measurement = subject.representation().map( - move |rep| rep[axis as usize] - ); - let set_point = create_signal(SpecifiedValue::from_empty_spec()); - Self { subject, axis, measurement, set_point, serial: Self::next_serial() } - } -} - -impl Serial for PointCoordinateRegulator { - fn serial(&self) -> u64 { self.serial } -} - -impl Regulator for PointCoordinateRegulator { - fn subjects(&self) -> Vec> { vec![self.subject.clone()] } - fn measurement(&self) -> ReadSignal { self.measurement } - fn set_point(&self) -> Signal { self.set_point } -} - -impl ProblemPoser for PointCoordinateRegulator { - fn pose(&self, problem: &mut ConstraintProblem) { - self.set_point.with_untracked(|set_pt| { - if let Some(val) = set_pt.value { - let col = self.subject.column_index().expect( - indexing_error("Subject", &self.subject.id, - "point-coordinate regulator").as_str()); - problem.frozen.push(self.axis as usize, col, val); - // If all three of the subject's spatial coordinates have been - // frozen, then freeze its norm component: - let mut coords = [0.0; Axis::CARDINALITY]; - let mut nset: usize = 0; - for &MatrixEntry {index, value} in &(problem.frozen) { - if index.1 == col && index.0 < Axis::CARDINALITY { - nset += 1; - coords[index.0] = value - } - } - if nset == Axis::CARDINALITY { - let [x, y, z] = coords; - problem.frozen.push( - Point::NORM_COMPONENT, col, point(x,y,z)[Point::NORM_COMPONENT]); - } - } - }); - } -} - // the velocity is expressed in uniform coordinates pub struct ElementMotion<'a> { pub element: Rc, @@ -623,7 +534,6 @@ pub struct Assembly { // realization diagnostics pub realization_status: Signal>, pub descent_history: Signal, - pub step: Signal, } impl Assembly { @@ -637,33 +547,20 @@ impl Assembly { realization_trigger: create_signal(()), realization_status: create_signal(Ok(())), descent_history: create_signal(DescentHistory::new()), - step: create_signal(SpecifiedValue::from_empty_spec()), }; // realize the assembly whenever the element list, the regulator list, // a regulator's set point, or the realization trigger is updated - let assembly_for_realization = assembly.clone(); + let assembly_for_effect = assembly.clone(); create_effect(move || { - assembly_for_realization.elements.track(); - assembly_for_realization.regulators.with( + assembly_for_effect.elements.track(); + assembly_for_effect.regulators.with( |regs| for reg in regs { reg.set_point().track(); } ); - assembly_for_realization.realization_trigger.track(); - assembly_for_realization.realize(); - }); - - // load a configuration from the descent history whenever the active - // step is updated - let assembly_for_step_selection = assembly.clone(); - create_effect(move || { - if let Some(step) = assembly.step.with(|n| n.value) { - let config = assembly.descent_history.with_untracked( - |history| history.config[step as usize].clone() - ); - assembly_for_step_selection.load_config(&config) - } + assembly_for_effect.realization_trigger.track(); + assembly_for_effect.realize(); }); assembly @@ -750,16 +647,6 @@ impl Assembly { }); } - // --- updating the configuration --- - - pub fn load_config(&self, config: &DMatrix) { - for elt in self.elements.get_clone_untracked() { - elt.representation().update( - |rep| rep.set_column(0, &config.column(elt.column_index().unwrap())) - ); - } - } - // --- realization --- pub fn realize(&self) { @@ -787,7 +674,6 @@ impl Assembly { /* DEBUG */ // log the Gram matrix console_log!("Gram matrix:\n{}", problem.gram); - console_log!("Frozen entries:\n{}", problem.frozen); /* DEBUG */ // log the initial configuration matrix @@ -810,12 +696,11 @@ impl Assembly { console_log!("Loss: {}", history.scaled_loss.last().unwrap()); } - // report the descent history - let step_cnt = history.config.len(); + // report the loss history self.descent_history.set(history); match result { - Ok(ConfigNeighborhood { nbhd: tangent, .. }) => { + Ok(ConfigNeighborhood { config, nbhd: tangent }) => { /* DEBUG */ // report the tangent dimension console_log!("Tangent dimension: {}", tangent.dim()); @@ -823,15 +708,12 @@ impl Assembly { // report the realization status self.realization_status.set(Ok(())); - // display the last realization step - self.step.set( - if step_cnt > 0 { - let last_step = step_cnt - 1; - SpecifiedValue::try_from(last_step.to_string()).unwrap() - } else { - SpecifiedValue::from_empty_spec() - } - ); + // read out the solution + for elt in self.elements.get_clone_untracked() { + elt.representation().update( + |rep| rep.set_column(0, &config.column(elt.column_index().unwrap())) + ); + } // save the tangent space self.tangent.set_silent(tangent); @@ -841,10 +723,7 @@ impl Assembly { // setting the status to has a different type than the // `Err(message)` we received from the match: we're changing the // `Ok` type from `Realization` to `()` - self.realization_status.set(Err(message)); - - // display the initial guess - self.step.set(SpecifiedValue::from(Some(0.0))); + self.realization_status.set(Err(message)) }, } } @@ -947,8 +826,7 @@ mod tests { use crate::engine; #[test] - #[should_panic(expected = - "Sphere \"sphere\" must be indexed before it writes problem data")] + #[should_panic(expected = "Sphere \"sphere\" should be indexed before writing problem data")] fn unindexed_element_test() { let _ = create_root(|| { let elt = Sphere::default("sphere".to_string(), 0); @@ -957,8 +835,7 @@ mod tests { } #[test] - #[should_panic(expected = "Subject \"sphere1\" must be indexed before \ - inversive distance regulator writes problem data")] + #[should_panic(expected = "Subjects should be indexed before inversive distance regulator writes problem data")] fn unindexed_subject_test_inversive_distance() { let _ = create_root(|| { let subjects = [0, 1].map( @@ -1019,4 +896,4 @@ mod tests { assert!((final_half_curv / INITIAL_HALF_CURV - 1.0).abs() < DRIFT_TOL); }); } -} +} \ No newline at end of file diff --git a/app-proto/src/components/diagnostics.rs b/app-proto/src/components/diagnostics.rs index 51d58f1..b274dca 100644 --- a/app-proto/src/components/diagnostics.rs +++ b/app-proto/src/components/diagnostics.rs @@ -7,7 +7,7 @@ use charming::{ }; use sycamore::prelude::*; -use crate::{AppState, specified::SpecifiedValue}; +use crate::AppState; #[derive(Clone)] struct DiagnosticsState { @@ -15,8 +15,8 @@ struct DiagnosticsState { } 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) } } } @@ -48,69 +48,6 @@ fn RealizationStatus() -> View { } } -// history step input -#[component] -fn StepInput() -> View { - // get the assembly - let state = use_context::(); - let assembly = state.assembly; - - // the `last_step` signal holds the index of the last step - let last_step = assembly.descent_history.map( - |history| match history.config.len() { - 0 => None, - n => Some(n - 1), - } - ); - let input_max = last_step.map(|last| last.unwrap_or(0)); - - // these signals hold the entered step number - let value = create_signal(String::new()); - let value_as_number = create_signal(0.0); - - create_effect(move || { - value.set(assembly.step.with(|n| n.spec.clone())); - }); - - view! { - div(id = "step-input") { - label { "Step" } - input( - r#type = "number", - min = "0", - max = input_max.with(|max| max.to_string()), - bind:value = value, - bind:valueAsNumber = value_as_number, - on:change = move |_| { - if last_step.with(|last| last.is_some()) { - // clamp the step within its allowed range. the lower - // bound is redundant on browsers that make it - // impossible to type negative values into a number - // input with a non-negative `min`, but there's no harm - // in being careful - let step_raw = value.with( - |val| SpecifiedValue::try_from(val.clone()) - .unwrap_or(SpecifiedValue::from_empty_spec() - ) - ); - let step = SpecifiedValue::from( - step_raw.value.map( - |val| val.clamp(0.0, input_max.get() as f64) - ) - ); - - // set the input string and the assembly's active step - value.set(step.spec.clone()); - assembly.step.set(step); - } else { - value.set(String::new()); - } - }, - ) - } - } -} - fn into_log10_time_point((step, value): (usize, f64)) -> Vec> { vec![ Some(step as f64), @@ -311,7 +248,6 @@ pub fn Diagnostics() -> View { option(value = "loss") { "Loss" } option(value = "spectrum") { "Spectrum" } } - StepInput {} } DiagnosticsPanel(name = "loss") { LossHistory {} } DiagnosticsPanel(name = "spectrum") { SpectrumHistory {} } diff --git a/app-proto/src/components/display.rs b/app-proto/src/components/display.rs index 98be85e..a0cdba6 100644 --- a/app-proto/src/components/display.rs +++ b/app-proto/src/components/display.rs @@ -41,8 +41,8 @@ struct SceneSpheres { } impl SceneSpheres { - fn new() -> Self { - Self { + fn new() -> SceneSpheres { + SceneSpheres { representations: Vec::new(), colors_with_opacity: Vec::new(), highlights: Vec::new(), @@ -71,8 +71,8 @@ struct ScenePoints { } impl ScenePoints { - fn new() -> Self { - Self { + fn new() -> ScenePoints { + ScenePoints { representations: Vec::new(), colors_with_opacity: Vec::new(), highlights: Vec::new(), @@ -97,8 +97,8 @@ pub struct Scene { } impl Scene { - fn new() -> Self { - Self { + fn new() -> Scene { + Scene { spheres: SceneSpheres::new(), points: ScenePoints::new(), } @@ -588,25 +588,7 @@ pub fn Display() -> View { location_z *= (time_step * ZOOM_SPEED * zoom).exp(); // manipulate the assembly - /* KLUDGE */ - // to avoid the complexity of making tangent space projection - // conditional and dealing with unnormalized representation vectors, - // we only allow manipulation when we're looking at the last step of - // a successful realization - let realization_successful = state.assembly.realization_status.with( - |status| status.is_ok() - ); - let step_val = state.assembly.step.with_untracked(|step| step.value); - let on_init_step = step_val.is_some_and(|n| n == 0.0); - let on_last_step = step_val.is_some_and( - |n| state.assembly.descent_history.with_untracked( - |history| n as usize + 1 == history.config.len().max(1) - ) - ); - let on_manipulable_step = - !realization_successful && on_init_step - || realization_successful && on_last_step; - if on_manipulable_step && state.selection.with(|sel| sel.len() == 1) { + if state.selection.with(|sel| sel.len() == 1) { let sel = state.selection.with( |sel| sel.into_iter().next().unwrap().clone() ); diff --git a/app-proto/src/components/outline.rs b/app-proto/src/components/outline.rs index 547b73b..5355042 100644 --- a/app-proto/src/components/outline.rs +++ b/app-proto/src/components/outline.rs @@ -9,7 +9,6 @@ use crate::{ Element, HalfCurvatureRegulator, InversiveDistanceRegulator, - PointCoordinateRegulator, Regulator, }, specified::SpecifiedValue @@ -120,20 +119,6 @@ impl OutlineItem for HalfCurvatureRegulator { } } -impl OutlineItem for PointCoordinateRegulator { - fn outline_item(self: Rc, _element: &Rc) -> View { - let name = format!("{} coordinate", self.axis); - view! { - li(class = "regulator") { - div(class = "regulator-label") // for spacing - div(class = "regulator-type") { (name) } - RegulatorInput(regulator = self) - div(class = "status") - } - } - } -} - // a list item that shows an element in an outline view of an assembly #[component(inline_props)] fn ElementOutlineItem(element: Rc) -> View { diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 0d387d3..5ed94ad 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -26,7 +26,7 @@ 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"), @@ -77,7 +77,7 @@ fn load_general(assembly: &Assembly) { ); } -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( @@ -196,7 +196,7 @@ 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"), @@ -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]; @@ -409,7 +409,7 @@ 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( @@ -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; @@ -611,7 +611,7 @@ 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( @@ -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 @@ -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], @@ -909,15 +909,15 @@ 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), _ => (), }; }); @@ -927,10 +927,10 @@ pub fn TestAssemblyChooser() -> View { view! { select(bind:value = assembly_name) { option(value = "general") { "General" } - option(value = "low-curvature") { "Low-curvature" } + option(value = "low-curv") { "Low-curvature" } option(value = "pointed") { "Pointed" } - option(value = "tridiminished-icosahedron") { "Tridiminished icosahedron" } - option(value = "dodecahedral-packing") { "Dodecahedral packing" } + 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" } diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index 0f26f02..dc6b470 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -52,19 +52,19 @@ pub fn project_point_to_normalized(rep: &mut DVector) { // --- partial matrices --- pub struct MatrixEntry { - pub index: (usize, usize), - pub value: f64, + index: (usize, usize), + 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; + let PartialMatrix(entries) = self; entries.push(MatrixEntry { index: (row, col), value }); } @@ -114,7 +114,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() } } @@ -139,8 +139,8 @@ pub struct ConfigSubspace { } impl ConfigSubspace { - pub fn zero(assembly_dim: usize) -> Self { - Self { + pub fn zero(assembly_dim: usize) -> ConfigSubspace { + ConfigSubspace { assembly_dim, basis_proj: Vec::new(), basis_std: Vec::new(), @@ -154,7 +154,7 @@ impl ConfigSubspace { a: DMatrix, proj_to_std: DMatrix, assembly_dim: usize, - ) -> Self { + ) -> 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 @@ -173,7 +173,7 @@ impl ConfigSubspace { const ELEMENT_DIM: usize = 5; const UNIFORM_DIM: usize = 4; - Self { + ConfigSubspace { assembly_dim, basis_std: basis_std.column_iter().map( |v| Into::>::into( @@ -224,8 +224,8 @@ pub struct DescentHistory { } impl DescentHistory { - pub fn new() -> Self { - Self { + pub fn new() -> DescentHistory { + DescentHistory { config: Vec::>::new(), scaled_loss: Vec::::new(), neg_grad: Vec::>::new(), @@ -245,9 +245,9 @@ 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), @@ -255,8 +255,8 @@ impl ConstraintProblem { } #[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), @@ -284,10 +284,10 @@ struct SearchState { } 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, err_proj, loss } } } @@ -353,7 +353,7 @@ fn seek_better_config( // a first-order neighborhood of a configuration pub struct ConfigNeighborhood { - #[cfg(feature = "dev")] pub config: DMatrix, + pub config: DMatrix, pub nbhd: ConfigSubspace, } @@ -388,7 +388,7 @@ pub fn realize_gram( if assembly_dim == 0 { let result = Ok( ConfigNeighborhood { - #[cfg(feature = "dev")] config: guess.clone(), + config: guess.clone(), nbhd: ConfigSubspace::zero(0), } ); @@ -509,7 +509,7 @@ pub fn realize_gram( // find the kernel of the Hessian. give it the uniform inner product let tangent = ConfigSubspace::symmetric_kernel(hess, unif_to_std, assembly_dim); - Ok(ConfigNeighborhood { #[cfg(feature = "dev")] config: state.config, nbhd: tangent }) + Ok(ConfigNeighborhood { config: state.config, nbhd: tangent }) } else { Err("Failed to reach target accuracy".to_string()) }; diff --git a/app-proto/src/main.rs b/app-proto/src/main.rs index a03b026..7ca0731 100644 --- a/app-proto/src/main.rs +++ b/app-proto/src/main.rs @@ -24,8 +24,8 @@ struct AppState { } impl AppState { - fn new() -> Self { - Self { + fn new() -> AppState { + AppState { assembly: Assembly::new(), selection: create_signal(BTreeSet::default()), } diff --git a/app-proto/src/specified.rs b/app-proto/src/specified.rs index b0f04b5..ea1731c 100644 --- a/app-proto/src/specified.rs +++ b/app-proto/src/specified.rs @@ -17,8 +17,8 @@ pub struct SpecifiedValue { } 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 { @@ -26,17 +26,6 @@ impl SpecifiedValue { } } -// a `SpecifiedValue` can be constructed from a floating-point option, which is -// given a canonical specification -impl From> for SpecifiedValue { - fn from(value: Option) -> Self { - match value { - Some(x) => SpecifiedValue{ spec: x.to_string(), value }, - None => SpecifiedValue::from_empty_spec(), - } - } -} - // a `SpecifiedValue` can be constructed from a specification string, formatted // as described in the comment on the structure definition. the result is `Ok` // if the specification is properly formatted, and `Error` if not @@ -45,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, 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"