From ecbbe2068c2003ff848292364d42a63f97b03698 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sat, 20 Sep 2025 00:51:26 -0700 Subject: [PATCH 01/11] 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 9d9e0da2c393c3580472e7dc97d2d4b74976ba49 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sun, 21 Sep 2025 21:47:54 -0700 Subject: [PATCH 02/11] feat[WIP]: begin a test assembly for the doubly augmented 554 acron --- app-proto/rust-toolchain.toml | 2 + app-proto/src/assembly.rs | 12 +- .../src/components/test_assembly_chooser.rs | 254 +++++++++++------- app-proto/src/lib.rs | 2 + app-proto/src/main.rs | 2 + 5 files changed, 176 insertions(+), 96 deletions(-) create mode 100644 app-proto/rust-toolchain.toml diff --git a/app-proto/rust-toolchain.toml b/app-proto/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/app-proto/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index c263692..acef6c8 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -274,14 +274,14 @@ impl Point { const NORM_COMPONENT: usize = 4; pub fn new( - id: String, - label: String, + id: &str, + label: &str, color: ElementColor, representation: DVector, ) -> Self { Self { - id, - label, + id: id.to_string(), + label: label.to_string(), color, representation: create_signal(representation), ghost: create_signal(false), @@ -299,8 +299,8 @@ impl Element for Point { fn default(id: String, id_num: u64) -> Self { Self::new( - id, - format!("Point {id_num}"), + &id, + &format!("Point {id_num}"), [0.75_f32, 0.75_f32, 0.75_f32], point(0.0, 0.0, 0.0), ) diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 0d387d3..86ad836 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -15,10 +15,61 @@ use crate::{ Sphere, }, engine, - engine::DescentHistory, + engine::{DescentHistory, point}, specified::SpecifiedValue, }; +// Convenience: use sqrt() as a function to get the square root of +// anything that can be losslessly converted to f64, since we happen +// to always want our sqrts to be f64. +// RUST KVETCHES: (I) Why is this so convoluted? +// In particular, what would be so bad about allowing +// const fn sqrt>(x: T) -> f64 { (x as f64).sqrt() } +// ??? +// (II) Oh dear, sqrt is not computable in a const context, so we have to +// roll our own. I hope I get it right! I definitely don't know how to ensure +// that the final double is correctly rounded :-( + +const SIXTH: f64 = 1./6.; +const FOURTH: f64 = 1./4.; +const fn invsqrt(xin: f64) -> f64 { + if !xin.is_finite() { return f64::NAN; } + let mut log4 = 0; + let mut x = xin; + while x < 1. { x *= 4.; log4 -= 1; } + while x > 4. { x *= FOURTH; log4 += 1; } + let mut next = 1.1 - SIXTH * x; + let mut diff = 1.; + while diff > 4. * f64::EPSILON { // termination condition?? + let last = next; + next = last * (1.5 - 0.5 * x * last * last); + diff = next - last; + if diff < 0. { diff *= -1.; } + } + while log4 > 0 { next *= 0.5; log4 -= 1; } + while log4 < 0 { next *= 2.; log4 += 1; } + return next; +} + +#[const_trait] +trait ConstSqrt {fn sqr(self) -> f64;} +impl const ConstSqrt for f64 {fn sqr(self) -> f64 {self * invsqrt(self)}} +impl const ConstSqrt for i32 {fn sqr(self) -> f64 {(self as f64).sqr()}} +const fn sqrt(x: T) -> f64 {x.sqr()} + +// RUST KVETCHES: (I) It is annoying that we must redundantly specify +// the type of the well-typed RHS to assign it to a const. +// See https://github.com/rust-lang/rfcs/pull/3546 +// Can we fix this in husht? Seems like we will need to be able to do full +// rust type inference in the transpiler; how will we accomplish that? +// (2) It is very annoying that there doesn't seem to be any way to specify +// that we want an expression like `5.0 / 2` to be evaluated by converting +// the 2 to a float, because of the "orphan rule". Can we fix this in husht? +// Again, it seems we would need full rust type inference. + +// FIXME: replace with std::f64::consts::PHI when that gets stabilized +const PHI: f64 = (1. + sqrt(5)) / 2.; + // --- loaders --- /* DEBUG */ @@ -79,7 +130,7 @@ fn load_general(assembly: &Assembly) { fn load_low_curvature(assembly: &Assembly) { // create the spheres - let a = 0.75_f64.sqrt(); + const A: f64 = sqrt(0.75); let _ = assembly.try_insert_element( Sphere::new( "central".to_string(), @@ -109,7 +160,7 @@ fn load_low_curvature(assembly: &Assembly) { "side2".to_string(), "Side 2".to_string(), [0.25_f32, 1.00_f32, 0.00_f32], - engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0), + engine::sphere_with_offset(-0.5, A, 0.0, 1.0, 0.0), ) ); let _ = assembly.try_insert_element( @@ -117,7 +168,7 @@ fn load_low_curvature(assembly: &Assembly) { "side3".to_string(), "Side 3".to_string(), [0.00_f32, 0.25_f32, 1.00_f32], - engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0), + engine::sphere_with_offset(-0.5, -A, 0.0, 1.0, 0.0), ) ); let _ = assembly.try_insert_element( @@ -133,7 +184,7 @@ fn load_low_curvature(assembly: &Assembly) { "corner2".to_string(), "Corner 2".to_string(), [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0), + engine::sphere(2.0/3.0, -4.0/3.0 * A, 0.0, 1.0/3.0), ) ); let _ = assembly.try_insert_element( @@ -141,7 +192,7 @@ fn load_low_curvature(assembly: &Assembly) { String::from("corner3"), String::from("Corner 3"), [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0), + engine::sphere(2.0/3.0, 4.0/3.0 * A, 0.0, 1.0/3.0), ) ); @@ -199,16 +250,16 @@ fn load_low_curvature(assembly: &Assembly) { fn load_pointed(assembly: &Assembly) { let _ = assembly.try_insert_element( Point::new( - format!("point_front"), - format!("Front point"), + "point_front", + "Front point", [0.75_f32, 0.75_f32, 0.75_f32], engine::point(0.0, 0.0, FRAC_1_SQRT_2), ) ); let _ = assembly.try_insert_element( Point::new( - format!("point_back"), - format!("Back point"), + "point_back", + "Back point", [0.75_f32, 0.75_f32, 0.75_f32], engine::point(0.0, 0.0, -FRAC_1_SQRT_2), ) @@ -229,8 +280,8 @@ fn load_pointed(assembly: &Assembly) { let _ = assembly.try_insert_element( Point::new( - format!("point{index_x}{index_y}"), - format!("Point {index_x}{index_y}"), + &format!("point{index_x}{index_y}"), + &format!("Point {index_x}{index_y}"), [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], engine::point(x, y, 0.0), ) @@ -252,60 +303,15 @@ fn load_tridiminished_icosahedron(assembly: &Assembly) { const COLOR_B: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; const COLOR_C: ElementColor = [0.25_f32, 0.50_f32, 1.00_f32]; let vertices = [ - Point::new( - "a1".to_string(), - "A₁".to_string(), - COLOR_A, - engine::point(0.25, 0.75, 0.75), - ), - Point::new( - "a2".to_string(), - "A₂".to_string(), - COLOR_A, - engine::point(0.75, 0.25, 0.75), - ), - Point::new( - "a3".to_string(), - "A₃".to_string(), - COLOR_A, - engine::point(0.75, 0.75, 0.25), - ), - Point::new( - "b1".to_string(), - "B₁".to_string(), - COLOR_B, - engine::point(0.75, -0.25, -0.25), - ), - Point::new( - "b2".to_string(), - "B₂".to_string(), - COLOR_B, - engine::point(-0.25, 0.75, -0.25), - ), - Point::new( - "b3".to_string(), - "B₃".to_string(), - COLOR_B, - engine::point(-0.25, -0.25, 0.75), - ), - Point::new( - "c1".to_string(), - "C₁".to_string(), - COLOR_C, - engine::point(0.0, -1.0, -1.0), - ), - Point::new( - "c2".to_string(), - "C₂".to_string(), - COLOR_C, - engine::point(-1.0, 0.0, -1.0), - ), - Point::new( - "c3".to_string(), - "C₃".to_string(), - COLOR_C, - engine::point(-1.0, -1.0, 0.0), - ), + Point::new("a1", "A₁", COLOR_A, point( 0.25, 0.75, 0.75)), + Point::new("a2", "A₂", COLOR_A, point( 0.75, 0.25, 0.75)), + Point::new("a3", "A₃", COLOR_A, point( 0.75, 0.75, 0.25)), + Point::new("b1", "B₁", COLOR_B, point( 0.75, -0.25, -0.25)), + Point::new("b2", "B₂", COLOR_B, point(-0.25, 0.75, -0.25)), + Point::new("b3", "B₃", COLOR_B, point(-0.25, -0.25, 0.75)), + Point::new("c1", "C₁", COLOR_C, point( 0.0, -1.0, -1.0)), + Point::new("c2", "C₂", COLOR_C, point(-1.0, 0.0, -1.0)), + Point::new("c3", "C₃", COLOR_C, point(-1.0, -1.0, 0.0)), ]; for vertex in vertices { let _ = assembly.try_insert_element(vertex); @@ -313,26 +319,26 @@ fn load_tridiminished_icosahedron(assembly: &Assembly) { // create the faces const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; - let frac_1_sqrt_6 = 1.0 / 6.0_f64.sqrt(); - let frac_2_sqrt_6 = 2.0 * frac_1_sqrt_6; + const SQRT_1_6: f64 = invsqrt(6.); + const SQRT_2_3: f64 = 2. * SQRT_1_6; let faces = [ Sphere::new( "face1".to_string(), "Face 1".to_string(), COLOR_FACE, - engine::sphere_with_offset(frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0), + engine::sphere_with_offset(SQRT_2_3, -SQRT_1_6, -SQRT_1_6, -SQRT_1_6, 0.0), ), Sphere::new( "face2".to_string(), "Face 2".to_string(), COLOR_FACE, - engine::sphere_with_offset(-frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0), + engine::sphere_with_offset(-SQRT_1_6, SQRT_2_3, -SQRT_1_6, -SQRT_1_6, 0.0), ), Sphere::new( "face3".to_string(), "Face 3".to_string(), COLOR_FACE, - engine::sphere_with_offset(-frac_1_sqrt_6, -frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, 0.0), + engine::sphere_with_offset(-SQRT_1_6, -SQRT_1_6, SQRT_2_3, -SQRT_1_6, 0.0), ), ]; for face in faces { @@ -434,17 +440,16 @@ fn load_dodecahedral_packing(assembly: &Assembly) { const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.00_f32]; const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32]; const COLOR_C: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; - let phi = 0.5 + 1.25_f64.sqrt(); /* TO DO */ // replace with std::f64::consts::PHI when that gets stabilized - let phi_inv = 1.0 / phi; - let coord_scale = (phi + 2.0).sqrt(); - let face_scales = [phi_inv, (13.0 / 12.0) / coord_scale]; - let face_radii = [phi_inv, 5.0 / 12.0]; + const PHI_INV: f64 = 1.0 / PHI; + const COORD_SCALE: f64 = sqrt(PHI + 2.0); + const FACE_SCALES: [f64; 2] = [PHI_INV, (13.0 / 12.0) / COORD_SCALE]; + const FACE_RADII: [f64; 2] = [PHI_INV, 5.0 / 12.0]; let mut faces = Vec::>::new(); let subscripts = ["₀", "₁"]; for j in 0..2 { for k in 0..2 { - let small_coord = face_scales[k] * (2.0*(j as f64) - 1.0); - let big_coord = face_scales[k] * (2.0*(k as f64) - 1.0) * phi; + let small_coord = FACE_SCALES[k] * if j > 0 {1.} else {-1.}; + let big_coord = FACE_SCALES[k] * PHI * if k > 0 {1.} else {-1.}; let id_num = format!("{j}{k}"); let label_sub = format!("{}{}", subscripts[j], subscripts[k]); @@ -456,7 +461,7 @@ fn load_dodecahedral_packing(assembly: &Assembly) { id_a.clone(), format!("A{label_sub}"), COLOR_A, - engine::sphere(0.0, small_coord, big_coord, face_radii[k]), + engine::sphere(0.0, small_coord, big_coord, FACE_RADII[k]), ) ); faces.push( @@ -472,7 +477,7 @@ fn load_dodecahedral_packing(assembly: &Assembly) { id_b.clone(), format!("B{label_sub}"), COLOR_B, - engine::sphere(small_coord, big_coord, 0.0, face_radii[k]), + engine::sphere(small_coord, big_coord, 0.0, FACE_RADII[k]), ) ); faces.push( @@ -488,7 +493,7 @@ fn load_dodecahedral_packing(assembly: &Assembly) { id_c.clone(), format!("C{label_sub}"), COLOR_C, - engine::sphere(big_coord, 0.0, small_coord, face_radii[k]), + engine::sphere(big_coord, 0.0, small_coord, FACE_RADII[k]), ) ); faces.push( @@ -614,12 +619,7 @@ fn load_balanced(assembly: &Assembly) { fn load_off_center(assembly: &Assembly) { // create a point almost at the origin and a sphere centered on the origin let _ = assembly.try_insert_element( - Point::new( - "point".to_string(), - "Point".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::point(1e-9, 0.0, 0.0), - ), + Point::new("point", "Point", [0.75, 0.75, 0.75], point(1e-9, 0.0, 0.0)), ); let _ = assembly.try_insert_element( Sphere::new( @@ -689,8 +689,8 @@ fn load_radius_ratio(assembly: &Assembly) { ).map( |(k, color, representation)| { Point::new( - format!("v{k}"), - format!("Vertex {k}"), + &format!("v{k}"), + &format!("Vertex {k}"), color, representation, ) @@ -882,6 +882,76 @@ fn load_irisawa_hexlet(assembly: &Assembly) { assembly.insert_regulator(Rc::new(outer_moon_tangency)); } +const HPHI: f64 = PHI / 2.; // "half phi" +const RTHPHI: f64 = sqrt(HPHI); // "root half phi" +const RTPHIPH: f64 = sqrt(PHI + 0.5); // "root phi plus (half)" +const P: bool = true; // Pinned +const F: bool = false; // Free + +// Initial data for the vertices commmon to 554aug2 and 554domed. +// Used the Vectornaut near_miss branch final positions for 554aug2, +// to the 3 decimal places currently shown. +const ACRON554_COMMON: [(&str, (f64, f64, f64), bool, usize, &str); 38] = [ + // id, coordinates, Pin/Free, level, earlier neighbor IDs. + ("A_NE", ( 0.5, 0.5, 0.), P, 0, ""), + ("A_NW", (-0.5, 0.5, 0.), P, 0, ""), + ("A_SE", ( 0.5, -0.5, 0.), P, 0, ""), + ("A_SW", (-0.5, -0.5, 0.), P, 0, ""), + ("Z_E", ( 0.229, -0.002, 0.821), F, 1, "A_NE,A_SE"), + ("Z_S", ( 0.002, -0.229, 0.821), F, 1, "A_SE,A_SW"), + ("B_NE", ( HPHI, HPHI, RTHPHI), P, 2, "Z_E"), + ("B_NW", (-HPHI, HPHI, RTHPHI), P, 2, ""), + ("B_SW", (-HPHI, -HPHI, RTHPHI), P, 2, "Z_S"), + ("B_SE", ( 0.812, -0.812, 0.89), F, 2, "Z_E,Z_S"), + ("Y_NE", ( 0.11, 0.103, 1.019), F, 3, "B_NE"), + ("Y_NW", (-0.103, 0.103, 1.02), F, 3, "B_NW"), + ("Y_SE", ( 0.11, -0.11, 1.017), F, 3, "B_SE"), + ("Y_SW", (-0.103, -0.11, 1.019), F, 3, "B_SW"), + ("C_N", ( 0., 1., RTPHIPH), P, 4, "Y_NE,Y_NW"), + ("C_W", (-1., 0., RTPHIPH), P, 4, "Y_NW,Y_SW"), + ("C_E", ( 1.006, -0.006, 1.45), F, 4, "Y_NE,Y_SE"), + ("C_S", ( 0.006, -1.006, 1.45), F, 4, "Y_SE,Y_SW"), + ("D_NE", ( 0.2, 0.181, 2.011), F, 5, "Y_NE,C_N,C_E"), + ("D_NW", (-0.181, 0.181, 2.014), F, 5, "Y_NW,C_N,C_W"), + ("D_SE", ( 0.2, -0.2, 2.009), F, 5, "Y_SE,C_E,C_S"), + ("D_SW", (-0.181, -0.2, 2.011), F, 5, "Y_SW,C_W,C_S"), + ("E_N", ( 0.012, 1.055, 2.46), F, 6, "C_N,D_NE,D_NW"), + ("E_W", (-1.055, -0.012, 2.46), F, 6, "C_W,D_NW,D_SW"), + ("E_E", ( 1.079, -0.012, 2.447), F, 6, "C_E,D_NE,D_SE"), + ("E_S", ( 0.012, -1.079, 2.447), F, 6, "C_S,D_SE,D_SW"), + ("F_NE", ( 0.296, 0.265, 3.003), F, 7, "D_NE,E_N,E_E"), + ("F_NW", (-0.265, 0.265, 3.007), F, 7, "D_NW,E_N,E_W"), + ("F_SE", ( 0.296, -0.296, 3.0), F, 7, "D_SE,E_E,E_S"), + ("F_SW", (-0.265, -0.296, 3.003), F, 7, "D_SW,E_W,E_S"), + ("G_1", ( 0.517, 1.19, 3.312), F, 8, "E_N,F_NE"), + ("G_2", ( 1.224, 0.483, 3.304), F, 8, "E_E,F_NE,G_1"), + ("G_4", ( 1.224, -0.517, 3.298), F, 8, "E_E,F_SE,G_2"), + ("G_5", ( 0.517, -1.224, 3.298), F, 8, "E_S,F_SE,G_4"), + ("G_7", (-0.483, -1.224, 3.304), F, 8, "E_S,F_SW,G_5"), + ("G_8", (-1.19, -0.517, 3.312), F, 8, "E_W,F_SW,G_7"), + ("G_10", (-1.19, 0.483, 3.318), F, 8, "E_W,F_NW,G_8"), + ("G_11", (-0.483, 1.19, 3.318), F, 8, "E_N,F_NW,G_1,G_10"), +]; + +const LEVEL_COLORS: [[f32; 3]; 9] = [ + [0.75, 0., 0.75], + [1., 0.4, 0.6], + [1., 0., 0.25], + [1., 0.75, 0.25], + [0.75, 0.5, 0.], + [0.9, 0.9, 0.], + [0.25, 0.75, 0.], + [0., 0.5, 0.75], + [0.25, 0., 1.], +]; + +fn load_554aug2(assembly: &Assembly) { + for (id, v, _pinned, level, _neighbors) in ACRON554_COMMON { + let pt = Point::new(id, id, LEVEL_COLORS[level], point(v.0, v.1, v.2)); + assembly.try_insert_element(pt); + } +} + // --- chooser --- /* DEBUG */ @@ -918,11 +988,14 @@ pub fn TestAssemblyChooser() -> View { "off-center" => load_off_center(assembly), "radius-ratio" => load_radius_ratio(assembly), "irisawa-hexlet" => load_irisawa_hexlet(assembly), + "aug554" => load_554aug2(assembly), _ => (), }; }); }); - + + // FIXME: Non-DRY -- should not need to reiterate thie list of assembly + // labels // build the chooser view! { select(bind:value = assembly_name) { @@ -935,6 +1008,7 @@ pub fn TestAssemblyChooser() -> View { option(value = "off-center") { "Off-center" } option(value = "radius-ratio") { "Radius ratio" } option(value = "irisawa-hexlet") { "Irisawa hexlet" } + option(value = "aug554") { "McNeill acron 554" } option(value = "empty") { "Empty" } } } diff --git a/app-proto/src/lib.rs b/app-proto/src/lib.rs index 0d9bc4a..f37e0d6 100644 --- a/app-proto/src/lib.rs +++ b/app-proto/src/lib.rs @@ -1 +1,3 @@ +#![feature(const_trait_impl)] + pub mod engine; \ No newline at end of file diff --git a/app-proto/src/main.rs b/app-proto/src/main.rs index a03b026..cb51571 100644 --- a/app-proto/src/main.rs +++ b/app-proto/src/main.rs @@ -1,3 +1,5 @@ +#![feature(const_trait_impl)] + mod assembly; mod components; mod engine; From aeace1a56291a1bf8316ce46acccc51db7f805eb Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sun, 21 Sep 2025 22:30:38 -0700 Subject: [PATCH 03/11] refactor: macro to infer const array length --- .../src/components/test_assembly_chooser.rs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 86ad836..1444325 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -19,6 +19,14 @@ use crate::{ specified::SpecifiedValue, }; +// Convenience: macro to allow elision of const array lengths +// adapted from https://stackoverflow.com/a/59905715 +macro_rules! const_array { + ($name: ident: $ty: ty = $value: expr) => { + const $name: [$ty; $value.len()] = $value; + } +} + // Convenience: use sqrt() as a function to get the square root of // anything that can be losslessly converted to f64, since we happen // to always want our sqrts to be f64. @@ -299,9 +307,9 @@ fn load_pointed(assembly: &Assembly) { // A-C -0.25 * φ^2 = -0.6545084971874737 fn load_tridiminished_icosahedron(assembly: &Assembly) { // create the vertices - const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.25_f32]; - const COLOR_B: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; - const COLOR_C: ElementColor = [0.25_f32, 0.50_f32, 1.00_f32]; + const COLOR_A: ElementColor = [1.00, 0.25, 0.25]; + const COLOR_B: ElementColor = [0.75, 0.75, 0.75]; + const COLOR_C: ElementColor = [0.25, 0.50, 1.00]; let vertices = [ Point::new("a1", "A₁", COLOR_A, point( 0.25, 0.75, 0.75)), Point::new("a2", "A₂", COLOR_A, point( 0.75, 0.25, 0.75)), @@ -318,7 +326,7 @@ fn load_tridiminished_icosahedron(assembly: &Assembly) { } // create the faces - const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + const COLOR_FACE: ElementColor = [0.75, 0.75, 0.75]; const SQRT_1_6: f64 = invsqrt(6.); const SQRT_2_3: f64 = 2. * SQRT_1_6; let faces = [ @@ -442,8 +450,8 @@ fn load_dodecahedral_packing(assembly: &Assembly) { const COLOR_C: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; const PHI_INV: f64 = 1.0 / PHI; const COORD_SCALE: f64 = sqrt(PHI + 2.0); - const FACE_SCALES: [f64; 2] = [PHI_INV, (13.0 / 12.0) / COORD_SCALE]; - const FACE_RADII: [f64; 2] = [PHI_INV, 5.0 / 12.0]; + const_array!(FACE_SCALES: f64 = [PHI_INV, (13.0 / 12.0) / COORD_SCALE]); + const_array!(FACE_RADII: f64 = [PHI_INV, 5.0 / 12.0]); let mut faces = Vec::>::new(); let subscripts = ["₀", "₁"]; for j in 0..2 { @@ -652,7 +660,7 @@ fn load_radius_ratio(assembly: &Assembly) { let index_range = 1..=4; // create the spheres - const GRAY: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + const GRAY: ElementColor = [0.75, 0.75, 0.75]; let spheres = [ Sphere::new( "sphere_faces".to_string(), @@ -891,7 +899,7 @@ const F: bool = false; // Free // Initial data for the vertices commmon to 554aug2 and 554domed. // Used the Vectornaut near_miss branch final positions for 554aug2, // to the 3 decimal places currently shown. -const ACRON554_COMMON: [(&str, (f64, f64, f64), bool, usize, &str); 38] = [ +const_array!(ACRON554_COMMON: (&str, (f64, f64, f64), bool, usize, &str) = [ // id, coordinates, Pin/Free, level, earlier neighbor IDs. ("A_NE", ( 0.5, 0.5, 0.), P, 0, ""), ("A_NW", (-0.5, 0.5, 0.), P, 0, ""), @@ -931,9 +939,9 @@ const ACRON554_COMMON: [(&str, (f64, f64, f64), bool, usize, &str); 38] = [ ("G_8", (-1.19, -0.517, 3.312), F, 8, "E_W,F_SW,G_7"), ("G_10", (-1.19, 0.483, 3.318), F, 8, "E_W,F_NW,G_8"), ("G_11", (-0.483, 1.19, 3.318), F, 8, "E_N,F_NW,G_1,G_10"), -]; +]); -const LEVEL_COLORS: [[f32; 3]; 9] = [ +const_array!(LEVEL_COLORS: ElementColor = [ [0.75, 0., 0.75], [1., 0.4, 0.6], [1., 0., 0.25], @@ -943,7 +951,7 @@ const LEVEL_COLORS: [[f32; 3]; 9] = [ [0.25, 0.75, 0.], [0., 0.5, 0.75], [0.25, 0., 1.], -]; +]); fn load_554aug2(assembly: &Assembly) { for (id, v, _pinned, level, _neighbors) in ACRON554_COMMON { From c3c3e14a43922a1cef8c94f20e787b4260cf507e Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 22 Sep 2025 04:20:49 -0700 Subject: [PATCH 04/11] feat: planar octagon with correct dimensions for 554aug2 --- app-proto/src/assembly.rs | 52 ++- .../src/components/test_assembly_chooser.rs | 392 +++++++++--------- 2 files changed, 232 insertions(+), 212 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index acef6c8..233ef66 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -1,6 +1,7 @@ use enum_iterator::{all, Sequence}; use nalgebra::{DMatrix, DVector, DVectorView}; use std::{ + any::Any, cell::Cell, cmp::Ordering, collections::{BTreeMap, BTreeSet}, @@ -95,7 +96,7 @@ pub trait Element: Serial + ProblemPoser + DisplayItem { fn default_id() -> String where Self: Sized; // the default example of an element of this type - fn default(id: String, id_num: u64) -> Self where Self: Sized; + fn default(id: &str, id_num: u64) -> Self where Self: Sized; // the default regulators that come with this element fn default_regulators(self: Rc) -> Vec> { @@ -173,14 +174,14 @@ impl Sphere { const CURVATURE_COMPONENT: usize = 3; pub fn new( - id: String, - label: String, + id: &str, + label: &str, color: ElementColor, representation: DVector, ) -> Self { Self { - id, - label, + id: id.to_string(), + label: label.to_string(), color, representation: create_signal(representation), ghost: create_signal(false), @@ -196,11 +197,11 @@ impl Element for Sphere { "sphere".to_string() } - fn default(id: String, id_num: u64) -> Self { + fn default(id: &str, id_num: u64) -> Self { Self::new( id, - format!("Sphere {id_num}"), - [0.75_f32, 0.75_f32, 0.75_f32], + &format!("Sphere {id_num}"), + [0.75, 0.75, 0.75], sphere(0.0, 0.0, 0.0, 1.0), ) } @@ -297,9 +298,9 @@ impl Element for Point { "point".to_string() } - fn default(id: String, id_num: u64) -> Self { + fn default(id: &str, id_num: u64) -> Self { Self::new( - &id, + id, &format!("Point {id_num}"), [0.75_f32, 0.75_f32, 0.75_f32], point(0.0, 0.0, 0.0), @@ -365,10 +366,22 @@ impl ProblemPoser for Point { } } -pub trait Regulator: Serial + ProblemPoser + OutlineItem { +pub trait Regulator: Any + Serial + ProblemPoser + OutlineItem { fn subjects(&self) -> Vec>; fn measurement(&self) -> ReadSignal; fn set_point(&self) -> Signal; + fn set_to(&self, val: f64) { + self.set_point().set(SpecifiedValue::from(Some(val))) + } + // QUESTION: Why doesn't the following work? When I used it to + // set the coordinate regulators on pinned points, dyna3 would panic + // when trying to nudge unpinned points :-( + // fn set_to_current(&self) { + // self.set_point().set( + // SpecifiedValue::from(Some(self.measurement().get())), + // ) + // } + fn as_any(&self) -> &dyn Any; } impl Hash for dyn Regulator { @@ -434,6 +447,8 @@ impl Regulator for InversiveDistanceRegulator { fn set_point(&self) -> Signal { self.set_point } + + fn as_any(&self) -> &dyn Any {self} } impl Serial for InversiveDistanceRegulator { @@ -489,6 +504,8 @@ impl Regulator for HalfCurvatureRegulator { fn set_point(&self) -> Signal { self.set_point } + + fn as_any(&self) -> &dyn Any {self} } impl Serial for HalfCurvatureRegulator { @@ -544,6 +561,7 @@ 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 as_any(&self) -> &dyn Any {self} } impl ProblemPoser for PointCoordinateRegulator { @@ -696,9 +714,12 @@ impl Assembly { } // create and insert the default example of `T` - let _ = self.insert_element_unchecked(T::default(id, id_num)); + let _ = self.insert_element_unchecked(T::default(&id, id_num)); } - + + // FIXME: insert_element and insert_regulator are not parallel; + // the former takes an element and calls Rc::new, inserting clones of + // the result, whereas the latter takes an Rc and inserts clones of that pub fn insert_regulator(&self, regulator: Rc) { // add the regulator to the assembly's regulator list self.regulators.update( @@ -735,6 +756,11 @@ impl Assembly { } }); } + + // --- finding entities --- + pub fn find_element(&self, id: &str) -> Rc { + self.elements_by_id.with_untracked( |elts| elts[id].clone() ) + } // --- updating the configuration --- diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 1444325..f6e1f84 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -1,5 +1,5 @@ use itertools::izip; -use std::{f64::consts::{FRAC_1_SQRT_2, PI}, rc::Rc}; +use std::{f64::consts::{FRAC_1_SQRT_2, PI, SQRT_2}, rc::Rc}; use nalgebra::Vector3; use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; @@ -12,6 +12,8 @@ use crate::{ ElementColor, InversiveDistanceRegulator, Point, + PointCoordinateRegulator, + Regulator, Sphere, }, engine, @@ -78,6 +80,12 @@ const fn sqrt(x: T) -> f64 {x.sqr()} // FIXME: replace with std::f64::consts::PHI when that gets stabilized const PHI: f64 = (1. + sqrt(5)) / 2.; +const fn gray(level: f32) -> ElementColor { [level, level, level] } +const GRAY: ElementColor = gray(0.75); +const RED: ElementColor = [1., 0., 0.25]; +const GREEN: ElementColor = [0.25, 1., 0.]; +const BLUE: ElementColor = [0., 0.25, 1.]; + // --- loaders --- /* DEBUG */ @@ -88,49 +96,37 @@ const PHI: f64 = (1. + sqrt(5)) / 2.; fn load_general(assembly: &Assembly) { let _ = assembly.try_insert_element( Sphere::new( - String::from("gemini_a"), - String::from("Castor"), - [1.00_f32, 0.25_f32, 0.00_f32], + "gemini_a", "Castor", [1.00, 0.25, 0.00], engine::sphere(0.5, 0.5, 0.0, 1.0), ) ); let _ = assembly.try_insert_element( Sphere::new( - String::from("gemini_b"), - String::from("Pollux"), - [0.00_f32, 0.25_f32, 1.00_f32], + "gemini_b", "Pollux", BLUE, engine::sphere(-0.5, -0.5, 0.0, 1.0), ) ); let _ = assembly.try_insert_element( Sphere::new( - String::from("ursa_major"), - String::from("Ursa major"), - [0.25_f32, 0.00_f32, 1.00_f32], + "ursa_major", "Ursa major", [0.25, 0.00, 1.00], engine::sphere(-0.5, 0.5, 0.0, 0.75), ) ); let _ = assembly.try_insert_element( Sphere::new( - String::from("ursa_minor"), - String::from("Ursa minor"), - [0.25_f32, 1.00_f32, 0.00_f32], + "ursa_minor", "Ursa minor", GREEN, engine::sphere(0.5, -0.5, 0.0, 0.5), ) ); let _ = assembly.try_insert_element( Sphere::new( - String::from("moon_deimos"), - String::from("Deimos"), - [0.75_f32, 0.75_f32, 0.00_f32], + "moon_deimos", "Deimos", [0.75, 0.75, 0.00], engine::sphere(0.0, 0.15, 1.0, 0.25), ) ); let _ = assembly.try_insert_element( Sphere::new( - String::from("moon_phobos"), - String::from("Phobos"), - [0.00_f32, 0.75_f32, 0.50_f32], + "moon_phobos", "Phobos", [0.00, 0.75, 0.50], engine::sphere(0.0, -0.15, -1.0, 0.25), ) ); @@ -141,65 +137,49 @@ fn load_low_curvature(assembly: &Assembly) { const A: f64 = sqrt(0.75); let _ = assembly.try_insert_element( Sphere::new( - "central".to_string(), - "Central".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], + "central", "Central", GRAY, engine::sphere(0.0, 0.0, 0.0, 1.0), ) ); let _ = assembly.try_insert_element( Sphere::new( - "assemb_plane".to_string(), - "Assembly plane".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], + "assemb_plane", "Assembly plane", GRAY, engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0), ) ); let _ = assembly.try_insert_element( Sphere::new( - "side1".to_string(), - "Side 1".to_string(), - [1.00_f32, 0.00_f32, 0.25_f32], + "side1", "Side 1", RED, engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0), ) ); let _ = assembly.try_insert_element( Sphere::new( - "side2".to_string(), - "Side 2".to_string(), - [0.25_f32, 1.00_f32, 0.00_f32], + "side2", "Side 2", GREEN, engine::sphere_with_offset(-0.5, A, 0.0, 1.0, 0.0), ) ); let _ = assembly.try_insert_element( Sphere::new( - "side3".to_string(), - "Side 3".to_string(), - [0.00_f32, 0.25_f32, 1.00_f32], + "side3", "Side 3", BLUE, engine::sphere_with_offset(-0.5, -A, 0.0, 1.0, 0.0), ) ); let _ = assembly.try_insert_element( Sphere::new( - "corner1".to_string(), - "Corner 1".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], + "corner1", "Corner 1", GRAY, engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0), ) ); let _ = assembly.try_insert_element( Sphere::new( - "corner2".to_string(), - "Corner 2".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], + "corner2", "Corner 2", GRAY, engine::sphere(2.0/3.0, -4.0/3.0 * A, 0.0, 1.0/3.0), ) ); let _ = assembly.try_insert_element( Sphere::new( - String::from("corner3"), - String::from("Corner 3"), - [0.75_f32, 0.75_f32, 0.75_f32], + "corner3", "Corner 3", GRAY, engine::sphere(2.0/3.0, 4.0/3.0 * A, 0.0, 1.0/3.0), ) ); @@ -207,19 +187,13 @@ fn load_low_curvature(assembly: &Assembly) { // impose the desired tangencies and make the sides planar let index_range = 1..=3; let [central, assemb_plane] = ["central", "assemb_plane"].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[id].clone() - ) + |id| assembly.find_element(id) ); let sides = index_range.clone().map( - |k| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("side{k}")].clone() - ) + |k| assembly.find_element(&format!("side{k}")) ); let corners = index_range.map( - |k| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("corner{k}")].clone() - ) + |k| assembly.find_element(&format!("corner{k}")) ); for plane in [assemb_plane.clone()].into_iter().chain(sides.clone()) { // fix the curvature of each plane @@ -279,8 +253,8 @@ fn load_pointed(assembly: &Assembly) { let _ = assembly.try_insert_element( Sphere::new( - format!("sphere{index_x}{index_y}"), - format!("Sphere {index_x}{index_y}"), + &format!("sphere{index_x}{index_y}"), + &format!("Sphere {index_x}{index_y}"), [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], engine::sphere(x, y, 0.0, 1.0), ) @@ -326,27 +300,23 @@ fn load_tridiminished_icosahedron(assembly: &Assembly) { } // create the faces - const COLOR_FACE: ElementColor = [0.75, 0.75, 0.75]; const SQRT_1_6: f64 = invsqrt(6.); const SQRT_2_3: f64 = 2. * SQRT_1_6; let faces = [ Sphere::new( - "face1".to_string(), - "Face 1".to_string(), - COLOR_FACE, - engine::sphere_with_offset(SQRT_2_3, -SQRT_1_6, -SQRT_1_6, -SQRT_1_6, 0.0), + "face1", "Face 1", GRAY, + engine::sphere_with_offset( + SQRT_2_3, -SQRT_1_6, -SQRT_1_6, -SQRT_1_6, 0.0), ), Sphere::new( - "face2".to_string(), - "Face 2".to_string(), - COLOR_FACE, - engine::sphere_with_offset(-SQRT_1_6, SQRT_2_3, -SQRT_1_6, -SQRT_1_6, 0.0), + "face2", "Face 2", GRAY, + engine::sphere_with_offset( + -SQRT_1_6, SQRT_2_3, -SQRT_1_6, -SQRT_1_6, 0.0), ), Sphere::new( - "face3".to_string(), - "Face 3".to_string(), - COLOR_FACE, - engine::sphere_with_offset(-SQRT_1_6, -SQRT_1_6, SQRT_2_3, -SQRT_1_6, 0.0), + "face3", "Face 3", GRAY, + engine::sphere_with_offset( + -SQRT_1_6, -SQRT_1_6, SQRT_2_3, -SQRT_1_6, 0.0), ), ]; for face in faces { @@ -427,9 +397,7 @@ fn load_dodecahedral_packing(assembly: &Assembly) { // add the substrate let _ = assembly.try_insert_element( Sphere::new( - "substrate".to_string(), - "Substrate".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], + "substrate", "Substrate", GRAY, engine::sphere(0.0, 0.0, 0.0, 1.0), ) ); @@ -466,8 +434,8 @@ fn load_dodecahedral_packing(assembly: &Assembly) { let id_a = format!("a{id_num}"); let _ = assembly.try_insert_element( Sphere::new( - id_a.clone(), - format!("A{label_sub}"), + &id_a, + &format!("A{label_sub}"), COLOR_A, engine::sphere(0.0, small_coord, big_coord, FACE_RADII[k]), ) @@ -482,8 +450,8 @@ fn load_dodecahedral_packing(assembly: &Assembly) { let id_b = format!("b{id_num}"); let _ = assembly.try_insert_element( Sphere::new( - id_b.clone(), - format!("B{label_sub}"), + &id_b, + &format!("B{label_sub}"), COLOR_B, engine::sphere(small_coord, big_coord, 0.0, FACE_RADII[k]), ) @@ -498,8 +466,8 @@ fn load_dodecahedral_packing(assembly: &Assembly) { let id_c = format!("c{id_num}"); let _ = assembly.try_insert_element( Sphere::new( - id_c.clone(), - format!("C{label_sub}"), + &id_c, + &format!("C{label_sub}"), COLOR_C, engine::sphere(big_coord, 0.0, small_coord, FACE_RADII[k]), ) @@ -569,23 +537,9 @@ fn load_balanced(assembly: &Assembly) { const R_INNER: f64 = 4.0; let spheres = [ Sphere::new( - "outer".to_string(), - "Outer".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, R_OUTER), - ), - Sphere::new( - "a".to_string(), - "A".to_string(), - [1.00_f32, 0.00_f32, 0.25_f32], - engine::sphere(0.0, 4.0, 0.0, R_INNER), - ), - Sphere::new( - "b".to_string(), - "B".to_string(), - [0.00_f32, 0.25_f32, 1.00_f32], - engine::sphere(0.0, -4.0, 0.0, R_INNER), - ), + "outer","Outer", GRAY, engine::sphere(0.0, 0.0, 0.0, R_OUTER)), + Sphere::new("a", "A", RED, engine::sphere(0.0, 4.0, 0.0, R_INNER)), + Sphere::new("b", "B", BLUE, engine::sphere(0.0, -4.0, 0.0, R_INNER)), ]; for sphere in spheres { let _ = assembly.try_insert_element(sphere); @@ -593,9 +547,7 @@ fn load_balanced(assembly: &Assembly) { // get references to the spheres let [outer, a, b] = ["outer", "a", "b"].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[id].clone() - ) + |id| assembly.find_element(id) ); // fix the diameters of the outer, sun, and moon spheres @@ -627,27 +579,22 @@ fn load_balanced(assembly: &Assembly) { fn load_off_center(assembly: &Assembly) { // create a point almost at the origin and a sphere centered on the origin let _ = assembly.try_insert_element( - Point::new("point", "Point", [0.75, 0.75, 0.75], point(1e-9, 0.0, 0.0)), + Point::new("point", "Point", GRAY, point(1e-9, 0.0, 0.0)), ); let _ = assembly.try_insert_element( Sphere::new( - "sphere".to_string(), - "Sphere".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, 1.0), - ), + "sphere", "Sphere", GRAY, engine::sphere(0.0, 0.0, 0.0, 1.0)), ); + // get references to the elements let point_and_sphere = ["point", "sphere"].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[id].clone() - ) + |id| assembly.find_element(id) ); // put the point on the sphere let incidence = InversiveDistanceRegulator::new(point_and_sphere); - incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + incidence.set_to(0.); assembly.insert_regulator(Rc::new(incidence)); } @@ -660,18 +607,13 @@ fn load_radius_ratio(assembly: &Assembly) { let index_range = 1..=4; // create the spheres - const GRAY: ElementColor = [0.75, 0.75, 0.75]; let spheres = [ Sphere::new( - "sphere_faces".to_string(), - "Insphere".to_string(), - GRAY, + "sphere_faces", "Insphere", GRAY, engine::sphere(0.0, 0.0, 0.0, 0.5), ), Sphere::new( - "sphere_vertices".to_string(), - "Circumsphere".to_string(), - GRAY, + "sphere_vertices", "Circumsphere", GRAY, engine::sphere(0.0, 0.0, 0.0, 0.25), ), ]; @@ -728,8 +670,8 @@ fn load_radius_ratio(assembly: &Assembly) { ).map( |(k, color, representation)| { Sphere::new( - format!("f{k}"), - format!("Face {k}"), + &format!("f{k}"), + &format!("Face {k}"), color, representation, ) @@ -742,27 +684,18 @@ fn load_radius_ratio(assembly: &Assembly) { // impose the constraints for j in index_range.clone() { - let [face_j, vertex_j] = [ - format!("f{j}"), - format!("v{j}"), - ].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&id].clone() - ) - ); + let [face_j, vertex_j] = [format!("f{j}"),format!("v{j}")] + .map(|id| assembly.find_element(&id)); + // make the faces planar let curvature_regulator = face_j.regulators().with_untracked( |regs| regs.first().unwrap().clone() ); - curvature_regulator.set_point().set( - SpecifiedValue::try_from("0".to_string()).unwrap() - ); + curvature_regulator.set_to(0.); for k in index_range.clone().filter(|&index| index != j) { - let vertex_k = assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("v{k}")].clone() - ); + let vertex_k = assembly.find_element(&format!("v{k}")); // fix the distances between the vertices if j < k { @@ -774,7 +707,7 @@ fn load_radius_ratio(assembly: &Assembly) { // put the vertices on the faces let incidence_regulator = InversiveDistanceRegulator::new([face_j.clone(), vertex_k.clone()]); - incidence_regulator.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + incidence_regulator.set_to(0.); assembly.insert_regulator(Rc::new(incidence_regulator)); } } @@ -800,32 +733,26 @@ fn load_radius_ratio(assembly: &Assembly) { fn load_irisawa_hexlet(assembly: &Assembly) { let index_range = 1..=6; let colors = [ - [1.00_f32, 0.00_f32, 0.25_f32], - [1.00_f32, 0.25_f32, 0.00_f32], - [0.75_f32, 0.75_f32, 0.00_f32], - [0.25_f32, 1.00_f32, 0.00_f32], - [0.00_f32, 0.25_f32, 1.00_f32], - [0.25_f32, 0.00_f32, 1.00_f32], + [1.00, 0.00, 0.25], + [1.00, 0.25, 0.00], + [0.75, 0.75, 0.00], + [0.25, 1.00, 0.00], + [0.00, 0.25, 1.00], + [0.25, 0.00, 1.00], ].into_iter(); // create the spheres let spheres = [ Sphere::new( - "outer".to_string(), - "Outer".to_string(), - [0.5_f32, 0.5_f32, 0.5_f32], + "outer", "Outer", gray(0.5), engine::sphere(0.0, 0.0, 0.0, 1.5), ), Sphere::new( - "sun".to_string(), - "Sun".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], + "sun", "Sun", GRAY, engine::sphere(0.0, -0.75, 0.0, 0.75), ), Sphere::new( - "moon".to_string(), - "Moon".to_string(), - [0.25_f32, 0.25_f32, 0.25_f32], + "moon", "Moon", gray(0.25), engine::sphere(0.0, 0.75, 0.0, 0.75), ), ].into_iter().chain( @@ -833,8 +760,8 @@ fn load_irisawa_hexlet(assembly: &Assembly) { |(k, color)| { let ang = (k as f64) * PI/3.0; Sphere::new( - format!("chain{k}"), - format!("Chain {k}"), + &format!("chain{k}"), + &format!("Chain {k}"), color, engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5), ) @@ -846,9 +773,7 @@ fn load_irisawa_hexlet(assembly: &Assembly) { } // put the outer sphere in ghost mode and fix its curvature - let outer = assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id["outer"].clone() - ); + let outer = assembly.find_element("outer"); outer.ghost().set(true); let outer_curvature_regulator = outer.regulators().with_untracked( |regs| regs.first().unwrap().clone() @@ -858,15 +783,9 @@ fn load_irisawa_hexlet(assembly: &Assembly) { ); // impose the desired tangencies - let [outer, sun, moon] = ["outer", "sun", "moon"].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[id].clone() - ) - ); + let [sun, moon] = ["sun", "moon"].map(|id| assembly.find_element(id)); let chain = index_range.map( - |k| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("chain{k}")].clone() - ) + |k| assembly.find_element(&format!("chain{k}")) ); for (chain_sphere, chain_sphere_next) in chain.clone().zip(chain.cycle().skip(1)) { for (other_sphere, inversive_distance) in [ @@ -899,46 +818,47 @@ const F: bool = false; // Free // Initial data for the vertices commmon to 554aug2 and 554domed. // Used the Vectornaut near_miss branch final positions for 554aug2, // to the 3 decimal places currently shown. -const_array!(ACRON554_COMMON: (&str, (f64, f64, f64), bool, usize, &str) = [ +const_array!(ACRON554_COMMON: (&str, [f64; 3], bool, usize, &str) = [ // id, coordinates, Pin/Free, level, earlier neighbor IDs. - ("A_NE", ( 0.5, 0.5, 0.), P, 0, ""), - ("A_NW", (-0.5, 0.5, 0.), P, 0, ""), - ("A_SE", ( 0.5, -0.5, 0.), P, 0, ""), - ("A_SW", (-0.5, -0.5, 0.), P, 0, ""), - ("Z_E", ( 0.229, -0.002, 0.821), F, 1, "A_NE,A_SE"), - ("Z_S", ( 0.002, -0.229, 0.821), F, 1, "A_SE,A_SW"), - ("B_NE", ( HPHI, HPHI, RTHPHI), P, 2, "Z_E"), - ("B_NW", (-HPHI, HPHI, RTHPHI), P, 2, ""), - ("B_SW", (-HPHI, -HPHI, RTHPHI), P, 2, "Z_S"), - ("B_SE", ( 0.812, -0.812, 0.89), F, 2, "Z_E,Z_S"), - ("Y_NE", ( 0.11, 0.103, 1.019), F, 3, "B_NE"), - ("Y_NW", (-0.103, 0.103, 1.02), F, 3, "B_NW"), - ("Y_SE", ( 0.11, -0.11, 1.017), F, 3, "B_SE"), - ("Y_SW", (-0.103, -0.11, 1.019), F, 3, "B_SW"), - ("C_N", ( 0., 1., RTPHIPH), P, 4, "Y_NE,Y_NW"), - ("C_W", (-1., 0., RTPHIPH), P, 4, "Y_NW,Y_SW"), - ("C_E", ( 1.006, -0.006, 1.45), F, 4, "Y_NE,Y_SE"), - ("C_S", ( 0.006, -1.006, 1.45), F, 4, "Y_SE,Y_SW"), - ("D_NE", ( 0.2, 0.181, 2.011), F, 5, "Y_NE,C_N,C_E"), - ("D_NW", (-0.181, 0.181, 2.014), F, 5, "Y_NW,C_N,C_W"), - ("D_SE", ( 0.2, -0.2, 2.009), F, 5, "Y_SE,C_E,C_S"), - ("D_SW", (-0.181, -0.2, 2.011), F, 5, "Y_SW,C_W,C_S"), - ("E_N", ( 0.012, 1.055, 2.46), F, 6, "C_N,D_NE,D_NW"), - ("E_W", (-1.055, -0.012, 2.46), F, 6, "C_W,D_NW,D_SW"), - ("E_E", ( 1.079, -0.012, 2.447), F, 6, "C_E,D_NE,D_SE"), - ("E_S", ( 0.012, -1.079, 2.447), F, 6, "C_S,D_SE,D_SW"), - ("F_NE", ( 0.296, 0.265, 3.003), F, 7, "D_NE,E_N,E_E"), - ("F_NW", (-0.265, 0.265, 3.007), F, 7, "D_NW,E_N,E_W"), - ("F_SE", ( 0.296, -0.296, 3.0), F, 7, "D_SE,E_E,E_S"), - ("F_SW", (-0.265, -0.296, 3.003), F, 7, "D_SW,E_W,E_S"), - ("G_1", ( 0.517, 1.19, 3.312), F, 8, "E_N,F_NE"), - ("G_2", ( 1.224, 0.483, 3.304), F, 8, "E_E,F_NE,G_1"), - ("G_4", ( 1.224, -0.517, 3.298), F, 8, "E_E,F_SE,G_2"), - ("G_5", ( 0.517, -1.224, 3.298), F, 8, "E_S,F_SE,G_4"), - ("G_7", (-0.483, -1.224, 3.304), F, 8, "E_S,F_SW,G_5"), - ("G_8", (-1.19, -0.517, 3.312), F, 8, "E_W,F_SW,G_7"), - ("G_10", (-1.19, 0.483, 3.318), F, 8, "E_W,F_NW,G_8"), - ("G_11", (-0.483, 1.19, 3.318), F, 8, "E_N,F_NW,G_1,G_10"), + ("A_NE", [ 0.5, 0.5, 0.], P, 0, ""), + ("A_NW", [-0.5, 0.5, 0.], P, 0, ""), + ("A_SE", [ 0.5, -0.5, 0.], P, 0, ""), + ("A_SW", [-0.5, -0.5, 0.], P, 0, ""), + ("Z_E", [ 0.229, -0.002, 0.821], F, 1, "A_NE,A_SE"), + ("Z_S", [ 0.002, -0.229, 0.821], F, 1, "A_SE,A_SW"), + ("B_NE", [ HPHI, HPHI, RTHPHI], P, 2, "Z_E"), + ("B_NW", [-HPHI, HPHI, RTHPHI], P, 2, ""), + ("B_SW", [-HPHI, -HPHI, RTHPHI], P, 2, "Z_S"), + ("B_SE", [ 0.812, -0.812, 0.89], F, 2, "Z_E,Z_S"), + ("Y_NE", [ 0.11, 0.103, 1.019], F, 3, "B_NE"), + ("Y_NW", [-0.103, 0.103, 1.02], F, 3, "B_NW"), + ("Y_SE", [ 0.11, -0.11, 1.017], F, 3, "B_SE"), + ("Y_SW", [-0.103, -0.11, 1.019], F, 3, "B_SW"), + ("C_N", [ 0., 1., RTPHIPH], P, 4, "Y_NE,Y_NW"), + ("C_W", [-1., 0., RTPHIPH], P, 4, "Y_NW,Y_SW"), + ("C_E", [ 1.006, -0.006, 1.45], F, 4, "Y_NE,Y_SE"), + ("C_S", [ 0.006, -1.006, 1.45], F, 4, "Y_SE,Y_SW"), + ("D_NE", [ 0.2, 0.181, 2.011], F, 5, "Y_NE,C_N,C_E"), + ("D_NW", [-0.181, 0.181, 2.014], F, 5, "Y_NW,C_N,C_W"), + ("D_SE", [ 0.2, -0.2, 2.009], F, 5, "Y_SE,C_E,C_S"), + ("D_SW", [-0.181, -0.2, 2.011], F, 5, "Y_SW,C_W,C_S"), + ("E_N", [ 0.012, 1.055, 2.46], F, 6, "C_N,D_NE,D_NW"), + ("E_W", [-1.055, -0.012, 2.46], F, 6, "C_W,D_NW,D_SW"), + ("E_E", [ 1.079, -0.012, 2.447], F, 6, "C_E,D_NE,D_SE"), + ("E_S", [ 0.012, -1.079, 2.447], F, 6, "C_S,D_SE,D_SW"), + ("F_NE", [ 0.296, 0.265, 3.003], F, 7, "D_NE,E_N,E_E"), + ("F_NW", [-0.265, 0.265, 3.007], F, 7, "D_NW,E_N,E_W"), + ("F_SE", [ 0.296, -0.296, 3.0], F, 7, "D_SE,E_E,E_S"), + ("F_SW", [-0.265, -0.296, 3.003], F, 7, "D_SW,E_W,E_S"), + // The following must be in order around the octagon (in some orientation) + ("G_1", [ 0.517, 1.19, 3.312], F, 8, "E_N,F_NE"), + ("G_2", [ 1.224, 0.483, 3.304], F, 8, "E_E,F_NE"), + ("G_4", [ 1.224, -0.517, 3.298], F, 8, "E_E,F_SE"), + ("G_5", [ 0.517, -1.224, 3.298], F, 8, "E_S,F_SE"), + ("G_7", [-0.483, -1.224, 3.304], F, 8, "E_S,F_SW"), + ("G_8", [-1.19, -0.517, 3.312], F, 8, "E_W,F_SW"), + ("G_10", [-1.19, 0.483, 3.318], F, 8, "E_W,F_NW"), + ("G_11", [-0.483, 1.19, 3.318], F, 8, "E_N,F_NW"), ]); const_array!(LEVEL_COLORS: ElementColor = [ @@ -954,9 +874,83 @@ const_array!(LEVEL_COLORS: ElementColor = [ ]); fn load_554aug2(assembly: &Assembly) { - for (id, v, _pinned, level, _neighbors) in ACRON554_COMMON { - let pt = Point::new(id, id, LEVEL_COLORS[level], point(v.0, v.1, v.2)); + // first a plane for the octagon + let oct_id = "f_g"; + let engsph = engine::sphere_with_offset( + 0., 0., 1., ACRON554_COMMON[37].1[2], 0.); + let octaface = Sphere::new(oct_id, "Octagon", [0.7, 0.7, 0.7], engsph); + octaface.ghost().set(true); + assembly.try_insert_element(octaface); + let face_rc = assembly.find_element(oct_id); + let face_curvature = face_rc.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + face_curvature.set_to(0.); + // Octagon vertices and side/diagonal lengths + let mut oct_verts = Vec::>::new(); + const OCT_LONG: usize = 4; + const OCT_N_DIAG: usize = 5; + const OCT_N: usize = 8; + const OCT_DIST: [f64; OCT_N_DIAG] = [0., // dummy at start + -0.5, + -0.5*(2. + SQRT_2), + -0.5*(3. + 2.*SQRT_2), + -0.5*(4. + 2.*SQRT_2) + ]; + // Now process the acron data + for (id, v, pinned, l, _neighbors) in ACRON554_COMMON { + let pt = Point::new(id, id, LEVEL_COLORS[l], point(v[0], v[1], v[2])); assembly.try_insert_element(pt); + if pinned { // regulate each coordinate to its given value + let mut freeze = Vec::>::new(); + // QUESTION: Would there be a way to insert an Rc into + // an assembly to avoid the need to re-lookup pt in the assembly + // after just inserting it? + let pt_rc = assembly.elements_by_id.with_untracked( + |elts| elts[id].clone() + ); + // filter the three coordinate regulators into freeze + pt_rc.regulators().with_untracked( |regs| { + for reg in regs { + if let Some(_pcr) = reg + .as_any().downcast_ref::() { + freeze.push(reg.clone()) + } + } + }); + // now set them to their originally specified values. + let mut coord: usize = 0; + for reg in freeze { + reg.set_to(v[coord]); + coord += 1; + } + } + // If part of the octagon, make incident to the plane: + if l == 8 { + let pt_rc = assembly.find_element(id); + let oct_index = oct_verts.len(); + oct_verts.push(pt_rc.clone()); + let incidence = InversiveDistanceRegulator::new( + [face_rc.clone(), pt_rc.clone()]); + incidence.set_to(0.0); + assembly.insert_regulator(Rc::new(incidence)); + // And regulate the length to the other vertices of the octagon + for offset in 1..OCT_N_DIAG { + if offset <= oct_index { + let dist = InversiveDistanceRegulator::new( + [oct_verts[oct_index - offset].clone(), pt_rc.clone()]); + dist.set_to(OCT_DIST[offset]); + assembly.insert_regulator(Rc::new(dist)); + } + if offset < OCT_LONG && oct_index + offset >= OCT_N { + let forward = oct_index + offset - OCT_N; + let dist = InversiveDistanceRegulator::new( + [oct_verts[forward].clone(), pt_rc.clone()]); + dist.set_to(OCT_DIST[offset]); + assembly.insert_regulator(Rc::new(dist)); + } + } + } } } From d572231a7183c8105fbcd8b2a74d52d73afff8d3 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 22 Sep 2025 07:44:14 -0700 Subject: [PATCH 05/11] feat: add final struts and dump the coordinates --- app-proto/src/components/outline.rs | 27 ++++++++++++++++++- .../src/components/test_assembly_chooser.rs | 23 ++++++++++------ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/app-proto/src/components/outline.rs b/app-proto/src/components/outline.rs index 79781fa..ed6c921 100644 --- a/app-proto/src/components/outline.rs +++ b/app-proto/src/components/outline.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use std::rc::Rc; use sycamore::prelude::*; -use web_sys::{KeyboardEvent, MouseEvent, wasm_bindgen::JsCast}; +use web_sys::{KeyboardEvent, MouseEvent, wasm_bindgen::{JsCast,}}; use crate::{ AppState, @@ -261,6 +261,31 @@ pub fn Outline() -> View { on:click = { let state = use_context::(); move |_| state.selection.update(|sel| sel.clear()) + }, + on:keydown = { + let rep_list = state.assembly.elements.map( + |elts| elts + .clone() + .into_iter() + .map(|elt| format!("{}: {}", elt.id(), elt.representation())) + .collect::>() + ); + + move |event: KeyboardEvent| { + match event.key().as_str() { + "c" => { + console_log!("Dumping all coordinates"); + rep_list.map( + |reps| { + for item in reps { + console_log!("{}", item); + } + } + ); + }, + _ => {}, + } + } } ) { Keyed( diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index f6e1f84..c80e2ba 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -898,17 +898,17 @@ fn load_554aug2(assembly: &Assembly) { -0.5*(4. + 2.*SQRT_2) ]; // Now process the acron data - for (id, v, pinned, l, _neighbors) in ACRON554_COMMON { + for (id, v, pinned, l, neighbors) in ACRON554_COMMON { let pt = Point::new(id, id, LEVEL_COLORS[l], point(v[0], v[1], v[2])); assembly.try_insert_element(pt); + // QUESTION: Would there be a way to insert an Rc into + // an assembly to avoid the need to re-lookup pt in the assembly + // after just inserting it? Or could/should try_insert_element return + // the Rc ? + let pt_rc = assembly.find_element(id); + if pinned { // regulate each coordinate to its given value let mut freeze = Vec::>::new(); - // QUESTION: Would there be a way to insert an Rc into - // an assembly to avoid the need to re-lookup pt in the assembly - // after just inserting it? - let pt_rc = assembly.elements_by_id.with_untracked( - |elts| elts[id].clone() - ); // filter the three coordinate regulators into freeze pt_rc.regulators().with_untracked( |regs| { for reg in regs { @@ -927,7 +927,6 @@ fn load_554aug2(assembly: &Assembly) { } // If part of the octagon, make incident to the plane: if l == 8 { - let pt_rc = assembly.find_element(id); let oct_index = oct_verts.len(); oct_verts.push(pt_rc.clone()); let incidence = InversiveDistanceRegulator::new( @@ -951,6 +950,14 @@ fn load_554aug2(assembly: &Assembly) { } } } + // Finally, add any specified neighbors + for id in neighbors.split(",") { + if id.len() == 0 { continue; } + let strut = InversiveDistanceRegulator::new( + [assembly.find_element(id), pt_rc.clone()]); + strut.set_to(-0.5); + assembly.insert_regulator(Rc::new(strut)); + } } } From 02cf7580bcd9107ac1c63e93b36d5986c2e4bad3 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 22 Sep 2025 07:47:48 -0700 Subject: [PATCH 06/11] chore: record 554aug2 data in git --- data554/pinnedAug2.txt | 378 +++++++++++++++++++++++++++++++++++++++++ data554/processData.py | 82 +++++++++ 2 files changed, 460 insertions(+) create mode 100644 data554/pinnedAug2.txt create mode 100644 data554/processData.py diff --git a/data554/pinnedAug2.txt b/data554/pinnedAug2.txt new file mode 100644 index 0000000..719a0fb --- /dev/null +++ b/data554/pinnedAug2.txt @@ -0,0 +1,378 @@ +A_NE: + ┌ ┐ + │ 0.5 │ + │ 0.5 │ + │ 0 │ + │ 0.5 │ + │ 0.25 │ + └ ┘ + + +A_NW: + ┌ ┐ + │ -0.5 │ + │ 0.5 │ + │ 0 │ + │ 0.5 │ + │ 0.25 │ + └ ┘ + + +A_SE: + ┌ ┐ + │ 0.5 │ + │ -0.5 │ + │ 0 │ + │ 0.5 │ + │ 0.25 │ + └ ┘ + + +A_SW: + ┌ ┐ + │ -0.5 │ + │ -0.5 │ + │ 0 │ + │ 0.5 │ + │ 0.25 │ + └ ┘ + + +Z_E: + ┌ ┐ + │ 0.22640100987140263 │ + │ -0.0000000000000011264771679683783 │ + │ 0.8216712192845821 │ + │ 0.5 │ + │ 0.36320050493570133 │ + └ ┘ + + +Z_S: + ┌ ┐ + │ 0.0000000000000011207778651773038 │ + │ -0.22640100987140266 │ + │ 0.8216712192845821 │ + │ 0.5 │ + │ 0.3632005049357014 │ + └ ┘ + + +B_NE: + ┌ ┐ + │ 0.8090169943749475 │ + │ 0.8090169943749475 │ + │ 0.8994537199739336 │ + │ 0.5 │ + │ 1.0590169943749475 │ + └ ┘ + + +B_NW: + ┌ ┐ + │ -0.8090169943749475 │ + │ 0.8090169943749475 │ + │ 0.8994537199739336 │ + │ 0.5 │ + │ 1.0590169943749475 │ + └ ┘ + + +B_SW: + ┌ ┐ + │ -0.8090169943749475 │ + │ -0.8090169943749475 │ + │ 0.8994537199739336 │ + │ 0.5 │ + │ 1.0590169943749475 │ + └ ┘ + + +B_SE: + ┌ ┐ + │ 0.809033099711693 │ + │ -0.8090330997116932 │ + │ 0.8991650349038145 │ + │ 0.5 │ + │ 1.0587834364258994 │ + └ ┘ + + +Y_NE: + ┌ ┐ + │ 0.1069938166551964 │ + │ 0.10687496051180567 │ + │ 1.0184481498960403 │ + │ 0.5 │ + │ 0.5300532840864689 │ + └ ┘ + + +Y_NW: + ┌ ┐ + │ -0.10692428477988265 │ + │ 0.10692428477988265 │ + │ 1.018330847132124 │ + │ 0.5 │ + │ 0.5299316597860524 │ + └ ┘ + + +Y_SE: + ┌ ┐ + │ 0.10698231945637848 │ + │ -0.10698231945639325 │ + │ 1.018535901893324 │ + │ 0.5 │ + │ 0.5301529083990917 │ + └ ┘ + + +Y_SW: + ┌ ┐ + │ -0.10687496051180383 │ + │ -0.1069938166551987 │ + │ 1.018448149896045 │ + │ 0.5 │ + │ 0.5300532840864739 │ + └ ┘ + + +C_N: + ┌ ┐ + │ 0 │ + │ 1 │ + │ 1.4553466902253547 │ + │ 0.5 │ + │ 1.5590169943749472 │ + └ ┘ + + +C_W: + ┌ ┐ + │ -1 │ + │ 0 │ + │ 1.4553466902253547 │ + │ 0.5 │ + │ 1.5590169943749472 │ + └ ┘ + + +C_E: + ┌ ┐ + │ 0.999774682263142 │ + │ 0.00007707371674579322 │ + │ 1.4560976171636484 │ + │ 0.5 │ + │ 1.5598847602606936 │ + └ ┘ + + +C_S: + ┌ ┐ + │ -0.00007707371679479347 │ + │ -0.9997746822631508 │ + │ 1.4560976171636524 │ + │ 0.5 │ + │ 1.5598847602607082 │ + └ ┘ + + +D_NE: + ┌ ┐ + │ 0.19033806936716854 │ + │ 0.19095328682585785 │ + │ 2.0114149031061697 │ + │ 0.5 │ + │ 2.0592414502287557 │ + └ ┘ + + +D_NW: + ┌ ┐ + │ -0.19098096419177987 │ + │ 0.19098096419177987 │ + │ 2.011241314951004 │ + │ 0.5 │ + │ 2.0590184768076125 │ + └ ┘ + + +D_SE: + ┌ ┐ + │ 0.19032946838241013 │ + │ -0.1903294683824241 │ + │ 2.0115648207047037 │ + │ 0.5 │ + │ 2.0594218564511593 │ + └ ┘ + + +D_SW: + ┌ ┐ + │ -0.1909532868258615 │ + │ -0.1903380693671672 │ + │ 2.011414903106175 │ + │ 0.5 │ + │ 2.0592414502287664 │ + └ ┘ + + +E_N: + ┌ ┐ + │ -0.0004604038856374817 │ + │ 1.0676219893284662 │ + │ 2.4530572130770922 │ + │ 0.5 │ + │ 3.5786536184595112 │ + └ ┘ + + +E_W: + ┌ ┐ + │ -1.0676219893284706 │ + │ 0.00046040388565697025 │ + │ 2.4530572130770927 │ + │ 0.5 │ + │ 3.5786536184595152 │ + └ ┘ + + +E_E: + ┌ ┐ + │ 1.0666707731054204 │ + │ 0.00046707469035518474 │ + │ 2.4538581237635935 │ + │ 0.5 │ + │ 3.5796028507332176 │ + └ ┘ + + +E_S: + ┌ ┐ + │ -0.0004670746903934693 │ + │ -1.0666707731054261 │ + │ 2.4538581237635966 │ + │ 0.5 │ + │ 3.5796028507332323 │ + └ ┘ + + +F_NE: + ┌ ┐ + │ 0.2796758584560578 │ + │ 0.28103193693793593 │ + │ 3.003332401794294 │ + │ 0.5 │ + │ 4.588601770269869 │ + └ ┘ + + +F_NW: + ┌ ┐ + │ -0.2810301396616794 │ + │ 0.28103013966168067 │ + │ 3.003103384288446 │ + │ 0.5 │ + │ 4.588292426736586 │ + └ ┘ + + +F_SE: + ┌ ┐ + │ 0.2796768046605061 │ + │ -0.2796768046605201 │ + │ 3.003549612841133 │ + │ 0.5 │ + │ 4.588874270244861 │ + └ ┘ + + +F_SW: + ┌ ┐ + │ -0.2810319369379494 │ + │ -0.2796758584560562 │ + │ 3.0033324017942995 │ + │ 0.5 │ + │ 4.588601770269887 │ + └ ┘ + + +G_1: + ┌ ┐ + │ 0.4992113404748246 │ + │ 1.2078950246429414 │ + │ 3.3078353127534763 │ + │ 0.5 │ + │ 6.324999358939364 │ + └ ┘ + + +G_2: + ┌ ┐ + │ 1.2063182040451104 │ + │ 0.5007887810363889 │ + │ 3.308363220828225 │ + │ 0.5 │ + │ 6.325629722163805 │ + └ ┘ + + +G_4: + ┌ ┐ + │ 1.2063180313441202 │ + │ -0.49921120092990906 │ + │ 3.3087390625721245 │ + │ 0.5 │ + │ 6.326084592820001 │ + └ ┘ + + +G_5: + ┌ ┐ + │ 0.49921120092986965 │ + │ -1.2063180313441402 │ + │ 3.3087390625721245 │ + │ 0.5 │ + │ 6.326084592820007 │ + └ ┘ + + +G_7: + ┌ ┐ + │ -0.5007887810364288 │ + │ -1.2063182040451044 │ + │ 3.3083632208282268 │ + │ 0.5 │ + │ 6.325629722163823 │ + └ ┘ + + +G_8: + ┌ ┐ + │ -1.207895024642962 │ + │ -0.4992113404748007 │ + │ 3.307835312753477 │ + │ 0.5 │ + │ 6.32499935893938 │ + └ ┘ + + +G_10: + ┌ ┐ + │ -1.2078955260488684 │ + │ 0.5007885331479526 │ + │ 3.307456458408049 │ + │ 0.5 │ + │ 6.324533930139645 │ + └ ┘ + + +G_11: + ┌ ┐ + │ -0.5007885331479277 │ + │ 1.2078955260488742 │ + │ 3.3074564584080495 │ + │ 0.5 │ + │ 6.324533930139641 │ + └ ┘ diff --git a/data554/processData.py b/data554/processData.py new file mode 100644 index 0000000..7560efa --- /dev/null +++ b/data554/processData.py @@ -0,0 +1,82 @@ +from math import fabs, nan, sqrt +import sys +vertices = {} +current_id = "" +for line in sys.stdin: + text = line.strip() + if text.endswith(":"): + new_id = text.strip(":") + vertices[new_id] = [] + if current_id: print(current_id, vertices[current_id]) + current_id = new_id + continue + remainder = text.strip("┌ ┐└┘│") + if len(remainder): vertices[current_id].append(float(remainder)) +print(len(vertices), "vertices found") + +P = True +F = False +HPHI = nan +RTHPHI = nan +RTPHIPH = nan +# Taken verbatim from test_assembly_chooser.rs +acron_data = [ + ("A_NE", [ 0.5, 0.5, 0.], P, 0, ""), + ("A_NW", [-0.5, 0.5, 0.], P, 0, ""), + ("A_SE", [ 0.5, -0.5, 0.], P, 0, ""), + ("A_SW", [-0.5, -0.5, 0.], P, 0, ""), + ("Z_E", [ 0.229, -0.002, 0.821], F, 1, "A_NE,A_SE"), + ("Z_S", [ 0.002, -0.229, 0.821], F, 1, "A_SE,A_SW"), + ("B_NE", [ HPHI, HPHI, RTHPHI], P, 2, "Z_E"), + ("B_NW", [-HPHI, HPHI, RTHPHI], P, 2, ""), + ("B_SW", [-HPHI, -HPHI, RTHPHI], P, 2, "Z_S"), + ("B_SE", [ 0.812, -0.812, 0.89], F, 2, "Z_E,Z_S"), + ("Y_NE", [ 0.11, 0.103, 1.019], F, 3, "B_NE"), + ("Y_NW", [-0.103, 0.103, 1.02], F, 3, "B_NW"), + ("Y_SE", [ 0.11, -0.11, 1.017], F, 3, "B_SE"), + ("Y_SW", [-0.103, -0.11, 1.019], F, 3, "B_SW"), + ("C_N", [ 0., 1., RTPHIPH], P, 4, "Y_NE,Y_NW"), + ("C_W", [-1., 0., RTPHIPH], P, 4, "Y_NW,Y_SW"), + ("C_E", [ 1.006, -0.006, 1.45], F, 4, "Y_NE,Y_SE"), + ("C_S", [ 0.006, -1.006, 1.45], F, 4, "Y_SE,Y_SW"), + ("D_NE", [ 0.2, 0.181, 2.011], F, 5, "Y_NE,C_N,C_E"), + ("D_NW", [-0.181, 0.181, 2.014], F, 5, "Y_NW,C_N,C_W"), + ("D_SE", [ 0.2, -0.2, 2.009], F, 5, "Y_SE,C_E,C_S"), + ("D_SW", [-0.181, -0.2, 2.011], F, 5, "Y_SW,C_W,C_S"), + ("E_N", [ 0.012, 1.055, 2.46], F, 6, "C_N,D_NE,D_NW"), + ("E_W", [-1.055, -0.012, 2.46], F, 6, "C_W,D_NW,D_SW"), + ("E_E", [ 1.079, -0.012, 2.447], F, 6, "C_E,D_NE,D_SE"), + ("E_S", [ 0.012, -1.079, 2.447], F, 6, "C_S,D_SE,D_SW"), + ("F_NE", [ 0.296, 0.265, 3.003], F, 7, "D_NE,E_N,E_E"), + ("F_NW", [-0.265, 0.265, 3.007], F, 7, "D_NW,E_N,E_W"), + ("F_SE", [ 0.296, -0.296, 3.0], F, 7, "D_SE,E_E,E_S"), + ("F_SW", [-0.265, -0.296, 3.003], F, 7, "D_SW,E_W,E_S"), + # The following must be in order around the octagon (in some orientation) + ("G_1", [ 0.517, 1.19, 3.312], F, 8, "E_N,F_NE"), + ("G_2", [ 1.224, 0.483, 3.304], F, 8, "E_E,F_NE"), + ("G_4", [ 1.224, -0.517, 3.298], F, 8, "E_E,F_SE"), + ("G_5", [ 0.517, -1.224, 3.298], F, 8, "E_S,F_SE"), + ("G_7", [-0.483, -1.224, 3.304], F, 8, "E_S,F_SW"), + ("G_8", [-1.19, -0.517, 3.312], F, 8, "E_W,F_SW"), + ("G_10", [-1.19, 0.483, 3.318], F, 8, "E_W,F_NW"), + ("G_11", [-0.483, 1.19, 3.318], F, 8, "E_N,F_NW"), +] + +E = 0.0 +for vi in range(0, len(acron_data)): + start_id = acron_data[vi][0] + start = vertices[start_id] + ends = acron_data[vi][4].split(",") + if acron_data[vi][3] == 8: + if start_id != "G_1": + ends.append(acron_data[vi-1][0]) + if vi+1 == len(acron_data): + ends.append("G_1") + for end_id in ends: + if not(end_id): continue + end = vertices[end_id] + dist = sqrt( + (end[0]-start[0])**2 + (end[1]-start[1])**2 + (end[2]-start[2])**2) + print(f"{start_id}-{end_id}: {dist} {dist-1}") + E += fabs(dist-1) +print(f"----> Total distortion E={E}") From 3afdd3a36bfb1cde71ab424d0b38fd2d40e65088 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 22 Sep 2025 08:38:29 -0700 Subject: [PATCH 07/11] fix: Add three missing edges --- .../src/components/test_assembly_chooser.rs | 6 +- data554/pinnedAug2.txt | 280 +++++++++--------- data554/processData.py | 9 +- 3 files changed, 149 insertions(+), 146 deletions(-) diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index c80e2ba..518a85c 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -829,15 +829,15 @@ const_array!(ACRON554_COMMON: (&str, [f64; 3], bool, usize, &str) = [ ("B_NE", [ HPHI, HPHI, RTHPHI], P, 2, "Z_E"), ("B_NW", [-HPHI, HPHI, RTHPHI], P, 2, ""), ("B_SW", [-HPHI, -HPHI, RTHPHI], P, 2, "Z_S"), - ("B_SE", [ 0.812, -0.812, 0.89], F, 2, "Z_E,Z_S"), + ("B_SE", [ 0.812, -0.812, 0.89], F, 2, "A_SE,Z_E,Z_S"), ("Y_NE", [ 0.11, 0.103, 1.019], F, 3, "B_NE"), ("Y_NW", [-0.103, 0.103, 1.02], F, 3, "B_NW"), ("Y_SE", [ 0.11, -0.11, 1.017], F, 3, "B_SE"), ("Y_SW", [-0.103, -0.11, 1.019], F, 3, "B_SW"), ("C_N", [ 0., 1., RTPHIPH], P, 4, "Y_NE,Y_NW"), ("C_W", [-1., 0., RTPHIPH], P, 4, "Y_NW,Y_SW"), - ("C_E", [ 1.006, -0.006, 1.45], F, 4, "Y_NE,Y_SE"), - ("C_S", [ 0.006, -1.006, 1.45], F, 4, "Y_SE,Y_SW"), + ("C_E", [ 1.006, -0.006, 1.45], F, 4, "B_NE,B_SE,Y_NE,Y_SE"), + ("C_S", [ 0.006, -1.006, 1.45], F, 4, "B_SE,B_SW,Y_SE,Y_SW"), ("D_NE", [ 0.2, 0.181, 2.011], F, 5, "Y_NE,C_N,C_E"), ("D_NW", [-0.181, 0.181, 2.014], F, 5, "Y_NW,C_N,C_W"), ("D_SE", [ 0.2, -0.2, 2.009], F, 5, "Y_SE,C_E,C_S"), diff --git a/data554/pinnedAug2.txt b/data554/pinnedAug2.txt index 719a0fb..d0e051e 100644 --- a/data554/pinnedAug2.txt +++ b/data554/pinnedAug2.txt @@ -39,23 +39,23 @@ A_SW: Z_E: - ┌ ┐ - │ 0.22640100987140263 │ - │ -0.0000000000000011264771679683783 │ - │ 0.8216712192845821 │ - │ 0.5 │ - │ 0.36320050493570133 │ - └ ┘ + ┌ ┐ + │ 0.22634143549538555 │ + │ 0.000032427771744506665 │ + │ 0.8216513780396574 │ + │ 0.5 │ + │ 0.363170717377161 │ + └ ┘ Z_S: - ┌ ┐ - │ 0.0000000000000011207778651773038 │ - │ -0.22640100987140266 │ - │ 0.8216712192845821 │ - │ 0.5 │ - │ 0.3632005049357014 │ - └ ┘ + ┌ ┐ + │ -0.000032427771744220924 │ + │ -0.22634143549538618 │ + │ 0.8216513780396576 │ + │ 0.5 │ + │ 0.36317071737716133 │ + └ ┘ B_NE: @@ -90,51 +90,51 @@ B_SW: B_SE: ┌ ┐ - │ 0.809033099711693 │ - │ -0.8090330997116932 │ - │ 0.8991650349038145 │ + │ 0.8089673881587364 │ + │ -0.8089673881587335 │ + │ 0.8995105586041169 │ │ 0.5 │ - │ 1.0587834364258994 │ + │ 1.0589799582637938 │ └ ┘ Y_NE: ┌ ┐ - │ 0.1069938166551964 │ - │ 0.10687496051180567 │ - │ 1.0184481498960403 │ + │ 0.10684030416478518 │ + │ 0.1069861535711018 │ + │ 1.0181778574241942 │ │ 0.5 │ - │ 0.5300532840864689 │ + │ 0.5297735192825956 │ └ ┘ Y_NW: ┌ ┐ - │ -0.10692428477988265 │ - │ 0.10692428477988265 │ - │ 1.018330847132124 │ + │ -0.10693925692260356 │ + │ 0.10693925692260356 │ + │ 1.0182278452645257 │ │ 0.5 │ - │ 0.5299316597860524 │ + │ 0.5298299819167654 │ └ ┘ Y_SE: ┌ ┐ - │ 0.10698231945637848 │ - │ -0.10698231945639325 │ - │ 1.018535901893324 │ + │ 0.10684605297358556 │ + │ -0.10684605297358302 │ + │ 1.0181767492960683 │ │ 0.5 │ - │ 0.5301529083990917 │ + │ 0.5297580227088338 │ └ ┘ Y_SW: ┌ ┐ - │ -0.10687496051180383 │ - │ -0.1069938166551987 │ - │ 1.018448149896045 │ + │ -0.10698615357110258 │ + │ -0.10684030416478382 │ + │ 1.0181778574241929 │ │ 0.5 │ - │ 0.5300532840864739 │ + │ 0.5297735192825943 │ └ ┘ @@ -159,220 +159,220 @@ C_W: C_E: - ┌ ┐ - │ 0.999774682263142 │ - │ 0.00007707371674579322 │ - │ 1.4560976171636484 │ - │ 0.5 │ - │ 1.5598847602606936 │ - └ ┘ + ┌ ┐ + │ 0.9997685166344451 │ + │ 0.000045591796741388215 │ + │ 1.4555126531079483 │ + │ 0.5 │ + │ 1.5590202705588938 │ + └ ┘ C_S: ┌ ┐ - │ -0.00007707371679479347 │ - │ -0.9997746822631508 │ - │ 1.4560976171636524 │ + │ -0.00004559179673933571 │ + │ -0.9997685166344433 │ + │ 1.4555126531079468 │ │ 0.5 │ - │ 1.5598847602607082 │ + │ 1.5590202705588896 │ └ ┘ D_NE: ┌ ┐ - │ 0.19033806936716854 │ - │ 0.19095328682585785 │ - │ 2.0114149031061697 │ + │ 0.19050130249898212 │ + │ 0.19080920821293734 │ + │ 2.0111349254444733 │ │ 0.5 │ - │ 2.0592414502287557 │ + │ 2.0586841044725492 │ └ ┘ D_NW: ┌ ┐ - │ -0.19098096419177987 │ - │ 0.19098096419177987 │ - │ 2.011241314951004 │ + │ -0.19087531658436754 │ + │ 0.1908753165843677 │ + │ 2.0111018697108243 │ │ 0.5 │ - │ 2.0590184768076125 │ + │ 2.058720870516732 │ └ ┘ D_SE: ┌ ┐ - │ 0.19032946838241013 │ - │ -0.1903294683824241 │ - │ 2.0115648207047037 │ + │ 0.1904710244638656 │ + │ -0.1904710244638643 │ + │ 2.011170408319603 │ │ 0.5 │ - │ 2.0594218564511593 │ + │ 2.0586783801164676 │ └ ┘ D_SW: - ┌ ┐ - │ -0.1909532868258615 │ - │ -0.1903380693671672 │ - │ 2.011414903106175 │ - │ 0.5 │ - │ 2.0592414502287664 │ - └ ┘ + ┌ ┐ + │ -0.19080920821293662 │ + │ -0.19050130249898098 │ + │ 2.011134925444472 │ + │ 0.5 │ + │ 2.0586841044725475 │ + └ ┘ E_N: ┌ ┐ - │ -0.0004604038856374817 │ - │ 1.0676219893284662 │ - │ 2.4530572130770922 │ + │ -0.0001774225161991373 │ + │ 1.0673561656973407 │ + │ 2.453087108367921 │ │ 0.5 │ - │ 3.5786536184595112 │ + │ 3.5784389787247153 │ └ ┘ E_W: ┌ ┐ - │ -1.0676219893284706 │ - │ 0.00046040388565697025 │ - │ 2.4530572130770927 │ + │ -1.0673561656973403 │ + │ 0.00017742251619961636 │ + │ 2.4530871083679195 │ │ 0.5 │ - │ 3.5786536184595152 │ + │ 3.578438978724712 │ └ ┘ E_E: - ┌ ┐ - │ 1.0666707731054204 │ - │ 0.00046707469035518474 │ - │ 2.4538581237635935 │ - │ 0.5 │ - │ 3.5796028507332176 │ - └ ┘ + ┌ ┐ + │ 1.0669548967200053 │ + │ 0.0001843766584768728 │ + │ 2.4532662028373866 │ + │ 0.5 │ + │ 3.5784510553790545 │ + └ ┘ E_S: - ┌ ┐ - │ -0.0004670746903934693 │ - │ -1.0666707731054261 │ - │ 2.4538581237635966 │ - │ 0.5 │ - │ 3.5796028507332323 │ - └ ┘ + ┌ ┐ + │ -0.00018437665847651806 │ + │ -1.0669548967200044 │ + │ 2.453266202837385 │ + │ 0.5 │ + │ 3.57845105537905 │ + └ ┘ F_NE: ┌ ┐ - │ 0.2796758584560578 │ - │ 0.28103193693793593 │ - │ 3.003332401794294 │ + │ 0.28011657185554717 │ + │ 0.280597474289424 │ + │ 3.003049209443169 │ │ 0.5 │ - │ 4.588601770269869 │ + │ 4.587753011062896 │ └ ┘ F_NW: ┌ ┐ - │ -0.2810301396616794 │ - │ 0.28103013966168067 │ - │ 3.003103384288446 │ + │ -0.280598407806719 │ + │ 0.28059840780671874 │ + │ 3.002988046197754 │ │ 0.5 │ - │ 4.588292426736586 │ + │ 4.587705454166589 │ └ ┘ F_SE: ┌ ┐ - │ 0.2796768046605061 │ - │ -0.2796768046605201 │ - │ 3.003549612841133 │ + │ 0.28011643423019444 │ + │ -0.2801164342301949 │ + │ 3.0031051243209257 │ │ 0.5 │ - │ 4.588874270244861 │ + │ 4.587785491049536 │ └ ┘ F_SW: - ┌ ┐ - │ -0.2810319369379494 │ - │ -0.2796758584560562 │ - │ 3.0033324017942995 │ - │ 0.5 │ - │ 4.588601770269887 │ - └ ┘ + ┌ ┐ + │ -0.2805974742894247 │ + │ -0.28011657185554784 │ + │ 3.003049209443168 │ + │ 0.5 │ + │ 4.587753011062893 │ + └ ┘ G_1: ┌ ┐ - │ 0.4992113404748246 │ - │ 1.2078950246429414 │ - │ 3.3078353127534763 │ + │ 0.4997377212516245 │ + │ 1.2073683664308739 │ + │ 3.3077679987306667 │ │ 0.5 │ - │ 6.324999358939364 │ + │ 6.324404048677748 │ └ ┘ G_2: ┌ ┐ - │ 1.2063182040451104 │ - │ 0.5007887810363889 │ - │ 3.308363220828225 │ + │ 1.2068449832232118 │ + │ 0.5002622274345682 │ + │ 3.307880423181695 │ │ 0.5 │ - │ 6.325629722163805 │ + │ 6.3244041808325075 │ └ ┘ G_4: - ┌ ┐ - │ 1.2063180313441202 │ - │ -0.49921120092990906 │ - │ 3.3087390625721245 │ - │ 0.5 │ - │ 6.326084592820001 │ - └ ┘ + ┌ ┐ + │ 1.2068444751045393 │ + │ -0.4997377595067899 │ + │ 3.307966697528916 │ + │ 0.5 │ + │ 6.324427820224868 │ + └ ┘ G_5: ┌ ┐ - │ 0.49921120092986965 │ - │ -1.2063180313441402 │ - │ 3.3087390625721245 │ + │ 0.4997377595067881 │ + │ -1.2068444751045408 │ + │ 3.307966697528914 │ │ 0.5 │ - │ 6.326084592820007 │ + │ 6.324427820224864 │ └ ┘ G_7: ┌ ┐ - │ -0.5007887810364288 │ - │ -1.2063182040451044 │ - │ 3.3083632208282268 │ + │ -0.5002622274345697 │ + │ -1.2068449832232129 │ + │ 3.307880423181691 │ │ 0.5 │ - │ 6.325629722163823 │ + │ 6.324404180832497 │ └ ┘ G_8: ┌ ┐ - │ -1.207895024642962 │ - │ -0.4992113404748007 │ - │ 3.307835312753477 │ + │ -1.2073683664308756 │ + │ -0.4997377212516256 │ + │ 3.307767998730663 │ │ 0.5 │ - │ 6.32499935893938 │ + │ 6.3244040486777395 │ └ ┘ G_10: ┌ ┐ - │ -1.2078955260488684 │ - │ 0.5007885331479526 │ - │ 3.307456458408049 │ + │ -1.2073693671510268 │ + │ 0.50026206687508 │ + │ 3.307678829535478 │ │ 0.5 │ - │ 6.324533930139645 │ + │ 6.3243704479462215 │ └ ┘ G_11: ┌ ┐ - │ -0.5007885331479277 │ - │ 1.2078955260488742 │ - │ 3.3074564584080495 │ + │ -0.5002620668750811 │ + │ 1.2073693671510257 │ + │ 3.30767882953548 │ │ 0.5 │ - │ 6.324533930139641 │ + │ 6.324370447946225 │ └ ┘ diff --git a/data554/processData.py b/data554/processData.py index 7560efa..342b54c 100644 --- a/data554/processData.py +++ b/data554/processData.py @@ -30,15 +30,15 @@ acron_data = [ ("B_NE", [ HPHI, HPHI, RTHPHI], P, 2, "Z_E"), ("B_NW", [-HPHI, HPHI, RTHPHI], P, 2, ""), ("B_SW", [-HPHI, -HPHI, RTHPHI], P, 2, "Z_S"), - ("B_SE", [ 0.812, -0.812, 0.89], F, 2, "Z_E,Z_S"), + ("B_SE", [ 0.812, -0.812, 0.89], F, 2, "A_SE,Z_E,Z_S"), ("Y_NE", [ 0.11, 0.103, 1.019], F, 3, "B_NE"), ("Y_NW", [-0.103, 0.103, 1.02], F, 3, "B_NW"), ("Y_SE", [ 0.11, -0.11, 1.017], F, 3, "B_SE"), ("Y_SW", [-0.103, -0.11, 1.019], F, 3, "B_SW"), ("C_N", [ 0., 1., RTPHIPH], P, 4, "Y_NE,Y_NW"), ("C_W", [-1., 0., RTPHIPH], P, 4, "Y_NW,Y_SW"), - ("C_E", [ 1.006, -0.006, 1.45], F, 4, "Y_NE,Y_SE"), - ("C_S", [ 0.006, -1.006, 1.45], F, 4, "Y_SE,Y_SW"), + ("C_E", [ 1.006, -0.006, 1.45], F, 4, "B_NE,B_SE,Y_NE,Y_SE"), + ("C_S", [ 0.006, -1.006, 1.45], F, 4, "B_SE,B_SW,Y_SE,Y_SW"), ("D_NE", [ 0.2, 0.181, 2.011], F, 5, "Y_NE,C_N,C_E"), ("D_NW", [-0.181, 0.181, 2.014], F, 5, "Y_NW,C_N,C_W"), ("D_SE", [ 0.2, -0.2, 2.009], F, 5, "Y_SE,C_E,C_S"), @@ -63,6 +63,7 @@ acron_data = [ ] E = 0.0 +n_struts = 11 # for the pinned vertices, which all have length exactly 1 for vi in range(0, len(acron_data)): start_id = acron_data[vi][0] start = vertices[start_id] @@ -79,4 +80,6 @@ for vi in range(0, len(acron_data)): (end[0]-start[0])**2 + (end[1]-start[1])**2 + (end[2]-start[2])**2) print(f"{start_id}-{end_id}: {dist} {dist-1}") E += fabs(dist-1) + n_struts += 1 +print(n_struts, "unit edges") print(f"----> Total distortion E={E}") From 0949dc5e8a055262428bd94f0e792aba1104cf0f Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 22 Sep 2025 09:31:30 -0700 Subject: [PATCH 08/11] refactor: leave octagon constraints unset so they can be filled in manually --- app-proto/src/components/test_assembly_chooser.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 518a85c..85f30b0 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -931,21 +931,21 @@ fn load_554aug2(assembly: &Assembly) { oct_verts.push(pt_rc.clone()); let incidence = InversiveDistanceRegulator::new( [face_rc.clone(), pt_rc.clone()]); - incidence.set_to(0.0); + // incidence.set_to(0.0); assembly.insert_regulator(Rc::new(incidence)); // And regulate the length to the other vertices of the octagon for offset in 1..OCT_N_DIAG { if offset <= oct_index { let dist = InversiveDistanceRegulator::new( [oct_verts[oct_index - offset].clone(), pt_rc.clone()]); - dist.set_to(OCT_DIST[offset]); + if offset == 1 { dist.set_to(OCT_DIST[offset]); } assembly.insert_regulator(Rc::new(dist)); } if offset < OCT_LONG && oct_index + offset >= OCT_N { let forward = oct_index + offset - OCT_N; let dist = InversiveDistanceRegulator::new( [oct_verts[forward].clone(), pt_rc.clone()]); - dist.set_to(OCT_DIST[offset]); + if offset == 1 { dist.set_to(OCT_DIST[offset]); } assembly.insert_regulator(Rc::new(dist)); } } From a1b7c1fcb07ff02179cba77c3b325594dcddf20f Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 22 Sep 2025 11:31:21 -0700 Subject: [PATCH 09/11] feat: add domed configuration for acron 554 --- .../src/components/test_assembly_chooser.rs | 94 ++-- data554/pinnedDomed1.txt | 422 ++++++++++++++++++ data554/processDomed.py | 95 ++++ 3 files changed, 582 insertions(+), 29 deletions(-) create mode 100644 data554/pinnedDomed1.txt create mode 100644 data554/processDomed.py diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 85f30b0..581aa6f 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -824,41 +824,41 @@ const_array!(ACRON554_COMMON: (&str, [f64; 3], bool, usize, &str) = [ ("A_NW", [-0.5, 0.5, 0.], P, 0, ""), ("A_SE", [ 0.5, -0.5, 0.], P, 0, ""), ("A_SW", [-0.5, -0.5, 0.], P, 0, ""), - ("Z_E", [ 0.229, -0.002, 0.821], F, 1, "A_NE,A_SE"), - ("Z_S", [ 0.002, -0.229, 0.821], F, 1, "A_SE,A_SW"), + ("Z_E", [ 0.229, -0.002, 0.822], F, 1, "A_NE,A_SE"), + ("Z_S", [ 0.002, -0.229, 0.822], F, 1, "A_SE,A_SW"), ("B_NE", [ HPHI, HPHI, RTHPHI], P, 2, "Z_E"), ("B_NW", [-HPHI, HPHI, RTHPHI], P, 2, ""), ("B_SW", [-HPHI, -HPHI, RTHPHI], P, 2, "Z_S"), - ("B_SE", [ 0.812, -0.812, 0.89], F, 2, "A_SE,Z_E,Z_S"), - ("Y_NE", [ 0.11, 0.103, 1.019], F, 3, "B_NE"), - ("Y_NW", [-0.103, 0.103, 1.02], F, 3, "B_NW"), - ("Y_SE", [ 0.11, -0.11, 1.017], F, 3, "B_SE"), - ("Y_SW", [-0.103, -0.11, 1.019], F, 3, "B_SW"), + ("B_SE", [ 0.812, -0.812, 0.897], F, 2, "A_SE,Z_E,Z_S"), + ("Y_NE", [ 0.11, 0.104, 1.019], F, 3, "B_NE"), + ("Y_NW", [-0.104, 0.104, 1.019], F, 3, "B_NW"), + ("Y_SE", [ 0.11, -0.11, 1.018], F, 3, "B_SE"), + ("Y_SW", [-0.104, -0.11, 1.019], F, 3, "B_SW"), ("C_N", [ 0., 1., RTPHIPH], P, 4, "Y_NE,Y_NW"), ("C_W", [-1., 0., RTPHIPH], P, 4, "Y_NW,Y_SW"), - ("C_E", [ 1.006, -0.006, 1.45], F, 4, "B_NE,B_SE,Y_NE,Y_SE"), - ("C_S", [ 0.006, -1.006, 1.45], F, 4, "B_SE,B_SW,Y_SE,Y_SW"), - ("D_NE", [ 0.2, 0.181, 2.011], F, 5, "Y_NE,C_N,C_E"), - ("D_NW", [-0.181, 0.181, 2.014], F, 5, "Y_NW,C_N,C_W"), - ("D_SE", [ 0.2, -0.2, 2.009], F, 5, "Y_SE,C_E,C_S"), - ("D_SW", [-0.181, -0.2, 2.011], F, 5, "Y_SW,C_W,C_S"), - ("E_N", [ 0.012, 1.055, 2.46], F, 6, "C_N,D_NE,D_NW"), - ("E_W", [-1.055, -0.012, 2.46], F, 6, "C_W,D_NW,D_SW"), - ("E_E", [ 1.079, -0.012, 2.447], F, 6, "C_E,D_NE,D_SE"), - ("E_S", [ 0.012, -1.079, 2.447], F, 6, "C_S,D_SE,D_SW"), - ("F_NE", [ 0.296, 0.265, 3.003], F, 7, "D_NE,E_N,E_E"), - ("F_NW", [-0.265, 0.265, 3.007], F, 7, "D_NW,E_N,E_W"), - ("F_SE", [ 0.296, -0.296, 3.0], F, 7, "D_SE,E_E,E_S"), - ("F_SW", [-0.265, -0.296, 3.003], F, 7, "D_SW,E_W,E_S"), + ("C_E", [ 1.003, -0.003, 1.454], F, 4, "B_NE,B_SE,Y_NE,Y_SE"), + ("C_S", [ 0.003, -1.003, 1.454], F, 4, "B_SE,B_SW,Y_SE,Y_SW"), + ("D_NE", [ 0.195, 0.186, 2.012], F, 5, "Y_NE,C_N,C_E"), + ("D_NW", [-0.186, 0.186, 2.012], F, 5, "Y_NW,C_N,C_W"), + ("D_SE", [ 0.195, -0.195, 2.011], F, 5, "Y_SE,C_E,C_S"), + ("D_SW", [-0.186, -0.195, 2.012], F, 5, "Y_SW,C_W,C_S"), + ("E_N", [ 0.005, 1.062, 2.455], F, 6, "C_N,D_NE,D_NW"), + ("E_W", [-1.063, -0.005, 2.455], F, 6, "C_W,D_NW,D_SW"), + ("E_E", [ 1.072, -0.005, 2.452], F, 6, "C_E,D_NE,D_SE"), + ("E_S", [ 0.005, -1.072, 2.452], F, 6, "C_S,D_SE,D_SW"), + ("F_NE", [ 0.286, 0.275, 3.003], F, 7, "D_NE,E_N,E_E"), + ("F_NW", [-0.275, 0.275, 3.004], F, 7, "D_NW,E_N,E_W"), + ("F_SE", [ 0.286, -0.286, 3.003], F, 7, "D_SE,E_E,E_S"), + ("F_SW", [-0.275, -0.286, 3.003], F, 7, "D_SW,E_W,E_S"), // The following must be in order around the octagon (in some orientation) - ("G_1", [ 0.517, 1.19, 3.312], F, 8, "E_N,F_NE"), - ("G_2", [ 1.224, 0.483, 3.304], F, 8, "E_E,F_NE"), - ("G_4", [ 1.224, -0.517, 3.298], F, 8, "E_E,F_SE"), - ("G_5", [ 0.517, -1.224, 3.298], F, 8, "E_S,F_SE"), - ("G_7", [-0.483, -1.224, 3.304], F, 8, "E_S,F_SW"), - ("G_8", [-1.19, -0.517, 3.312], F, 8, "E_W,F_SW"), - ("G_10", [-1.19, 0.483, 3.318], F, 8, "E_W,F_NW"), - ("G_11", [-0.483, 1.19, 3.318], F, 8, "E_N,F_NW"), + ("G_1", [ 0.506, 1.201, 3.309], F, 8, "E_N,F_NE"), + ("G_2", [ 1.213, 0.494, 3.307], F, 8, "E_E,F_NE"), + ("G_4", [ 1.213, -0.506, 3.306], F, 8, "E_E,F_SE"), + ("G_5", [ 0.506, -1.213, 3.306], F, 8, "E_S,F_SE"), + ("G_7", [-0.494, -1.213, 3.307], F, 8, "E_S,F_SW"), + ("G_8", [-1.201, -0.506, 3.309], F, 8, "E_W,F_SW"), + ("G_10", [-1.201, 0.494, 3.311], F, 8, "E_W,F_NW"), + ("G_11", [-0.494, 1.201, 3.311], F, 8, "E_N,F_NW"), ]); const_array!(LEVEL_COLORS: ElementColor = [ @@ -871,6 +871,9 @@ const_array!(LEVEL_COLORS: ElementColor = [ [0.25, 0.75, 0.], [0., 0.5, 0.75], [0.25, 0., 1.], + [0.6, 0.15, 0.6], + [0.9, 0.5, 0.6], + [0.85, 0.15, 0.35] ]); fn load_554aug2(assembly: &Assembly) { @@ -961,6 +964,37 @@ fn load_554aug2(assembly: &Assembly) { } } +const_array!(ACRON554_DOME: (&str, [f64; 3], bool, usize, &str) = [ + // id, coordinates, Pin/Free, level, earlier neighbor IDs. + ("H_E", [ 0.359, -0.006, 3.161], F, 9, "G_2,G_4"), + ("H_N", [ 0.005, 0.348, 3.158], F, 9, "G_1,G_11"), + ("H_S", [ 0.006, 0.359, 3.159], F, 9, "G_5,G_7"), + ("H_W", [-0.347, -0.005, 3.163], F, 9, "G_8,G_10"), + ("I_NE", [ 0.507, 0.493, 4.015], F, 10, "G_1,G_2,H_E,H_N"), + ("I_NW", [-0.493, 0.493, 4.013], F, 10, "G_10,G_11,H_N,H_W"), + ("I_SE", [ 0.507, -0.507, 4.012], F, 10, "G_4,G_5,H_E,H_S"), + ("I_SW", [-0.493, -0.507, 4.015], F, 10, "G_7,G_8,H_S,H_W"), + ("J", [ 0.004, -0.010, 3.303], F, 11, "I_NE,I_NW,I_SE,I_SW"), +]); + +fn load_554domed(assembly: &Assembly) { + load_554aug2(assembly); + // Now process the additional data + for (id, v, _pinned, l, neighbors) in ACRON554_DOME { + let pt = Point::new(id, id, LEVEL_COLORS[l], point(v[0], v[1], v[2])); + assembly.try_insert_element(pt); + let pt_rc = assembly.find_element(id); + // Add any specified neighbors + for id in neighbors.split(",") { + if id.len() == 0 { continue; } + let strut = InversiveDistanceRegulator::new( + [assembly.find_element(id), pt_rc.clone()]); + strut.set_to(-0.5); + assembly.insert_regulator(Rc::new(strut)); + } + } +} + // --- chooser --- /* DEBUG */ @@ -998,6 +1032,7 @@ pub fn TestAssemblyChooser() -> View { "radius-ratio" => load_radius_ratio(assembly), "irisawa-hexlet" => load_irisawa_hexlet(assembly), "aug554" => load_554aug2(assembly), + "domed554" => load_554domed(assembly), _ => (), }; }); @@ -1018,6 +1053,7 @@ pub fn TestAssemblyChooser() -> View { option(value = "radius-ratio") { "Radius ratio" } option(value = "irisawa-hexlet") { "Irisawa hexlet" } option(value = "aug554") { "McNeill acron 554" } + option(value = "domed554") { "Domed acron 554" } option(value = "empty") { "Empty" } } } diff --git a/data554/pinnedDomed1.txt b/data554/pinnedDomed1.txt new file mode 100644 index 0000000..cd0fbd1 --- /dev/null +++ b/data554/pinnedDomed1.txt @@ -0,0 +1,422 @@ +A_NE: + ┌ ┐ + │ 0.5 │ + │ 0.5 │ + │ 0 │ + │ 0.5 │ + │ 0.25 │ + └ ┘ + +A_NW: + ┌ ┐ + │ -0.5 │ + │ 0.5 │ + │ 0 │ + │ 0.5 │ + │ 0.25 │ + └ ┘ + +A_SE: + ┌ ┐ + │ 0.5 │ + │ -0.5 │ + │ 0 │ + │ 0.5 │ + │ 0.25 │ + └ ┘ + +A_SW: + ┌ ┐ + │ -0.5 │ + │ -0.5 │ + │ 0 │ + │ 0.5 │ + │ 0.25 │ + └ ┘ + +Z_E: + ┌ ┐ + │ 0.226403187618189 │ + │ -0.0000011852849153538265 │ + │ 0.8216719444221399 │ + │ 0.5 │ + │ 0.36320159380866957 │ + └ ┘ + +Z_S: + ┌ ┐ + │ 0.0000012064271737704876 │ + │ -0.22640322646306607 │ + │ 0.8216719573565728 │ + │ 0.5 │ + │ 0.36320161323110667 │ + └ ┘ + +B_NE: + ┌ ┐ + │ 0.8090169943749475 │ + │ 0.8090169943749475 │ + │ 0.8994537199739336 │ + │ 0.5 │ + │ 1.0590169943749475 │ + └ ┘ + +B_NW: + ┌ ┐ + │ -0.8090169943749475 │ + │ 0.8090169943749475 │ + │ 0.8994537199739336 │ + │ 0.5 │ + │ 1.0590169943749475 │ + └ ┘ + +B_SW: + ┌ ┐ + │ -0.8090169943749475 │ + │ -0.8090169943749475 │ + │ 0.8994537199739336 │ + │ 0.5 │ + │ 1.0590169943749475 │ + └ ┘ + +B_SE: + ┌ ┐ + │ 0.8090190229759617 │ + │ -0.8090188141637347 │ + │ 0.899451950040933 │ + │ 0.5 │ + │ 1.059018649731349 │ + └ ┘ + +Y_NE: + ┌ ┐ + │ 0.10639264310226318 │ + │ 0.10730337799321536 │ + │ 1.0174285282252145 │ + │ 0.5 │ + │ 0.5289971090900759 │ + └ ┘ + +Y_NW: + ┌ ┐ + │ -0.10692454007698927 │ + │ 0.10692454007699025 │ + │ 1.0183290894713781 │ + │ 0.5 │ + │ 0.5299299245035725 │ + └ ┘ + +Y_SE: + ┌ ┐ + │ 0.10619251333984216 │ + │ -0.10758965782463996 │ + │ 1.0179033510994517 │ + │ 0.5 │ + │ 0.5294898085465076 │ + └ ┘ + +Y_SW: + ┌ ┐ + │ -0.1067218861162948 │ + │ -0.10720833408227096 │ + │ 1.018815416393646 │ + │ 0.5 │ + │ 0.5304340206102979 │ + └ ┘ + +C_N: + ┌ ┐ + │ 0 │ + │ 1 │ + │ 1.4553466902253547 │ + │ 0.5 │ + │ 1.5590169943749472 │ + └ ┘ + +C_W: + ┌ ┐ + │ -1 │ + │ 0 │ + │ 1.4553466902253547 │ + │ 0.5 │ + │ 1.5590169943749472 │ + └ ┘ + +C_E: + ┌ ┐ + │ 0.9987752175493758 │ + │ -0.0000014991453936079486 │ + │ 1.4557639853989686 │ + │ 0.5 │ + │ 1.558400358790791 │ + └ ┘ + +C_S: + ┌ ┐ + │ 0.0000016241779451142522 │ + │ -1.0006558417024733 │ + │ 1.4551187604443314 │ + │ 0.5 │ + │ 1.5593413623171548 │ + └ ┘ + +D_NE: + ┌ ┐ + │ 0.18880014858596228 │ + │ 0.1900242678455326 │ + │ 2.0105891402812603 │ + │ 0.5 │ + │ 2.057111349663646 │ + └ ┘ + +D_NW: + ┌ ┐ + │ -0.19098138154171115 │ + │ 0.19098137963576622 │ + │ 2.0112375319589524 │ + │ 0.5 │ + │ 2.0590124438119553 │ + └ ┘ + +D_SE: + ┌ ┐ + │ 0.18930742120763205 │ + │ -0.19118865645940125 │ + │ 2.0109297224500113 │ + │ 0.5 │ + │ 2.0581147310902232 │ + └ ┘ + +D_SW: + ┌ ┐ + │ -0.19150073518747784 │ + │ -0.19215789298794494 │ + │ 2.011588230853374 │ + │ 0.5 │ + │ 2.060041842611764 │ + └ ┘ + +E_N: + ┌ ┐ + │ 0.0018705386847266854 │ + │ 1.067093230632768 │ + │ 2.4530916457713374 │ + │ 0.5 │ + │ 3.5781750425903667 │ + └ ┘ + +E_W: + ┌ ┐ + │ -1.0677431983248686 │ + │ 0.0010069929693730891 │ + │ 2.453048970298719 │ + │ 0.5 │ + │ 3.578762902144111 │ + └ ┘ + +E_E: + ┌ ┐ + │ 1.0651802756853244 │ + │ 0.0009763264191362732 │ + │ 2.4535562533844497 │ + │ 0.5 │ + │ 3.5772741307914204 │ + └ ┘ + +E_S: + ┌ ┐ + │ 0.0019014550057512374 │ + │ -1.0684657533845878 │ + │ 2.4528152065600635 │ + │ 0.5 │ + │ 3.5789625604223154 │ + └ ┘ + +F_NE: + ┌ ┐ + │ 0.2768627273275836 │ + │ 0.27828736361573514 │ + │ 3.0027865275597208 │ + │ 0.5 │ + │ 4.58541185017254 │ + └ ┘ + +F_NW: + ┌ ┐ + │ -0.28103002630311136 │ + │ 0.2812882756471644 │ + │ 3.0030716277259866 │ + │ 0.5 │ + │ 4.588270112066947 │ + └ ┘ + +F_SE: + ┌ ┐ + │ 0.2784445942560668 │ + │ -0.28049951604978735 │ + │ 3.002936318943545 │ + │ 0.5 │ + │ 4.586918980412344 │ + └ ┘ + +F_SW: + ┌ ┐ + │ -0.28267553068657514 │ + │ -0.2835640963165625 │ + │ 3.0032197706602255 │ + │ 0.5 │ + │ 4.58982149472019 │ + └ ┘ + +G_1: + ┌ ┐ + │ 0.49499953176717715 │ + │ 1.2038466206487632 │ + │ 3.3122320493386623 │ + │ 0.5 │ + │ 6.332576199448855 │ + └ ┘ + +G_2: + ┌ ┐ + │ 1.202713808441931 │ + │ 0.49734958577973293 │ + │ 3.3107015388161924 │ + │ 0.5 │ + │ 6.327310911161432 │ + └ ┘ + +G_4: + ┌ ┐ + │ 1.205139169495155 │ + │ -0.5026367636047644 │ + │ 3.3060733731956917 │ + │ 0.5 │ + │ 6.3175626265818385 │ + └ ┘ + +G_5: + ┌ ┐ + │ 0.49496884524644963 │ + │ -1.2066437505221832 │ + │ 3.3117630879125723 │ + │ 0.5 │ + │ 6.334379009718901 │ + └ ┘ + +G_7: + ┌ ┐ + │ -0.5049809193563459 │ + │ -1.2113397181531034 │ + │ 3.3029076805907547 │ + │ 0.5 │ + │ 6.315774407531318 │ + └ ┘ + +G_8: + ┌ ┐ + │ -1.2105141090163352 │ + │ -0.502666189603216 │ + │ 3.305064063797819 │ + │ 0.5 │ + │ 6.320733099502539 │ + └ ┘ + +G_10: + ┌ ┐ + │ -1.2079897553472638 │ + │ 0.49731945797605365 │ + │ 3.309789835947903 │ + │ 0.5 │ + │ 6.330637311024589 │ + └ ┘ + +G_11: + ┌ ┐ + │ -0.5049515565196006 │ + │ 1.2084435744379876 │ + │ 3.303474805253526 │ + │ 0.5 │ + │ 6.314128853476387 │ + └ ┘ + +H_E: + ┌ ┐ + │ 0.3493418610082748 │ + │ -0.004066882149056481 │ + │ 3.1680910206723154 │ + │ 0.5 │ + │ 5.079428493907433 │ + └ ┘ + +H_N: + ┌ ┐ + │ -0.007700036653444471 │ + │ 0.35115095882911657 │ + │ 3.1700941994675693 │ + │ 0.5 │ + │ 5.086431758305634 │ + └ ┘ + +H_S: + ┌ ┐ + │ -0.007865804611166411 │ + │ -0.3529429622292672 │ + │ 3.176292181037656 │ + │ 0.5 │ + │ 5.10673131179707 │ + └ ┘ + +H_W: + ┌ ┐ + │ -0.35269124233601623 │ + │ -0.004232365263278458 │ + │ 3.179750192542026 │ + │ 0.5 │ + │ 5.117610155394313 │ + └ ┘ + +I_NE: + ┌ ┐ + │ 0.49805064236295854 │ + │ 0.4976522130513484 │ + │ 4.020243412022774 │ + │ 0.5 │ + │ 8.329034633323353 │ + └ ┘ + +I_NW: + ┌ ┐ + │ -0.5060555022841527 │ + │ 0.5128022974270082 │ + │ 4.021863319771454 │ + │ 0.5 │ + │ 8.34722145884986 │ + └ ┘ + +I_SE: + ┌ ┐ + │ 0.5060495556329386 │ + │ -0.5018683273402639 │ + │ 4.021107068324604 │ + │ 0.5 │ + │ 8.338630006433696 │ + └ ┘ + +I_SW: + ┌ ┐ + │ -0.5143110146278468 │ + │ -0.5172754412791255 │ + │ 4.0227602162137135 │ + │ 0.5 │ + │ 8.357344731885544 │ + └ ┘ + +J: + ┌ ┐ + │ -0.012852519748535315 │ + │ -0.006830373978588703 │ + │ 3.3242009136904245 │ + │ 0.5 │ + │ 5.525261774194571 │ + └ ┘ diff --git a/data554/processDomed.py b/data554/processDomed.py new file mode 100644 index 0000000..b2fe7b8 --- /dev/null +++ b/data554/processDomed.py @@ -0,0 +1,95 @@ +from math import fabs, nan, sqrt +import sys +vertices = {} +current_id = "" +for line in sys.stdin: + text = line.strip() + if text.endswith(":"): + new_id = text.strip(":") + vertices[new_id] = [] + if current_id: print(current_id, vertices[current_id]) + current_id = new_id + continue + remainder = text.strip("┌ ┐└┘│") + if len(remainder): vertices[current_id].append(float(remainder)) +print(len(vertices), "vertices found") + +P = True +F = False +HPHI = nan +RTHPHI = nan +RTPHIPH = nan +# Taken verbatim from test_assembly_chooser.rs +acron_data = [ + ("A_NE", [ 0.5, 0.5, 0.], P, 0, ""), + ("A_NW", [-0.5, 0.5, 0.], P, 0, ""), + ("A_SE", [ 0.5, -0.5, 0.], P, 0, ""), + ("A_SW", [-0.5, -0.5, 0.], P, 0, ""), + ("Z_E", [ 0.229, -0.002, 0.821], F, 1, "A_NE,A_SE"), + ("Z_S", [ 0.002, -0.229, 0.821], F, 1, "A_SE,A_SW"), + ("B_NE", [ HPHI, HPHI, RTHPHI], P, 2, "Z_E"), + ("B_NW", [-HPHI, HPHI, RTHPHI], P, 2, ""), + ("B_SW", [-HPHI, -HPHI, RTHPHI], P, 2, "Z_S"), + ("B_SE", [ 0.812, -0.812, 0.89], F, 2, "A_SE,Z_E,Z_S"), + ("Y_NE", [ 0.11, 0.103, 1.019], F, 3, "B_NE"), + ("Y_NW", [-0.103, 0.103, 1.02], F, 3, "B_NW"), + ("Y_SE", [ 0.11, -0.11, 1.017], F, 3, "B_SE"), + ("Y_SW", [-0.103, -0.11, 1.019], F, 3, "B_SW"), + ("C_N", [ 0., 1., RTPHIPH], P, 4, "Y_NE,Y_NW"), + ("C_W", [-1., 0., RTPHIPH], P, 4, "Y_NW,Y_SW"), + ("C_E", [ 1.006, -0.006, 1.45], F, 4, "B_NE,B_SE,Y_NE,Y_SE"), + ("C_S", [ 0.006, -1.006, 1.45], F, 4, "B_SE,B_SW,Y_SE,Y_SW"), + ("D_NE", [ 0.2, 0.181, 2.011], F, 5, "Y_NE,C_N,C_E"), + ("D_NW", [-0.181, 0.181, 2.014], F, 5, "Y_NW,C_N,C_W"), + ("D_SE", [ 0.2, -0.2, 2.009], F, 5, "Y_SE,C_E,C_S"), + ("D_SW", [-0.181, -0.2, 2.011], F, 5, "Y_SW,C_W,C_S"), + ("E_N", [ 0.012, 1.055, 2.46], F, 6, "C_N,D_NE,D_NW"), + ("E_W", [-1.055, -0.012, 2.46], F, 6, "C_W,D_NW,D_SW"), + ("E_E", [ 1.079, -0.012, 2.447], F, 6, "C_E,D_NE,D_SE"), + ("E_S", [ 0.012, -1.079, 2.447], F, 6, "C_S,D_SE,D_SW"), + ("F_NE", [ 0.296, 0.265, 3.003], F, 7, "D_NE,E_N,E_E"), + ("F_NW", [-0.265, 0.265, 3.007], F, 7, "D_NW,E_N,E_W"), + ("F_SE", [ 0.296, -0.296, 3.0], F, 7, "D_SE,E_E,E_S"), + ("F_SW", [-0.265, -0.296, 3.003], F, 7, "D_SW,E_W,E_S"), + # The following must be in order around the octagon (in some orientation) + ("G_1", [ 0.517, 1.19, 3.312], F, 8, "E_N,F_NE"), + ("G_2", [ 1.224, 0.483, 3.304], F, 8, "E_E,F_NE"), + ("G_4", [ 1.224, -0.517, 3.298], F, 8, "E_E,F_SE"), + ("G_5", [ 0.517, -1.224, 3.298], F, 8, "E_S,F_SE"), + ("G_7", [-0.483, -1.224, 3.304], F, 8, "E_S,F_SW"), + ("G_8", [-1.19, -0.517, 3.312], F, 8, "E_W,F_SW"), + ("G_10", [-1.19, 0.483, 3.318], F, 8, "E_W,F_NW"), + ("G_11", [-0.483, 1.19, 3.318], F, 8, "E_N,F_NW"), + # Additional data + ("H_E", [ 0.359, -0.006, 3.161], F, 9, "G_2,G_4"), + ("H_N", [ 0.005, 0.348, 3.158], F, 9, "G_1,G_11"), + ("H_S", [ 0.006, 0.359, 3.159], F, 9, "G_5,G_7"), + ("H_W", [-0.347, -0.005, 3.163], F, 9, "G_8,G_10"), + ("I_NE", [ 0.507, 0.493, 4.015], F, 10, "G_1,G_2,H_E,H_N"), + ("I_NW", [-0.493, 0.493, 4.013], F, 10, "G_10,G_11,H_N,H_W"), + ("I_SE", [ 0.507, -0.507, 4.012], F, 10, "G_4,G_5,H_E,H_S"), + ("I_SW", [-0.493, -0.507, 4.015], F, 10, "G_7,G_8,H_S,H_W"), + ("J", [ 0.004, -0.010, 3.303], F, 11, "I_NE,I_NW,I_SE,I_SW"), +] + +E = 0.0 +n_struts = 11 # for the pinned vertices, which all have length exactly 1 +for vi in range(0, len(acron_data)): + start_id = acron_data[vi][0] + start = vertices[start_id] + ends = acron_data[vi][4].split(",") + if acron_data[vi][3] == 8: + if start_id != "G_1": + ends.append(acron_data[vi-1][0]) + if vi+1 == len(acron_data) or not(acron_data[vi+1][0].startswith("G")): + ends.append("G_1") + for end_id in ends: + if not(end_id): continue + end = vertices[end_id] + dist = sqrt( + (end[0]-start[0])**2 + (end[1]-start[1])**2 + (end[2]-start[2])**2) + print(f"{start_id}-{end_id}: {dist} {dist-1}") + E += fabs(dist-1) + n_struts += 1 +print(n_struts, "unit edges") +print(f"----> Total distortion E={E}") From 0b03ea2a0d9503849050264809e1a7b7de223aba Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 22 Sep 2025 16:34:53 -0700 Subject: [PATCH 10/11] fix: The last six elusive edges --- .../src/components/test_assembly_chooser.rs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 581aa6f..2f5f98d 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -836,8 +836,8 @@ const_array!(ACRON554_COMMON: (&str, [f64; 3], bool, usize, &str) = [ ("Y_SW", [-0.104, -0.11, 1.019], F, 3, "B_SW"), ("C_N", [ 0., 1., RTPHIPH], P, 4, "Y_NE,Y_NW"), ("C_W", [-1., 0., RTPHIPH], P, 4, "Y_NW,Y_SW"), - ("C_E", [ 1.003, -0.003, 1.454], F, 4, "B_NE,B_SE,Y_NE,Y_SE"), - ("C_S", [ 0.003, -1.003, 1.454], F, 4, "B_SE,B_SW,Y_SE,Y_SW"), + ("C_E", [ 1.003, -0.003, 1.454], F, 4, "Z_E,B_NE,B_SE,Y_NE,Y_SE"), + ("C_S", [ 0.003, -1.003, 1.454], F, 4, "Z_S,B_SE,B_SW,Y_SE,Y_SW"), ("D_NE", [ 0.195, 0.186, 2.012], F, 5, "Y_NE,C_N,C_E"), ("D_NW", [-0.186, 0.186, 2.012], F, 5, "Y_NW,C_N,C_W"), ("D_SE", [ 0.195, -0.195, 2.011], F, 5, "Y_SE,C_E,C_S"), @@ -964,23 +964,36 @@ fn load_554aug2(assembly: &Assembly) { } } -const_array!(ACRON554_DOME: (&str, [f64; 3], bool, usize, &str) = [ +const_array!(ACRON554_INDOME: (&str, [f64; 3], bool, usize, &str) = [ // id, coordinates, Pin/Free, level, earlier neighbor IDs. ("H_E", [ 0.359, -0.006, 3.161], F, 9, "G_2,G_4"), ("H_N", [ 0.005, 0.348, 3.158], F, 9, "G_1,G_11"), - ("H_S", [ 0.006, 0.359, 3.159], F, 9, "G_5,G_7"), + ("H_S", [ 0.006, -0.359, 3.159], F, 9, "G_5,G_7"), ("H_W", [-0.347, -0.005, 3.163], F, 9, "G_8,G_10"), ("I_NE", [ 0.507, 0.493, 4.015], F, 10, "G_1,G_2,H_E,H_N"), - ("I_NW", [-0.493, 0.493, 4.013], F, 10, "G_10,G_11,H_N,H_W"), - ("I_SE", [ 0.507, -0.507, 4.012], F, 10, "G_4,G_5,H_E,H_S"), - ("I_SW", [-0.493, -0.507, 4.015], F, 10, "G_7,G_8,H_S,H_W"), + ("I_NW", [-0.493, 0.493, 4.013], F, 10, "G_10,G_11,H_N,H_W,I_NE"), + ("I_SE", [ 0.507, -0.507, 4.012], F, 10, "G_4,G_5,H_E,H_S,I_NE"), + ("I_SW", [-0.493, -0.507, 4.015], F, 10, "G_7,G_8,H_S,H_W,I_NW,I_SE"), ("J", [ 0.004, -0.010, 3.303], F, 11, "I_NE,I_NW,I_SE,I_SW"), ]); +const_array!(ACRON554_OUTDOME: (&str, [f64; 3], bool, usize, &str) = [ + // id, coordinates, Pin/Free, level, earlier neighbor IDs. + ("H_E", [ 1.361, -0.007, 4.16], F, 9, "G_2,G_4"), + ("H_N", [ 0.007, 1.346, 4.164], F, 9, "G_1,G_11"), + ("H_S", [ 0.007, -1.361, 4.16], F, 9, "G_5,G_7"), + ("H_W", [-1.346, -0.007, 4.164], F, 9, "G_8,G_10"), + ("I_NE", [ 0.507, 0.493, 4.015], F, 10, "G_1,G_2,H_E,H_N"), + ("I_NW", [-0.493, 0.493, 4.013], F, 10, "G_10,G_11,H_N,H_W,I_NE"), + ("I_SE", [ 0.507, -0.507, 4.012], F, 10, "G_4,G_5,H_E,H_S,I_NE"), + ("I_SW", [-0.493, -0.507, 4.015], F, 10, "G_7,G_8,H_S,H_W,I_NW,I_SE"), + ("J", [ 0.008, -0.008, 4.722], F, 11, "I_NE,I_NW,I_SE,I_SW"), +]); + fn load_554domed(assembly: &Assembly) { load_554aug2(assembly); // Now process the additional data - for (id, v, _pinned, l, neighbors) in ACRON554_DOME { + for (id, v, _pinned, l, neighbors) in ACRON554_INDOME { let pt = Point::new(id, id, LEVEL_COLORS[l], point(v[0], v[1], v[2])); assembly.try_insert_element(pt); let pt_rc = assembly.find_element(id); From 2c8c09d20d3567a968acf8e839e6d04c1414751b Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 13 Oct 2025 22:52:02 +0000 Subject: [PATCH 11/11] feat: Point coordinate regulators (#118) Implement 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`. Co-authored-by: Glen Whitney Co-committed-by: Glen Whitney --- README.md | 64 +++++++------- app-proto/Cargo.lock | 21 +++++ app-proto/Cargo.toml | 1 + app-proto/src/assembly.rs | 124 ++++++++++++++++++++++++---- app-proto/src/components/outline.rs | 15 ++++ app-proto/src/engine.rs | 4 +- 6 files changed, 180 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index cf3e589..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,38 +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 +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 + - 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") @@ -64,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. 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..0264b75 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -1,10 +1,11 @@ +use enum_iterator::{all, Sequence}; use nalgebra::{DMatrix, DVector, DVectorView}; use std::{ cell::Cell, cmp::Ordering, collections::{BTreeMap, BTreeSet}, fmt, - fmt::{Debug, Formatter}, + fmt::{Debug, Display, Formatter}, hash::{Hash, Hasher}, rc::Rc, sync::{atomic, atomic::AtomicU64}, @@ -26,6 +27,7 @@ use crate::{ ConfigSubspace, ConstraintProblem, DescentHistory, + MatrixEntry, Realization, }, specified::SpecifiedValue, @@ -84,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); } @@ -125,8 +135,8 @@ pub trait Element: Serial + ProblemPoser + DisplayItem { } impl Debug for dyn Element { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { - self.id().fmt(f) + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.id(), f) } } @@ -249,8 +259,7 @@ impl Serial for Sphere { 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() - ); + 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()); } @@ -269,6 +278,7 @@ pub struct Point { impl Point { const WEIGHT_COMPONENT: usize = 3; + const NORM_COMPONENT: usize = 4; pub fn new( id: String, @@ -302,6 +312,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 @@ -345,8 +364,7 @@ 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() - ); + 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()); @@ -436,8 +454,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" - ) + indexing_error("Subject", subj.id(), + "inversive distance regulator").as_str()) ); problem.gram.push_sym(row, col, val); } @@ -446,14 +464,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] ); @@ -490,14 +508,85 @@ 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" - ); + indexing_error("Subject", &self.subject.id, + "half-curvature regulator").as_str()); 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, @@ -698,6 +787,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 @@ -857,7 +947,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); @@ -866,7 +957,8 @@ 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( @@ -927,4 +1019,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/outline.rs b/app-proto/src/components/outline.rs index 5355042..547b73b 100644 --- a/app-proto/src/components/outline.rs +++ b/app-proto/src/components/outline.rs @@ -9,6 +9,7 @@ use crate::{ Element, HalfCurvatureRegulator, InversiveDistanceRegulator, + PointCoordinateRegulator, Regulator, }, specified::SpecifiedValue @@ -119,6 +120,20 @@ 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/engine.rs b/app-proto/src/engine.rs index ef150a0..0f26f02 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -52,8 +52,8 @@ pub fn project_point_to_normalized(rep: &mut DVector) { // --- partial matrices --- pub struct MatrixEntry { - index: (usize, usize), - value: f64, + pub index: (usize, usize), + pub value: f64, } pub struct PartialMatrix(Vec);