diff --git a/README.md b/README.md index cf3e589..e3d1519 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,14 @@ The latest prototype is in the folder `app-proto`. It includes both a user inter 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`. 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` ### Play with the prototype diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 4da9422..04551d1 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -5,7 +5,7 @@ use std::{ cmp::Ordering, collections::{BTreeMap, BTreeSet}, fmt, - fmt::{Debug, Formatter}, + fmt::{Debug, Display, Formatter}, hash::{Hash, Hasher}, rc::Rc, sync::{atomic, atomic::AtomicU64}, @@ -128,7 +128,7 @@ pub trait Element: Serial + ProblemPoser + DisplayItem { impl Debug for dyn Element { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - self.id().fmt(f) + Debug::fmt(&self.id(), f) } } @@ -248,13 +248,19 @@ impl Serial for Sphere { } } +fn index_message(thing: &str, name: &str, actor: &str) -> String { + format!( + "{thing} \"{name}\" must be indexed before {actor} writes problem data" + ) +} + impl ProblemPoser for Sphere { fn pose(&self, problem: &mut ConstraintProblem) { let index = self.column_index().expect( - format!("Sphere \"{}\" should be indexed before writing problem data", self.id).as_str() - ); + index_message("Sphere", &self.id, "it").as_str()); problem.gram.push_sym(index, index, 1.0); - problem.guess.set_column(index, &self.representation.get_clone_untracked()); + problem.guess.set_column( + index, &self.representation.get_clone_untracked()); } } @@ -357,11 +363,11 @@ impl Serial for Point { impl ProblemPoser for Point { fn pose(&self, problem: &mut ConstraintProblem) { let index = self.column_index().expect( - format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str() - ); + index_message("Point", &self.id, "it").as_str()); problem.gram.push_sym(index, index, 0.0); problem.frozen.push(Self::WEIGHT_COMPONENT, index, 0.5); - problem.guess.set_column(index, &self.representation.get_clone_untracked()); + problem.guess.set_column( + index, &self.representation.get_clone_untracked()); } } @@ -406,7 +412,8 @@ pub struct InversiveDistanceRegulator { impl InversiveDistanceRegulator { pub fn new(subjects: [Rc; 2]) -> Self { - let representations = subjects.each_ref().map(|subj| subj.representation()); + let representations = subjects.each_ref().map( + |subj| subj.representation()); let measurement = create_memo(move || { representations[0].with(|rep_0| representations[1].with(|rep_1| @@ -448,8 +455,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( - "Subjects should be indexed before inversive distance regulator writes problem data" - ) + index_message("Subject", subj.id(), + "inversive distance regulator").as_str()) ); problem.gram.push_sym(row, col, val); } @@ -502,8 +509,8 @@ 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( - "Subject should be indexed before half-curvature regulator writes problem data" - ); + index_message("Subject", &self.subject.id, + "half-curvature regulator").as_str()); problem.frozen.push(Sphere::CURVATURE_COMPONENT, col, val); } }); @@ -519,7 +526,7 @@ impl Axis { } } -impl fmt::Display for Axis { +impl Display for Axis { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.name()) } @@ -539,7 +546,9 @@ impl PointCoordinateRegulator { 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() } + Self { + subject, axis, measurement, set_point, serial: Self::next_serial() + } } } @@ -558,10 +567,11 @@ impl ProblemPoser for PointCoordinateRegulator { self.set_point.with_untracked(|set_pt| { if let Some(val) = set_pt.value { let col = self.subject.column_index().expect( - "Subject must be indexed before point-coordinate regulator poses."); + index_message("Subject", &self.subject.id, + "point-coordinate regulator").as_str()); problem.frozen.push(self.axis as usize, col, val); - // Check if all three spatial coordinates have been frozen, and if so, - // freeze the norm component as well + // 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) { @@ -573,7 +583,9 @@ impl ProblemPoser for PointCoordinateRegulator { if nset == Axis::CARDINALITY { let [x, y, z] = coords; problem.frozen.push( - Point::NORM_COMPONENT, col, point(x,y,z)[Point::NORM_COMPONENT]); + Point::NORM_COMPONENT, + col, + point(x,y,z)[Point::NORM_COMPONENT]); } } }); @@ -672,7 +684,8 @@ impl Assembly { let id = elt.id().clone(); let elt_rc = Rc::new(elt); self.elements.update(|elts| elts.insert(elt_rc.clone())); - self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, elt_rc.clone())); + self.elements_by_id.update( + |elts_by_id| elts_by_id.insert(id, elt_rc.clone())); // create and insert the element's default regulators for reg in elt_rc.default_regulators() { @@ -748,7 +761,8 @@ impl Assembly { 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())) + |rep| rep.set_column( + 0, &config.column(elt.column_index().unwrap())) ); } } @@ -893,7 +907,8 @@ impl Assembly { if column_index < realized_dim { // this element had a column index when we started, so by // invariant (1), it's reflected in the tangent space - let mut target_columns = motion_proj.columns_mut(0, realized_dim); + let mut target_columns = + motion_proj.columns_mut(0, realized_dim); target_columns += self.tangent.with( |tan| tan.proj(&elt_motion.velocity, column_index) ); @@ -901,9 +916,10 @@ impl Assembly { // this element didn't have a column index when we started, so // by invariant (2), it's unconstrained let mut target_column = motion_proj.column_mut(column_index); - let unif_to_std = elt_motion.element.representation().with_untracked( - |rep| local_unif_to_std(rep.as_view()) - ); + let unif_to_std = elt_motion + .element.representation() + .with_untracked( + |rep| local_unif_to_std(rep.as_view())); target_column += unif_to_std * elt_motion.velocity; } } @@ -920,7 +936,9 @@ impl Assembly { elt.project_to_normalized(rep); }, None => { - console_log!("No velocity to unpack for fresh element \"{}\"", elt.id()) + console_log!( + "No velocity to unpack for fresh element \"{}\"", + elt.id()) }, }; }); @@ -940,7 +958,8 @@ mod tests { use crate::engine; #[test] - #[should_panic(expected = "Sphere \"sphere\" should be indexed before writing problem data")] + #[should_panic(expected = + "Sphere \"sphere\" must be indexed before it writes problem data")] fn unindexed_element_test() { let _ = create_root(|| { let elt = Sphere::default("sphere".to_string(), 0); @@ -949,17 +968,20 @@ mod tests { } #[test] - #[should_panic(expected = "Subjects should be indexed before inversive distance regulator writes problem data")] + #[should_panic(expected = "Subject \"sphere1\" must be indexed before \ +inversive distance regulator writes problem data")] fn unindexed_subject_test_inversive_distance() { let _ = create_root(|| { let subjects = [0, 1].map( - |k| Rc::new(Sphere::default(format!("sphere{k}"), k)) as Rc + |k| Rc::new(Sphere::default( + format!("sphere{k}"), k)) as Rc ); subjects[0].set_column_index(0); InversiveDistanceRegulator { subjects: subjects, measurement: create_memo(|| 0.0), - set_point: create_signal(SpecifiedValue::try_from("0.0".to_string()).unwrap()), + set_point: create_signal( + SpecifiedValue::try_from("0.0".to_string()).unwrap()), serial: InversiveDistanceRegulator::next_serial() }.pose(&mut ConstraintProblem::new(2)); }); @@ -988,8 +1010,10 @@ mod tests { // nudge the sphere repeatedly along the `z` axis const STEP_SIZE: f64 = 0.0025; const STEP_CNT: usize = 400; - let sphere = assembly.elements_by_id.with(|elts_by_id| elts_by_id[sphere_id].clone()); - let velocity = DVector::from_column_slice(&[0.0, 0.0, STEP_SIZE, 0.0]); + let sphere = assembly.elements_by_id.with( + |elts_by_id| elts_by_id[sphere_id].clone()); + let velocity = + DVector::from_column_slice(&[0.0, 0.0, STEP_SIZE, 0.0]); for _ in 0..STEP_CNT { assembly.deform( vec![ @@ -1007,7 +1031,8 @@ mod tests { let final_half_curv = sphere.representation().with_untracked( |rep| rep[Sphere::CURVATURE_COMPONENT] ); - assert!((final_half_curv / INITIAL_HALF_CURV - 1.0).abs() < DRIFT_TOL); + assert!((final_half_curv / INITIAL_HALF_CURV - 1.0).abs() + < DRIFT_TOL); }); } -} \ No newline at end of file +}