From ecbbe2068c2003ff848292364d42a63f97b03698 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sat, 20 Sep 2025 00:51:26 -0700 Subject: [PATCH 1/9] feat: Point coordinate regulators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements regulators for the Euclidean coordinates of Point entities, automatically creating all three of them for each added point entity. When such a regulator is set, it freezes the corresponding representation coordinate to the set point. In addition, if all three coordinates of a given Point are set, the coradius coordinate (which holds the norm of the point) is frozen as well. Note that a PointCoordinateRegulator must be created with a Point as the subject. This commit modifies HalfCurvatureRegulator analogously, so that it can only be created with a Sphere. A couple of prospective issues that should be filed in association with this commit: * The new coordinate regulators create redundant display information with the raw representation coordinates of a point that are already shown in the outline view. * The optimization status of these regulators together with HalfCurvature regulators (i.e., the ones implemented by freezing coordinates) is different from InversiveDistance regulators when an Assembly is unrealizable: the frozen-coordinate constraints will be "hard" in that they will be forced to precisely equal their set point, whereas the distance regulators are "soft" in that they can be relaxed from their set points in an effort to minimize the loss function of the configuration as compared to the values of the constraints. Perhaps at some point we should/will have a mechanism to specify the softness/hardness of constraints, but in the meantime, there should not be two different categories of constraints. Suppose we decide that by default that all constraints are soft. Then the optimizer should be able to search changing, for example, the radius of a curvature-constrained sphere, so as to minimize the loss function (for a loss that would therefore presumably have a term akin to the square of the difference between the specified and actual half-curvature of the sphere). For example, suppose you specify that the half-curvature of a sphere is 1 (so it has radius 1/2) but that its distance to a point is -1. These constraints cannot be satisfied, so the optimization fails, presumably with the point at the sphere center, and the sphere with radius 1/2. So all of the loss is concentrated in the difference between the actual point-sphere distance being -1/2, not -1. It would be more appropriate (in the all-soft constraint regime) to end up at something like a sphere of half-curvature 1/√2 with the point at the center, so that the loss is split between both the half-curvature and the distance to the sphere being off by 1 - 1/√2. (At a guess, that would minimize the sum of the squares of the two differences.) --- app-proto/Cargo.lock | 21 ++++++++ app-proto/Cargo.toml | 1 + app-proto/src/assembly.rs | 79 ++++++++++++++++++++++++++++- app-proto/src/components/outline.rs | 15 ++++++ app-proto/src/engine.rs | 6 +-- 5 files changed, 117 insertions(+), 5 deletions(-) diff --git a/app-proto/Cargo.lock b/app-proto/Cargo.lock index 4f75c45..731dd84 100644 --- a/app-proto/Cargo.lock +++ b/app-proto/Cargo.lock @@ -255,6 +255,7 @@ dependencies = [ "charming", "console_error_panic_hook", "dyna3", + "enum-iterator", "itertools", "js-sys", "lazy_static", @@ -271,6 +272,26 @@ 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 1230b47..d5221a1 100644 --- a/app-proto/Cargo.toml +++ b/app-proto/Cargo.toml @@ -10,6 +10,7 @@ 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/src/assembly.rs b/app-proto/src/assembly.rs index 669c0d0..c263692 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -1,3 +1,4 @@ +use enum_iterator::{all, Sequence}; use nalgebra::{DMatrix, DVector, DVectorView}; use std::{ cell::Cell, @@ -26,6 +27,7 @@ use crate::{ ConfigSubspace, ConstraintProblem, DescentHistory, + MatrixEntry, Realization, }, specified::SpecifiedValue, @@ -269,6 +271,7 @@ pub struct Point { impl Point { const WEIGHT_COMPONENT: usize = 3; + const NORM_COMPONENT: usize = 4; pub fn new( id: String, @@ -302,6 +305,15 @@ impl Element for Point { 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 @@ -446,14 +458,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) -> Self { let measurement = subject.representation().map( |rep| rep[Sphere::CURVATURE_COMPONENT] ); @@ -498,6 +510,69 @@ impl ProblemPoser for HalfCurvatureRegulator { } } +#[derive(Clone, Copy, Sequence)] +pub enum Axis {X = 0, Y = 1, Z = 2} + +impl Axis { + pub const N_AXIS: usize = (Axis::Z as usize) + 1; + pub const NAME: [&str; Axis::N_AXIS] = ["X", "Y", "Z"]; +} + +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( + "Subject must be indexed before point-coordinate regulator poses."); + problem.frozen.push(self.axis as usize, col, val); + // Check if all three coordinates have been frozen, and if so, + // freeze the coradius as well + let mut coords = [0.0; Axis::N_AXIS]; + let mut nset: usize = 0; + for &MatrixEntry {index, value} in &(problem.frozen) { + if index.1 == col && index.0 < Axis::N_AXIS { + nset += 1; + coords[index.0] = value + } + } + if nset == Axis::N_AXIS { + 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, diff --git a/app-proto/src/components/outline.rs b/app-proto/src/components/outline.rs index 5355042..79781fa 100644 --- a/app-proto/src/components/outline.rs +++ b/app-proto/src/components/outline.rs @@ -6,9 +6,11 @@ use web_sys::{KeyboardEvent, MouseEvent, wasm_bindgen::JsCast}; use crate::{ AppState, assembly::{ + Axis, Element, HalfCurvatureRegulator, InversiveDistanceRegulator, + PointCoordinateRegulator, Regulator, }, specified::SpecifiedValue @@ -119,6 +121,19 @@ impl OutlineItem for HalfCurvatureRegulator { } } +impl OutlineItem for PointCoordinateRegulator { + fn outline_item(self: Rc, _element: &Rc) -> View { + view! { + li(class = "regulator") { + div(class = "regulator-label") { (Axis::NAME[self.axis as usize]) } + div(class = "regulator-type") { "Coordinate" } + 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/engine.rs b/app-proto/src/engine.rs index ef150a0..feb23cf 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -46,14 +46,14 @@ pub fn project_sphere_to_normalized(rep: &mut DVector) { // normalize a point's representation vector by scaling pub fn project_point_to_normalized(rep: &mut DVector) { - rep.scale_mut(0.5 / rep[3]); + rep.scale_mut(0.5 / rep[3]); //FIXME: This 3 should be Point::WEIGHT_COMPONENT } // --- partial matrices --- pub struct MatrixEntry { - index: (usize, usize), - value: f64, + pub index: (usize, usize), + pub value: f64, } pub struct PartialMatrix(Vec); From 46ffd6c285b6a6b924a1083f67057bdff5206379 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 6 Oct 2025 16:31:02 -0600 Subject: [PATCH 2/9] chore: typographical improvements per review --- app-proto/src/assembly.rs | 14 +++++++------- app-proto/src/components/outline.rs | 5 +++-- app-proto/src/engine.rs | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index c263692..8e4b96f 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -514,8 +514,7 @@ impl ProblemPoser for HalfCurvatureRegulator { pub enum Axis {X = 0, Y = 1, Z = 2} impl Axis { - pub const N_AXIS: usize = (Axis::Z as usize) + 1; - pub const NAME: [&str; Axis::N_AXIS] = ["X", "Y", "Z"]; + pub const NAME: [&str; Axis::CARDINALITY] = ["X", "Y", "Z"]; } pub struct PointCoordinateRegulator { @@ -553,17 +552,17 @@ impl ProblemPoser for PointCoordinateRegulator { let col = self.subject.column_index().expect( "Subject must be indexed before point-coordinate regulator poses."); problem.frozen.push(self.axis as usize, col, val); - // Check if all three coordinates have been frozen, and if so, - // freeze the coradius as well - let mut coords = [0.0; Axis::N_AXIS]; + // Check if all three spatial coordinates have been frozen, and if so, + // freeze the norm component as well + 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::N_AXIS { + if index.1 == col && index.0 < Axis::CARDINALITY { nset += 1; coords[index.0] = value } } - if nset == Axis::N_AXIS { + if nset == Axis::CARDINALITY { let [x, y, z] = coords; problem.frozen.push( Point::NORM_COMPONENT, col, point(x,y,z)[Point::NORM_COMPONENT]); @@ -773,6 +772,7 @@ 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 diff --git a/app-proto/src/components/outline.rs b/app-proto/src/components/outline.rs index 79781fa..d9ab71d 100644 --- a/app-proto/src/components/outline.rs +++ b/app-proto/src/components/outline.rs @@ -123,10 +123,11 @@ impl OutlineItem for HalfCurvatureRegulator { impl OutlineItem for PointCoordinateRegulator { fn outline_item(self: Rc, _element: &Rc) -> View { + let name = format!("{} coordinate", Axis::NAME[self.axis as usize]); view! { li(class = "regulator") { - div(class = "regulator-label") { (Axis::NAME[self.axis as usize]) } - div(class = "regulator-type") { "Coordinate" } + div(class = "regulator-label") // for spacing + div(class = "regulator-type") { (name) } RegulatorInput(regulator = self) div(class = "status") } diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index feb23cf..0f26f02 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -46,7 +46,7 @@ pub fn project_sphere_to_normalized(rep: &mut DVector) { // normalize a point's representation vector by scaling pub fn project_point_to_normalized(rep: &mut DVector) { - rep.scale_mut(0.5 / rep[3]); //FIXME: This 3 should be Point::WEIGHT_COMPONENT + rep.scale_mut(0.5 / rep[3]); } // --- partial matrices --- From 27edbfb01089a1b3407dcc4094a66ba10567919a Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 7 Oct 2025 15:36:12 -0700 Subject: [PATCH 3/9] Streamline axis naming This makes it simpler, from the programmer's perspective, to get the name of an axis as a string slice and to format an axis name into a string. To me, the matching method `Axis::name` seems more direct than the explicit lookup table that it replaces, and I'm hoping that it will be about as easy for the compiler to inline, or even easier. Implementing `Display` enables us to hand an `Axis` to a string formatter without any explicit conversion. It adds extra code in the short run, but I'd expect it to simplify our code in the long run by fitting into the conventions set by the Rust standard library. --- app-proto/src/assembly.rs | 14 +++++++++++--- app-proto/src/components/outline.rs | 3 +-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 8e4b96f..4da9422 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -127,7 +127,7 @@ pub trait Element: Serial + ProblemPoser + DisplayItem { } impl Debug for dyn Element { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.id().fmt(f) } } @@ -511,10 +511,18 @@ impl ProblemPoser for HalfCurvatureRegulator { } #[derive(Clone, Copy, Sequence)] -pub enum Axis {X = 0, Y = 1, Z = 2} +pub enum Axis { X = 0, Y = 1, Z = 2 } impl Axis { - pub const NAME: [&str; Axis::CARDINALITY] = ["X", "Y", "Z"]; + fn name(&self) -> &'static str { + match self { Axis::X => "X", Axis::Y => "Y", Axis::Z => "Z" } + } +} + +impl fmt::Display for Axis { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.name()) + } } pub struct PointCoordinateRegulator { diff --git a/app-proto/src/components/outline.rs b/app-proto/src/components/outline.rs index d9ab71d..547b73b 100644 --- a/app-proto/src/components/outline.rs +++ b/app-proto/src/components/outline.rs @@ -6,7 +6,6 @@ use web_sys::{KeyboardEvent, MouseEvent, wasm_bindgen::JsCast}; use crate::{ AppState, assembly::{ - Axis, Element, HalfCurvatureRegulator, InversiveDistanceRegulator, @@ -123,7 +122,7 @@ impl OutlineItem for HalfCurvatureRegulator { impl OutlineItem for PointCoordinateRegulator { fn outline_item(self: Rc, _element: &Rc) -> View { - let name = format!("{} coordinate", Axis::NAME[self.axis as usize]); + let name = format!("{} coordinate", self.axis); view! { li(class = "regulator") { div(class = "regulator-label") // for spacing From adc60ac5c160ceee11ee205352a1665b29fc6e94 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 7 Oct 2025 16:19:14 -0700 Subject: [PATCH 4/9] Spruce up formatting and error messages Make the new code's formatting and error messages more consistent with the previous code. I don't necessarily have a strong preference for the previous conventions, but I do like stuff to be consistent. --- app-proto/src/assembly.rs | 63 +++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 4da9422..b1e5d7c 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -305,13 +305,14 @@ impl Element for Point { 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:: - }) + .map( + |axis| Rc::new( + PointCoordinateRegulator::new(self.clone(), axis) + ) as Rc:: + ) .collect() } @@ -538,19 +539,32 @@ impl PointCoordinateRegulator { 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() } + let serial = Self::next_serial(); + + Self { subject, axis, measurement, set_point, serial } } } impl Serial for PointCoordinateRegulator { - fn serial(&self) -> u64 { self.serial } + 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 } + 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 { @@ -558,22 +572,25 @@ 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."); + "Subject should be indexed before point coordinate regulator writes problem data" + ); 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 - 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 all three of the subject's spatial coordinates have been + // frozen, then freeze its norm component too + let mut coords_frozen = [0.0; Axis::CARDINALITY]; + let mut n_set: usize = 0; + for &MatrixEntry { index, value } in &(problem.frozen) { + let (row_frozen, col_frozen) = index; + if col_frozen == col && row_frozen < Axis::CARDINALITY { + n_set += 1; + coords_frozen[row_frozen] = value } } - if nset == Axis::CARDINALITY { - let [x, y, z] = coords; - problem.frozen.push( - Point::NORM_COMPONENT, col, point(x,y,z)[Point::NORM_COMPONENT]); + if n_set == Axis::CARDINALITY { + let [x, y, z] = coords_frozen; + let norm = point(x, y, z)[Point::NORM_COMPONENT]; + problem.frozen.push(Point::NORM_COMPONENT, col, norm); } } }); From c081f1a80912585bc851bc88568c07def8373e17 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Thu, 9 Oct 2025 22:36:37 -0700 Subject: [PATCH 5/9] Revert "Spruce up formatting and error messages" This reverts commit adc60ac5c160ceee11ee205352a1665b29fc6e94. We decided that it would be better for me to request formatting changes one by one. --- app-proto/src/assembly.rs | 63 ++++++++++++++------------------------- 1 file changed, 23 insertions(+), 40 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index b1e5d7c..4da9422 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -305,14 +305,13 @@ impl Element for Point { 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:: - ) + .map(|axis| { + Rc::new(PointCoordinateRegulator::new(self.clone(), axis)) + as Rc:: + }) .collect() } @@ -539,32 +538,19 @@ impl PointCoordinateRegulator { let measurement = subject.representation().map( move |rep| rep[axis as usize] ); - let set_point = create_signal(SpecifiedValue::from_empty_spec()); - let serial = Self::next_serial(); - - Self { subject, axis, measurement, set_point, serial } + Self { subject, axis, measurement, set_point, serial: Self::next_serial() } } } impl Serial for PointCoordinateRegulator { - fn serial(&self) -> u64 { - self.serial - } + 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 - } + 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 { @@ -572,25 +558,22 @@ 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 should be indexed before point coordinate regulator writes problem data" - ); + "Subject must be indexed before point-coordinate regulator poses."); 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 too - let mut coords_frozen = [0.0; Axis::CARDINALITY]; - let mut n_set: usize = 0; - for &MatrixEntry { index, value } in &(problem.frozen) { - let (row_frozen, col_frozen) = index; - if col_frozen == col && row_frozen < Axis::CARDINALITY { - n_set += 1; - coords_frozen[row_frozen] = value + // Check if all three spatial coordinates have been frozen, and if so, + // freeze the norm component as well + 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 n_set == Axis::CARDINALITY { - let [x, y, z] = coords_frozen; - let norm = point(x, y, z)[Point::NORM_COMPONENT]; - problem.frozen.push(Point::NORM_COMPONENT, col, norm); + if nset == Axis::CARDINALITY { + let [x, y, z] = coords; + problem.frozen.push( + Point::NORM_COMPONENT, col, point(x,y,z)[Point::NORM_COMPONENT]); } } }); From c0e6ebf3d6a879873a9203ee832e4998f2cf852c Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Fri, 10 Oct 2025 09:39:57 -0700 Subject: [PATCH 6/9] chore: uniformize error messages, 80-char lines, import fmt::Display --- README.md | 5 +++ app-proto/src/assembly.rs | 93 +++++++++++++++++++++++++-------------- 2 files changed, 64 insertions(+), 34 deletions(-) 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 +} From 50c51ca08ff187c696282d2d70da4ac678157984 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Fri, 10 Oct 2025 18:05:09 -0700 Subject: [PATCH 7/9] chore: revert 80-character limit, reorder and rename index message helper --- README.md | 7 +--- app-proto/src/assembly.rs | 80 +++++++++++++++------------------------ 2 files changed, 33 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index e3d1519..ec3d440 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,11 @@ 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`. + - 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` + - 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 04551d1..0d2a510 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -86,6 +86,14 @@ 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); } @@ -248,19 +256,12 @@ 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( - index_message("Sphere", &self.id, "it").as_str()); + indexing_error("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()); } } @@ -363,11 +364,10 @@ impl Serial for Point { impl ProblemPoser for Point { fn pose(&self, problem: &mut ConstraintProblem) { let index = self.column_index().expect( - index_message("Point", &self.id, "it").as_str()); + indexing_error("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()); } } @@ -412,8 +412,7 @@ 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| @@ -455,7 +454,7 @@ impl ProblemPoser for InversiveDistanceRegulator { if let Some(val) = set_pt.value { let [row, col] = self.subjects.each_ref().map( |subj| subj.column_index().expect( - index_message("Subject", subj.id(), + indexing_error("Subject", subj.id(), "inversive distance regulator").as_str()) ); problem.gram.push_sym(row, col, val); @@ -509,7 +508,7 @@ 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( - index_message("Subject", &self.subject.id, + indexing_error("Subject", &self.subject.id, "half-curvature regulator").as_str()); problem.frozen.push(Sphere::CURVATURE_COMPONENT, col, val); } @@ -546,9 +545,7 @@ 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() } } } @@ -567,7 +564,7 @@ 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( - index_message("Subject", &self.subject.id, + 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 @@ -583,9 +580,7 @@ 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]); } } }); @@ -684,8 +679,7 @@ 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() { @@ -694,9 +688,7 @@ impl Assembly { } pub fn try_insert_element(&self, elt: impl Element + 'static) -> bool { - let can_insert = self.elements_by_id.with_untracked( - |elts_by_id| !elts_by_id.contains_key(elt.id()) - ); + let can_insert = self.elements_by_id.with_untracked(|elts_by_id| !elts_by_id.contains_key(elt.id())); if can_insert { self.insert_element_unchecked(elt); } @@ -761,8 +753,7 @@ 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())) ); } } @@ -907,8 +898,7 @@ 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) ); @@ -916,10 +906,9 @@ 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; } } @@ -936,9 +925,7 @@ 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()) }, }; }); @@ -973,15 +960,13 @@ 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)); }); @@ -1010,10 +995,8 @@ inversive distance regulator writes problem data")] // 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![ @@ -1031,8 +1014,7 @@ inversive distance regulator writes problem data")] 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); }); } } From 627cea455ca3790e03969436e02eeaa5ad1947de Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Fri, 10 Oct 2025 22:48:46 -0700 Subject: [PATCH 8/9] chore: Properly punctuate README --- README.md | 66 +++++++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index ec3d440..ac2771b 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 -* Comfortable, intuitive UI +* Provide a comfortable, intuitive UI -* Able to run in browser (so implemented in WASM-compatible language) +* Allow execution 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,40 @@ 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. + - 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` + - 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 -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 +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 +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 +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 + - 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 ```julia include("irisawa-hexlet.jl") @@ -66,24 +66,24 @@ 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` +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 +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 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` + - 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 + - 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. From 6dbbe2ce2d9ed63a1a92705a1c18b32da0cb1921 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Fri, 10 Oct 2025 22:57:52 -0700 Subject: [PATCH 9/9] chore: Hopefully final formatting items from review --- app-proto/src/assembly.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 0d2a510..0264b75 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -688,7 +688,9 @@ impl Assembly { } pub fn try_insert_element(&self, elt: impl Element + 'static) -> bool { - let can_insert = self.elements_by_id.with_untracked(|elts_by_id| !elts_by_id.contains_key(elt.id())); + let can_insert = self.elements_by_id.with_untracked( + |elts_by_id| !elts_by_id.contains_key(elt.id()) + ); if can_insert { self.insert_element_unchecked(elt); } @@ -956,7 +958,7 @@ mod tests { #[test] #[should_panic(expected = "Subject \"sphere1\" must be indexed before \ -inversive distance regulator writes problem data")] + inversive distance regulator writes problem data")] fn unindexed_subject_test_inversive_distance() { let _ = create_root(|| { let subjects = [0, 1].map(