From 8a0d81d7073a41caad334aeaf27fa1a0b453b1ef Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Thu, 21 Aug 2025 14:49:32 -0400 Subject: [PATCH 01/13] Rewind through the descent history --- app-proto/src/components/display.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app-proto/src/components/display.rs b/app-proto/src/components/display.rs index 98be85e..c4803dc 100644 --- a/app-proto/src/components/display.rs +++ b/app-proto/src/components/display.rs @@ -597,16 +597,16 @@ pub fn Display() -> View { |status| status.is_ok() ); let step_val = state.assembly.step.with_untracked(|step| step.value); - let on_init_step = step_val.is_some_and(|n| n == 0.0); let on_last_step = step_val.is_some_and( |n| state.assembly.descent_history.with_untracked( |history| n as usize + 1 == history.config.len().max(1) ) ); - let on_manipulable_step = - !realization_successful && on_init_step - || realization_successful && on_last_step; - if on_manipulable_step && state.selection.with(|sel| sel.len() == 1) { + if + state.selection.with(|sel| sel.len() == 1) + && realization_successful + && on_last_step + { let sel = state.selection.with( |sel| sel.into_iter().next().unwrap().clone() ); From 8bedb0baf752a26ce636e0248434b78e42cb5024 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Sat, 23 Aug 2025 12:40:11 -0400 Subject: [PATCH 02/13] Sketch a 5-5-4 near miss test assembly --- .../src/components/test_assembly_chooser.rs | 434 +++++++++++++++++- 1 file changed, 433 insertions(+), 1 deletion(-) diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 0d387d3..f659215 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}; @@ -882,6 +882,436 @@ fn load_irisawa_hexlet(assembly: &Assembly) { assembly.insert_regulator(Rc::new(outer_moon_tangency)); } +fn load_554a(assembly: &Assembly) { + // create the vertices + const COLOR_A: ElementColor = [0.75_f32, 0.00_f32, 0.75_f32]; + const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32]; + const COLOR_C: ElementColor = [0.75_f32, 0.50_f32, 0.00_f32]; + const COLOR_D: ElementColor = [0.90_f32, 0.90_f32, 0.00_f32]; + const COLOR_E: ElementColor = [0.25_f32, 0.75_f32, 0.00_f32]; + const COLOR_F: ElementColor = [0.00_f32, 0.50_f32, 0.75_f32]; + const COLOR_G: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; + let vertices = [ + Point::new( + "a_NE".to_string(), + "A-NE".to_string(), + COLOR_A, + engine::point(0.5, 0.5, 0.0), + ), + Point::new( + "a_NW".to_string(), + "A-NW".to_string(), + COLOR_A, + engine::point(-0.5, 0.5, 0.0), + ), + Point::new( + "a_SW".to_string(), + "A-SW".to_string(), + COLOR_A, + engine::point(-0.5, -0.5, 0.0), + ), + Point::new( + "a_SE".to_string(), + "A-SE".to_string(), + COLOR_A, + engine::point(0.5, -0.5, 0.0), + ), + Point::new( + "b_NE".to_string(), + "B-NE".to_string(), + COLOR_B, + engine::point(0.7, 0.7, 0.9), + ), + Point::new( + "b_NW".to_string(), + "B-NW".to_string(), + COLOR_B, + engine::point(-0.7, 0.7, 0.9), + ), + Point::new( + "b_SW".to_string(), + "B-SW".to_string(), + COLOR_B, + engine::point(-0.7, -0.7, 0.9), + ), + Point::new( + "b_SE".to_string(), + "B-SE".to_string(), + COLOR_B, + engine::point(0.7, -0.7, 0.9), + ), + Point::new( + "c_N".to_string(), + "C-N".to_string(), + COLOR_C, + engine::point(0.0, 0.8, 1.4), + ), + Point::new( + "c_W".to_string(), + "C-W".to_string(), + COLOR_C, + engine::point(-0.8, 0.0, 1.4), + ), + Point::new( + "c_S".to_string(), + "C-S".to_string(), + COLOR_C, + engine::point(0.0, -0.8, 1.4), + ), + Point::new( + "c_E".to_string(), + "C-E".to_string(), + COLOR_C, + engine::point(0.8, 0.0, 1.4), + ), + Point::new( + "d_NE".to_string(), + "D-NE".to_string(), + COLOR_D, + engine::point(0.1, 0.1, 1.8), + ), + Point::new( + "d_NW".to_string(), + "D-NW".to_string(), + COLOR_D, + engine::point(-0.1, 0.1, 1.8), + ), + Point::new( + "d_SW".to_string(), + "D-SW".to_string(), + COLOR_D, + engine::point(-0.1, -0.1, 1.8), + ), + Point::new( + "d_SE".to_string(), + "D-SE".to_string(), + COLOR_D, + engine::point(0.1, -0.1, 1.8), + ), + Point::new( + "e_N".to_string(), + "E-N".to_string(), + COLOR_E, + engine::point(0.0, 0.7, 2.3), + ), + Point::new( + "e_W".to_string(), + "E-W".to_string(), + COLOR_E, + engine::point(-0.7, 0.0, 2.3), + ), + Point::new( + "e_S".to_string(), + "E-S".to_string(), + COLOR_E, + engine::point(0.0, -0.7, 2.3), + ), + Point::new( + "e_E".to_string(), + "E-E".to_string(), + COLOR_E, + engine::point(0.7, 0.0, 2.3), + ), + Point::new( + "f_NE".to_string(), + "F-NE".to_string(), + COLOR_F, + engine::point(0.2, 0.2, 2.7), + ), + Point::new( + "f_NW".to_string(), + "F-NW".to_string(), + COLOR_F, + engine::point(-0.2, 0.2, 2.7), + ), + Point::new( + "f_SW".to_string(), + "F-SW".to_string(), + COLOR_F, + engine::point(-0.2, -0.2, 2.7), + ), + Point::new( + "f_SE".to_string(), + "F-SE".to_string(), + COLOR_F, + engine::point(0.2, -0.2, 2.7), + ), + Point::new( + "g_NNE".to_string(), + "G-NNE".to_string(), + COLOR_G, + engine::point(0.5, 1.2, 3.0), + ), + Point::new( + "g_NNW".to_string(), + "G-NNW".to_string(), + COLOR_G, + engine::point(-0.5, 1.2, 3.0), + ), + Point::new( + "g_WNW".to_string(), + "G-WNW".to_string(), + COLOR_G, + engine::point(-1.2, 0.5, 3.0), + ), + Point::new( + "g_WSW".to_string(), + "G-WSW".to_string(), + COLOR_G, + engine::point(-1.2, -0.5, 3.0), + ), + Point::new( + "g_SSW".to_string(), + "G-SSW".to_string(), + COLOR_G, + engine::point(-0.5, -1.2, 3.0), + ), + Point::new( + "g_SSE".to_string(), + "G-SSE".to_string(), + COLOR_G, + engine::point(0.5, -1.2, 3.0), + ), + Point::new( + "g_ESE".to_string(), + "G-ESE".to_string(), + COLOR_G, + engine::point(1.2, -0.5, 3.0), + ), + Point::new( + "g_ENE".to_string(), + "G-ENE".to_string(), + COLOR_G, + engine::point(1.2, 0.5, 3.0), + ), + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // fix the distances between adjacent vertices + let square_in_octagon = (2.0 + SQRT_2).sqrt(); + let struts = [ + (1.0, vec![ + ["a_NE", "a_NW"], + ["a_NW", "a_SW"], + ["a_SW", "a_SE"], + ["a_SE", "a_NE"], + ["a_NE", "b_NE"], + ["a_NW", "b_NW"], + ["a_SW", "b_SW"], + ["a_SE", "b_SE"], + ["b_NE", "c_N"], + ["b_NW", "c_N"], + ["b_NW", "c_W"], + ["b_SW", "c_W"], + ["b_SW", "c_S"], + ["b_SE", "c_S"], + ["b_SE", "c_E"], + ["b_NE", "c_E"], + ["c_N", "d_NE"], + ["c_N", "d_NW"], + ["c_W", "d_NW"], + ["c_W", "d_SW"], + ["c_S", "d_SW"], + ["c_S", "d_SE"], + ["c_E", "d_SE"], + ["c_E", "d_NE"], + ["d_NE", "e_N"], + ["d_NW", "e_N"], + ["d_NW", "e_W"], + ["d_SW", "e_W"], + ["d_SW", "e_S"], + ["d_SE", "e_S"], + ["d_SE", "e_E"], + ["d_NE", "e_E"], + ["c_N", "e_N"], + ["c_W", "e_W"], + ["c_S", "e_S"], + ["c_E", "e_E"], + ["e_N", "f_NE"], + ["e_N", "f_NW"], + ["e_W", "f_NW"], + ["e_W", "f_SW"], + ["e_S", "f_SW"], + ["e_S", "f_SE"], + ["e_E", "f_SE"], + ["e_E", "f_NE"], + ["d_NE", "f_NE"], + ["d_NW", "f_NW"], + ["d_SW", "f_SW"], + ["d_SE", "f_SE"], + ["f_NE", "g_ENE"], + ["f_NE", "g_NNE"], + ["f_NW", "g_NNW"], + ["f_NW", "g_WNW"], + ["f_SW", "g_WSW"], + ["f_SW", "g_SSW"], + ["f_SE", "g_SSE"], + ["f_SE", "g_ESE"], + ["e_N", "g_NNE"], + ["e_N", "g_NNW"], + ["e_W", "g_WNW"], + ["e_W", "g_WSW"], + ["e_S", "g_SSW"], + ["e_S", "g_SSE"], + ["e_E", "g_ESE"], + ["e_E", "g_ENE"], + ["g_NNE", "g_NNW"], + ["g_NNW", "g_WNW"], + ["g_WNW", "g_WSW"], + ["g_WSW", "g_SSW"], + ["g_SSW", "g_SSE"], + ["g_SSE", "g_ESE"], + ["g_ESE", "g_ENE"], + ["g_ENE", "g_NNE"], + ]), + (SQRT_2, vec![ + ["a_NE", "a_SW"], + ["a_NW", "a_SE"], + ["b_NE", "d_NE"], + ["b_NW", "d_NW"], + ["b_SW", "d_SW"], + ["b_SE", "d_SE"], + ]), + (0.5*(1.0 + 5.0_f64.sqrt()), vec![ + ["a_NE", "c_N"], + ["a_NW", "c_N"], + ["a_NW", "c_W"], + ["a_SW", "c_W"], + ["a_SW", "c_S"], + ["a_SE", "c_S"], + ["a_SE", "c_E"], + ["a_NE", "c_E"], + ]), + (square_in_octagon, vec![ + ["g_NNE", "g_WNW"], + ["g_WNW", "g_SSW"], + ["g_SSW", "g_ESE"], + ["g_ESE", "g_NNE"], + ]), + (SQRT_2 * square_in_octagon, vec![ + ["g_NNE", "g_SSW"], + ]), + ]; + for (length, vertex_pairs) in struts { + let inv_dist = Some(-0.5 * length * length); + for pair in vertex_pairs { + let adjacent_vertices = pair.map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + let distance = InversiveDistanceRegulator::new(adjacent_vertices); + distance.set_point.set(SpecifiedValue::from(inv_dist)); + assembly.insert_regulator(Rc::new(distance)); + } + } + + // create the faces + const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let faces = [ + Sphere::new( + "f_a".to_string(), + "Face A".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, -1.0, 0.0, 0.0), + ), + Sphere::new( + "f_abc_N".to_string(), + "Face ABC-N".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_W".to_string(), + "Face ABC-W".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-1.0, 0.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_S".to_string(), + "Face ABC-S".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, -1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_E".to_string(), + "Face ABC-E".to_string(), + COLOR_FACE, + engine::sphere_with_offset(1.0, 0.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_bcd_NE".to_string(), + "Face BCD-NE".to_string(), + COLOR_FACE, + engine::sphere_with_offset(FRAC_1_SQRT_2, FRAC_1_SQRT_2, 0.0, FRAC_1_SQRT_2, 0.0), + ), + Sphere::new( + "f_bcd_NW".to_string(), + "Face BCD-NW".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-FRAC_1_SQRT_2, FRAC_1_SQRT_2, 0.0, FRAC_1_SQRT_2, 0.0), + ), + Sphere::new( + "f_bcd_SW".to_string(), + "Face BCD-SW".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-FRAC_1_SQRT_2, -FRAC_1_SQRT_2, 0.0, FRAC_1_SQRT_2, 0.0), + ), + Sphere::new( + "f_bcd_SE".to_string(), + "Face BCD-SE".to_string(), + COLOR_FACE, + engine::sphere_with_offset(FRAC_1_SQRT_2, -FRAC_1_SQRT_2, 0.0, FRAC_1_SQRT_2, 0.0), + ), + Sphere::new( + "f_g".to_string(), + "Face G".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, 1.0, 3.0, 0.0), + ), + ]; + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + // make the faces planar and make them pass through their vertices + let face_incidences = [ + ("f_a", vec!["a_NE", "a_NW", "a_SW", "a_SE"]), + ("f_abc_N", vec!["a_NE", "a_NW", "b_NE", "b_NW", "c_N"]), + ("f_abc_W", vec!["a_NW", "a_SW", "b_NW", "b_SW", "c_W"]), + ("f_abc_S", vec!["a_SW", "a_SE", "b_SW", "b_SE", "c_S"]), + ("f_abc_E", vec!["a_SE", "a_NE", "b_SE", "b_NE", "c_E"]), + ("f_bcd_NE", vec!["b_NE", "c_N", "c_E", "d_NE"]), + ("f_bcd_NW", vec!["b_NW", "c_N", "c_W", "d_NW"]), + ("f_bcd_SW", vec!["b_SW", "c_S", "c_W", "d_SW"]), + ("f_bcd_SE", vec!["b_SE", "c_S", "c_E", "d_SE"]), + ("f_g", vec!["g_NNE", "g_NNW", "g_WNW", "g_WSW", "g_SSW", "g_SSE", "g_ESE", "g_ENE"]), + ]; + for (face_id, vertex_ids) in face_incidences { + // make the face planar + let face = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[face_id].clone() + ); + let curvature_regulator = face.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set(SpecifiedValue::from(Some(0.0))); + + // make the face pass through its vertices + for v_id in vertex_ids { + let vertex = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[v_id].clone() + ); + let incidence = InversiveDistanceRegulator::new([face.clone(), vertex]); + incidence.set_point.set(SpecifiedValue::from(Some(0.0))); + assembly.insert_regulator(Rc::new(incidence)); + } + } +} + // --- chooser --- /* DEBUG */ @@ -918,6 +1348,7 @@ pub fn TestAssemblyChooser() -> View { "off-center" => load_off_center(assembly), "radius-ratio" => load_radius_ratio(assembly), "irisawa-hexlet" => load_irisawa_hexlet(assembly), + "554a" => load_554a(assembly), _ => (), }; }); @@ -935,6 +1366,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 = "554a") { "5-5-4 near miss A" } option(value = "empty") { "Empty" } } } From 48a640605ad496db78723543f953c01586f7dc2d Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Sat, 23 Aug 2025 15:23:20 -0400 Subject: [PATCH 03/13] Regulate all the diagonals of the 5-5-4 near miss This should help us find the total distortion of an almost-realization. --- .../src/components/test_assembly_chooser.rs | 87 ++++++++++--------- 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index f659215..18c122c 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, SQRT_2}, rc::Rc}; +use std::{f64::consts::{FRAC_1_SQRT_2, PI}, rc::Rc}; use nalgebra::Vector3; use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; @@ -882,6 +882,15 @@ fn load_irisawa_hexlet(assembly: &Assembly) { assembly.insert_regulator(Rc::new(outer_moon_tangency)); } +fn regular_diagonals<'a, const N: usize>(vertex_ids: [&'a str; N]) -> Vec<(f64, Vec<[&'a str; 2]>)> { + let ang = PI / (N as f64); + let ang_sin = ang.sin(); + (2..N-1).map(|sep| ( + (sep as f64 * ang).sin() / ang_sin, + (0..N-sep).map(|k| [vertex_ids[k], vertex_ids[k + sep]]).collect() + )).collect() +} + fn load_554a(assembly: &Assembly) { // create the vertices const COLOR_A: ElementColor = [0.75_f32, 0.00_f32, 0.75_f32]; @@ -1090,8 +1099,20 @@ fn load_554a(assembly: &Assembly) { } // fix the distances between adjacent vertices - let square_in_octagon = (2.0 + SQRT_2).sqrt(); - let struts = [ + let f_a = ["a_SE", "a_SW", "a_NW", "a_NE"]; + let f_abc_n = ["a_NW", "b_NW", "c_N", "b_NE", "a_NE"]; + let f_abc_w = ["a_SW", "b_SW", "c_W", "b_NW", "a_NW"]; + let f_abc_s = ["a_SE", "b_SE", "c_S", "b_SW", "a_SW"]; + let f_abc_e = ["a_NE", "b_NE", "c_E", "b_SE", "a_SE"]; + let f_bcd_ne = ["b_NE", "c_N", "d_NE", "c_E"]; + let f_bcd_nw = ["b_NW", "c_W", "d_NW", "c_N"]; + let f_bcd_sw = ["b_SW", "c_S", "d_SW", "c_W"]; + let f_bcd_se = ["b_SE", "c_E", "d_SE", "c_S"]; + let f_g = [ + "g_NNE", "g_NNW", "g_WNW", "g_WSW", + "g_SSW", "g_SSE", "g_ESE", "g_ENE", + ]; + let struts: Vec<_> = [ (1.0, vec![ ["a_NE", "a_NW"], ["a_NW", "a_SW"], @@ -1166,34 +1187,18 @@ fn load_554a(assembly: &Assembly) { ["g_ESE", "g_ENE"], ["g_ENE", "g_NNE"], ]), - (SQRT_2, vec![ - ["a_NE", "a_SW"], - ["a_NW", "a_SE"], - ["b_NE", "d_NE"], - ["b_NW", "d_NW"], - ["b_SW", "d_SW"], - ["b_SE", "d_SE"], - ]), - (0.5*(1.0 + 5.0_f64.sqrt()), vec![ - ["a_NE", "c_N"], - ["a_NW", "c_N"], - ["a_NW", "c_W"], - ["a_SW", "c_W"], - ["a_SW", "c_S"], - ["a_SE", "c_S"], - ["a_SE", "c_E"], - ["a_NE", "c_E"], - ]), - (square_in_octagon, vec![ - ["g_NNE", "g_WNW"], - ["g_WNW", "g_SSW"], - ["g_SSW", "g_ESE"], - ["g_ESE", "g_NNE"], - ]), - (SQRT_2 * square_in_octagon, vec![ - ["g_NNE", "g_SSW"], - ]), - ]; + ].into_iter() + .chain(regular_diagonals(f_a)) + .chain(regular_diagonals(f_bcd_ne)) + .chain(regular_diagonals(f_bcd_nw)) + .chain(regular_diagonals(f_bcd_sw)) + .chain(regular_diagonals(f_bcd_se)) + .chain(regular_diagonals(f_abc_n)) + .chain(regular_diagonals(f_abc_w)) + .chain(regular_diagonals(f_abc_s)) + .chain(regular_diagonals(f_abc_e)) + .chain(regular_diagonals(f_g)) + .collect(); for (length, vertex_pairs) in struts { let inv_dist = Some(-0.5 * length * length); for pair in vertex_pairs { @@ -1279,16 +1284,16 @@ fn load_554a(assembly: &Assembly) { // make the faces planar and make them pass through their vertices let face_incidences = [ - ("f_a", vec!["a_NE", "a_NW", "a_SW", "a_SE"]), - ("f_abc_N", vec!["a_NE", "a_NW", "b_NE", "b_NW", "c_N"]), - ("f_abc_W", vec!["a_NW", "a_SW", "b_NW", "b_SW", "c_W"]), - ("f_abc_S", vec!["a_SW", "a_SE", "b_SW", "b_SE", "c_S"]), - ("f_abc_E", vec!["a_SE", "a_NE", "b_SE", "b_NE", "c_E"]), - ("f_bcd_NE", vec!["b_NE", "c_N", "c_E", "d_NE"]), - ("f_bcd_NW", vec!["b_NW", "c_N", "c_W", "d_NW"]), - ("f_bcd_SW", vec!["b_SW", "c_S", "c_W", "d_SW"]), - ("f_bcd_SE", vec!["b_SE", "c_S", "c_E", "d_SE"]), - ("f_g", vec!["g_NNE", "g_NNW", "g_WNW", "g_WSW", "g_SSW", "g_SSE", "g_ESE", "g_ENE"]), + ("f_a", Vec::from(f_a)), + ("f_abc_N", Vec::from(f_abc_n)), + ("f_abc_W", Vec::from(f_abc_w)), + ("f_abc_S", Vec::from(f_abc_s)), + ("f_abc_E", Vec::from(f_abc_e)), + ("f_bcd_NE", Vec::from(f_bcd_ne)), + ("f_bcd_NW", Vec::from(f_bcd_nw)), + ("f_bcd_SW", Vec::from(f_bcd_sw)), + ("f_bcd_SE", Vec::from(f_bcd_se)), + ("f_g", Vec::from(f_g)), ]; for (face_id, vertex_ids) in face_incidences { // make the face planar From 0de32f5e112fce2afb66fa73684f256b3115bde1 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Sun, 31 Aug 2025 11:16:34 +0200 Subject: [PATCH 04/13] Measure distortion --- app-proto/main.css | 4 +++ app-proto/src/assembly.rs | 34 ++++++++++++++++++++++++- app-proto/src/components/diagnostics.rs | 23 +++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/app-proto/main.css b/app-proto/main.css index a00d309..0ec33e9 100644 --- a/app-proto/main.css +++ b/app-proto/main.css @@ -227,6 +227,10 @@ details[open]:has(li) .element-switch::after { border-radius: 8px; } +#distortion-gauge { + margin-top: 8px; +} + /* display */ #display { diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 669c0d0..b5b2bd9 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -3,6 +3,7 @@ use std::{ cell::Cell, cmp::Ordering, collections::{BTreeMap, BTreeSet}, + f64::consts::SQRT_2, fmt, fmt::{Debug, Formatter}, hash::{Hash, Hasher}, @@ -122,6 +123,11 @@ pub trait Element: Serial + ProblemPoser + DisplayItem { // be used carefully to preserve invariant (1), described in the comment on // the `tangent` field of the `Assembly` structure fn set_column_index(&self, index: usize); + + /* KLUDGE */ + fn has_distortion(&self) -> bool { + false + } } impl Debug for dyn Element { @@ -334,6 +340,10 @@ impl Element for Point { fn set_column_index(&self, index: usize) { self.column_index.set(Some(index)); } + + fn has_distortion(&self) -> bool { + true + } } impl Serial for Point { @@ -357,6 +367,9 @@ pub trait Regulator: Serial + ProblemPoser + OutlineItem { fn subjects(&self) -> Vec>; fn measurement(&self) -> ReadSignal; fn set_point(&self) -> Signal; + fn distortion(&self) -> Option> { /* KLUDGE */ + None + } } impl Hash for dyn Regulator { @@ -389,6 +402,7 @@ pub struct InversiveDistanceRegulator { pub subjects: [Rc; 2], pub measurement: ReadSignal, pub set_point: Signal, + distortion: Option>, /* KLUDGE */ serial: u64, } @@ -404,9 +418,23 @@ impl InversiveDistanceRegulator { }); let set_point = create_signal(SpecifiedValue::from_empty_spec()); + let distortion = if subjects.iter().all(|subj| subj.has_distortion()) { + Some(create_memo(move || { + let set_point_opt = set_point.with(|set_pt| set_pt.value); + let measurement_val = measurement.get(); + match set_point_opt { + None => 0.0, + Some(set_point_val) => SQRT_2 * ( + (-set_point_val).sqrt() - (-measurement_val).sqrt() + ).abs(), + } + })) + } else { + None + }; let serial = Self::next_serial(); - Self { subjects, measurement, set_point, serial } + Self { subjects, measurement, set_point, distortion, serial } } } @@ -422,6 +450,10 @@ impl Regulator for InversiveDistanceRegulator { fn set_point(&self) -> Signal { self.set_point } + + fn distortion(&self) -> Option> { + self.distortion + } } impl Serial for InversiveDistanceRegulator { diff --git a/app-proto/src/components/diagnostics.rs b/app-proto/src/components/diagnostics.rs index 51d58f1..ce4a0b4 100644 --- a/app-proto/src/components/diagnostics.rs +++ b/app-proto/src/components/diagnostics.rs @@ -111,6 +111,28 @@ fn StepInput() -> View { } } +#[component] +fn DistortionGauge() -> View { + let state = use_context::(); + let total_distortion = create_memo(move || { + state.assembly.regulators.with(|regs| { + let mut total = 0.0; + for reg in regs { + if let Some(distortion) = reg.distortion() { + total += distortion.get(); + } + } + total + }) + }); + + view! { + div(id = "distortion-gauge") { + "Distortion: " (total_distortion.with(|distort| distort.to_string())) + } + } +} + fn into_log10_time_point((step, value): (usize, f64)) -> Vec> { vec![ Some(step as f64), @@ -315,6 +337,7 @@ pub fn Diagnostics() -> View { } DiagnosticsPanel(name = "loss") { LossHistory {} } DiagnosticsPanel(name = "spectrum") { SpectrumHistory {} } + DistortionGauge {} } } } \ No newline at end of file From 9e74d4e837e3e5455c210f7d833d816da2190598 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Thu, 4 Sep 2025 09:57:10 +0200 Subject: [PATCH 05/13] Add more 5-5-4 near misses --- .../src/components/test_assembly_chooser.rs | 1825 ++++++++++++++++- 1 file changed, 1822 insertions(+), 3 deletions(-) diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 18c122c..799e7d4 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -891,7 +891,7 @@ fn regular_diagonals<'a, const N: usize>(vertex_ids: [&'a str; N]) -> Vec<(f64, )).collect() } -fn load_554a(assembly: &Assembly) { +fn load_554_base(assembly: &Assembly) { // create the vertices const COLOR_A: ElementColor = [0.75_f32, 0.00_f32, 0.75_f32]; const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32]; @@ -1317,6 +1317,1817 @@ fn load_554a(assembly: &Assembly) { } } +fn load_554_aug1(assembly: &Assembly) { + // create the vertices + const COLOR_A: ElementColor = [0.75_f32, 0.00_f32, 0.75_f32]; + const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32]; + const COLOR_C: ElementColor = [0.75_f32, 0.50_f32, 0.00_f32]; + const COLOR_D: ElementColor = [0.90_f32, 0.90_f32, 0.00_f32]; + const COLOR_E: ElementColor = [0.25_f32, 0.75_f32, 0.00_f32]; + const COLOR_F: ElementColor = [0.00_f32, 0.50_f32, 0.75_f32]; + const COLOR_G: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; + const GRAY: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let vertices = [ + Point::new( + "a_NE".to_string(), + "A-NE".to_string(), + COLOR_A, + engine::point(0.5, 0.5, 0.0), + ), + Point::new( + "a_NW".to_string(), + "A-NW".to_string(), + COLOR_A, + engine::point(-0.5, 0.5, 0.0), + ), + Point::new( + "a_SW".to_string(), + "A-SW".to_string(), + COLOR_A, + engine::point(-0.5, -0.5, 0.0), + ), + Point::new( + "a_SE".to_string(), + "A-SE".to_string(), + COLOR_A, + engine::point(0.5, -0.5, 0.0), + ), + Point::new( + "b_NE".to_string(), + "B-NE".to_string(), + COLOR_B, + engine::point(0.7, 0.7, 0.9), + ), + Point::new( + "b_NW".to_string(), + "B-NW".to_string(), + COLOR_B, + engine::point(-0.7, 0.7, 0.9), + ), + Point::new( + "b_SW".to_string(), + "B-SW".to_string(), + COLOR_B, + engine::point(-0.7, -0.7, 0.9), + ), + Point::new( + "b_SE".to_string(), + "B-SE".to_string(), + COLOR_B, + engine::point(0.7, -0.7, 0.9), + ), + Point::new( + "c_N".to_string(), + "C-N".to_string(), + COLOR_C, + engine::point(0.0, 0.8, 1.4), + ), + Point::new( + "c_W".to_string(), + "C-W".to_string(), + COLOR_C, + engine::point(-0.8, 0.0, 1.4), + ), + Point::new( + "c_S".to_string(), + "C-S".to_string(), + COLOR_C, + engine::point(0.0, -0.8, 1.4), + ), + Point::new( + "c_E".to_string(), + "C-E".to_string(), + COLOR_C, + engine::point(0.8, 0.0, 1.4), + ), + Point::new( + "y_NE".to_string(), + "Y-NE".to_string(), + GRAY, + engine::point(0.7, 0.7, 1.6), + ), + Point::new( + "y_NW".to_string(), + "Y-NW".to_string(), + GRAY, + engine::point(-0.7, 0.7, 1.6), + ), + Point::new( + "y_SW".to_string(), + "Y-SW".to_string(), + GRAY, + engine::point(-0.7, -0.7, 1.6), + ), + Point::new( + "y_SE".to_string(), + "Y-SE".to_string(), + GRAY, + engine::point(0.7, -0.7, 1.6), + ), + Point::new( + "d_NE".to_string(), + "D-NE".to_string(), + COLOR_D, + engine::point(0.1, 0.1, 1.8), + ), + Point::new( + "d_NW".to_string(), + "D-NW".to_string(), + COLOR_D, + engine::point(-0.1, 0.1, 1.8), + ), + Point::new( + "d_SW".to_string(), + "D-SW".to_string(), + COLOR_D, + engine::point(-0.1, -0.1, 1.8), + ), + Point::new( + "d_SE".to_string(), + "D-SE".to_string(), + COLOR_D, + engine::point(0.1, -0.1, 1.8), + ), + Point::new( + "e_N".to_string(), + "E-N".to_string(), + COLOR_E, + engine::point(0.0, 0.7, 2.3), + ), + Point::new( + "e_W".to_string(), + "E-W".to_string(), + COLOR_E, + engine::point(-0.7, 0.0, 2.3), + ), + Point::new( + "e_S".to_string(), + "E-S".to_string(), + COLOR_E, + engine::point(0.0, -0.7, 2.3), + ), + Point::new( + "e_E".to_string(), + "E-E".to_string(), + COLOR_E, + engine::point(0.7, 0.0, 2.3), + ), + Point::new( + "f_NE".to_string(), + "F-NE".to_string(), + COLOR_F, + engine::point(0.2, 0.2, 2.7), + ), + Point::new( + "f_NW".to_string(), + "F-NW".to_string(), + COLOR_F, + engine::point(-0.2, 0.2, 2.7), + ), + Point::new( + "f_SW".to_string(), + "F-SW".to_string(), + COLOR_F, + engine::point(-0.2, -0.2, 2.7), + ), + Point::new( + "f_SE".to_string(), + "F-SE".to_string(), + COLOR_F, + engine::point(0.2, -0.2, 2.7), + ), + Point::new( + "g_NNE".to_string(), + "G-NNE".to_string(), + COLOR_G, + engine::point(0.5, 1.2, 3.0), + ), + Point::new( + "g_NNW".to_string(), + "G-NNW".to_string(), + COLOR_G, + engine::point(-0.5, 1.2, 3.0), + ), + Point::new( + "g_WNW".to_string(), + "G-WNW".to_string(), + COLOR_G, + engine::point(-1.2, 0.5, 3.0), + ), + Point::new( + "g_WSW".to_string(), + "G-WSW".to_string(), + COLOR_G, + engine::point(-1.2, -0.5, 3.0), + ), + Point::new( + "g_SSW".to_string(), + "G-SSW".to_string(), + COLOR_G, + engine::point(-0.5, -1.2, 3.0), + ), + Point::new( + "g_SSE".to_string(), + "G-SSE".to_string(), + COLOR_G, + engine::point(0.5, -1.2, 3.0), + ), + Point::new( + "g_ESE".to_string(), + "G-ESE".to_string(), + COLOR_G, + engine::point(1.2, -0.5, 3.0), + ), + Point::new( + "g_ENE".to_string(), + "G-ENE".to_string(), + COLOR_G, + engine::point(1.2, 0.5, 3.0), + ), + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // fix the distances between adjacent vertices + let f_a = ["a_SE", "a_SW", "a_NW", "a_NE"]; + let f_abc_n = ["a_NW", "b_NW", "c_N", "b_NE", "a_NE"]; + let f_abc_w = ["a_SW", "b_SW", "c_W", "b_NW", "a_NW"]; + let f_abc_s = ["a_SE", "b_SE", "c_S", "b_SW", "a_SW"]; + let f_abc_e = ["a_NE", "b_NE", "c_E", "b_SE", "a_SE"]; + let f_g = [ + "g_NNE", "g_NNW", "g_WNW", "g_WSW", + "g_SSW", "g_SSE", "g_ESE", "g_ENE", + ]; + let struts: Vec<_> = [ + (1.0, vec![ + ["a_NE", "a_NW"], + ["a_NW", "a_SW"], + ["a_SW", "a_SE"], + ["a_SE", "a_NE"], + ["a_NE", "b_NE"], + ["a_NW", "b_NW"], + ["a_SW", "b_SW"], + ["a_SE", "b_SE"], + ["b_NE", "c_N"], + ["b_NW", "c_N"], + ["b_NW", "c_W"], + ["b_SW", "c_W"], + ["b_SW", "c_S"], + ["b_SE", "c_S"], + ["b_SE", "c_E"], + ["b_NE", "c_E"], + ["c_N", "d_NE"], + ["c_N", "d_NW"], + ["c_W", "d_NW"], + ["c_W", "d_SW"], + ["c_S", "d_SW"], + ["c_S", "d_SE"], + ["c_E", "d_SE"], + ["c_E", "d_NE"], + ["y_NE", "b_NE"], + ["y_NW", "b_NW"], + ["y_SW", "b_SW"], + ["y_SE", "b_SE"], + ["y_NE", "c_N"], + ["y_NW", "c_N"], + ["y_NW", "c_W"], + ["y_SW", "c_W"], + ["y_SW", "c_S"], + ["y_SE", "c_S"], + ["y_SE", "c_E"], + ["y_NE", "c_E"], + ["y_NE", "d_NE"], + ["y_NW", "d_NW"], + ["y_SW", "d_SW"], + ["y_SE", "d_SE"], + ["d_NE", "e_N"], + ["d_NW", "e_N"], + ["d_NW", "e_W"], + ["d_SW", "e_W"], + ["d_SW", "e_S"], + ["d_SE", "e_S"], + ["d_SE", "e_E"], + ["d_NE", "e_E"], + ["c_N", "e_N"], + ["c_W", "e_W"], + ["c_S", "e_S"], + ["c_E", "e_E"], + ["e_N", "f_NE"], + ["e_N", "f_NW"], + ["e_W", "f_NW"], + ["e_W", "f_SW"], + ["e_S", "f_SW"], + ["e_S", "f_SE"], + ["e_E", "f_SE"], + ["e_E", "f_NE"], + ["d_NE", "f_NE"], + ["d_NW", "f_NW"], + ["d_SW", "f_SW"], + ["d_SE", "f_SE"], + ["f_NE", "g_ENE"], + ["f_NE", "g_NNE"], + ["f_NW", "g_NNW"], + ["f_NW", "g_WNW"], + ["f_SW", "g_WSW"], + ["f_SW", "g_SSW"], + ["f_SE", "g_SSE"], + ["f_SE", "g_ESE"], + ["e_N", "g_NNE"], + ["e_N", "g_NNW"], + ["e_W", "g_WNW"], + ["e_W", "g_WSW"], + ["e_S", "g_SSW"], + ["e_S", "g_SSE"], + ["e_E", "g_ESE"], + ["e_E", "g_ENE"], + ["g_NNE", "g_NNW"], + ["g_NNW", "g_WNW"], + ["g_WNW", "g_WSW"], + ["g_WSW", "g_SSW"], + ["g_SSW", "g_SSE"], + ["g_SSE", "g_ESE"], + ["g_ESE", "g_ENE"], + ["g_ENE", "g_NNE"], + ]), + ].into_iter() + .chain(regular_diagonals(f_a)) + .chain(regular_diagonals(f_abc_n)) + .chain(regular_diagonals(f_abc_w)) + .chain(regular_diagonals(f_abc_s)) + .chain(regular_diagonals(f_abc_e)) + .chain(regular_diagonals(f_g)) + .collect(); + for (length, vertex_pairs) in struts { + let inv_dist = Some(-0.5 * length * length); + for pair in vertex_pairs { + let adjacent_vertices = pair.map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + let distance = InversiveDistanceRegulator::new(adjacent_vertices); + distance.set_point.set(SpecifiedValue::from(inv_dist)); + assembly.insert_regulator(Rc::new(distance)); + } + } + + // create the faces + const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let faces = [ + Sphere::new( + "f_a".to_string(), + "Face A".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, -1.0, 0.0, 0.0), + ), + Sphere::new( + "f_abc_N".to_string(), + "Face ABC-N".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_W".to_string(), + "Face ABC-W".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-1.0, 0.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_S".to_string(), + "Face ABC-S".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, -1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_E".to_string(), + "Face ABC-E".to_string(), + COLOR_FACE, + engine::sphere_with_offset(1.0, 0.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_g".to_string(), + "Face G".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, 1.0, 3.0, 0.0), + ), + ]; + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + // make the faces planar and make them pass through their vertices + let face_incidences = [ + ("f_a", Vec::from(f_a)), + ("f_abc_N", Vec::from(f_abc_n)), + ("f_abc_W", Vec::from(f_abc_w)), + ("f_abc_S", Vec::from(f_abc_s)), + ("f_abc_E", Vec::from(f_abc_e)), + ("f_g", Vec::from(f_g)), + ]; + for (face_id, vertex_ids) in face_incidences { + // make the face planar + let face = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[face_id].clone() + ); + let curvature_regulator = face.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set(SpecifiedValue::from(Some(0.0))); + + // make the face pass through its vertices + for v_id in vertex_ids { + let vertex = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[v_id].clone() + ); + let incidence = InversiveDistanceRegulator::new([face.clone(), vertex]); + incidence.set_point.set(SpecifiedValue::from(Some(0.0))); + assembly.insert_regulator(Rc::new(incidence)); + } + } +} + +fn load_554_aug1_inner(assembly: &Assembly) { + // create the vertices + const COLOR_A: ElementColor = [0.75_f32, 0.00_f32, 0.75_f32]; + const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32]; + const COLOR_C: ElementColor = [0.75_f32, 0.50_f32, 0.00_f32]; + const COLOR_D: ElementColor = [0.90_f32, 0.90_f32, 0.00_f32]; + const COLOR_E: ElementColor = [0.25_f32, 0.75_f32, 0.00_f32]; + const COLOR_F: ElementColor = [0.00_f32, 0.50_f32, 0.75_f32]; + const COLOR_G: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; + const GRAY: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let vertices = [ + Point::new( + "a_NE".to_string(), + "A-NE".to_string(), + COLOR_A, + engine::point(0.5, 0.5, 0.0), + ), + Point::new( + "a_NW".to_string(), + "A-NW".to_string(), + COLOR_A, + engine::point(-0.5, 0.5, 0.0), + ), + Point::new( + "a_SW".to_string(), + "A-SW".to_string(), + COLOR_A, + engine::point(-0.5, -0.5, 0.0), + ), + Point::new( + "a_SE".to_string(), + "A-SE".to_string(), + COLOR_A, + engine::point(0.5, -0.5, 0.0), + ), + Point::new( + "b_NE".to_string(), + "B-NE".to_string(), + COLOR_B, + engine::point(0.7, 0.7, 0.9), + ), + Point::new( + "b_NW".to_string(), + "B-NW".to_string(), + COLOR_B, + engine::point(-0.7, 0.7, 0.9), + ), + Point::new( + "b_SW".to_string(), + "B-SW".to_string(), + COLOR_B, + engine::point(-0.7, -0.7, 0.9), + ), + Point::new( + "b_SE".to_string(), + "B-SE".to_string(), + COLOR_B, + engine::point(0.7, -0.7, 0.9), + ), + Point::new( + "y_NE".to_string(), + "Y-NE".to_string(), + GRAY, + engine::point(0.1, 0.1, 1.0), + ), + Point::new( + "y_NW".to_string(), + "Y-NW".to_string(), + GRAY, + engine::point(-0.1, 0.1, 1.0), + ), + Point::new( + "y_SW".to_string(), + "Y-SW".to_string(), + GRAY, + engine::point(-0.1, -0.1, 1.0), + ), + Point::new( + "y_SE".to_string(), + "Y-SE".to_string(), + GRAY, + engine::point(0.1, -0.1, 1.0), + ), + Point::new( + "c_N".to_string(), + "C-N".to_string(), + COLOR_C, + engine::point(0.0, 0.8, 1.4), + ), + Point::new( + "c_W".to_string(), + "C-W".to_string(), + COLOR_C, + engine::point(-0.8, 0.0, 1.4), + ), + Point::new( + "c_S".to_string(), + "C-S".to_string(), + COLOR_C, + engine::point(0.0, -0.8, 1.4), + ), + Point::new( + "c_E".to_string(), + "C-E".to_string(), + COLOR_C, + engine::point(0.8, 0.0, 1.4), + ), + Point::new( + "d_NE".to_string(), + "D-NE".to_string(), + COLOR_D, + engine::point(0.1, 0.1, 1.8), + ), + Point::new( + "d_NW".to_string(), + "D-NW".to_string(), + COLOR_D, + engine::point(-0.1, 0.1, 1.8), + ), + Point::new( + "d_SW".to_string(), + "D-SW".to_string(), + COLOR_D, + engine::point(-0.1, -0.1, 1.8), + ), + Point::new( + "d_SE".to_string(), + "D-SE".to_string(), + COLOR_D, + engine::point(0.1, -0.1, 1.8), + ), + Point::new( + "e_N".to_string(), + "E-N".to_string(), + COLOR_E, + engine::point(0.0, 0.7, 2.3), + ), + Point::new( + "e_W".to_string(), + "E-W".to_string(), + COLOR_E, + engine::point(-0.7, 0.0, 2.3), + ), + Point::new( + "e_S".to_string(), + "E-S".to_string(), + COLOR_E, + engine::point(0.0, -0.7, 2.3), + ), + Point::new( + "e_E".to_string(), + "E-E".to_string(), + COLOR_E, + engine::point(0.7, 0.0, 2.3), + ), + Point::new( + "f_NE".to_string(), + "F-NE".to_string(), + COLOR_F, + engine::point(0.2, 0.2, 2.7), + ), + Point::new( + "f_NW".to_string(), + "F-NW".to_string(), + COLOR_F, + engine::point(-0.2, 0.2, 2.7), + ), + Point::new( + "f_SW".to_string(), + "F-SW".to_string(), + COLOR_F, + engine::point(-0.2, -0.2, 2.7), + ), + Point::new( + "f_SE".to_string(), + "F-SE".to_string(), + COLOR_F, + engine::point(0.2, -0.2, 2.7), + ), + Point::new( + "g_NNE".to_string(), + "G-NNE".to_string(), + COLOR_G, + engine::point(0.5, 1.2, 3.0), + ), + Point::new( + "g_NNW".to_string(), + "G-NNW".to_string(), + COLOR_G, + engine::point(-0.5, 1.2, 3.0), + ), + Point::new( + "g_WNW".to_string(), + "G-WNW".to_string(), + COLOR_G, + engine::point(-1.2, 0.5, 3.0), + ), + Point::new( + "g_WSW".to_string(), + "G-WSW".to_string(), + COLOR_G, + engine::point(-1.2, -0.5, 3.0), + ), + Point::new( + "g_SSW".to_string(), + "G-SSW".to_string(), + COLOR_G, + engine::point(-0.5, -1.2, 3.0), + ), + Point::new( + "g_SSE".to_string(), + "G-SSE".to_string(), + COLOR_G, + engine::point(0.5, -1.2, 3.0), + ), + Point::new( + "g_ESE".to_string(), + "G-ESE".to_string(), + COLOR_G, + engine::point(1.2, -0.5, 3.0), + ), + Point::new( + "g_ENE".to_string(), + "G-ENE".to_string(), + COLOR_G, + engine::point(1.2, 0.5, 3.0), + ), + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // fix the distances between adjacent vertices + let f_a = ["a_SE", "a_SW", "a_NW", "a_NE"]; + let f_abc_n = ["a_NW", "b_NW", "c_N", "b_NE", "a_NE"]; + let f_abc_w = ["a_SW", "b_SW", "c_W", "b_NW", "a_NW"]; + let f_abc_s = ["a_SE", "b_SE", "c_S", "b_SW", "a_SW"]; + let f_abc_e = ["a_NE", "b_NE", "c_E", "b_SE", "a_SE"]; + let f_g = [ + "g_NNE", "g_NNW", "g_WNW", "g_WSW", + "g_SSW", "g_SSE", "g_ESE", "g_ENE", + ]; + let struts: Vec<_> = [ + (1.0, vec![ + ["a_NE", "a_NW"], + ["a_NW", "a_SW"], + ["a_SW", "a_SE"], + ["a_SE", "a_NE"], + ["a_NE", "b_NE"], + ["a_NW", "b_NW"], + ["a_SW", "b_SW"], + ["a_SE", "b_SE"], + ["b_NE", "c_N"], + ["b_NW", "c_N"], + ["b_NW", "c_W"], + ["b_SW", "c_W"], + ["b_SW", "c_S"], + ["b_SE", "c_S"], + ["b_SE", "c_E"], + ["b_NE", "c_E"], + ["c_N", "d_NE"], + ["c_N", "d_NW"], + ["c_W", "d_NW"], + ["c_W", "d_SW"], + ["c_S", "d_SW"], + ["c_S", "d_SE"], + ["c_E", "d_SE"], + ["c_E", "d_NE"], + ["y_NE", "b_NE"], + ["y_NW", "b_NW"], + ["y_SW", "b_SW"], + ["y_SE", "b_SE"], + ["y_NE", "c_N"], + ["y_NW", "c_N"], + ["y_NW", "c_W"], + ["y_SW", "c_W"], + ["y_SW", "c_S"], + ["y_SE", "c_S"], + ["y_SE", "c_E"], + ["y_NE", "c_E"], + ["y_NE", "d_NE"], + ["y_NW", "d_NW"], + ["y_SW", "d_SW"], + ["y_SE", "d_SE"], + ["d_NE", "e_N"], + ["d_NW", "e_N"], + ["d_NW", "e_W"], + ["d_SW", "e_W"], + ["d_SW", "e_S"], + ["d_SE", "e_S"], + ["d_SE", "e_E"], + ["d_NE", "e_E"], + ["c_N", "e_N"], + ["c_W", "e_W"], + ["c_S", "e_S"], + ["c_E", "e_E"], + ["e_N", "f_NE"], + ["e_N", "f_NW"], + ["e_W", "f_NW"], + ["e_W", "f_SW"], + ["e_S", "f_SW"], + ["e_S", "f_SE"], + ["e_E", "f_SE"], + ["e_E", "f_NE"], + ["d_NE", "f_NE"], + ["d_NW", "f_NW"], + ["d_SW", "f_SW"], + ["d_SE", "f_SE"], + ["f_NE", "g_ENE"], + ["f_NE", "g_NNE"], + ["f_NW", "g_NNW"], + ["f_NW", "g_WNW"], + ["f_SW", "g_WSW"], + ["f_SW", "g_SSW"], + ["f_SE", "g_SSE"], + ["f_SE", "g_ESE"], + ["e_N", "g_NNE"], + ["e_N", "g_NNW"], + ["e_W", "g_WNW"], + ["e_W", "g_WSW"], + ["e_S", "g_SSW"], + ["e_S", "g_SSE"], + ["e_E", "g_ESE"], + ["e_E", "g_ENE"], + ["g_NNE", "g_NNW"], + ["g_NNW", "g_WNW"], + ["g_WNW", "g_WSW"], + ["g_WSW", "g_SSW"], + ["g_SSW", "g_SSE"], + ["g_SSE", "g_ESE"], + ["g_ESE", "g_ENE"], + ["g_ENE", "g_NNE"], + ]), + ].into_iter() + .chain(regular_diagonals(f_a)) + .chain(regular_diagonals(f_abc_n)) + .chain(regular_diagonals(f_abc_w)) + .chain(regular_diagonals(f_abc_s)) + .chain(regular_diagonals(f_abc_e)) + .chain(regular_diagonals(f_g)) + .collect(); + for (length, vertex_pairs) in struts { + let inv_dist = Some(-0.5 * length * length); + for pair in vertex_pairs { + let adjacent_vertices = pair.map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + let distance = InversiveDistanceRegulator::new(adjacent_vertices); + distance.set_point.set(SpecifiedValue::from(inv_dist)); + assembly.insert_regulator(Rc::new(distance)); + } + } + + // create the faces + const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let faces = [ + Sphere::new( + "f_a".to_string(), + "Face A".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, -1.0, 0.0, 0.0), + ), + Sphere::new( + "f_abc_N".to_string(), + "Face ABC-N".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_W".to_string(), + "Face ABC-W".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-1.0, 0.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_S".to_string(), + "Face ABC-S".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, -1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_E".to_string(), + "Face ABC-E".to_string(), + COLOR_FACE, + engine::sphere_with_offset(1.0, 0.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_g".to_string(), + "Face G".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, 1.0, 3.0, 0.0), + ), + ]; + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + // make the faces planar and make them pass through their vertices + let face_incidences = [ + ("f_a", Vec::from(f_a)), + ("f_abc_N", Vec::from(f_abc_n)), + ("f_abc_W", Vec::from(f_abc_w)), + ("f_abc_S", Vec::from(f_abc_s)), + ("f_abc_E", Vec::from(f_abc_e)), + ("f_g", Vec::from(f_g)), + ]; + for (face_id, vertex_ids) in face_incidences { + // make the face planar + let face = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[face_id].clone() + ); + let curvature_regulator = face.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set(SpecifiedValue::from(Some(0.0))); + + // make the face pass through its vertices + for v_id in vertex_ids { + let vertex = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[v_id].clone() + ); + let incidence = InversiveDistanceRegulator::new([face.clone(), vertex]); + incidence.set_point.set(SpecifiedValue::from(Some(0.0))); + assembly.insert_regulator(Rc::new(incidence)); + } + } +} + +fn load_554_aug2(assembly: &Assembly) { + // create the vertices + const COLOR_A: ElementColor = [0.75_f32, 0.00_f32, 0.75_f32]; + const COLOR_Z: ElementColor = [1.00_f32, 0.40_f32, 0.60_f32]; + const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32]; + const COLOR_Y: ElementColor = [1.00_f32, 0.75_f32, 0.25_f32]; + const COLOR_C: ElementColor = [0.75_f32, 0.50_f32, 0.00_f32]; + const COLOR_D: ElementColor = [0.90_f32, 0.90_f32, 0.00_f32]; + const COLOR_E: ElementColor = [0.25_f32, 0.75_f32, 0.00_f32]; + const COLOR_F: ElementColor = [0.00_f32, 0.50_f32, 0.75_f32]; + const COLOR_G: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; + let vertices = [ + Point::new( + "a_NE".to_string(), + "A-NE".to_string(), + COLOR_A, + engine::point(0.5, 0.5, 0.0), + ), + Point::new( + "a_NW".to_string(), + "A-NW".to_string(), + COLOR_A, + engine::point(-0.5, 0.5, 0.0), + ), + Point::new( + "a_SW".to_string(), + "A-SW".to_string(), + COLOR_A, + engine::point(-0.5, -0.5, 0.0), + ), + Point::new( + "a_SE".to_string(), + "A-SE".to_string(), + COLOR_A, + engine::point(0.5, -0.5, 0.0), + ), + Point::new( + "z_S".to_string(), + "Z-S".to_string(), + COLOR_Z, + engine::point(0.0, -0.4, 0.6), + ), + Point::new( + "z_E".to_string(), + "Z-E".to_string(), + COLOR_Z, + engine::point(0.4, 0.0, 0.6), + ), + Point::new( + "b_NE".to_string(), + "B-NE".to_string(), + COLOR_B, + engine::point(0.7, 0.7, 0.9), + ), + Point::new( + "b_NW".to_string(), + "B-NW".to_string(), + COLOR_B, + engine::point(-0.7, 0.7, 0.9), + ), + Point::new( + "b_SW".to_string(), + "B-SW".to_string(), + COLOR_B, + engine::point(-0.7, -0.7, 0.9), + ), + Point::new( + "b_SE".to_string(), + "B-SE".to_string(), + COLOR_B, + engine::point(0.7, -0.7, 0.9), + ), + Point::new( + "y_NE".to_string(), + "Y-NE".to_string(), + COLOR_Y, + engine::point(0.1, 0.1, 1.0), + ), + Point::new( + "y_NW".to_string(), + "Y-NW".to_string(), + COLOR_Y, + engine::point(-0.1, 0.1, 1.0), + ), + Point::new( + "y_SW".to_string(), + "Y-SW".to_string(), + COLOR_Y, + engine::point(-0.1, -0.1, 1.0), + ), + Point::new( + "y_SE".to_string(), + "Y-SE".to_string(), + COLOR_Y, + engine::point(0.1, -0.1, 1.0), + ), + Point::new( + "c_N".to_string(), + "C-N".to_string(), + COLOR_C, + engine::point(0.0, 0.8, 1.4), + ), + Point::new( + "c_W".to_string(), + "C-W".to_string(), + COLOR_C, + engine::point(-0.8, 0.0, 1.4), + ), + Point::new( + "c_S".to_string(), + "C-S".to_string(), + COLOR_C, + engine::point(0.0, -0.8, 1.4), + ), + Point::new( + "c_E".to_string(), + "C-E".to_string(), + COLOR_C, + engine::point(0.8, 0.0, 1.4), + ), + Point::new( + "d_NE".to_string(), + "D-NE".to_string(), + COLOR_D, + engine::point(0.1, 0.1, 1.8), + ), + Point::new( + "d_NW".to_string(), + "D-NW".to_string(), + COLOR_D, + engine::point(-0.1, 0.1, 1.8), + ), + Point::new( + "d_SW".to_string(), + "D-SW".to_string(), + COLOR_D, + engine::point(-0.1, -0.1, 1.8), + ), + Point::new( + "d_SE".to_string(), + "D-SE".to_string(), + COLOR_D, + engine::point(0.1, -0.1, 1.8), + ), + Point::new( + "e_N".to_string(), + "E-N".to_string(), + COLOR_E, + engine::point(0.0, 0.7, 2.3), + ), + Point::new( + "e_W".to_string(), + "E-W".to_string(), + COLOR_E, + engine::point(-0.7, 0.0, 2.3), + ), + Point::new( + "e_S".to_string(), + "E-S".to_string(), + COLOR_E, + engine::point(0.0, -0.7, 2.3), + ), + Point::new( + "e_E".to_string(), + "E-E".to_string(), + COLOR_E, + engine::point(0.7, 0.0, 2.3), + ), + Point::new( + "f_NE".to_string(), + "F-NE".to_string(), + COLOR_F, + engine::point(0.2, 0.2, 2.7), + ), + Point::new( + "f_NW".to_string(), + "F-NW".to_string(), + COLOR_F, + engine::point(-0.2, 0.2, 2.7), + ), + Point::new( + "f_SW".to_string(), + "F-SW".to_string(), + COLOR_F, + engine::point(-0.2, -0.2, 2.7), + ), + Point::new( + "f_SE".to_string(), + "F-SE".to_string(), + COLOR_F, + engine::point(0.2, -0.2, 2.7), + ), + Point::new( + "g_NNE".to_string(), + "G-NNE".to_string(), + COLOR_G, + engine::point(0.5, 1.2, 3.0), + ), + Point::new( + "g_NNW".to_string(), + "G-NNW".to_string(), + COLOR_G, + engine::point(-0.5, 1.2, 3.0), + ), + Point::new( + "g_WNW".to_string(), + "G-WNW".to_string(), + COLOR_G, + engine::point(-1.2, 0.5, 3.0), + ), + Point::new( + "g_WSW".to_string(), + "G-WSW".to_string(), + COLOR_G, + engine::point(-1.2, -0.5, 3.0), + ), + Point::new( + "g_SSW".to_string(), + "G-SSW".to_string(), + COLOR_G, + engine::point(-0.5, -1.2, 3.0), + ), + Point::new( + "g_SSE".to_string(), + "G-SSE".to_string(), + COLOR_G, + engine::point(0.5, -1.2, 3.0), + ), + Point::new( + "g_ESE".to_string(), + "G-ESE".to_string(), + COLOR_G, + engine::point(1.2, -0.5, 3.0), + ), + Point::new( + "g_ENE".to_string(), + "G-ENE".to_string(), + COLOR_G, + engine::point(1.2, 0.5, 3.0), + ), + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // fix the distances between adjacent vertices + let f_a = ["a_SE", "a_SW", "a_NW", "a_NE"]; + let f_abc_n = ["a_NW", "b_NW", "c_N", "b_NE", "a_NE"]; + let f_abc_w = ["a_SW", "b_SW", "c_W", "b_NW", "a_NW"]; + let f_g = [ + "g_NNE", "g_NNW", "g_WNW", "g_WSW", + "g_SSW", "g_SSE", "g_ESE", "g_ENE", + ]; + let struts: Vec<_> = [ + (1.0, vec![ + ["a_NE", "a_NW"], + ["a_NW", "a_SW"], + ["a_SW", "a_SE"], + ["a_SE", "a_NE"], + ["a_NE", "b_NE"], + ["a_NW", "b_NW"], + ["a_SW", "b_SW"], + ["a_SE", "b_SE"], + ["b_NE", "c_N"], + ["b_NW", "c_N"], + ["b_NW", "c_W"], + ["b_SW", "c_W"], + ["b_SW", "c_S"], + ["b_SE", "c_S"], + ["b_SE", "c_E"], + ["b_NE", "c_E"], + ["z_S", "a_SW"], + ["z_S", "a_SE"], + ["z_E", "a_SE"], + ["z_E", "a_NE"], + ["z_S", "b_SW"], + ["z_S", "b_SE"], + ["z_E", "b_SE"], + ["z_E", "b_NE"], + ["z_S", "c_S"], + ["z_E", "c_E"], + ["c_N", "d_NE"], + ["c_N", "d_NW"], + ["c_W", "d_NW"], + ["c_W", "d_SW"], + ["c_S", "d_SW"], + ["c_S", "d_SE"], + ["c_E", "d_SE"], + ["c_E", "d_NE"], + ["y_NE", "b_NE"], + ["y_NW", "b_NW"], + ["y_SW", "b_SW"], + ["y_SE", "b_SE"], + ["y_NE", "c_N"], + ["y_NW", "c_N"], + ["y_NW", "c_W"], + ["y_SW", "c_W"], + ["y_SW", "c_S"], + ["y_SE", "c_S"], + ["y_SE", "c_E"], + ["y_NE", "c_E"], + ["y_NE", "d_NE"], + ["y_NW", "d_NW"], + ["y_SW", "d_SW"], + ["y_SE", "d_SE"], + ["d_NE", "e_N"], + ["d_NW", "e_N"], + ["d_NW", "e_W"], + ["d_SW", "e_W"], + ["d_SW", "e_S"], + ["d_SE", "e_S"], + ["d_SE", "e_E"], + ["d_NE", "e_E"], + ["c_N", "e_N"], + ["c_W", "e_W"], + ["c_S", "e_S"], + ["c_E", "e_E"], + ["e_N", "f_NE"], + ["e_N", "f_NW"], + ["e_W", "f_NW"], + ["e_W", "f_SW"], + ["e_S", "f_SW"], + ["e_S", "f_SE"], + ["e_E", "f_SE"], + ["e_E", "f_NE"], + ["d_NE", "f_NE"], + ["d_NW", "f_NW"], + ["d_SW", "f_SW"], + ["d_SE", "f_SE"], + ["f_NE", "g_ENE"], + ["f_NE", "g_NNE"], + ["f_NW", "g_NNW"], + ["f_NW", "g_WNW"], + ["f_SW", "g_WSW"], + ["f_SW", "g_SSW"], + ["f_SE", "g_SSE"], + ["f_SE", "g_ESE"], + ["e_N", "g_NNE"], + ["e_N", "g_NNW"], + ["e_W", "g_WNW"], + ["e_W", "g_WSW"], + ["e_S", "g_SSW"], + ["e_S", "g_SSE"], + ["e_E", "g_ESE"], + ["e_E", "g_ENE"], + ["g_NNE", "g_NNW"], + ["g_NNW", "g_WNW"], + ["g_WNW", "g_WSW"], + ["g_WSW", "g_SSW"], + ["g_SSW", "g_SSE"], + ["g_SSE", "g_ESE"], + ["g_ESE", "g_ENE"], + ["g_ENE", "g_NNE"], + ]), + ].into_iter() + .chain(regular_diagonals(f_a)) + .chain(regular_diagonals(f_abc_n)) + .chain(regular_diagonals(f_abc_w)) + .chain(regular_diagonals(f_g)) + .collect(); + for (length, vertex_pairs) in struts { + let inv_dist = Some(-0.5 * length * length); + for pair in vertex_pairs { + let adjacent_vertices = pair.map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + let distance = InversiveDistanceRegulator::new(adjacent_vertices); + distance.set_point.set(SpecifiedValue::from(inv_dist)); + assembly.insert_regulator(Rc::new(distance)); + } + } + + // create the faces + const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let faces = [ + Sphere::new( + "f_a".to_string(), + "Face A".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, -1.0, 0.0, 0.0), + ), + Sphere::new( + "f_abc_N".to_string(), + "Face ABC-N".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_W".to_string(), + "Face ABC-W".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-1.0, 0.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_g".to_string(), + "Face G".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, 1.0, 3.0, 0.0), + ), + ]; + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + // make the faces planar and make them pass through their vertices + let face_incidences = [ + ("f_a", Vec::from(f_a)), + ("f_abc_N", Vec::from(f_abc_n)), + ("f_abc_W", Vec::from(f_abc_w)), + ("f_g", Vec::from(f_g)), + ]; + for (face_id, vertex_ids) in face_incidences { + // make the face planar + let face = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[face_id].clone() + ); + let curvature_regulator = face.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set(SpecifiedValue::from(Some(0.0))); + + // make the face pass through its vertices + for v_id in vertex_ids { + let vertex = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[v_id].clone() + ); + let incidence = InversiveDistanceRegulator::new([face.clone(), vertex]); + incidence.set_point.set(SpecifiedValue::from(Some(0.0))); + assembly.insert_regulator(Rc::new(incidence)); + } + } +} + +fn load_554_domed(assembly: &Assembly) { + // create the vertices + const COLOR_A: ElementColor = [0.75_f32, 0.00_f32, 0.75_f32]; + const COLOR_Z: ElementColor = [1.00_f32, 0.40_f32, 0.60_f32]; + const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32]; + const COLOR_Y: ElementColor = [1.00_f32, 0.75_f32, 0.25_f32]; + const COLOR_C: ElementColor = [0.75_f32, 0.50_f32, 0.00_f32]; + const COLOR_D: ElementColor = [0.90_f32, 0.90_f32, 0.00_f32]; + const COLOR_E: ElementColor = [0.25_f32, 0.75_f32, 0.00_f32]; + const COLOR_F: ElementColor = [0.00_f32, 0.50_f32, 0.75_f32]; + const COLOR_G: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; + const COLOR_H: ElementColor = COLOR_A; + const COLOR_I: ElementColor = COLOR_B; + const COLOR_J: ElementColor = COLOR_C; + let vertices = [ + Point::new( + "a_NE".to_string(), + "A-NE".to_string(), + COLOR_A, + engine::point(0.5, 0.5, 0.0), + ), + Point::new( + "a_NW".to_string(), + "A-NW".to_string(), + COLOR_A, + engine::point(-0.5, 0.5, 0.0), + ), + Point::new( + "a_SW".to_string(), + "A-SW".to_string(), + COLOR_A, + engine::point(-0.5, -0.5, 0.0), + ), + Point::new( + "a_SE".to_string(), + "A-SE".to_string(), + COLOR_A, + engine::point(0.5, -0.5, 0.0), + ), + Point::new( + "z_S".to_string(), + "Z-S".to_string(), + COLOR_Z, + engine::point(0.0, -0.4, 0.6), + ), + Point::new( + "z_E".to_string(), + "Z-E".to_string(), + COLOR_Z, + engine::point(0.4, 0.0, 0.6), + ), + Point::new( + "b_NE".to_string(), + "B-NE".to_string(), + COLOR_B, + engine::point(0.7, 0.7, 0.9), + ), + Point::new( + "b_NW".to_string(), + "B-NW".to_string(), + COLOR_B, + engine::point(-0.7, 0.7, 0.9), + ), + Point::new( + "b_SW".to_string(), + "B-SW".to_string(), + COLOR_B, + engine::point(-0.7, -0.7, 0.9), + ), + Point::new( + "b_SE".to_string(), + "B-SE".to_string(), + COLOR_B, + engine::point(0.7, -0.7, 0.9), + ), + Point::new( + "y_NE".to_string(), + "Y-NE".to_string(), + COLOR_Y, + engine::point(0.1, 0.1, 1.0), + ), + Point::new( + "y_NW".to_string(), + "Y-NW".to_string(), + COLOR_Y, + engine::point(-0.1, 0.1, 1.0), + ), + Point::new( + "y_SW".to_string(), + "Y-SW".to_string(), + COLOR_Y, + engine::point(-0.1, -0.1, 1.0), + ), + Point::new( + "y_SE".to_string(), + "Y-SE".to_string(), + COLOR_Y, + engine::point(0.1, -0.1, 1.0), + ), + Point::new( + "c_N".to_string(), + "C-N".to_string(), + COLOR_C, + engine::point(0.0, 0.8, 1.4), + ), + Point::new( + "c_W".to_string(), + "C-W".to_string(), + COLOR_C, + engine::point(-0.8, 0.0, 1.4), + ), + Point::new( + "c_S".to_string(), + "C-S".to_string(), + COLOR_C, + engine::point(0.0, -0.8, 1.4), + ), + Point::new( + "c_E".to_string(), + "C-E".to_string(), + COLOR_C, + engine::point(0.8, 0.0, 1.4), + ), + Point::new( + "d_NE".to_string(), + "D-NE".to_string(), + COLOR_D, + engine::point(0.1, 0.1, 1.8), + ), + Point::new( + "d_NW".to_string(), + "D-NW".to_string(), + COLOR_D, + engine::point(-0.1, 0.1, 1.8), + ), + Point::new( + "d_SW".to_string(), + "D-SW".to_string(), + COLOR_D, + engine::point(-0.1, -0.1, 1.8), + ), + Point::new( + "d_SE".to_string(), + "D-SE".to_string(), + COLOR_D, + engine::point(0.1, -0.1, 1.8), + ), + Point::new( + "e_N".to_string(), + "E-N".to_string(), + COLOR_E, + engine::point(0.0, 0.7, 2.3), + ), + Point::new( + "e_W".to_string(), + "E-W".to_string(), + COLOR_E, + engine::point(-0.7, 0.0, 2.3), + ), + Point::new( + "e_S".to_string(), + "E-S".to_string(), + COLOR_E, + engine::point(0.0, -0.7, 2.3), + ), + Point::new( + "e_E".to_string(), + "E-E".to_string(), + COLOR_E, + engine::point(0.7, 0.0, 2.3), + ), + Point::new( + "f_NE".to_string(), + "F-NE".to_string(), + COLOR_F, + engine::point(0.2, 0.2, 2.7), + ), + Point::new( + "f_NW".to_string(), + "F-NW".to_string(), + COLOR_F, + engine::point(-0.2, 0.2, 2.7), + ), + Point::new( + "f_SW".to_string(), + "F-SW".to_string(), + COLOR_F, + engine::point(-0.2, -0.2, 2.7), + ), + Point::new( + "f_SE".to_string(), + "F-SE".to_string(), + COLOR_F, + engine::point(0.2, -0.2, 2.7), + ), + Point::new( + "g_NNE".to_string(), + "G-NNE".to_string(), + COLOR_G, + engine::point(0.5, 1.2, 3.0), + ), + Point::new( + "g_NNW".to_string(), + "G-NNW".to_string(), + COLOR_G, + engine::point(-0.5, 1.2, 3.0), + ), + Point::new( + "g_WNW".to_string(), + "G-WNW".to_string(), + COLOR_G, + engine::point(-1.2, 0.5, 3.0), + ), + Point::new( + "g_WSW".to_string(), + "G-WSW".to_string(), + COLOR_G, + engine::point(-1.2, -0.5, 3.0), + ), + Point::new( + "g_SSW".to_string(), + "G-SSW".to_string(), + COLOR_G, + engine::point(-0.5, -1.2, 3.0), + ), + Point::new( + "g_SSE".to_string(), + "G-SSE".to_string(), + COLOR_G, + engine::point(0.5, -1.2, 3.0), + ), + Point::new( + "g_ESE".to_string(), + "G-ESE".to_string(), + COLOR_G, + engine::point(1.2, -0.5, 3.0), + ), + Point::new( + "g_ENE".to_string(), + "G-ENE".to_string(), + COLOR_G, + engine::point(1.2, 0.5, 3.0), + ), + Point::new( + "h_N".to_string(), + "H-N".to_string(), + COLOR_H, + engine::point(0.0, 1.1, 3.6), + ), + Point::new( + "h_W".to_string(), + "H-W".to_string(), + COLOR_H, + engine::point(-1.1, 0.0, 3.6), + ), + Point::new( + "h_S".to_string(), + "H-S".to_string(), + COLOR_H, + engine::point(0.0, -1.1, 3.6), + ), + Point::new( + "h_E".to_string(), + "H-E".to_string(), + COLOR_H, + engine::point(1.1, 0.0, 3.6), + ), + Point::new( + "i_NE".to_string(), + "I-NE".to_string(), + COLOR_I, + engine::point(0.5, 0.5, 3.5), + ), + Point::new( + "i_NW".to_string(), + "I-NW".to_string(), + COLOR_I, + engine::point(-0.5, 0.5, 3.5), + ), + Point::new( + "i_SW".to_string(), + "I-SW".to_string(), + COLOR_I, + engine::point(-0.5, -0.5, 3.5), + ), + Point::new( + "i_SE".to_string(), + "I-SE".to_string(), + COLOR_I, + engine::point(0.5, -0.5, 3.5), + ), + Point::new( + "j".to_string(), + "J".to_string(), + COLOR_J, + engine::point(0.0, 0.0, 4.3), + ), + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // fix the distances between adjacent vertices + let f_a = ["a_SE", "a_SW", "a_NW", "a_NE"]; + let f_abc_n = ["a_NW", "b_NW", "c_N", "b_NE", "a_NE"]; + let f_abc_w = ["a_SW", "b_SW", "c_W", "b_NW", "a_NW"]; + let struts: Vec<_> = [ + (1.0, vec![ + ["a_NE", "a_NW"], + ["a_NW", "a_SW"], + ["a_SW", "a_SE"], + ["a_SE", "a_NE"], + ["a_NE", "b_NE"], + ["a_NW", "b_NW"], + ["a_SW", "b_SW"], + ["a_SE", "b_SE"], + ["b_NE", "c_N"], + ["b_NW", "c_N"], + ["b_NW", "c_W"], + ["b_SW", "c_W"], + ["b_SW", "c_S"], + ["b_SE", "c_S"], + ["b_SE", "c_E"], + ["b_NE", "c_E"], + ["z_S", "a_SW"], + ["z_S", "a_SE"], + ["z_E", "a_SE"], + ["z_E", "a_NE"], + ["z_S", "b_SW"], + ["z_S", "b_SE"], + ["z_E", "b_SE"], + ["z_E", "b_NE"], + ["z_S", "c_S"], + ["z_E", "c_E"], + ["c_N", "d_NE"], + ["c_N", "d_NW"], + ["c_W", "d_NW"], + ["c_W", "d_SW"], + ["c_S", "d_SW"], + ["c_S", "d_SE"], + ["c_E", "d_SE"], + ["c_E", "d_NE"], + ["y_NE", "b_NE"], + ["y_NW", "b_NW"], + ["y_SW", "b_SW"], + ["y_SE", "b_SE"], + ["y_NE", "c_N"], + ["y_NW", "c_N"], + ["y_NW", "c_W"], + ["y_SW", "c_W"], + ["y_SW", "c_S"], + ["y_SE", "c_S"], + ["y_SE", "c_E"], + ["y_NE", "c_E"], + ["y_NE", "d_NE"], + ["y_NW", "d_NW"], + ["y_SW", "d_SW"], + ["y_SE", "d_SE"], + ["d_NE", "e_N"], + ["d_NW", "e_N"], + ["d_NW", "e_W"], + ["d_SW", "e_W"], + ["d_SW", "e_S"], + ["d_SE", "e_S"], + ["d_SE", "e_E"], + ["d_NE", "e_E"], + ["c_N", "e_N"], + ["c_W", "e_W"], + ["c_S", "e_S"], + ["c_E", "e_E"], + ["e_N", "f_NE"], + ["e_N", "f_NW"], + ["e_W", "f_NW"], + ["e_W", "f_SW"], + ["e_S", "f_SW"], + ["e_S", "f_SE"], + ["e_E", "f_SE"], + ["e_E", "f_NE"], + ["d_NE", "f_NE"], + ["d_NW", "f_NW"], + ["d_SW", "f_SW"], + ["d_SE", "f_SE"], + ["f_NE", "g_ENE"], + ["f_NE", "g_NNE"], + ["f_NW", "g_NNW"], + ["f_NW", "g_WNW"], + ["f_SW", "g_WSW"], + ["f_SW", "g_SSW"], + ["f_SE", "g_SSE"], + ["f_SE", "g_ESE"], + ["e_N", "g_NNE"], + ["e_N", "g_NNW"], + ["e_W", "g_WNW"], + ["e_W", "g_WSW"], + ["e_S", "g_SSW"], + ["e_S", "g_SSE"], + ["e_E", "g_ESE"], + ["e_E", "g_ENE"], + ["g_NNE", "g_NNW"], + ["g_NNW", "g_WNW"], + ["g_WNW", "g_WSW"], + ["g_WSW", "g_SSW"], + ["g_SSW", "g_SSE"], + ["g_SSE", "g_ESE"], + ["g_ESE", "g_ENE"], + ["g_ENE", "g_NNE"], + ["g_NNE", "h_N"], + ["g_NNW", "h_N"], + ["g_WNW", "h_W"], + ["g_WSW", "h_W"], + ["g_SSW", "h_S"], + ["g_SSE", "h_S"], + ["g_ESE", "h_E"], + ["g_ENE", "h_E"], + ["h_N", "i_NE"], + ["h_N", "i_NW"], + ["h_W", "i_NW"], + ["h_W", "i_SW"], + ["h_S", "i_SW"], + ["h_S", "i_SE"], + ["h_E", "i_SE"], + ["h_E", "i_NE"], + ["g_NNE", "i_NE"], + ["g_NNW", "i_NW"], + ["g_WNW", "i_NW"], + ["g_WSW", "i_SW"], + ["g_SSW", "i_SW"], + ["g_SSE", "i_SE"], + ["g_ESE", "i_SE"], + ["g_ENE", "i_NE"], + ["i_NE", "i_NW"], + ["i_NW", "i_SW"], + ["i_SW", "i_SE"], + ["i_SE", "i_NE"], + ["i_NE", "j"], + ["i_NW", "j"], + ["i_SW", "j"], + ["i_SE", "j"], + ]), + ].into_iter() + .chain(regular_diagonals(f_a)) + .chain(regular_diagonals(f_abc_n)) + .chain(regular_diagonals(f_abc_w)) + .collect(); + for (length, vertex_pairs) in struts { + let inv_dist = Some(-0.5 * length * length); + for pair in vertex_pairs { + let adjacent_vertices = pair.map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + let distance = InversiveDistanceRegulator::new(adjacent_vertices); + distance.set_point.set(SpecifiedValue::from(inv_dist)); + assembly.insert_regulator(Rc::new(distance)); + } + } + + // create the faces + const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let faces = [ + Sphere::new( + "f_a".to_string(), + "Face A".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, -1.0, 0.0, 0.0), + ), + Sphere::new( + "f_abc_N".to_string(), + "Face ABC-N".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_W".to_string(), + "Face ABC-W".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-1.0, 0.0, 0.0, 0.5, 0.0), + ), + ]; + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + // make the faces planar and make them pass through their vertices + let face_incidences = [ + ("f_a", Vec::from(f_a)), + ("f_abc_N", Vec::from(f_abc_n)), + ("f_abc_W", Vec::from(f_abc_w)), + ]; + for (face_id, vertex_ids) in face_incidences { + // make the face planar + let face = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[face_id].clone() + ); + let curvature_regulator = face.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set(SpecifiedValue::from(Some(0.0))); + + // make the face pass through its vertices + for v_id in vertex_ids { + let vertex = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[v_id].clone() + ); + let incidence = InversiveDistanceRegulator::new([face.clone(), vertex]); + incidence.set_point.set(SpecifiedValue::from(Some(0.0))); + assembly.insert_regulator(Rc::new(incidence)); + } + } +} + // --- chooser --- /* DEBUG */ @@ -1353,7 +3164,11 @@ pub fn TestAssemblyChooser() -> View { "off-center" => load_off_center(assembly), "radius-ratio" => load_radius_ratio(assembly), "irisawa-hexlet" => load_irisawa_hexlet(assembly), - "554a" => load_554a(assembly), + "554-base" => load_554_base(assembly), + "554-aug1" => load_554_aug1(assembly), + "554-aug1-inner" => load_554_aug1_inner(assembly), + "554-aug2" => load_554_aug2(assembly), + "554-domed" => load_554_domed(assembly), _ => (), }; }); @@ -1371,7 +3186,11 @@ pub fn TestAssemblyChooser() -> View { option(value = "off-center") { "Off-center" } option(value = "radius-ratio") { "Radius ratio" } option(value = "irisawa-hexlet") { "Irisawa hexlet" } - option(value = "554a") { "5-5-4 near miss A" } + option(value = "554-base") { "5-5-4 base" } + option(value = "554-aug1") { "5-5-4 once augmented" } + option(value = "554-aug1-inner") { "5-5-4 once augmented (inner)" } + option(value = "554-aug2") { "5-5-4 twice augmented" } + option(value = "554-domed") { "5-5-4 domed" } option(value = "empty") { "Empty" } } } From 3664ea73b1e0f97726011afeeef631689b6c98db Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Thu, 18 Sep 2025 10:31:44 -0700 Subject: [PATCH 06/13] Introduce soft constraints Use a penalty method as a quick & dirty way to get started. --- app-proto/src/assembly.rs | 11 +- .../src/components/test_assembly_chooser.rs | 106 ++++++++++-------- app-proto/src/engine.rs | 47 ++++++-- 3 files changed, 105 insertions(+), 59 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index b5b2bd9..c31df47 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -402,6 +402,7 @@ pub struct InversiveDistanceRegulator { pub subjects: [Rc; 2], pub measurement: ReadSignal, pub set_point: Signal, + pub soft: Signal, distortion: Option>, /* KLUDGE */ serial: u64, } @@ -432,9 +433,10 @@ impl InversiveDistanceRegulator { } else { None }; + let soft = create_signal(false); let serial = Self::next_serial(); - Self { subjects, measurement, set_point, distortion, serial } + Self { subjects, measurement, set_point, soft, distortion, serial } } } @@ -464,6 +466,7 @@ impl Serial for InversiveDistanceRegulator { impl ProblemPoser for InversiveDistanceRegulator { fn pose(&self, problem: &mut ConstraintProblem) { + let soft = self.soft.get_untracked(); self.set_point.with_untracked(|set_pt| { if let Some(val) = set_pt.value { let [row, col] = self.subjects.each_ref().map( @@ -471,7 +474,11 @@ impl ProblemPoser for InversiveDistanceRegulator { "Subjects should be indexed before inversive distance regulator writes problem data" ) ); - problem.gram.push_sym(row, col, val); + if soft { + problem.soft.push_sym(row, col, val); + } else { + problem.gram.push_sym(row, col, val); + } } }); } diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 799e7d4..accbd1f 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -882,10 +882,11 @@ fn load_irisawa_hexlet(assembly: &Assembly) { assembly.insert_regulator(Rc::new(outer_moon_tangency)); } -fn regular_diagonals<'a, const N: usize>(vertex_ids: [&'a str; N]) -> Vec<(f64, Vec<[&'a str; 2]>)> { +fn regular_diagonals<'a, const N: usize>(vertex_ids: [&'a str; N]) -> Vec<(bool, f64, Vec<[&'a str; 2]>)> { let ang = PI / (N as f64); let ang_sin = ang.sin(); (2..N-1).map(|sep| ( + false, (sep as f64 * ang).sin() / ang_sin, (0..N-sep).map(|k| [vertex_ids[k], vertex_ids[k + sep]]).collect() )).collect() @@ -1113,7 +1114,7 @@ fn load_554_base(assembly: &Assembly) { "g_SSW", "g_SSE", "g_ESE", "g_ENE", ]; let struts: Vec<_> = [ - (1.0, vec![ + (false, 1.0, vec![ ["a_NE", "a_NW"], ["a_NW", "a_SW"], ["a_SW", "a_SE"], @@ -1138,6 +1139,16 @@ fn load_554_base(assembly: &Assembly) { ["c_S", "d_SE"], ["c_E", "d_SE"], ["c_E", "d_NE"], + ["g_NNE", "g_NNW"], + ["g_NNW", "g_WNW"], + ["g_WNW", "g_WSW"], + ["g_WSW", "g_SSW"], + ["g_SSW", "g_SSE"], + ["g_SSE", "g_ESE"], + ["g_ESE", "g_ENE"], + ["g_ENE", "g_NNE"], + ]), + (true, 1.0, vec![ ["d_NE", "e_N"], ["d_NW", "e_N"], ["d_NW", "e_W"], @@ -1178,14 +1189,6 @@ fn load_554_base(assembly: &Assembly) { ["e_S", "g_SSE"], ["e_E", "g_ESE"], ["e_E", "g_ENE"], - ["g_NNE", "g_NNW"], - ["g_NNW", "g_WNW"], - ["g_WNW", "g_WSW"], - ["g_WSW", "g_SSW"], - ["g_SSW", "g_SSE"], - ["g_SSE", "g_ESE"], - ["g_ESE", "g_ENE"], - ["g_ENE", "g_NNE"], ]), ].into_iter() .chain(regular_diagonals(f_a)) @@ -1199,7 +1202,7 @@ fn load_554_base(assembly: &Assembly) { .chain(regular_diagonals(f_abc_e)) .chain(regular_diagonals(f_g)) .collect(); - for (length, vertex_pairs) in struts { + for (soft, length, vertex_pairs) in struts { let inv_dist = Some(-0.5 * length * length); for pair in vertex_pairs { let adjacent_vertices = pair.map( @@ -1209,6 +1212,7 @@ fn load_554_base(assembly: &Assembly) { ); let distance = InversiveDistanceRegulator::new(adjacent_vertices); distance.set_point.set(SpecifiedValue::from(inv_dist)); + distance.soft.set(soft); assembly.insert_regulator(Rc::new(distance)); } } @@ -1560,7 +1564,7 @@ fn load_554_aug1(assembly: &Assembly) { "g_SSW", "g_SSE", "g_ESE", "g_ENE", ]; let struts: Vec<_> = [ - (1.0, vec![ + (false, 1.0, vec![ ["a_NE", "a_NW"], ["a_NW", "a_SW"], ["a_SW", "a_SE"], @@ -1577,6 +1581,16 @@ fn load_554_aug1(assembly: &Assembly) { ["b_SE", "c_S"], ["b_SE", "c_E"], ["b_NE", "c_E"], + ["g_NNE", "g_NNW"], + ["g_NNW", "g_WNW"], + ["g_WNW", "g_WSW"], + ["g_WSW", "g_SSW"], + ["g_SSW", "g_SSE"], + ["g_SSE", "g_ESE"], + ["g_ESE", "g_ENE"], + ["g_ENE", "g_NNE"], + ]), + (true, 1.0, vec![ ["c_N", "d_NE"], ["c_N", "d_NW"], ["c_W", "d_NW"], @@ -1641,14 +1655,6 @@ fn load_554_aug1(assembly: &Assembly) { ["e_S", "g_SSE"], ["e_E", "g_ESE"], ["e_E", "g_ENE"], - ["g_NNE", "g_NNW"], - ["g_NNW", "g_WNW"], - ["g_WNW", "g_WSW"], - ["g_WSW", "g_SSW"], - ["g_SSW", "g_SSE"], - ["g_SSE", "g_ESE"], - ["g_ESE", "g_ENE"], - ["g_ENE", "g_NNE"], ]), ].into_iter() .chain(regular_diagonals(f_a)) @@ -1658,7 +1664,7 @@ fn load_554_aug1(assembly: &Assembly) { .chain(regular_diagonals(f_abc_e)) .chain(regular_diagonals(f_g)) .collect(); - for (length, vertex_pairs) in struts { + for (soft, length, vertex_pairs) in struts { let inv_dist = Some(-0.5 * length * length); for pair in vertex_pairs { let adjacent_vertices = pair.map( @@ -1668,6 +1674,7 @@ fn load_554_aug1(assembly: &Assembly) { ); let distance = InversiveDistanceRegulator::new(adjacent_vertices); distance.set_point.set(SpecifiedValue::from(inv_dist)); + distance.soft.set(soft); assembly.insert_regulator(Rc::new(distance)); } } @@ -1991,7 +1998,7 @@ fn load_554_aug1_inner(assembly: &Assembly) { "g_SSW", "g_SSE", "g_ESE", "g_ENE", ]; let struts: Vec<_> = [ - (1.0, vec![ + (false, 1.0, vec![ ["a_NE", "a_NW"], ["a_NW", "a_SW"], ["a_SW", "a_SE"], @@ -2008,6 +2015,16 @@ fn load_554_aug1_inner(assembly: &Assembly) { ["b_SE", "c_S"], ["b_SE", "c_E"], ["b_NE", "c_E"], + ["g_NNE", "g_NNW"], + ["g_NNW", "g_WNW"], + ["g_WNW", "g_WSW"], + ["g_WSW", "g_SSW"], + ["g_SSW", "g_SSE"], + ["g_SSE", "g_ESE"], + ["g_ESE", "g_ENE"], + ["g_ENE", "g_NNE"], + ]), + (true, 1.0, vec![ ["c_N", "d_NE"], ["c_N", "d_NW"], ["c_W", "d_NW"], @@ -2072,14 +2089,6 @@ fn load_554_aug1_inner(assembly: &Assembly) { ["e_S", "g_SSE"], ["e_E", "g_ESE"], ["e_E", "g_ENE"], - ["g_NNE", "g_NNW"], - ["g_NNW", "g_WNW"], - ["g_WNW", "g_WSW"], - ["g_WSW", "g_SSW"], - ["g_SSW", "g_SSE"], - ["g_SSE", "g_ESE"], - ["g_ESE", "g_ENE"], - ["g_ENE", "g_NNE"], ]), ].into_iter() .chain(regular_diagonals(f_a)) @@ -2089,7 +2098,7 @@ fn load_554_aug1_inner(assembly: &Assembly) { .chain(regular_diagonals(f_abc_e)) .chain(regular_diagonals(f_g)) .collect(); - for (length, vertex_pairs) in struts { + for (soft, length, vertex_pairs) in struts { let inv_dist = Some(-0.5 * length * length); for pair in vertex_pairs { let adjacent_vertices = pair.map( @@ -2099,6 +2108,7 @@ fn load_554_aug1_inner(assembly: &Assembly) { ); let distance = InversiveDistanceRegulator::new(adjacent_vertices); distance.set_point.set(SpecifiedValue::from(inv_dist)); + distance.soft.set(soft); assembly.insert_regulator(Rc::new(distance)); } } @@ -2433,7 +2443,7 @@ fn load_554_aug2(assembly: &Assembly) { "g_SSW", "g_SSE", "g_ESE", "g_ENE", ]; let struts: Vec<_> = [ - (1.0, vec![ + (false, 1.0, vec![ ["a_NE", "a_NW"], ["a_NW", "a_SW"], ["a_SW", "a_SE"], @@ -2441,11 +2451,21 @@ fn load_554_aug2(assembly: &Assembly) { ["a_NE", "b_NE"], ["a_NW", "b_NW"], ["a_SW", "b_SW"], - ["a_SE", "b_SE"], ["b_NE", "c_N"], ["b_NW", "c_N"], ["b_NW", "c_W"], ["b_SW", "c_W"], + ["g_NNE", "g_NNW"], + ["g_NNW", "g_WNW"], + ["g_WNW", "g_WSW"], + ["g_WSW", "g_SSW"], + ["g_SSW", "g_SSE"], + ["g_SSE", "g_ESE"], + ["g_ESE", "g_ENE"], + ["g_ENE", "g_NNE"], + ]), + (true, 1.0, vec![ + ["a_SE", "b_SE"], ["b_SW", "c_S"], ["b_SE", "c_S"], ["b_SE", "c_E"], @@ -2524,14 +2544,6 @@ fn load_554_aug2(assembly: &Assembly) { ["e_S", "g_SSE"], ["e_E", "g_ESE"], ["e_E", "g_ENE"], - ["g_NNE", "g_NNW"], - ["g_NNW", "g_WNW"], - ["g_WNW", "g_WSW"], - ["g_WSW", "g_SSW"], - ["g_SSW", "g_SSE"], - ["g_SSE", "g_ESE"], - ["g_ESE", "g_ENE"], - ["g_ENE", "g_NNE"], ]), ].into_iter() .chain(regular_diagonals(f_a)) @@ -2539,7 +2551,7 @@ fn load_554_aug2(assembly: &Assembly) { .chain(regular_diagonals(f_abc_w)) .chain(regular_diagonals(f_g)) .collect(); - for (length, vertex_pairs) in struts { + for (soft, length, vertex_pairs) in struts { let inv_dist = Some(-0.5 * length * length); for pair in vertex_pairs { let adjacent_vertices = pair.map( @@ -2549,6 +2561,7 @@ fn load_554_aug2(assembly: &Assembly) { ); let distance = InversiveDistanceRegulator::new(adjacent_vertices); distance.set_point.set(SpecifiedValue::from(inv_dist)); + distance.soft.set(soft); assembly.insert_regulator(Rc::new(distance)); } } @@ -2922,7 +2935,7 @@ fn load_554_domed(assembly: &Assembly) { let f_abc_n = ["a_NW", "b_NW", "c_N", "b_NE", "a_NE"]; let f_abc_w = ["a_SW", "b_SW", "c_W", "b_NW", "a_NW"]; let struts: Vec<_> = [ - (1.0, vec![ + (false, 1.0, vec![ ["a_NE", "a_NW"], ["a_NW", "a_SW"], ["a_SW", "a_SE"], @@ -2930,11 +2943,13 @@ fn load_554_domed(assembly: &Assembly) { ["a_NE", "b_NE"], ["a_NW", "b_NW"], ["a_SW", "b_SW"], - ["a_SE", "b_SE"], ["b_NE", "c_N"], ["b_NW", "c_N"], ["b_NW", "c_W"], ["b_SW", "c_W"], + ]), + (true, 1.0, vec![ + ["a_SE", "b_SE"], ["b_SW", "c_S"], ["b_SE", "c_S"], ["b_SE", "c_E"], @@ -3059,7 +3074,7 @@ fn load_554_domed(assembly: &Assembly) { .chain(regular_diagonals(f_abc_n)) .chain(regular_diagonals(f_abc_w)) .collect(); - for (length, vertex_pairs) in struts { + for (soft, length, vertex_pairs) in struts { let inv_dist = Some(-0.5 * length * length); for pair in vertex_pairs { let adjacent_vertices = pair.map( @@ -3069,6 +3084,7 @@ fn load_554_domed(assembly: &Assembly) { ); let distance = InversiveDistanceRegulator::new(adjacent_vertices); distance.set_point.set(SpecifiedValue::from(inv_dist)); + distance.soft.set(soft); assembly.insert_regulator(Rc::new(distance)); } } diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index ef150a0..441d854 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -1,6 +1,7 @@ use lazy_static::lazy_static; use nalgebra::{Const, DMatrix, DVector, DVectorView, Dyn, SymmetricEigen}; use std::fmt::{Display, Error, Formatter}; +use sycamore::prelude::console_log; /* DEBUG */ // --- elements --- @@ -240,6 +241,7 @@ impl DescentHistory { pub struct ConstraintProblem { pub gram: PartialMatrix, + pub soft: PartialMatrix, pub frozen: PartialMatrix, pub guess: DMatrix, } @@ -249,6 +251,7 @@ impl ConstraintProblem { const ELEMENT_DIM: usize = 5; Self { gram: PartialMatrix::new(), + soft: PartialMatrix::new(), frozen: PartialMatrix::new(), guess: DMatrix::::zeros(ELEMENT_DIM, element_count), } @@ -258,6 +261,7 @@ impl ConstraintProblem { pub fn from_guess(guess_columns: &[DVector]) -> Self { Self { gram: PartialMatrix::new(), + soft: PartialMatrix::new(), frozen: PartialMatrix::new(), guess: DMatrix::from_columns(guess_columns), } @@ -280,14 +284,18 @@ lazy_static! { struct SearchState { config: DMatrix, err_proj: DMatrix, + loss_hard: f64, loss: f64, } impl SearchState { - fn from_config(gram: &PartialMatrix, config: DMatrix) -> Self { - let err_proj = gram.sub_proj(&(config.tr_mul(&*Q) * &config)); + fn from_config(gram: &PartialMatrix, soft: &PartialMatrix, softness: f64, config: DMatrix) -> Self { + let config_gram = &(config.tr_mul(&*Q) * &config); + let err_proj_hard = gram.sub_proj(config_gram); + let err_proj = &err_proj_hard + softness * soft.sub_proj(config_gram); + let loss_hard = err_proj_hard.norm_squared(); let loss = err_proj.norm_squared(); - Self { config, err_proj, loss } + Self { config, err_proj, loss_hard, loss } } } @@ -331,6 +339,8 @@ pub fn local_unif_to_std(v: DVectorView) -> DMatrix { // use backtracking line search to find a better configuration fn seek_better_config( gram: &PartialMatrix, + soft: &PartialMatrix, + softness: f64, state: &SearchState, base_step: &DMatrix, base_target_improvement: f64, @@ -341,7 +351,7 @@ fn seek_better_config( let mut rate = 1.0; for backoff_steps in 0..max_backoff_steps { let trial_config = &state.config + rate * base_step; - let trial_state = SearchState::from_config(gram, trial_config); + let trial_state = SearchState::from_config(gram, soft, softness, trial_config); let improvement = state.loss - trial_state.loss; if improvement >= min_efficiency * rate * base_target_improvement { return Some((trial_state, backoff_steps)); @@ -376,7 +386,7 @@ pub fn realize_gram( max_backoff_steps: i32, ) -> Realization { // destructure the problem data - let ConstraintProblem { gram, guess, frozen } = problem; + let ConstraintProblem { gram, soft, guess, frozen } = problem; // start the descent history let mut history = DescentHistory::new(); @@ -403,13 +413,16 @@ pub fn realize_gram( let scale_adjustment = (gram.0.len() as f64).sqrt(); let tol = scale_adjustment * scaled_tol; + // initialize the softness parameter + let mut softness = 1.0; + // convert the frozen indices to stacked format let frozen_stacked: Vec = frozen.into_iter().map( |MatrixEntry { index: (row, col), .. }| col*element_dim + row ).collect(); // use a regularized Newton's method with backtracking - let mut state = SearchState::from_config(gram, frozen.freeze(guess)); + let mut state = SearchState::from_config(gram, soft, softness, frozen.freeze(guess)); let mut hess = DMatrix::zeros(element_dim, assembly_dim); for _ in 0..max_descent_steps { // find the negative gradient of the loss function @@ -426,7 +439,7 @@ pub fn realize_gram( let neg_d_err = basis_mat.tr_mul(&*Q) * &state.config + state.config.tr_mul(&*Q) * &basis_mat; - let neg_d_err_proj = gram.proj(&neg_d_err); + let neg_d_err_proj = gram.proj(&neg_d_err) + softness * soft.proj(&neg_d_err); let deriv_grad = 4.0 * &*Q * ( -&basis_mat * &state.err_proj + &state.config * &neg_d_err_proj @@ -455,10 +468,10 @@ pub fn realize_gram( hess[(k, k)] = 1.0; } - // stop if the loss is tolerably low + // stop if the hard loss is tolerably low history.config.push(state.config.clone()); - history.scaled_loss.push(state.loss / scale_adjustment); - if state.loss < tol { break; } + history.scaled_loss.push(state.loss_hard / scale_adjustment); + if state.loss_hard < tol { break; } // compute the Newton step /* TO DO */ @@ -482,7 +495,7 @@ pub fn realize_gram( // use backtracking line search to find a better configuration if let Some((better_state, backoff_steps)) = seek_better_config( - gram, &state, &base_step, neg_grad.dot(&base_step), + gram, soft, softness, &state, &base_step, neg_grad.dot(&base_step), min_efficiency, backoff, max_backoff_steps, ) { state = better_state; @@ -493,8 +506,18 @@ pub fn realize_gram( history, }; } + + // if we're near a minimum of the total loss, but the hard loss still + // isn't tolerably low, make the soft constraints softer + const GRAD_TOL: f64 = 1e-4; + const SOFTNESS_BACKOFF: f64 = 0.5; + if neg_grad.norm_squared() < GRAD_TOL { + // if we're close to a minimum, make the soft constraints softer + softness *= SOFTNESS_BACKOFF; + console_log!("Softness decreased to {softness}"); + } } - let result = if state.loss < tol { + let result = if state.loss_hard < tol { // express the uniform basis in the standard basis const UNIFORM_DIM: usize = 4; let total_dim_unif = UNIFORM_DIM * assembly_dim; From a203f6bc1b37c6d10693a2011000d9fbfd87e56f Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Thu, 18 Sep 2025 13:20:04 -0700 Subject: [PATCH 07/13] Keep optimizing until the total loss is stationary --- app-proto/src/engine.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index 441d854..e51fcbc 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -413,7 +413,9 @@ pub fn realize_gram( let scale_adjustment = (gram.0.len() as f64).sqrt(); let tol = scale_adjustment * scaled_tol; - // initialize the softness parameter + // set up constants and variables related to minimizing the soft loss + const GRAD_TOL: f64 = 1e-9; + let mut grad_size = f64::INFINITY; let mut softness = 1.0; // convert the frozen indices to stacked format @@ -468,10 +470,13 @@ pub fn realize_gram( hess[(k, k)] = 1.0; } - // stop if the hard loss is tolerably low + // stop if the hard loss is tolerably low and the total loss is close to + // stationary. we use `neg_grad_stacked` to measure the size of the + // gradient because it's been projected onto the frozen subspace history.config.push(state.config.clone()); history.scaled_loss.push(state.loss_hard / scale_adjustment); - if state.loss_hard < tol { break; } + grad_size = neg_grad_stacked.norm_squared(); + if state.loss_hard < tol && grad_size < GRAD_TOL { break; } // compute the Newton step /* TO DO */ @@ -509,15 +514,14 @@ pub fn realize_gram( // if we're near a minimum of the total loss, but the hard loss still // isn't tolerably low, make the soft constraints softer - const GRAD_TOL: f64 = 1e-4; + const SOFTNESS_BACKOFF_THRESHOLD: f64 = 1e-6; const SOFTNESS_BACKOFF: f64 = 0.5; - if neg_grad.norm_squared() < GRAD_TOL { - // if we're close to a minimum, make the soft constraints softer + if state.loss_hard >= tol && grad_size < SOFTNESS_BACKOFF_THRESHOLD { softness *= SOFTNESS_BACKOFF; console_log!("Softness decreased to {softness}"); } } - let result = if state.loss_hard < tol { + let result = if state.loss_hard < tol && grad_size < GRAD_TOL { // express the uniform basis in the standard basis const UNIFORM_DIM: usize = 4; let total_dim_unif = UNIFORM_DIM * assembly_dim; From bc17d71f4aea3e422db9b3f05f4f2b2fc39198b4 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Fri, 19 Sep 2025 11:14:38 -0700 Subject: [PATCH 08/13] Update the search state when the softness changes Also, tighten the convergence requirements to account for how the softness parameter affects the loss function. --- app-proto/src/engine.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index e51fcbc..491a2f9 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -476,7 +476,7 @@ pub fn realize_gram( history.config.push(state.config.clone()); history.scaled_loss.push(state.loss_hard / scale_adjustment); grad_size = neg_grad_stacked.norm_squared(); - if state.loss_hard < tol && grad_size < GRAD_TOL { break; } + if state.loss_hard < tol && grad_size < softness * GRAD_TOL { break; } // compute the Newton step /* TO DO */ @@ -515,13 +515,14 @@ pub fn realize_gram( // if we're near a minimum of the total loss, but the hard loss still // isn't tolerably low, make the soft constraints softer const SOFTNESS_BACKOFF_THRESHOLD: f64 = 1e-6; - const SOFTNESS_BACKOFF: f64 = 0.5; - if state.loss_hard >= tol && grad_size < SOFTNESS_BACKOFF_THRESHOLD { + const SOFTNESS_BACKOFF: f64 = 0.9; + if state.loss_hard >= tol && grad_size < softness * SOFTNESS_BACKOFF_THRESHOLD { softness *= SOFTNESS_BACKOFF; + state = SearchState::from_config(gram, soft, softness, state.config); console_log!("Softness decreased to {softness}"); } } - let result = if state.loss_hard < tol && grad_size < GRAD_TOL { + let result = if state.loss_hard < tol && grad_size < softness * GRAD_TOL { // express the uniform basis in the standard basis const UNIFORM_DIM: usize = 4; let total_dim_unif = UNIFORM_DIM * assembly_dim; From b74cbf10c1a1d9aba9d1a7b076614d897739bbd9 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Fri, 19 Sep 2025 12:35:11 -0700 Subject: [PATCH 09/13] Tighten the tolerances --- app-proto/src/assembly.rs | 2 +- app-proto/src/engine.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index c31df47..6550238 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -744,7 +744,7 @@ impl Assembly { // look for a configuration with the given Gram matrix let Realization { result, history } = realize_gram( - &problem, 1.0e-12, 0.5, 0.9, 1.1, 200, 110 + &problem, 1.0e-20, 0.5, 0.9, 1.1, 400, 110 ); /* DEBUG */ diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index 491a2f9..4679aed 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -414,7 +414,7 @@ pub fn realize_gram( let tol = scale_adjustment * scaled_tol; // set up constants and variables related to minimizing the soft loss - const GRAD_TOL: f64 = 1e-9; + const GRAD_TOL: f64 = 1e-12; let mut grad_size = f64::INFINITY; let mut softness = 1.0; @@ -515,7 +515,7 @@ pub fn realize_gram( // if we're near a minimum of the total loss, but the hard loss still // isn't tolerably low, make the soft constraints softer const SOFTNESS_BACKOFF_THRESHOLD: f64 = 1e-6; - const SOFTNESS_BACKOFF: f64 = 0.9; + const SOFTNESS_BACKOFF: f64 = 0.95; if state.loss_hard >= tol && grad_size < softness * SOFTNESS_BACKOFF_THRESHOLD { softness *= SOFTNESS_BACKOFF; state = SearchState::from_config(gram, soft, softness, state.config); From cc2da3406b32e1caecf68c48d0c76c92da3626a4 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Fri, 19 Sep 2025 14:32:50 -0700 Subject: [PATCH 10/13] Print the edge distortions --- app-proto/main.css | 8 +++- app-proto/src/assembly.rs | 11 ++++- app-proto/src/components/diagnostics.rs | 59 ++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/app-proto/main.css b/app-proto/main.css index 0ec33e9..ef8aaf9 100644 --- a/app-proto/main.css +++ b/app-proto/main.css @@ -227,8 +227,14 @@ details[open]:has(li) .element-switch::after { border-radius: 8px; } -#distortion-gauge { +#distortion-bar { + display: flex; margin-top: 8px; + gap: 8px; +} + +#distortion-gauge { + flex-grow: 1; } /* display */ diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 6550238..9cd4533 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -367,6 +367,9 @@ pub trait Regulator: Serial + ProblemPoser + OutlineItem { fn subjects(&self) -> Vec>; fn measurement(&self) -> ReadSignal; fn set_point(&self) -> Signal; + fn soft(&self) -> Option> { + None + } fn distortion(&self) -> Option> { /* KLUDGE */ None } @@ -426,8 +429,8 @@ impl InversiveDistanceRegulator { match set_point_opt { None => 0.0, Some(set_point_val) => SQRT_2 * ( - (-set_point_val).sqrt() - (-measurement_val).sqrt() - ).abs(), + (-measurement_val).sqrt() - (-set_point_val).sqrt() + ), } })) } else { @@ -453,6 +456,10 @@ impl Regulator for InversiveDistanceRegulator { self.set_point } + fn soft(&self) -> Option> { + Some(self.soft) + } + fn distortion(&self) -> Option> { self.distortion } diff --git a/app-proto/src/components/diagnostics.rs b/app-proto/src/components/diagnostics.rs index ce4a0b4..52e7ffa 100644 --- a/app-proto/src/components/diagnostics.rs +++ b/app-proto/src/components/diagnostics.rs @@ -119,7 +119,7 @@ fn DistortionGauge() -> View { let mut total = 0.0; for reg in regs { if let Some(distortion) = reg.distortion() { - total += distortion.get(); + total += distortion.get().abs(); } } total @@ -133,6 +133,58 @@ fn DistortionGauge() -> View { } } +#[component] +fn DistortionPrintButton() -> View { + view! { + button( + on:click = |_| { + let state = use_context::(); + let mut hard_distortion_table = String::new(); + let mut soft_distortion_table = String::new(); + let mut highest_distortion = f64::NEG_INFINITY; + let mut lowest_distortion = f64::INFINITY; + let mut largest_hard_distortion = f64::NEG_INFINITY; + state.assembly.regulators.with_untracked(|regs| { + for reg in regs { + if let Some(distortion) = reg.distortion() { + let distortion_val = distortion.get(); + let subjects = reg.subjects(); + let distortion_line = format!( + "{}, {}: {distortion_val}\n", + subjects[0].id(), + subjects[1].id(), + ); + match reg.soft() { + Some(soft) if soft.get() => { + soft_distortion_table += &distortion_line; + highest_distortion = highest_distortion.max(distortion_val); + lowest_distortion = lowest_distortion.min(distortion_val); + }, + _ => { + hard_distortion_table += &distortion_line; + largest_hard_distortion = largest_hard_distortion.max(distortion_val.abs()); + } + }; + } + } + }); + console_log!("\ + === Distortions of flexible edges (for labels) ===\n\n\ + --- Range ---\n\n\ + Highest: {highest_distortion}\n\ + Lowest: {lowest_distortion}\n\n\ + --- Table ---\n\n{soft_distortion_table}\n\ + === Distortions of rigid edges (for validation) ===\n\n\ + These values should be small relative to the ones for the flexible edges\n\n\ + --- Range ---\n\n\ + Largest absolute: {largest_hard_distortion}\n\n\ + --- Table ---\n\n{hard_distortion_table}\ + "); + }, + ) { "Print" } + } +} + fn into_log10_time_point((step, value): (usize, f64)) -> Vec> { vec![ Some(step as f64), @@ -337,7 +389,10 @@ pub fn Diagnostics() -> View { } DiagnosticsPanel(name = "loss") { LossHistory {} } DiagnosticsPanel(name = "spectrum") { SpectrumHistory {} } - DistortionGauge {} + div(id = "distortion-bar") { + DistortionGauge {} + DistortionPrintButton {} + } } } } \ No newline at end of file From 1054f4e85b5c3957f095da0f3b29d2ccba691b17 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 22 Sep 2025 12:30:38 -0700 Subject: [PATCH 11/13] Print the vertex coordinates --- app-proto/src/assembly.rs | 6 +++--- app-proto/src/components/diagnostics.rs | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 9cd4533..bacc63b 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -125,7 +125,7 @@ pub trait Element: Serial + ProblemPoser + DisplayItem { fn set_column_index(&self, index: usize); /* KLUDGE */ - fn has_distortion(&self) -> bool { + fn is_point(&self) -> bool { false } } @@ -341,7 +341,7 @@ impl Element for Point { self.column_index.set(Some(index)); } - fn has_distortion(&self) -> bool { + fn is_point(&self) -> bool { true } } @@ -422,7 +422,7 @@ impl InversiveDistanceRegulator { }); let set_point = create_signal(SpecifiedValue::from_empty_spec()); - let distortion = if subjects.iter().all(|subj| subj.has_distortion()) { + let distortion = if subjects.iter().all(|subj| subj.is_point()) { Some(create_memo(move || { let set_point_opt = set_point.with(|set_pt| set_pt.value); let measurement_val = measurement.get(); diff --git a/app-proto/src/components/diagnostics.rs b/app-proto/src/components/diagnostics.rs index 52e7ffa..bab6f35 100644 --- a/app-proto/src/components/diagnostics.rs +++ b/app-proto/src/components/diagnostics.rs @@ -134,11 +134,13 @@ fn DistortionGauge() -> View { } #[component] -fn DistortionPrintButton() -> View { +fn PrintButton() -> View { view! { button( on:click = |_| { let state = use_context::(); + + // print the edge length distortions let mut hard_distortion_table = String::new(); let mut soft_distortion_table = String::new(); let mut highest_distortion = f64::NEG_INFINITY; @@ -180,6 +182,18 @@ fn DistortionPrintButton() -> View { Largest absolute: {largest_hard_distortion}\n\n\ --- Table ---\n\n{hard_distortion_table}\ "); + + // print the vertex coordinates + let mut coords_table = String::new(); + state.assembly.elements.with_untracked(|elts| { + for elt in elts.iter().filter(|elt| elt.is_point()) { + let (x, y, z) = elt.representation().with( + |rep| (rep[0], rep[1], rep[2]) + ); + coords_table += &format!("{}: {x}, {y}, {z}\n", elt.id()); + } + }); + console_log!("=== Vertex coordinates ===\n\n{coords_table}"); }, ) { "Print" } } @@ -391,7 +405,7 @@ pub fn Diagnostics() -> View { DiagnosticsPanel(name = "spectrum") { SpectrumHistory {} } div(id = "distortion-bar") { DistortionGauge {} - DistortionPrintButton {} + PrintButton {} } } } From 476b13c87f3d631ad59e560c5055629a4fc853a7 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sat, 20 Sep 2025 00:51:26 -0700 Subject: [PATCH 12/13] 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 bacc63b..e84554d 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, @@ -27,6 +28,7 @@ use crate::{ ConfigSubspace, ConstraintProblem, DescentHistory, + MatrixEntry, Realization, }, specified::SpecifiedValue, @@ -275,6 +277,7 @@ pub struct Point { impl Point { const WEIGHT_COMPONENT: usize = 3; + const NORM_COMPONENT: usize = 4; pub fn new( id: String, @@ -308,6 +311,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 @@ -492,14 +504,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] ); @@ -544,6 +556,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 4679aed..4148ae2 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -47,14 +47,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 096a153f043b48c83dbb6f89e3b09a94220ae8b4 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 22 Sep 2025 16:56:13 -0700 Subject: [PATCH 13/13] Try using pinned vertices as the hard constraints --- app-proto/src/assembly.rs | 30 +- .../src/components/test_assembly_chooser.rs | 492 ++++++++++++++++++ 2 files changed, 518 insertions(+), 4 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index e84554d..0bde53d 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}, @@ -375,7 +376,7 @@ 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; @@ -385,6 +386,7 @@ pub trait Regulator: Serial + ProblemPoser + OutlineItem { fn distortion(&self) -> Option> { /* KLUDGE */ None } + fn as_any(&self) -> &dyn Any; } impl Hash for dyn Regulator { @@ -475,6 +477,10 @@ impl Regulator for InversiveDistanceRegulator { fn distortion(&self) -> Option> { self.distortion } + + fn as_any(&self) -> &dyn Any { + self + } } impl Serial for InversiveDistanceRegulator { @@ -535,6 +541,10 @@ impl Regulator for HalfCurvatureRegulator { fn set_point(&self) -> Signal { self.set_point } + + fn as_any(&self) -> &dyn Any { + self + } } impl Serial for HalfCurvatureRegulator { @@ -587,9 +597,21 @@ impl Serial for PointCoordinateRegulator { } impl Regulator for PointCoordinateRegulator { - fn subjects(&self) -> Vec> { vec![self.subject.clone()] } - fn measurement(&self) -> ReadSignal { self.measurement } - fn set_point(&self) -> Signal { self.set_point } + fn subjects(&self) -> Vec> { + vec![self.subject.clone()] + } + + fn measurement(&self) -> ReadSignal { + self.measurement + } + + fn set_point(&self) -> Signal { + self.set_point + } + + fn as_any(&self) -> &dyn Any { + self + } } impl ProblemPoser for PointCoordinateRegulator { diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index accbd1f..1e6e124 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -12,6 +12,8 @@ use crate::{ ElementColor, InversiveDistanceRegulator, Point, + PointCoordinateRegulator, + Regulator, Sphere, }, engine, @@ -3144,6 +3146,494 @@ fn load_554_domed(assembly: &Assembly) { } } +fn load_554_domed_pinned(assembly: &Assembly) { + // create the vertices + const COLOR_A: ElementColor = [0.75_f32, 0.00_f32, 0.75_f32]; + const COLOR_Z: ElementColor = [1.00_f32, 0.40_f32, 0.60_f32]; + const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32]; + const COLOR_Y: ElementColor = [1.00_f32, 0.75_f32, 0.25_f32]; + const COLOR_C: ElementColor = [0.75_f32, 0.50_f32, 0.00_f32]; + const COLOR_D: ElementColor = [0.90_f32, 0.90_f32, 0.00_f32]; + const COLOR_E: ElementColor = [0.25_f32, 0.75_f32, 0.00_f32]; + const COLOR_F: ElementColor = [0.00_f32, 0.50_f32, 0.75_f32]; + const COLOR_G: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; + const COLOR_H: ElementColor = COLOR_A; + const COLOR_I: ElementColor = COLOR_B; + const COLOR_J: ElementColor = COLOR_C; + let vertices = [ + Point::new( + "a_NE".to_string(), + "A-NE".to_string(), + COLOR_A, + engine::point(0.5, 0.5, 0.0), + ), + Point::new( + "a_NW".to_string(), + "A-NW".to_string(), + COLOR_A, + engine::point(-0.5, 0.5, 0.0), + ), + Point::new( + "a_SW".to_string(), + "A-SW".to_string(), + COLOR_A, + engine::point(-0.5, -0.5, 0.0), + ), + Point::new( + "a_SE".to_string(), + "A-SE".to_string(), + COLOR_A, + engine::point(0.5, -0.5, 0.0), + ), + Point::new( + "z_S".to_string(), + "Z-S".to_string(), + COLOR_Z, + engine::point(0.0, -0.4, 0.6), + ), + Point::new( + "z_E".to_string(), + "Z-E".to_string(), + COLOR_Z, + engine::point(0.4, 0.0, 0.6), + ), + Point::new( + "b_NE".to_string(), + "B-NE".to_string(), + COLOR_B, + engine::point(0.7, 0.7, 0.9), + ), + Point::new( + "b_NW".to_string(), + "B-NW".to_string(), + COLOR_B, + engine::point(-0.7, 0.7, 0.9), + ), + Point::new( + "b_SW".to_string(), + "B-SW".to_string(), + COLOR_B, + engine::point(-0.7, -0.7, 0.9), + ), + Point::new( + "b_SE".to_string(), + "B-SE".to_string(), + COLOR_B, + engine::point(0.7, -0.7, 0.9), + ), + Point::new( + "y_NE".to_string(), + "Y-NE".to_string(), + COLOR_Y, + engine::point(0.1, 0.1, 1.0), + ), + Point::new( + "y_NW".to_string(), + "Y-NW".to_string(), + COLOR_Y, + engine::point(-0.1, 0.1, 1.0), + ), + Point::new( + "y_SW".to_string(), + "Y-SW".to_string(), + COLOR_Y, + engine::point(-0.1, -0.1, 1.0), + ), + Point::new( + "y_SE".to_string(), + "Y-SE".to_string(), + COLOR_Y, + engine::point(0.1, -0.1, 1.0), + ), + Point::new( + "c_N".to_string(), + "C-N".to_string(), + COLOR_C, + engine::point(0.0, 0.8, 1.4), + ), + Point::new( + "c_W".to_string(), + "C-W".to_string(), + COLOR_C, + engine::point(-0.8, 0.0, 1.4), + ), + Point::new( + "c_S".to_string(), + "C-S".to_string(), + COLOR_C, + engine::point(0.0, -0.8, 1.4), + ), + Point::new( + "c_E".to_string(), + "C-E".to_string(), + COLOR_C, + engine::point(0.8, 0.0, 1.4), + ), + Point::new( + "d_NE".to_string(), + "D-NE".to_string(), + COLOR_D, + engine::point(0.1, 0.1, 1.8), + ), + Point::new( + "d_NW".to_string(), + "D-NW".to_string(), + COLOR_D, + engine::point(-0.1, 0.1, 1.8), + ), + Point::new( + "d_SW".to_string(), + "D-SW".to_string(), + COLOR_D, + engine::point(-0.1, -0.1, 1.8), + ), + Point::new( + "d_SE".to_string(), + "D-SE".to_string(), + COLOR_D, + engine::point(0.1, -0.1, 1.8), + ), + Point::new( + "e_N".to_string(), + "E-N".to_string(), + COLOR_E, + engine::point(0.0, 0.7, 2.3), + ), + Point::new( + "e_W".to_string(), + "E-W".to_string(), + COLOR_E, + engine::point(-0.7, 0.0, 2.3), + ), + Point::new( + "e_S".to_string(), + "E-S".to_string(), + COLOR_E, + engine::point(0.0, -0.7, 2.3), + ), + Point::new( + "e_E".to_string(), + "E-E".to_string(), + COLOR_E, + engine::point(0.7, 0.0, 2.3), + ), + Point::new( + "f_NE".to_string(), + "F-NE".to_string(), + COLOR_F, + engine::point(0.2, 0.2, 2.7), + ), + Point::new( + "f_NW".to_string(), + "F-NW".to_string(), + COLOR_F, + engine::point(-0.2, 0.2, 2.7), + ), + Point::new( + "f_SW".to_string(), + "F-SW".to_string(), + COLOR_F, + engine::point(-0.2, -0.2, 2.7), + ), + Point::new( + "f_SE".to_string(), + "F-SE".to_string(), + COLOR_F, + engine::point(0.2, -0.2, 2.7), + ), + Point::new( + "g_NNE".to_string(), + "G-NNE".to_string(), + COLOR_G, + engine::point(0.5, 1.2, 3.0), + ), + Point::new( + "g_NNW".to_string(), + "G-NNW".to_string(), + COLOR_G, + engine::point(-0.5, 1.2, 3.0), + ), + Point::new( + "g_WNW".to_string(), + "G-WNW".to_string(), + COLOR_G, + engine::point(-1.2, 0.5, 3.0), + ), + Point::new( + "g_WSW".to_string(), + "G-WSW".to_string(), + COLOR_G, + engine::point(-1.2, -0.5, 3.0), + ), + Point::new( + "g_SSW".to_string(), + "G-SSW".to_string(), + COLOR_G, + engine::point(-0.5, -1.2, 3.0), + ), + Point::new( + "g_SSE".to_string(), + "G-SSE".to_string(), + COLOR_G, + engine::point(0.5, -1.2, 3.0), + ), + Point::new( + "g_ESE".to_string(), + "G-ESE".to_string(), + COLOR_G, + engine::point(1.2, -0.5, 3.0), + ), + Point::new( + "g_ENE".to_string(), + "G-ENE".to_string(), + COLOR_G, + engine::point(1.2, 0.5, 3.0), + ), + Point::new( + "h_N".to_string(), + "H-N".to_string(), + COLOR_H, + engine::point(0.0, 0.6, 2.9), + ), + Point::new( + "h_W".to_string(), + "H-W".to_string(), + COLOR_H, + engine::point(-0.6, 0.0, 2.9), + ), + Point::new( + "h_S".to_string(), + "H-S".to_string(), + COLOR_H, + engine::point(0.0, -0.6, 2.9), + ), + Point::new( + "h_E".to_string(), + "H-E".to_string(), + COLOR_H, + engine::point(0.6, 0.0, 2.9), + ), + Point::new( + "i_NE".to_string(), + "I-NE".to_string(), + COLOR_I, + engine::point(0.5, 0.5, 3.5), + ), + Point::new( + "i_NW".to_string(), + "I-NW".to_string(), + COLOR_I, + engine::point(-0.5, 0.5, 3.5), + ), + Point::new( + "i_SW".to_string(), + "I-SW".to_string(), + COLOR_I, + engine::point(-0.5, -0.5, 3.5), + ), + Point::new( + "i_SE".to_string(), + "I-SE".to_string(), + COLOR_I, + engine::point(0.5, -0.5, 3.5), + ), + Point::new( + "j".to_string(), + "J".to_string(), + COLOR_J, + engine::point(0.0, 0.0, 3.0), + ), + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // fix the distances between adjacent vertices + let struts: Vec<_> = vec![ + (false, 1.0, vec![ + ["a_SE", "b_SE"], + ["b_SW", "c_S"], + ["b_SE", "c_S"], + ["b_SE", "c_E"], + ["b_NE", "c_E"], + ["z_S", "a_SW"], + ["z_S", "a_SE"], + ["z_E", "a_SE"], + ["z_E", "a_NE"], + ["z_S", "b_SW"], + ["z_S", "b_SE"], + ["z_E", "b_SE"], + ["z_E", "b_NE"], + ["z_S", "c_S"], + ["z_E", "c_E"], + ["c_N", "d_NE"], + ["c_N", "d_NW"], + ["c_W", "d_NW"], + ["c_W", "d_SW"], + ["c_S", "d_SW"], + ["c_S", "d_SE"], + ["c_E", "d_SE"], + ["c_E", "d_NE"], + ["y_NE", "b_NE"], + ["y_NW", "b_NW"], + ["y_SW", "b_SW"], + ["y_SE", "b_SE"], + ["y_NE", "c_N"], + ["y_NW", "c_N"], + ["y_NW", "c_W"], + ["y_SW", "c_W"], + ["y_SW", "c_S"], + ["y_SE", "c_S"], + ["y_SE", "c_E"], + ["y_NE", "c_E"], + ["y_NE", "d_NE"], + ["y_NW", "d_NW"], + ["y_SW", "d_SW"], + ["y_SE", "d_SE"], + ["d_NE", "e_N"], + ["d_NW", "e_N"], + ["d_NW", "e_W"], + ["d_SW", "e_W"], + ["d_SW", "e_S"], + ["d_SE", "e_S"], + ["d_SE", "e_E"], + ["d_NE", "e_E"], + ["c_N", "e_N"], + ["c_W", "e_W"], + ["c_S", "e_S"], + ["c_E", "e_E"], + ["e_N", "f_NE"], + ["e_N", "f_NW"], + ["e_W", "f_NW"], + ["e_W", "f_SW"], + ["e_S", "f_SW"], + ["e_S", "f_SE"], + ["e_E", "f_SE"], + ["e_E", "f_NE"], + ["d_NE", "f_NE"], + ["d_NW", "f_NW"], + ["d_SW", "f_SW"], + ["d_SE", "f_SE"], + ["f_NE", "g_ENE"], + ["f_NE", "g_NNE"], + ["f_NW", "g_NNW"], + ["f_NW", "g_WNW"], + ["f_SW", "g_WSW"], + ["f_SW", "g_SSW"], + ["f_SE", "g_SSE"], + ["f_SE", "g_ESE"], + ["e_N", "g_NNE"], + ["e_N", "g_NNW"], + ["e_W", "g_WNW"], + ["e_W", "g_WSW"], + ["e_S", "g_SSW"], + ["e_S", "g_SSE"], + ["e_E", "g_ESE"], + ["e_E", "g_ENE"], + ["g_NNE", "g_NNW"], + ["g_NNW", "g_WNW"], + ["g_WNW", "g_WSW"], + ["g_WSW", "g_SSW"], + ["g_SSW", "g_SSE"], + ["g_SSE", "g_ESE"], + ["g_ESE", "g_ENE"], + ["g_ENE", "g_NNE"], + ["g_NNE", "h_N"], + ["g_NNW", "h_N"], + ["g_WNW", "h_W"], + ["g_WSW", "h_W"], + ["g_SSW", "h_S"], + ["g_SSE", "h_S"], + ["g_ESE", "h_E"], + ["g_ENE", "h_E"], + ["h_N", "i_NE"], + ["h_N", "i_NW"], + ["h_W", "i_NW"], + ["h_W", "i_SW"], + ["h_S", "i_SW"], + ["h_S", "i_SE"], + ["h_E", "i_SE"], + ["h_E", "i_NE"], + ["g_NNE", "i_NE"], + ["g_NNW", "i_NW"], + ["g_WNW", "i_NW"], + ["g_WSW", "i_SW"], + ["g_SSW", "i_SW"], + ["g_SSE", "i_SE"], + ["g_ESE", "i_SE"], + ["g_ENE", "i_NE"], + ["i_NE", "i_NW"], + ["i_NW", "i_SW"], + ["i_SW", "i_SE"], + ["i_SE", "i_NE"], + ["i_NE", "j"], + ["i_NW", "j"], + ["i_SW", "j"], + ["i_SE", "j"], + ]), + ]; + for (soft, length, vertex_pairs) in struts { + let inv_dist = Some(-0.5 * length * length); + for pair in vertex_pairs { + let adjacent_vertices = pair.map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + let distance = InversiveDistanceRegulator::new(adjacent_vertices); + distance.set_point.set(SpecifiedValue::from(inv_dist)); + distance.soft.set(soft); + assembly.insert_regulator(Rc::new(distance)); + } + } + + // pin the vertices of the rigid faces + let phi = 0.5 + 1.25_f64.sqrt(); /* TO DO */ // replace with std::f64::consts::PHI when that gets stabilized + let phi_2 = 0.5 * phi; + let height_b = phi_2.sqrt(); + let height_c = (phi + 0.5).sqrt(); + let pinned_vertices = [ + ("a_NE", [0.5, 0.5, 0.0]), + ("a_NW", [-0.5, 0.5, 0.0]), + ("a_SW", [-0.5, -0.5, 0.0]), + ("a_SE", [0.5, -0.5, 0.0]), + ("b_NE", [phi_2, phi_2, height_b]), + ("b_NW", [-phi_2, phi_2, height_b]), + ("b_SW", [-phi_2, -phi_2, height_b]), + ("c_N", [0.0, 1.0, height_c]), + ("c_W", [-1.0, 0.0, height_c]), + ]; + for (id, coords) in pinned_vertices { + // get the point's coordinate regulators + let point = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ); + let coord_regs: Vec> = point.regulators().with_untracked( + |regs| regs.into_iter().filter_map( + /* KLUDGE */ + // there must be a better way to do the type-checking here + move |reg| reg + .as_any() + .downcast_ref::() + .map(|_| reg.clone()) + ).collect() + ); + + // set the coordinates + for reg in coord_regs { + let reg_downcast_ref = reg + .as_any() + .downcast_ref::(); + if let Some(coord_reg) = reg_downcast_ref { + let coord_val = SpecifiedValue::from( + Some(coords[coord_reg.axis as usize]) + ); + reg.set_point().set(coord_val); + } + } + } +} + // --- chooser --- /* DEBUG */ @@ -3185,6 +3675,7 @@ pub fn TestAssemblyChooser() -> View { "554-aug1-inner" => load_554_aug1_inner(assembly), "554-aug2" => load_554_aug2(assembly), "554-domed" => load_554_domed(assembly), + "554-domed-pinned" => load_554_domed_pinned(assembly), _ => (), }; }); @@ -3207,6 +3698,7 @@ pub fn TestAssemblyChooser() -> View { option(value = "554-aug1-inner") { "5-5-4 once augmented (inner)" } option(value = "554-aug2") { "5-5-4 twice augmented" } option(value = "554-domed") { "5-5-4 domed" } + option(value = "554-domed-pinned") { "5-5-4 domed (pinned)" } option(value = "empty") { "Empty" } } }