From 8a0d81d7073a41caad334aeaf27fa1a0b453b1ef Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Thu, 21 Aug 2025 14:49:32 -0400 Subject: [PATCH 01/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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 def40714a705f27efc0bc6cdff512bfc042b8ddb Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 29 Sep 2025 16:30:02 -0700 Subject: [PATCH 12/16] Add twice-augmented data --- impossolid/twice-augmented | 150 ++++++++++++++++++++ impossolid/twice-augmented_edge-distortions | 98 +++++++++++++ impossolid/twice-augmented_vertices | 38 +++++ 3 files changed, 286 insertions(+) create mode 100644 impossolid/twice-augmented create mode 100644 impossolid/twice-augmented_edge-distortions create mode 100644 impossolid/twice-augmented_vertices diff --git a/impossolid/twice-augmented b/impossolid/twice-augmented new file mode 100644 index 0000000..5e0fca2 --- /dev/null +++ b/impossolid/twice-augmented @@ -0,0 +1,150 @@ +=== Distortions of flexible edges (for labels) === + +--- Range --- + +Highest: 0.000039581305103782685 +Lowest: -0.00003959022952266363 + +--- Table --- + +a_SE, b_SE: 0.000015249448583054653 +b_SW, c_S: 0.000006918255000202575 +b_SE, c_S: 0.00001022525369434997 +b_SE, c_E: 0.000010225282824746383 +b_NE, c_E: 0.000006918450075555058 +z_S, a_SW: -0.0000219346808875234 +z_S, a_SE: 0.00002021118621352795 +z_E, a_SE: 0.00002021122888534378 +z_E, a_NE: -0.00002193465480671757 +z_S, b_SW: 0.000015276737515369292 +z_S, b_SE: -0.000010771908316892743 +z_E, b_SE: -0.00001077200999765046 +z_E, b_NE: 0.000015276631362360217 +z_S, c_S: -0.0000027853351723499016 +z_E, c_E: -0.000002785228607191555 +c_N, d_NE: -0.000007632775579087326 +c_N, d_NW: 0.00001402754980516112 +c_W, d_NW: 0.000014028001282264439 +c_W, d_SW: -0.00000763316944431703 +c_S, d_SW: 0.000006566974972180328 +c_S, d_SE: -0.0000033161321458747414 +c_E, d_SE: -0.00000331586236646513 +c_E, d_NE: 0.000006566624318564243 +y_NE, b_NE: -0.0000017608392620486498 +y_NW, b_NW: -0.00003959022952266363 +y_SW, b_SW: -0.0000017608153313274316 +y_SE, b_SE: 0.000005637007821097203 +y_NE, c_N: 0.000001760310254250749 +y_NW, c_N: 0.00003958128591112948 +y_NW, c_W: 0.000039581305103782685 +y_SW, c_W: 0.0000017603065237110672 +y_SW, c_S: 0.0000017602552603493003 +y_SE, c_S: -0.00000563497752606038 +y_SE, c_E: -0.0000056349905677193694 +y_NE, c_E: 0.0000017602528935919282 +y_NE, d_NE: -0.0000017608481082635805 +y_NW, d_NW: -0.0000395891283971962 +y_SW, d_SW: -0.0000017608381747596222 +y_SE, d_SE: 0.0000056371661583272735 +d_NE, e_N: 0.000007303314580670295 +d_NW, e_N: -0.000016255656816635924 +d_NW, e_W: -0.000016256069869023512 +d_SW, e_W: 0.00000730369455466398 +d_SW, e_S: -0.000006000901856373712 +d_SE, e_S: 0.000003879307405619109 +d_SE, e_E: 0.000003879053499059208 +d_NE, e_E: -0.00000600059436381427 +c_N, e_N: 0.00000899767908293228 +c_W, e_W: 0.000008997328779446816 +c_S, e_S: 0.0000042757526759396545 +c_E, e_E: 0.000004275945999382972 +e_N, f_NE: 0.0000026971057195376348 +e_N, f_NW: 0.000006498321003978716 +e_W, f_NW: 0.000006498333167484994 +e_W, f_SW: 0.000002697065747495785 +e_S, f_SW: 0.000001359196116957704 +e_S, f_SE: 0.0000010550443264554641 +e_E, f_SE: 0.0000010550540852081317 +e_E, f_NE: 0.000001359177866673992 +d_NE, f_NE: -0.0000029405886467824407 +d_NW, f_NW: -0.000009421855439304757 +d_SW, f_SW: -0.000002940615976753832 +d_SE, f_SE: -0.000001529705558996715 +f_NE, g_ENE: -0.00000011649787286240574 +f_NE, g_NNE: -0.000002135681696645076 +f_NW, g_NNW: -0.000003607774033318148 +f_NW, g_WNW: -0.000003607785721714447 +f_SW, g_WSW: -0.0000021356459140809334 +f_SW, g_SSW: -0.00000011652747271646458 +f_SE, g_SSE: -0.0000005858121892925972 +f_SE, g_ESE: -0.0000005858113552594831 +e_N, g_NNE: -0.0000034483169191011346 +e_N, g_NNW: 0.0000034059972640161084 +e_W, g_WNW: 0.000003405934362501001 +e_W, g_WSW: -0.00000344858668736309 +e_S, g_SSW: 0.0000029689592297656908 +e_S, g_SSE: -0.0000006279732347365118 +e_E, g_ESE: -0.0000006278110244023648 +e_E, g_ENE: 0.0000029689801689897473 + +=== Distortions of rigid edges (for validation) === + +These values should be small relative to the ones for the flexible edges + +--- Range --- + +Largest absolute: 0.00000000002916368237382178 + +--- Table --- + +a_NE, a_NW: 0.000000000024039999653399018 +a_NW, a_SW: 0.00000000002451793579782236 +a_SW, a_SE: -0.000000000028570972470668657 +a_SE, a_NE: -0.00000000002916368237382178 +a_NE, b_NE: -0.000000000022712800498073622 +a_NW, b_NW: 0.0000000000010783395006240168 +a_SW, b_SW: -0.00000000002146677512286218 +b_NE, c_N: 0.00000000000907905964233893 +b_NW, c_N: -0.00000000002003437977280497 +b_NW, c_W: -0.000000000019747523880603447 +b_SW, c_W: 0.000000000009824853560213723 +g_NNE, g_NNW: 0.0000000000003400820265509057 +g_NNW, g_WNW: -0.0000000000004886127731423908 +g_WNW, g_WSW: 0.00000000000005762239323369454 +g_WSW, g_SSW: -0.00000000000020348398264541725 +g_SSW, g_SSE: -0.0000000000010258984125039788 +g_SSE, g_ESE: -0.0000000000001306316925624901 +g_ESE, g_ENE: -0.0000000000011334497459238174 +g_ENE, g_NNE: -0.00000000000007913265991766227 +a_SE, a_NW: 0.00000000001299251509560824 +a_SW, a_NE: -0.000000000019852249047597653 +a_NW, c_N: 0.00000000000757318396521532 +b_NW, b_NE: -0.000000000015613313427643198 +c_N, a_NE: -0.000000000013250324277324116 +a_NW, b_NE: 0.0000000000145795645528458 +b_NW, a_NE: 0.000000000004191204809210469 +a_SW, c_W: -0.000000000012779924576702457 +b_SW, b_NW: -0.000000000015326928563179278 +c_W, a_NW: 0.00000000000786836134744787 +a_SW, b_NW: 0.000000000004368311238549999 +b_SW, a_NW: 0.00000000001505750069726914 +g_NNE, g_WNW: 0.0000000000005718276734526309 +g_NNW, g_WSW: 0.0000000000004788781998985515 +g_WNW, g_SSW: -0.00000000000035703902510469045 +g_WSW, g_SSE: 0.0000000000002254652770669901 +g_SSW, g_ESE: -0.0000000000002515288118811408 +g_SSE, g_ENE: -0.00000000000030679606642680965 +g_NNE, g_WSW: 0.0000000000003206128800632269 +g_NNW, g_SSW: 0.00000000000032846334235664577 +g_WNW, g_SSE: 0.00000000000012278123026907122 +g_WSW, g_ESE: 0.00000000000014507654318238083 +g_SSW, g_ENE: 0.000000000000033599978615832786 +g_NNE, g_SSW: 0.0000000000001815026882238444 +g_NNW, g_SSE: 0.0000000000002631474960754007 +g_WNW, g_ESE: 0.00000000000031684465816238584 +g_WSW, g_ENE: 0.0000000000002348858318190928 +g_NNE, g_SSE: 0.00000000000007913265991766227 +g_NNW, g_ESE: 0.0000000000001438204692154338 +g_WNW, g_ENE: 0.0000000000004248670193198296 +g_NNE, g_ESE: 0.00000000000019783164979415566 +g_NNW, g_ENE: -0.0000000000002364559242777765 diff --git a/impossolid/twice-augmented_edge-distortions b/impossolid/twice-augmented_edge-distortions new file mode 100644 index 0000000..9cb5946 --- /dev/null +++ b/impossolid/twice-augmented_edge-distortions @@ -0,0 +1,98 @@ +a_SE, b_SE: 0.000015249448583054653 +b_SW, c_S: 0.000006918255000202575 +b_SE, c_S: 0.00001022525369434997 +b_SE, c_E: 0.000010225282824746383 +b_NE, c_E: 0.000006918450075555058 +z_S, a_SW: -0.0000219346808875234 +z_S, a_SE: 0.00002021118621352795 +z_E, a_SE: 0.00002021122888534378 +z_E, a_NE: -0.00002193465480671757 +z_S, b_SW: 0.000015276737515369292 +z_S, b_SE: -0.000010771908316892743 +z_E, b_SE: -0.00001077200999765046 +z_E, b_NE: 0.000015276631362360217 +z_S, c_S: -0.0000027853351723499016 +z_E, c_E: -0.000002785228607191555 +c_N, d_NE: -0.000007632775579087326 +c_N, d_NW: 0.00001402754980516112 +c_W, d_NW: 0.000014028001282264439 +c_W, d_SW: -0.00000763316944431703 +c_S, d_SW: 0.000006566974972180328 +c_S, d_SE: -0.0000033161321458747414 +c_E, d_SE: -0.00000331586236646513 +c_E, d_NE: 0.000006566624318564243 +y_NE, b_NE: -0.0000017608392620486498 +y_NW, b_NW: -0.00003959022952266363 +y_SW, b_SW: -0.0000017608153313274316 +y_SE, b_SE: 0.000005637007821097203 +y_NE, c_N: 0.000001760310254250749 +y_NW, c_N: 0.00003958128591112948 +y_NW, c_W: 0.000039581305103782685 +y_SW, c_W: 0.0000017603065237110672 +y_SW, c_S: 0.0000017602552603493003 +y_SE, c_S: -0.00000563497752606038 +y_SE, c_E: -0.0000056349905677193694 +y_NE, c_E: 0.0000017602528935919282 +y_NE, d_NE: -0.0000017608481082635805 +y_NW, d_NW: -0.0000395891283971962 +y_SW, d_SW: -0.0000017608381747596222 +y_SE, d_SE: 0.0000056371661583272735 +d_NE, e_N: 0.000007303314580670295 +d_NW, e_N: -0.000016255656816635924 +d_NW, e_W: -0.000016256069869023512 +d_SW, e_W: 0.00000730369455466398 +d_SW, e_S: -0.000006000901856373712 +d_SE, e_S: 0.000003879307405619109 +d_SE, e_E: 0.000003879053499059208 +d_NE, e_E: -0.00000600059436381427 +c_N, e_N: 0.00000899767908293228 +c_W, e_W: 0.000008997328779446816 +c_S, e_S: 0.0000042757526759396545 +c_E, e_E: 0.000004275945999382972 +e_N, f_NE: 0.0000026971057195376348 +e_N, f_NW: 0.000006498321003978716 +e_W, f_NW: 0.000006498333167484994 +e_W, f_SW: 0.000002697065747495785 +e_S, f_SW: 0.000001359196116957704 +e_S, f_SE: 0.0000010550443264554641 +e_E, f_SE: 0.0000010550540852081317 +e_E, f_NE: 0.000001359177866673992 +d_NE, f_NE: -0.0000029405886467824407 +d_NW, f_NW: -0.000009421855439304757 +d_SW, f_SW: -0.000002940615976753832 +d_SE, f_SE: -0.000001529705558996715 +f_NE, g_ENE: -0.00000011649787286240574 +f_NE, g_NNE: -0.000002135681696645076 +f_NW, g_NNW: -0.000003607774033318148 +f_NW, g_WNW: -0.000003607785721714447 +f_SW, g_WSW: -0.0000021356459140809334 +f_SW, g_SSW: -0.00000011652747271646458 +f_SE, g_SSE: -0.0000005858121892925972 +f_SE, g_ESE: -0.0000005858113552594831 +e_N, g_NNE: -0.0000034483169191011346 +e_N, g_NNW: 0.0000034059972640161084 +e_W, g_WNW: 0.000003405934362501001 +e_W, g_WSW: -0.00000344858668736309 +e_S, g_SSW: 0.0000029689592297656908 +e_S, g_SSE: -0.0000006279732347365118 +e_E, g_ESE: -0.0000006278110244023648 +e_E, g_ENE: 0.0000029689801689897473 +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 +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 diff --git a/impossolid/twice-augmented_vertices b/impossolid/twice-augmented_vertices new file mode 100644 index 0000000..ecd6206 --- /dev/null +++ b/impossolid/twice-augmented_vertices @@ -0,0 +1,38 @@ +a_NE: 0.4974660839869507, 0.5025284618118182, -0.21962917587407324 +a_NW: -0.5025160085784002, 0.5025084403663106, -0.21364467368968995 +a_SW: -0.5025317897634418, -0.4974736643225916, -0.21962716242129268 +a_SE: 0.49745030267915796, -0.4974536427543331, -0.22561166444858374 +z_S: 0.0023385661484333744, -0.22870402619169428, 0.60063190420276 +z_E: 0.22870024445875012, -0.0023413854336990325, 0.6006314485234188 +b_NE: 0.8118652156106724, 0.8061651522670251, 0.6797917189944392 +b_NW: -0.8061397981609031, 0.8061327572110616, 0.6894748470124282 +b_SW: -0.8061653323313128, -0.8118722761768307, 0.679794976829472 +b_SE: 0.8117686673287107, -0.8117688670778128, 0.6701787153604973 +y_NE: 0.1103960413184542, 0.10342511854710223, 0.798509539299725 +y_NW: -0.10337789916924411, 0.1033740688261596, 0.7998232055607494 +y_SW: -0.10342804260149929, -0.11039987437192703, 0.7985099696006345 +y_SE: 0.11038804399025821, -0.11039097027269067, 0.7972438212830046 +c_N: 0.006192472465608106, 0.9938029259455414, 1.2416489044968044 +c_W: -0.9938054007689278, -0.006199199673009326, 1.2416509179495838 +c_S: 0.006109939936188693, -1.0059122097709343, 1.229859721030557 +c_E: 1.0059097110061566, -0.00610818737102383, 1.2298577082098059 +d_NE: 0.19998199287503826, 0.18129904829618806, 1.7914377777202868 +d_NW: -0.18132979121792334, 0.1813276309223748, 1.7936881702060035 +d_SW: -0.18129959231726273, -0.19998415749541185, 1.791438545222019 +d_SE: 0.1999582803508571, -0.1999588290387209, 1.7891944479793136 +e_N: 0.011969023209503969, 1.0551851488412447, 2.239755536903205 +e_W: -1.0551855894711406, -0.011973997889437291, 2.2397576852440584 +e_S: 0.011933151451289365, -1.0790685618433953, 2.2271674919335998 +e_E: 1.0790680956836711, -0.011929083518777098, 2.2271653437223433 +f_NE: 0.29554147977141343, 0.26516582064069727, 2.7833192564200235 +f_NW: -0.26515631709994025, 0.26515579997276006, 2.786626792661052 +f_SW: -0.26516396217611815, -0.2955420036217133, 2.783320385102162 +f_SE: 0.2955327295186172, -0.29553087779401516, 2.780016555503281 +g_NNE: 0.5169980642139731, 1.1901085117178565, 3.092242755151354 +g_NNW: -0.4829845300027995, 1.1900889935835326, 3.0981428299427223 +g_WNW: -1.190089809078002, 0.4829807102891247, 3.0981442533320376 +g_WSW: -1.1901050905533348, -0.5170018958762016, 3.0922461915171433 +g_SSW: -0.48302142275001, -1.2240825763590955, 3.083903649106097 +g_SSE: 0.516961171466923, -1.224063058223441, 3.0780035743129184 +g_ESE: 1.224066450541603, -0.516954774929556, 3.078002150923603 +g_ENE: 1.2240817320182662, 0.4830278312359312, 3.083900212740308 \ No newline at end of file From 5b331dbbeea2357b787e7877828b318f55e59ad8 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 29 Sep 2025 16:31:36 -0700 Subject: [PATCH 13/16] Calculate angle distortions --- impossolid/angle-distortion.py | 59 ++++ impossolid/twice-augmented_angle-distortions | 295 +++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 impossolid/angle-distortion.py create mode 100644 impossolid/twice-augmented_angle-distortions diff --git a/impossolid/angle-distortion.py b/impossolid/angle-distortion.py new file mode 100644 index 0000000..6a19cfc --- /dev/null +++ b/impossolid/angle-distortion.py @@ -0,0 +1,59 @@ +import collections +import math +import sys + +def read_edge_distortions(filename): + vertices = set() + distortions = {} + with open(filename, 'r') as edge_file: + while edge_line := edge_file.readline(): + line_parts = edge_line.rstrip().split(': ') + endpoints = tuple(sorted(line_parts[0].split(', '))) + vertices.update(endpoints) + if len(line_parts) > 1: + distortions[endpoints] = float(line_parts[1]) + else: + distortions[endpoints] = 0 + return (vertices, distortions) + +def find_triangles(vertices, edges): + triangles = [] + for e in edges: + for v in vertices: + if e[1] < v: + if (e[0], v) in edges and (e[1], v) in edges: + triangles.append((e[0], e[1], v)) + return triangles + +# use the law of cosines to get the angle distortion +def angle_distortion(edge_distortions): + a, b, c = list(edge_distortions) + cos_angle_a = (1 + 2*(b + c - a) + b*b + c*c - a*a) / (2*(1 + b)*(1 + c)) + return math.degrees(math.acos(cos_angle_a)) - 60 + +if __name__ == '__main__': + if len(sys.argv) <= 1: + print('Pass the path to the file that lists the edge distortions') + else: + vertices, distortions = read_edge_distortions(sys.argv[1]) + triangles = find_triangles(vertices, distortions.keys()) + total_angle_distortion = 0 + highest_angle_distortion = -math.inf + lowest_angle_distortion = math.inf + print('{} triangles\n'.format(len(triangles))) + for t in triangles: + print('Triangle {0}, {1}, {2}'.format(t[0], t[1], t[2])) + edge_distortions = collections.deque( + [distortions[(t[j], t[k])] for (j, k) in [(1, 2), (0, 2), (0, 1)]] + ) + for k in range(3): + ang_distort = angle_distortion(edge_distortions) + total_angle_distortion += abs(ang_distort) + highest_angle_distortion = max(highest_angle_distortion, ang_distort) + lowest_angle_distortion = min(lowest_angle_distortion, ang_distort) + print(' {0}: {1}°'.format(t[k], ang_distort)) + edge_distortions.rotate() + print() + print('Total angle distortion: {}°'.format(total_angle_distortion)) + print('Highest angle distortion: {}°'.format(highest_angle_distortion)) + print('Lowest angle distortion: {}°'.format(lowest_angle_distortion)) diff --git a/impossolid/twice-augmented_angle-distortions b/impossolid/twice-augmented_angle-distortions new file mode 100644 index 0000000..cf03128 --- /dev/null +++ b/impossolid/twice-augmented_angle-distortions @@ -0,0 +1,295 @@ +58 triangles + +Triangle a_SE, b_SE, z_S + a_SE: -0.0018856502946889009° + b_SE: 0.0006966207168233041° + z_S: 0.0011890295778727022° + +Triangle a_SE, b_SE, z_E + a_SE: -0.0018856584331388149° + b_SE: 0.0006966226686131449° + z_E: 0.0011890357645540917° + +Triangle b_SW, c_S, y_SW + b_SW: -5.415090214455631e-05° + c_S: 0.00045772681384903535° + y_SW: -0.00040357591168316276° + +Triangle b_SW, c_S, z_S + b_SW: -0.0009184701495854597° + c_S: 4.448743641916053e-05° + z_S: 0.00087398271318051° + +Triangle b_SE, c_S, z_S + b_SE: -0.00016620401637368332° + c_S: 0.0011249765751415453° + z_S: -0.0009587725587678619° + +Triangle b_SE, c_S, y_SE + b_SE: -0.0008975178741081891° + c_S: 0.0006764281771083347° + y_SE: 0.00022108969701406522° + +Triangle b_SE, c_E, y_SE + b_SE: -0.000897519700536975° + c_E: 0.0006764305357691569° + y_SE: 0.0002210891647536073° + +Triangle b_SE, c_E, z_E + b_SE: -0.0001661945662831954° + c_E: 0.0011249783407549785° + z_E: -0.0009587837744717831° + +Triangle b_NE, c_E, y_NE + b_NE: -5.4156720231901545e-05° + c_E: 0.00045774058987291255° + y_NE: -0.00040358386964811643° + +Triangle b_NE, c_E, z_E + b_NE: -0.0009184660407299816° + c_E: 4.450032897551637e-05° + z_E: 0.0008739657117544652° + +Triangle c_N, d_NE, y_NE + c_N: 7.77608707309696e-05° + d_NE: -0.0005049622408037635° + y_NE: 0.0004272013700656885° + +Triangle c_N, d_NE, e_N + c_N: 0.00043802608161769285° + d_NE: -0.0010442017109895119° + e_N: 0.0006061756293931353° + +Triangle c_N, d_NW, y_NW + c_N: -0.004392411525167006° + d_NW: 0.0009281613228964147° + y_NW: 0.0034642502022776966° + +Triangle c_N, d_NW, e_N + c_N: -0.0018371050143599632° + d_NW: 0.0011681390506907974° + e_N: 0.0006689659636691658° + +Triangle c_W, d_NW, e_W + c_W: -0.0018371356879498535° + d_NW: 0.0011681941726706668° + e_W: 0.0006689415152791867° + +Triangle c_W, d_NW, y_NW + c_W: -0.00439242709246912° + d_NW: 0.000928190557552° + y_NW: 0.00346423653491712° + +Triangle c_W, d_SW, e_W + c_W: 0.0004380758375859273° + d_SW: -0.0010442287499685676° + e_W: 0.0006061529123826404° + +Triangle c_W, d_SW, y_SW + c_W: 7.777468017877709e-05° + d_SW: -0.0005049885038488355° + y_SW: 0.00042721382367005845° + +Triangle c_S, d_SW, y_SW + c_S: -0.0003919572288921813° + d_SW: 0.00043448701768511455° + y_SW: -4.252978876451152e-05° + +Triangle c_S, d_SW, e_S + c_S: -0.0007556856092989506° + d_SW: 0.0004915334549480121° + e_S: 0.0002641521543509384° + +Triangle c_S, d_SE, y_SE + c_S: 0.0006690562241331577° + d_SE: -0.00021946896695368423° + y_SE: -0.000449587257172368° + +Triangle c_S, d_SE, e_S + c_S: 0.00022490750998827025° + d_SE: -0.0004891581593469141° + e_S: 0.00026425064935864384° + +Triangle c_E, d_SE, y_SE + c_E: 0.0006690477311224186° + d_SE: -0.000219450687097833° + y_SE: -0.0004495970440387964° + +Triangle c_E, d_SE, e_E + c_E: 0.00022487539229842923° + d_SE: -0.0004891383069605126° + e_E: 0.000264262914654978° + +Triangle c_E, d_NE, y_NE + c_E: -0.0003919462083246117° + d_NE: 0.00043446422546367103° + y_NE: -4.2518017124848484e-05° + +Triangle c_E, d_NE, e_E + c_E: -0.0007556600615572506° + d_NE: 0.0004914936888269494° + e_E: 0.0002641663727089849° + +Triangle d_NE, e_N, f_NE + d_NE: 3.4118590889420375e-05° + e_N: 0.0004912376328576329° + f_NE: -0.0005253562237612641° + +Triangle d_NW, e_N, f_NW + d_NW: 0.0012793501149417352° + e_N: -0.0009787611630969195° + f_NW: -0.0003005889518448157° + +Triangle d_NW, e_W, f_NW + d_NW: 0.0012793645837376744° + e_W: -0.0009787888926879873° + f_NW: -0.00030057569104258164° + +Triangle d_SW, e_W, f_SW + d_SW: 3.4104280807412124e-05° + e_W: 0.0004912649981676509° + f_SW: -0.0005253692789679576° + +Triangle d_SW, e_S, f_SW + d_SW: 0.0003857085871175059° + e_S: -0.00034470395510055596° + f_SW: -4.10046320098445e-05° + +Triangle d_SE, e_S, f_SE + d_SE: -7.92374907376825e-06° + e_S: 0.0002723546649647801° + f_SE: -0.00026443091589101186° + +Triangle d_SE, e_E, f_SE + d_SE: -7.914704212907964e-06° + e_E: 0.0002723375437980735° + f_SE: -0.00026442283958516555° + +Triangle d_NE, e_E, f_NE + d_NE: 0.00038569630375207° + e_E: -0.0003446839119192191° + f_NE: -4.1012391825745453e-05° + +Triangle e_N, f_NE, g_NNE + e_N: -0.00011644664380128233° + f_NE: 0.000363157568820327° + g_NNE: -0.0002467109250190447° + +Triangle e_N, f_NW, g_NNW + e_N: -0.0005663172445693476° + f_NW: 0.000436599482547706° + g_NNW: 0.00012971776202874707° + +Triangle e_W, f_NW, g_WNW + e_W: -0.0005663163395084325° + f_NW: 0.00043660275473200727° + g_WNW: 0.00012971358477642525° + +Triangle e_W, f_SW, g_WSW + e_W: -0.00011643403040295652° + f_SW: 0.0003631626645272945° + g_WSW: -0.000246728634124338° + +Triangle e_S, f_SW, g_SSW + e_S: -0.00015088330234647174° + f_SW: -4.434221558824447e-06° + g_SSW: 0.00015531752390529618° + +Triangle e_S, f_SE, g_SSE + e_S: -5.28844724030364e-05° + f_SE: 0.00010995296653959485° + g_SSE: -5.706849415076931e-05° + +Triangle e_E, f_SE, g_ESE + e_E: -5.289010589137888e-05° + f_SE: 0.00010994821870013993° + g_ESE: -5.705811280876105e-05° + +Triangle e_E, f_NE, g_ENE + e_E: -0.00015088143299379908° + f_NE: -4.437100798782012e-06° + g_ENE: 0.0001553185337925811° + +Triangle f_NE, g_ENE, g_NNE + f_NE: 7.450149718835064e-05° + g_ENE: 6.294030865205968e-05° + g_NNE: -0.00013744180584041032° + +Triangle f_NW, g_NNW, g_WNW + f_NW: 0.00023868980004237983° + g_NNW: -0.00011934432004778728° + g_WNW: -0.00011934547999459255° + +Triangle f_SW, g_SSW, g_WSW + f_SW: 7.450129267994043e-05° + g_SSW: 6.293716665339844e-05° + g_WSW: -0.00013743845931912801° + +Triangle f_SE, g_ESE, g_SSE + f_SE: 3.87570213717936e-05° + g_ESE: -1.937846930388787e-05° + g_SSE: -1.9378552060800303e-05° + +Triangle e_N, g_NNE, g_NNW + e_N: 1.3987573481699656e-06° + g_NNE: -0.00034080770834776786° + g_NNW: 0.0003394089509995979° + +Triangle e_W, g_WNW, g_WSW + e_W: 1.4097619143171869e-06° + g_WNW: 0.0003394137133483355° + g_WSW: -0.00034082347526265266° + +Triangle e_S, g_SSE, g_SSW + e_S: -7.743940860649445e-05° + g_SSE: -0.00013975871755889102° + g_SSW: 0.00021719812617959633° + +Triangle e_E, g_ENE, g_ESE + e_E: -7.744546709176348e-05° + g_ENE: 0.0002171941456126092° + g_ESE: -0.000139748678492424° + +Triangle a_SE, a_SW, z_S + a_SE: -0.0021197448587955137° + a_SW: 5.696873318328244e-05° + z_S: 0.0020627761256122312° + +Triangle a_NE, a_SE, z_E + a_NE: 0.0020627780860564826° + a_SE: 5.696645881414497e-05° + z_E: -0.0021197445448706276° + +Triangle a_NE, b_NE, z_E + a_NE: 0.0017363010366011622° + b_NE: 0.00022021214437017989° + z_E: -0.001956513180971342° + +Triangle a_SW, b_SW, z_S + a_SW: 0.0017363089225312933° + b_SW: 0.00022020949535317413° + z_S: -0.001956518417863151° + +Triangle b_NE, c_N, y_NE + b_NE: 0.00017470943667774463° + c_N: 1.7191837287100498e-08° + y_NE: -0.0001747266285079263° + +Triangle b_NW, c_N, y_NW + b_NW: 0.003928388803437599° + c_N: 1.4034125683792809e-07° + y_NW: -0.0039285291447015425° + +Triangle b_NW, c_W, y_NW + b_NW: 0.0039283900732698385° + c_W: 1.3970630163839814e-07° + y_NW: -0.0039285297795643714° + +Triangle b_SW, c_W, y_SW + b_SW: 0.00017470839824795803° + c_W: 1.6523628687536984e-08° + y_SW: -0.0001747249218624347° + +Total angle distortion: 0.11233054610809035° +Highest angle distortion: 0.0039283900732698385° +Lowest angle distortion: -0.00439242709246912° From a309870968edc389a8514e22206a668f8a3c809e Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 29 Sep 2025 17:12:24 -0700 Subject: [PATCH 14/16] Fix the order of the triangle list I've confirmed that this commit's output matches the previous commit's up to ordering. --- impossolid/angle-distortion.py | 4 +- impossolid/twice-augmented_angle-distortions | 358 +++++++++---------- 2 files changed, 181 insertions(+), 181 deletions(-) diff --git a/impossolid/angle-distortion.py b/impossolid/angle-distortion.py index 6a19cfc..6d600d3 100644 --- a/impossolid/angle-distortion.py +++ b/impossolid/angle-distortion.py @@ -18,8 +18,8 @@ def read_edge_distortions(filename): def find_triangles(vertices, edges): triangles = [] - for e in edges: - for v in vertices: + for e in sorted(edges): + for v in sorted(vertices): if e[1] < v: if (e[0], v) in edges and (e[1], v) in edges: triangles.append((e[0], e[1], v)) diff --git a/impossolid/twice-augmented_angle-distortions b/impossolid/twice-augmented_angle-distortions index cf03128..2a11203 100644 --- a/impossolid/twice-augmented_angle-distortions +++ b/impossolid/twice-augmented_angle-distortions @@ -1,44 +1,34 @@ 58 triangles -Triangle a_SE, b_SE, z_S - a_SE: -0.0018856502946889009° - b_SE: 0.0006966207168233041° - z_S: 0.0011890295778727022° +Triangle a_NE, a_SE, z_E + a_NE: 0.0020627780860564826° + a_SE: 5.696645881414497e-05° + z_E: -0.0021197445448706276° + +Triangle a_NE, b_NE, z_E + a_NE: 0.0017363010366011622° + b_NE: 0.00022021214437017989° + z_E: -0.001956513180971342° + +Triangle a_SE, a_SW, z_S + a_SE: -0.0021197448587955137° + a_SW: 5.696873318328244e-05° + z_S: 0.0020627761256122312° Triangle a_SE, b_SE, z_E a_SE: -0.0018856584331388149° b_SE: 0.0006966226686131449° z_E: 0.0011890357645540917° -Triangle b_SW, c_S, y_SW - b_SW: -5.415090214455631e-05° - c_S: 0.00045772681384903535° - y_SW: -0.00040357591168316276° +Triangle a_SE, b_SE, z_S + a_SE: -0.0018856502946889009° + b_SE: 0.0006966207168233041° + z_S: 0.0011890295778727022° -Triangle b_SW, c_S, z_S - b_SW: -0.0009184701495854597° - c_S: 4.448743641916053e-05° - z_S: 0.00087398271318051° - -Triangle b_SE, c_S, z_S - b_SE: -0.00016620401637368332° - c_S: 0.0011249765751415453° - z_S: -0.0009587725587678619° - -Triangle b_SE, c_S, y_SE - b_SE: -0.0008975178741081891° - c_S: 0.0006764281771083347° - y_SE: 0.00022108969701406522° - -Triangle b_SE, c_E, y_SE - b_SE: -0.000897519700536975° - c_E: 0.0006764305357691569° - y_SE: 0.0002210891647536073° - -Triangle b_SE, c_E, z_E - b_SE: -0.0001661945662831954° - c_E: 0.0011249783407549785° - z_E: -0.0009587837744717831° +Triangle a_SW, b_SW, z_S + a_SW: 0.0017363089225312933° + b_SW: 0.00022020949535317413° + z_S: -0.001956518417863151° Triangle b_NE, c_E, y_NE b_NE: -5.4156720231901545e-05° @@ -50,26 +40,116 @@ Triangle b_NE, c_E, z_E c_E: 4.450032897551637e-05° z_E: 0.0008739657117544652° -Triangle c_N, d_NE, y_NE - c_N: 7.77608707309696e-05° - d_NE: -0.0005049622408037635° - y_NE: 0.0004272013700656885° +Triangle b_NE, c_N, y_NE + b_NE: 0.00017470943667774463° + c_N: 1.7191837287100498e-08° + y_NE: -0.0001747266285079263° + +Triangle b_NW, c_N, y_NW + b_NW: 0.003928388803437599° + c_N: 1.4034125683792809e-07° + y_NW: -0.0039285291447015425° + +Triangle b_NW, c_W, y_NW + b_NW: 0.0039283900732698385° + c_W: 1.3970630163839814e-07° + y_NW: -0.0039285297795643714° + +Triangle b_SE, c_E, y_SE + b_SE: -0.000897519700536975° + c_E: 0.0006764305357691569° + y_SE: 0.0002210891647536073° + +Triangle b_SE, c_E, z_E + b_SE: -0.0001661945662831954° + c_E: 0.0011249783407549785° + z_E: -0.0009587837744717831° + +Triangle b_SE, c_S, y_SE + b_SE: -0.0008975178741081891° + c_S: 0.0006764281771083347° + y_SE: 0.00022108969701406522° + +Triangle b_SE, c_S, z_S + b_SE: -0.00016620401637368332° + c_S: 0.0011249765751415453° + z_S: -0.0009587725587678619° + +Triangle b_SW, c_S, y_SW + b_SW: -5.415090214455631e-05° + c_S: 0.00045772681384903535° + y_SW: -0.00040357591168316276° + +Triangle b_SW, c_S, z_S + b_SW: -0.0009184701495854597° + c_S: 4.448743641916053e-05° + z_S: 0.00087398271318051° + +Triangle b_SW, c_W, y_SW + b_SW: 0.00017470839824795803° + c_W: 1.6523628687536984e-08° + y_SW: -0.0001747249218624347° + +Triangle c_E, d_NE, e_E + c_E: -0.0007556600615572506° + d_NE: 0.0004914936888269494° + e_E: 0.0002641663727089849° + +Triangle c_E, d_NE, y_NE + c_E: -0.0003919462083246117° + d_NE: 0.00043446422546367103° + y_NE: -4.2518017124848484e-05° + +Triangle c_E, d_SE, e_E + c_E: 0.00022487539229842923° + d_SE: -0.0004891383069605126° + e_E: 0.000264262914654978° + +Triangle c_E, d_SE, y_SE + c_E: 0.0006690477311224186° + d_SE: -0.000219450687097833° + y_SE: -0.0004495970440387964° Triangle c_N, d_NE, e_N c_N: 0.00043802608161769285° d_NE: -0.0010442017109895119° e_N: 0.0006061756293931353° -Triangle c_N, d_NW, y_NW - c_N: -0.004392411525167006° - d_NW: 0.0009281613228964147° - y_NW: 0.0034642502022776966° +Triangle c_N, d_NE, y_NE + c_N: 7.77608707309696e-05° + d_NE: -0.0005049622408037635° + y_NE: 0.0004272013700656885° Triangle c_N, d_NW, e_N c_N: -0.0018371050143599632° d_NW: 0.0011681390506907974° e_N: 0.0006689659636691658° +Triangle c_N, d_NW, y_NW + c_N: -0.004392411525167006° + d_NW: 0.0009281613228964147° + y_NW: 0.0034642502022776966° + +Triangle c_S, d_SE, e_S + c_S: 0.00022490750998827025° + d_SE: -0.0004891581593469141° + e_S: 0.00026425064935864384° + +Triangle c_S, d_SE, y_SE + c_S: 0.0006690562241331577° + d_SE: -0.00021946896695368423° + y_SE: -0.000449587257172368° + +Triangle c_S, d_SW, e_S + c_S: -0.0007556856092989506° + d_SW: 0.0004915334549480121° + e_S: 0.0002641521543509384° + +Triangle c_S, d_SW, y_SW + c_S: -0.0003919572288921813° + d_SW: 0.00043448701768511455° + y_SW: -4.252978876451152e-05° + Triangle c_W, d_NW, e_W c_W: -0.0018371356879498535° d_NW: 0.0011681941726706668° @@ -90,45 +170,10 @@ Triangle c_W, d_SW, y_SW d_SW: -0.0005049885038488355° y_SW: 0.00042721382367005845° -Triangle c_S, d_SW, y_SW - c_S: -0.0003919572288921813° - d_SW: 0.00043448701768511455° - y_SW: -4.252978876451152e-05° - -Triangle c_S, d_SW, e_S - c_S: -0.0007556856092989506° - d_SW: 0.0004915334549480121° - e_S: 0.0002641521543509384° - -Triangle c_S, d_SE, y_SE - c_S: 0.0006690562241331577° - d_SE: -0.00021946896695368423° - y_SE: -0.000449587257172368° - -Triangle c_S, d_SE, e_S - c_S: 0.00022490750998827025° - d_SE: -0.0004891581593469141° - e_S: 0.00026425064935864384° - -Triangle c_E, d_SE, y_SE - c_E: 0.0006690477311224186° - d_SE: -0.000219450687097833° - y_SE: -0.0004495970440387964° - -Triangle c_E, d_SE, e_E - c_E: 0.00022487539229842923° - d_SE: -0.0004891383069605126° - e_E: 0.000264262914654978° - -Triangle c_E, d_NE, y_NE - c_E: -0.0003919462083246117° - d_NE: 0.00043446422546367103° - y_NE: -4.2518017124848484e-05° - -Triangle c_E, d_NE, e_E - c_E: -0.0007556600615572506° - d_NE: 0.0004914936888269494° - e_E: 0.0002641663727089849° +Triangle d_NE, e_E, f_NE + d_NE: 0.00038569630375207° + e_E: -0.0003446839119192191° + f_NE: -4.1012391825745453e-05° Triangle d_NE, e_N, f_NE d_NE: 3.4118590889420375e-05° @@ -145,30 +190,40 @@ Triangle d_NW, e_W, f_NW e_W: -0.0009787888926879873° f_NW: -0.00030057569104258164° -Triangle d_SW, e_W, f_SW - d_SW: 3.4104280807412124e-05° - e_W: 0.0004912649981676509° - f_SW: -0.0005253692789679576° - -Triangle d_SW, e_S, f_SW - d_SW: 0.0003857085871175059° - e_S: -0.00034470395510055596° - f_SW: -4.10046320098445e-05° +Triangle d_SE, e_E, f_SE + d_SE: -7.914704212907964e-06° + e_E: 0.0002723375437980735° + f_SE: -0.00026442283958516555° Triangle d_SE, e_S, f_SE d_SE: -7.92374907376825e-06° e_S: 0.0002723546649647801° f_SE: -0.00026443091589101186° -Triangle d_SE, e_E, f_SE - d_SE: -7.914704212907964e-06° - e_E: 0.0002723375437980735° - f_SE: -0.00026442283958516555° +Triangle d_SW, e_S, f_SW + d_SW: 0.0003857085871175059° + e_S: -0.00034470395510055596° + f_SW: -4.10046320098445e-05° -Triangle d_NE, e_E, f_NE - d_NE: 0.00038569630375207° - e_E: -0.0003446839119192191° - f_NE: -4.1012391825745453e-05° +Triangle d_SW, e_W, f_SW + d_SW: 3.4104280807412124e-05° + e_W: 0.0004912649981676509° + f_SW: -0.0005253692789679576° + +Triangle e_E, f_NE, g_ENE + e_E: -0.00015088143299379908° + f_NE: -4.437100798782012e-06° + g_ENE: 0.0001553185337925811° + +Triangle e_E, f_SE, g_ESE + e_E: -5.289010589137888e-05° + f_SE: 0.00010994821870013993° + g_ESE: -5.705811280876105e-05° + +Triangle e_E, g_ENE, g_ESE + e_E: -7.744546709176348e-05° + g_ENE: 0.0002171941456126092° + g_ESE: -0.000139748678492424° Triangle e_N, f_NE, g_NNE e_N: -0.00011644664380128233° @@ -180,6 +235,26 @@ Triangle e_N, f_NW, g_NNW f_NW: 0.000436599482547706° g_NNW: 0.00012971776202874707° +Triangle e_N, g_NNE, g_NNW + e_N: 1.3987573481699656e-06° + g_NNE: -0.00034080770834776786° + g_NNW: 0.0003394089509995979° + +Triangle e_S, f_SE, g_SSE + e_S: -5.28844724030364e-05° + f_SE: 0.00010995296653959485° + g_SSE: -5.706849415076931e-05° + +Triangle e_S, f_SW, g_SSW + e_S: -0.00015088330234647174° + f_SW: -4.434221558824447e-06° + g_SSW: 0.00015531752390529618° + +Triangle e_S, g_SSE, g_SSW + e_S: -7.743940860649445e-05° + g_SSE: -0.00013975871755889102° + g_SSW: 0.00021719812617959633° + Triangle e_W, f_NW, g_WNW e_W: -0.0005663163395084325° f_NW: 0.00043660275473200727° @@ -190,25 +265,10 @@ Triangle e_W, f_SW, g_WSW f_SW: 0.0003631626645272945° g_WSW: -0.000246728634124338° -Triangle e_S, f_SW, g_SSW - e_S: -0.00015088330234647174° - f_SW: -4.434221558824447e-06° - g_SSW: 0.00015531752390529618° - -Triangle e_S, f_SE, g_SSE - e_S: -5.28844724030364e-05° - f_SE: 0.00010995296653959485° - g_SSE: -5.706849415076931e-05° - -Triangle e_E, f_SE, g_ESE - e_E: -5.289010589137888e-05° - f_SE: 0.00010994821870013993° - g_ESE: -5.705811280876105e-05° - -Triangle e_E, f_NE, g_ENE - e_E: -0.00015088143299379908° - f_NE: -4.437100798782012e-06° - g_ENE: 0.0001553185337925811° +Triangle e_W, g_WNW, g_WSW + e_W: 1.4097619143171869e-06° + g_WNW: 0.0003394137133483355° + g_WSW: -0.00034082347526265266° Triangle f_NE, g_ENE, g_NNE f_NE: 7.450149718835064e-05° @@ -220,75 +280,15 @@ Triangle f_NW, g_NNW, g_WNW g_NNW: -0.00011934432004778728° g_WNW: -0.00011934547999459255° -Triangle f_SW, g_SSW, g_WSW - f_SW: 7.450129267994043e-05° - g_SSW: 6.293716665339844e-05° - g_WSW: -0.00013743845931912801° - Triangle f_SE, g_ESE, g_SSE f_SE: 3.87570213717936e-05° g_ESE: -1.937846930388787e-05° g_SSE: -1.9378552060800303e-05° -Triangle e_N, g_NNE, g_NNW - e_N: 1.3987573481699656e-06° - g_NNE: -0.00034080770834776786° - g_NNW: 0.0003394089509995979° - -Triangle e_W, g_WNW, g_WSW - e_W: 1.4097619143171869e-06° - g_WNW: 0.0003394137133483355° - g_WSW: -0.00034082347526265266° - -Triangle e_S, g_SSE, g_SSW - e_S: -7.743940860649445e-05° - g_SSE: -0.00013975871755889102° - g_SSW: 0.00021719812617959633° - -Triangle e_E, g_ENE, g_ESE - e_E: -7.744546709176348e-05° - g_ENE: 0.0002171941456126092° - g_ESE: -0.000139748678492424° - -Triangle a_SE, a_SW, z_S - a_SE: -0.0021197448587955137° - a_SW: 5.696873318328244e-05° - z_S: 0.0020627761256122312° - -Triangle a_NE, a_SE, z_E - a_NE: 0.0020627780860564826° - a_SE: 5.696645881414497e-05° - z_E: -0.0021197445448706276° - -Triangle a_NE, b_NE, z_E - a_NE: 0.0017363010366011622° - b_NE: 0.00022021214437017989° - z_E: -0.001956513180971342° - -Triangle a_SW, b_SW, z_S - a_SW: 0.0017363089225312933° - b_SW: 0.00022020949535317413° - z_S: -0.001956518417863151° - -Triangle b_NE, c_N, y_NE - b_NE: 0.00017470943667774463° - c_N: 1.7191837287100498e-08° - y_NE: -0.0001747266285079263° - -Triangle b_NW, c_N, y_NW - b_NW: 0.003928388803437599° - c_N: 1.4034125683792809e-07° - y_NW: -0.0039285291447015425° - -Triangle b_NW, c_W, y_NW - b_NW: 0.0039283900732698385° - c_W: 1.3970630163839814e-07° - y_NW: -0.0039285297795643714° - -Triangle b_SW, c_W, y_SW - b_SW: 0.00017470839824795803° - c_W: 1.6523628687536984e-08° - y_SW: -0.0001747249218624347° +Triangle f_SW, g_SSW, g_WSW + f_SW: 7.450129267994043e-05° + g_SSW: 6.293716665339844e-05° + g_WSW: -0.00013743845931912801° Total angle distortion: 0.11233054610809035° Highest angle distortion: 0.0039283900732698385° From de4c2ef4821b480fec72f1056c076670f15dc991 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 29 Sep 2025 17:37:17 -0700 Subject: [PATCH 15/16] Correct mixed-up angles The angle distortions at the second and third vertices in each triangle seem to have been mixed up in the previous commit. I've checked, for a few triangles, that the output of this commit is consistent with the law of sines. --- impossolid/angle-distortion.py | 2 +- impossolid/twice-augmented_angle-distortions | 232 +++++++++---------- 2 files changed, 117 insertions(+), 117 deletions(-) diff --git a/impossolid/angle-distortion.py b/impossolid/angle-distortion.py index 6d600d3..084a231 100644 --- a/impossolid/angle-distortion.py +++ b/impossolid/angle-distortion.py @@ -44,7 +44,7 @@ if __name__ == '__main__': for t in triangles: print('Triangle {0}, {1}, {2}'.format(t[0], t[1], t[2])) edge_distortions = collections.deque( - [distortions[(t[j], t[k])] for (j, k) in [(1, 2), (0, 2), (0, 1)]] + [distortions[(t[j], t[k])] for (j, k) in [(1, 2), (0, 1), (0, 2)]] ) for k in range(3): ang_distort = angle_distortion(edge_distortions) diff --git a/impossolid/twice-augmented_angle-distortions b/impossolid/twice-augmented_angle-distortions index 2a11203..07ffffc 100644 --- a/impossolid/twice-augmented_angle-distortions +++ b/impossolid/twice-augmented_angle-distortions @@ -2,293 +2,293 @@ Triangle a_NE, a_SE, z_E a_NE: 0.0020627780860564826° - a_SE: 5.696645881414497e-05° - z_E: -0.0021197445448706276° + a_SE: -0.0021197445448706276° + z_E: 5.696645881414497e-05° Triangle a_NE, b_NE, z_E a_NE: 0.0017363010366011622° - b_NE: 0.00022021214437017989° - z_E: -0.001956513180971342° + b_NE: -0.001956513180971342° + z_E: 0.00022021214437017989° Triangle a_SE, a_SW, z_S a_SE: -0.0021197448587955137° - a_SW: 5.696873318328244e-05° - z_S: 0.0020627761256122312° + a_SW: 0.0020627761256122312° + z_S: 5.696873318328244e-05° Triangle a_SE, b_SE, z_E a_SE: -0.0018856584331388149° - b_SE: 0.0006966226686131449° - z_E: 0.0011890357645540917° + b_SE: 0.0011890357645540917° + z_E: 0.0006966226686131449° Triangle a_SE, b_SE, z_S a_SE: -0.0018856502946889009° - b_SE: 0.0006966207168233041° - z_S: 0.0011890295778727022° + b_SE: 0.0011890295778727022° + z_S: 0.0006966207168233041° Triangle a_SW, b_SW, z_S a_SW: 0.0017363089225312933° - b_SW: 0.00022020949535317413° - z_S: -0.001956518417863151° + b_SW: -0.001956518417863151° + z_S: 0.00022020949535317413° Triangle b_NE, c_E, y_NE b_NE: -5.4156720231901545e-05° - c_E: 0.00045774058987291255° - y_NE: -0.00040358386964811643° + c_E: -0.00040358386964811643° + y_NE: 0.00045774058987291255° Triangle b_NE, c_E, z_E b_NE: -0.0009184660407299816° - c_E: 4.450032897551637e-05° - z_E: 0.0008739657117544652° + c_E: 0.0008739657117544652° + z_E: 4.450032897551637e-05° Triangle b_NE, c_N, y_NE b_NE: 0.00017470943667774463° - c_N: 1.7191837287100498e-08° - y_NE: -0.0001747266285079263° + c_N: -0.0001747266285079263° + y_NE: 1.7191837287100498e-08° Triangle b_NW, c_N, y_NW b_NW: 0.003928388803437599° - c_N: 1.4034125683792809e-07° - y_NW: -0.0039285291447015425° + c_N: -0.0039285291447015425° + y_NW: 1.4034125683792809e-07° Triangle b_NW, c_W, y_NW b_NW: 0.0039283900732698385° - c_W: 1.3970630163839814e-07° - y_NW: -0.0039285297795643714° + c_W: -0.0039285297795643714° + y_NW: 1.3970630163839814e-07° Triangle b_SE, c_E, y_SE b_SE: -0.000897519700536975° - c_E: 0.0006764305357691569° - y_SE: 0.0002210891647536073° + c_E: 0.0002210891647536073° + y_SE: 0.0006764305357691569° Triangle b_SE, c_E, z_E b_SE: -0.0001661945662831954° - c_E: 0.0011249783407549785° - z_E: -0.0009587837744717831° + c_E: -0.0009587837744717831° + z_E: 0.0011249783407549785° Triangle b_SE, c_S, y_SE b_SE: -0.0008975178741081891° - c_S: 0.0006764281771083347° - y_SE: 0.00022108969701406522° + c_S: 0.00022108969701406522° + y_SE: 0.0006764281771083347° Triangle b_SE, c_S, z_S b_SE: -0.00016620401637368332° - c_S: 0.0011249765751415453° - z_S: -0.0009587725587678619° + c_S: -0.0009587725587678619° + z_S: 0.0011249765751415453° Triangle b_SW, c_S, y_SW b_SW: -5.415090214455631e-05° - c_S: 0.00045772681384903535° - y_SW: -0.00040357591168316276° + c_S: -0.00040357591168316276° + y_SW: 0.00045772681384903535° Triangle b_SW, c_S, z_S b_SW: -0.0009184701495854597° - c_S: 4.448743641916053e-05° - z_S: 0.00087398271318051° + c_S: 0.00087398271318051° + z_S: 4.448743641916053e-05° Triangle b_SW, c_W, y_SW b_SW: 0.00017470839824795803° - c_W: 1.6523628687536984e-08° - y_SW: -0.0001747249218624347° + c_W: -0.0001747249218624347° + y_SW: 1.6523628687536984e-08° Triangle c_E, d_NE, e_E c_E: -0.0007556600615572506° - d_NE: 0.0004914936888269494° - e_E: 0.0002641663727089849° + d_NE: 0.0002641663727089849° + e_E: 0.0004914936888269494° Triangle c_E, d_NE, y_NE c_E: -0.0003919462083246117° - d_NE: 0.00043446422546367103° - y_NE: -4.2518017124848484e-05° + d_NE: -4.2518017124848484e-05° + y_NE: 0.00043446422546367103° Triangle c_E, d_SE, e_E c_E: 0.00022487539229842923° - d_SE: -0.0004891383069605126° - e_E: 0.000264262914654978° + d_SE: 0.000264262914654978° + e_E: -0.0004891383069605126° Triangle c_E, d_SE, y_SE c_E: 0.0006690477311224186° - d_SE: -0.000219450687097833° - y_SE: -0.0004495970440387964° + d_SE: -0.0004495970440387964° + y_SE: -0.000219450687097833° Triangle c_N, d_NE, e_N c_N: 0.00043802608161769285° - d_NE: -0.0010442017109895119° - e_N: 0.0006061756293931353° + d_NE: 0.0006061756293931353° + e_N: -0.0010442017109895119° Triangle c_N, d_NE, y_NE c_N: 7.77608707309696e-05° - d_NE: -0.0005049622408037635° - y_NE: 0.0004272013700656885° + d_NE: 0.0004272013700656885° + y_NE: -0.0005049622408037635° Triangle c_N, d_NW, e_N c_N: -0.0018371050143599632° - d_NW: 0.0011681390506907974° - e_N: 0.0006689659636691658° + d_NW: 0.0006689659636691658° + e_N: 0.0011681390506907974° Triangle c_N, d_NW, y_NW c_N: -0.004392411525167006° - d_NW: 0.0009281613228964147° - y_NW: 0.0034642502022776966° + d_NW: 0.0034642502022776966° + y_NW: 0.0009281613228964147° Triangle c_S, d_SE, e_S c_S: 0.00022490750998827025° - d_SE: -0.0004891581593469141° - e_S: 0.00026425064935864384° + d_SE: 0.00026425064935864384° + e_S: -0.0004891581593469141° Triangle c_S, d_SE, y_SE c_S: 0.0006690562241331577° - d_SE: -0.00021946896695368423° - y_SE: -0.000449587257172368° + d_SE: -0.000449587257172368° + y_SE: -0.00021946896695368423° Triangle c_S, d_SW, e_S c_S: -0.0007556856092989506° - d_SW: 0.0004915334549480121° - e_S: 0.0002641521543509384° + d_SW: 0.0002641521543509384° + e_S: 0.0004915334549480121° Triangle c_S, d_SW, y_SW c_S: -0.0003919572288921813° - d_SW: 0.00043448701768511455° - y_SW: -4.252978876451152e-05° + d_SW: -4.252978876451152e-05° + y_SW: 0.00043448701768511455° Triangle c_W, d_NW, e_W c_W: -0.0018371356879498535° - d_NW: 0.0011681941726706668° - e_W: 0.0006689415152791867° + d_NW: 0.0006689415152791867° + e_W: 0.0011681941726706668° Triangle c_W, d_NW, y_NW c_W: -0.00439242709246912° - d_NW: 0.000928190557552° - y_NW: 0.00346423653491712° + d_NW: 0.00346423653491712° + y_NW: 0.000928190557552° Triangle c_W, d_SW, e_W c_W: 0.0004380758375859273° - d_SW: -0.0010442287499685676° - e_W: 0.0006061529123826404° + d_SW: 0.0006061529123826404° + e_W: -0.0010442287499685676° Triangle c_W, d_SW, y_SW c_W: 7.777468017877709e-05° - d_SW: -0.0005049885038488355° - y_SW: 0.00042721382367005845° + d_SW: 0.00042721382367005845° + y_SW: -0.0005049885038488355° Triangle d_NE, e_E, f_NE d_NE: 0.00038569630375207° - e_E: -0.0003446839119192191° - f_NE: -4.1012391825745453e-05° + e_E: -4.1012391825745453e-05° + f_NE: -0.0003446839119192191° Triangle d_NE, e_N, f_NE d_NE: 3.4118590889420375e-05° - e_N: 0.0004912376328576329° - f_NE: -0.0005253562237612641° + e_N: -0.0005253562237612641° + f_NE: 0.0004912376328576329° Triangle d_NW, e_N, f_NW d_NW: 0.0012793501149417352° - e_N: -0.0009787611630969195° - f_NW: -0.0003005889518448157° + e_N: -0.0003005889518448157° + f_NW: -0.0009787611630969195° Triangle d_NW, e_W, f_NW d_NW: 0.0012793645837376744° - e_W: -0.0009787888926879873° - f_NW: -0.00030057569104258164° + e_W: -0.00030057569104258164° + f_NW: -0.0009787888926879873° Triangle d_SE, e_E, f_SE d_SE: -7.914704212907964e-06° - e_E: 0.0002723375437980735° - f_SE: -0.00026442283958516555° + e_E: -0.00026442283958516555° + f_SE: 0.0002723375437980735° Triangle d_SE, e_S, f_SE d_SE: -7.92374907376825e-06° - e_S: 0.0002723546649647801° - f_SE: -0.00026443091589101186° + e_S: -0.00026443091589101186° + f_SE: 0.0002723546649647801° Triangle d_SW, e_S, f_SW d_SW: 0.0003857085871175059° - e_S: -0.00034470395510055596° - f_SW: -4.10046320098445e-05° + e_S: -4.10046320098445e-05° + f_SW: -0.00034470395510055596° Triangle d_SW, e_W, f_SW d_SW: 3.4104280807412124e-05° - e_W: 0.0004912649981676509° - f_SW: -0.0005253692789679576° + e_W: -0.0005253692789679576° + f_SW: 0.0004912649981676509° Triangle e_E, f_NE, g_ENE e_E: -0.00015088143299379908° - f_NE: -4.437100798782012e-06° - g_ENE: 0.0001553185337925811° + f_NE: 0.0001553185337925811° + g_ENE: -4.437100798782012e-06° Triangle e_E, f_SE, g_ESE e_E: -5.289010589137888e-05° - f_SE: 0.00010994821870013993° - g_ESE: -5.705811280876105e-05° + f_SE: -5.705811280876105e-05° + g_ESE: 0.00010994821870013993° Triangle e_E, g_ENE, g_ESE e_E: -7.744546709176348e-05° - g_ENE: 0.0002171941456126092° - g_ESE: -0.000139748678492424° + g_ENE: -0.000139748678492424° + g_ESE: 0.0002171941456126092° Triangle e_N, f_NE, g_NNE e_N: -0.00011644664380128233° - f_NE: 0.000363157568820327° - g_NNE: -0.0002467109250190447° + f_NE: -0.0002467109250190447° + g_NNE: 0.000363157568820327° Triangle e_N, f_NW, g_NNW e_N: -0.0005663172445693476° - f_NW: 0.000436599482547706° - g_NNW: 0.00012971776202874707° + f_NW: 0.00012971776202874707° + g_NNW: 0.000436599482547706° Triangle e_N, g_NNE, g_NNW e_N: 1.3987573481699656e-06° - g_NNE: -0.00034080770834776786° - g_NNW: 0.0003394089509995979° + g_NNE: 0.0003394089509995979° + g_NNW: -0.00034080770834776786° Triangle e_S, f_SE, g_SSE e_S: -5.28844724030364e-05° - f_SE: 0.00010995296653959485° - g_SSE: -5.706849415076931e-05° + f_SE: -5.706849415076931e-05° + g_SSE: 0.00010995296653959485° Triangle e_S, f_SW, g_SSW e_S: -0.00015088330234647174° - f_SW: -4.434221558824447e-06° - g_SSW: 0.00015531752390529618° + f_SW: 0.00015531752390529618° + g_SSW: -4.434221558824447e-06° Triangle e_S, g_SSE, g_SSW e_S: -7.743940860649445e-05° - g_SSE: -0.00013975871755889102° - g_SSW: 0.00021719812617959633° + g_SSE: 0.00021719812617959633° + g_SSW: -0.00013975871755889102° Triangle e_W, f_NW, g_WNW e_W: -0.0005663163395084325° - f_NW: 0.00043660275473200727° - g_WNW: 0.00012971358477642525° + f_NW: 0.00012971358477642525° + g_WNW: 0.00043660275473200727° Triangle e_W, f_SW, g_WSW e_W: -0.00011643403040295652° - f_SW: 0.0003631626645272945° - g_WSW: -0.000246728634124338° + f_SW: -0.000246728634124338° + g_WSW: 0.0003631626645272945° Triangle e_W, g_WNW, g_WSW e_W: 1.4097619143171869e-06° - g_WNW: 0.0003394137133483355° - g_WSW: -0.00034082347526265266° + g_WNW: -0.00034082347526265266° + g_WSW: 0.0003394137133483355° Triangle f_NE, g_ENE, g_NNE f_NE: 7.450149718835064e-05° - g_ENE: 6.294030865205968e-05° - g_NNE: -0.00013744180584041032° + g_ENE: -0.00013744180584041032° + g_NNE: 6.294030865205968e-05° Triangle f_NW, g_NNW, g_WNW f_NW: 0.00023868980004237983° - g_NNW: -0.00011934432004778728° - g_WNW: -0.00011934547999459255° + g_NNW: -0.00011934547999459255° + g_WNW: -0.00011934432004778728° Triangle f_SE, g_ESE, g_SSE f_SE: 3.87570213717936e-05° - g_ESE: -1.937846930388787e-05° - g_SSE: -1.9378552060800303e-05° + g_ESE: -1.9378552060800303e-05° + g_SSE: -1.937846930388787e-05° Triangle f_SW, g_SSW, g_WSW f_SW: 7.450129267994043e-05° - g_SSW: 6.293716665339844e-05° - g_WSW: -0.00013743845931912801° + g_SSW: -0.00013743845931912801° + g_WSW: 6.293716665339844e-05° Total angle distortion: 0.11233054610809035° Highest angle distortion: 0.0039283900732698385° From 2c8c09d20d3567a968acf8e839e6d04c1414751b Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 13 Oct 2025 22:52:02 +0000 Subject: [PATCH 16/16] feat: Point coordinate regulators (#118) Implement regulators for the Euclidean coordinates of `Point` entities, automatically creating all three of them for each added point entity. When such a regulator is set, it freezes the corresponding representation coordinate to the set point. In addition, if all three coordinates of a given `Point` are set, the coradius coordinate (which holds the norm of the point) is frozen as well. Note that a `PointCoordinateRegulator` must be created with a `Point` as the subject. This commit modifies `HalfCurvatureRegulator` analogously, so that it can only be created with a `Sphere`. Co-authored-by: Glen Whitney Co-committed-by: Glen Whitney --- README.md | 64 +++++++------- app-proto/Cargo.lock | 21 +++++ app-proto/Cargo.toml | 1 + app-proto/src/assembly.rs | 124 ++++++++++++++++++++++++---- app-proto/src/components/outline.rs | 15 ++++ app-proto/src/engine.rs | 4 +- 6 files changed, 180 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index cf3e589..ac2771b 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ Note that currently this is just the barest beginnings of the project, more of a ### Implementation goals -* Comfortable, intuitive UI +* Provide a comfortable, intuitive UI -* Able to run in browser (so implemented in WASM-compatible language) +* Allow execution in browser (so implemented in WASM-compatible language) -* Produce scalable graphics of 3D diagrams, and maybe STL files (or other fabricatable file format) as well. +* Produce scalable graphics of 3D diagrams, and maybe STL files (or other fabricatable file format) as well ## Prototype @@ -24,38 +24,40 @@ The latest prototype is in the folder `app-proto`. It includes both a user inter ### Install the prerequisites -1. Install [`rustup`](https://rust-lang.github.io/rustup/): the officially recommended Rust toolchain manager - - It's available on Ubuntu as a [Snap](https://snapcraft.io/rustup) -2. Call `rustup default stable` to "download the latest stable release of Rust and set it as your default toolchain" - - If you forget, the `rustup` [help system](https://github.com/rust-lang/rustup/blob/d9b3601c3feb2e88cf3f8ca4f7ab4fdad71441fd/src/errors.rs#L109-L112) will remind you -3. Call `rustup target add wasm32-unknown-unknown` to add the [most generic 32-bit WebAssembly target](https://doc.rust-lang.org/nightly/rustc/platform-support/wasm32-unknown-unknown.html) -4. Call `cargo install wasm-pack` to install the [WebAssembly toolchain](https://rustwasm.github.io/docs/wasm-pack/) -5. Call `cargo install trunk` to install the [Trunk](https://trunkrs.dev/) web-build tool +1. Install [`rustup`](https://rust-lang.github.io/rustup/): the officially recommended Rust toolchain manager. + - It's available on Ubuntu as a [Snap](https://snapcraft.io/rustup). +2. Call `rustup default stable` to "download the latest stable release of Rust and set it as your default toolchain". + - If you forget, the `rustup` [help system](https://github.com/rust-lang/rustup/blob/d9b3601c3feb2e88cf3f8ca4f7ab4fdad71441fd/src/errors.rs#L109-L112) will remind you. +3. Call `rustup target add wasm32-unknown-unknown` to add the [most generic 32-bit WebAssembly target](https://doc.rust-lang.org/nightly/rustc/platform-support/wasm32-unknown-unknown.html). +4. Call `cargo install wasm-pack` to install the [WebAssembly toolchain](https://rustwasm.github.io/docs/wasm-pack/). +5. Call `cargo install trunk` to install the [Trunk](https://trunkrs.dev/) web-build tool. + - In the future, `trunk` can be updated with the same command. (You may need the `--locked` flag if your ambient version of `rustc` does not match that required by `trunk`.) 6. Add the `.cargo/bin` folder in your home directory to your executable search path - - This lets you call Trunk, and other tools installed by Cargo, without specifying their paths - - On POSIX systems, the search path is stored in the `PATH` environment variable + - This lets you call Trunk, and other tools installed by Cargo, without specifying their paths. + - On POSIX systems, the search path is stored in the `PATH` environment variable. + - Alternatively, if you don't want to adjust your `PATH`, you can install `trunk` in another directory `DIR` via `cargo install --root DIR trunk`. ### Play with the prototype -1. From the `app-proto` folder, call `trunk serve --release` to build and serve the prototype - - The crates the prototype depends on will be downloaded and served automatically - - For a faster build, at the expense of a much slower prototype, you can call `trunk serve` without the `--release` flag +1. From the `app-proto` folder, call `trunk serve --release` to build and serve the prototype. + - The crates the prototype depends on will be downloaded and served automatically. + - For a faster build, at the expense of a much slower prototype, you can call `trunk serve` without the `--release` flag. - If you want to stay in the top-level folder, you can call `trunk serve --config app-proto [--release]` from there instead. -3. In a web browser, visit one of the URLs listed under the message `INFO 📡 server listening at:` - - Touching any file in the `app-proto` folder will make Trunk rebuild and live-reload the prototype -4. Press *ctrl+C* in the shell where Trunk is running to stop serving the prototype +3. In a web browser, visit one of the URLs listed under the message `INFO 📡 server listening at:`. + - Touching any file in the `app-proto` folder will make Trunk rebuild and live-reload the prototype. +4. Press *ctrl+C* in the shell where Trunk is running to stop serving the prototype. ### Run the engine on some example problems -1. Use `sh` to run the script `tools/run-examples.sh` - - The script is location-independent, so you can do this from anywhere in the dyna3 repository +1. Use `sh` to run the script `tools/run-examples.sh`. + - The script is location-independent, so you can do this from anywhere in the dyna3 repository. - The call from the top level of the repository is: ```bash sh tools/run-examples.sh ``` - - For each example problem, the engine will print the value of the loss function at each optimization step - - The first example that prints is the same as the Irisawa hexlet example from the Julia version of the engine prototype. If you go into `engine-proto/gram-test`, launch Julia, and then + - For each example problem, the engine will print the value of the loss function at each optimization step. + - The first example that prints is the same as the Irisawa hexlet example from the Julia version of the engine prototype. If you go into `engine-proto/gram-test`, launch Julia, and then execute ```julia include("irisawa-hexlet.jl") @@ -64,24 +66,24 @@ The latest prototype is in the folder `app-proto`. It includes both a user inter end ``` - you should see that it prints basically the same loss history until the last few steps, when the lower default precision of the Rust engine really starts to show + you should see that it prints basically the same loss history until the last few steps, when the lower default precision of the Rust engine really starts to show. ### Run the automated tests -1. Go into the `app-proto` folder -2. Call `cargo test` +1. Go into the `app-proto` folder. +2. Call `cargo test`. ### Deploy the prototype -1. From the `app-proto` folder, call `trunk build --release` - - Building in [release mode](https://doc.rust-lang.org/cargo/reference/profiles.html#release) produces an executable which is smaller and often much faster, but harder to debug and more time-consuming to build - - If you want to stay in the top-level folder, you can call `trunk build --config app-proto --release` from there instead +1. From the `app-proto` folder, call `trunk build --release`. + - Building in [release mode](https://doc.rust-lang.org/cargo/reference/profiles.html#release) produces an executable which is smaller and often much faster, but harder to debug and more time-consuming to build. + - If you want to stay in the top-level folder, you can call `trunk build --config app-proto --release` from there instead. 2. Use `sh` to run the packaging script `tools/package-for-deployment.sh`. - - The script is location-independent, so you can do this from anywhere in the dyna3 repository + - The script is location-independent, so you can do this from anywhere in the dyna3 repository. - The call from the top level of the repository is: ```bash sh tools/package-for-deployment.sh ``` - - This will overwrite or replace the files in `deploy/dyna3` + - This will overwrite or replace the files in `deploy/dyna3`. 3. Put the contents of `deploy/dyna3` in the folder on your server that the prototype will be served from. - - To simplify uploading, you might want to combine these files into an archive called `deploy/dyna3.zip`. Git has been set to ignore this path \ No newline at end of file + - To simplify uploading, you might want to combine these files into an archive called `deploy/dyna3.zip`. Git has been set to ignore this path. diff --git a/app-proto/Cargo.lock b/app-proto/Cargo.lock index 4f75c45..731dd84 100644 --- a/app-proto/Cargo.lock +++ b/app-proto/Cargo.lock @@ -255,6 +255,7 @@ dependencies = [ "charming", "console_error_panic_hook", "dyna3", + "enum-iterator", "itertools", "js-sys", "lazy_static", @@ -271,6 +272,26 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "enum-iterator" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4549325971814bda7a44061bf3fe7e487d447cba01e4220a4b454d630d7a016" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.1" diff --git a/app-proto/Cargo.toml b/app-proto/Cargo.toml index 1230b47..d5221a1 100644 --- a/app-proto/Cargo.toml +++ b/app-proto/Cargo.toml @@ -10,6 +10,7 @@ default = ["console_error_panic_hook"] dev = [] [dependencies] +enum-iterator = "2.3.0" itertools = "0.13.0" js-sys = "0.3.70" lazy_static = "1.5.0" diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 669c0d0..0264b75 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -1,10 +1,11 @@ +use enum_iterator::{all, Sequence}; use nalgebra::{DMatrix, DVector, DVectorView}; use std::{ cell::Cell, cmp::Ordering, collections::{BTreeMap, BTreeSet}, fmt, - fmt::{Debug, Formatter}, + fmt::{Debug, Display, Formatter}, hash::{Hash, Hasher}, rc::Rc, sync::{atomic, atomic::AtomicU64}, @@ -26,6 +27,7 @@ use crate::{ ConfigSubspace, ConstraintProblem, DescentHistory, + MatrixEntry, Realization, }, specified::SpecifiedValue, @@ -84,6 +86,14 @@ impl Ord for dyn Serial { } } +// Small helper function to generate consistent errors when there +// are indexing issues in a ProblemPoser +fn indexing_error(item: &str, name: &str, actor: &str) -> String { + format!( + "{item} \"{name}\" must be indexed before {actor} writes problem data" + ) +} + pub trait ProblemPoser { fn pose(&self, problem: &mut ConstraintProblem); } @@ -125,8 +135,8 @@ pub trait Element: Serial + ProblemPoser + DisplayItem { } impl Debug for dyn Element { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { - self.id().fmt(f) + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.id(), f) } } @@ -249,8 +259,7 @@ impl Serial for Sphere { impl ProblemPoser for Sphere { fn pose(&self, problem: &mut ConstraintProblem) { let index = self.column_index().expect( - format!("Sphere \"{}\" should be indexed before writing problem data", self.id).as_str() - ); + indexing_error("Sphere", &self.id, "it").as_str()); problem.gram.push_sym(index, index, 1.0); problem.guess.set_column(index, &self.representation.get_clone_untracked()); } @@ -269,6 +278,7 @@ pub struct Point { impl Point { const WEIGHT_COMPONENT: usize = 3; + const NORM_COMPONENT: usize = 4; pub fn new( id: String, @@ -302,6 +312,15 @@ impl Element for Point { point(0.0, 0.0, 0.0), ) } + + fn default_regulators(self: Rc) -> Vec> { + all::() + .map(|axis| { + Rc::new(PointCoordinateRegulator::new(self.clone(), axis)) + as Rc:: + }) + .collect() + } fn id(&self) -> &String { &self.id @@ -345,8 +364,7 @@ impl Serial for Point { impl ProblemPoser for Point { fn pose(&self, problem: &mut ConstraintProblem) { let index = self.column_index().expect( - format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str() - ); + indexing_error("Point", &self.id, "it").as_str()); problem.gram.push_sym(index, index, 0.0); problem.frozen.push(Self::WEIGHT_COMPONENT, index, 0.5); problem.guess.set_column(index, &self.representation.get_clone_untracked()); @@ -436,8 +454,8 @@ impl ProblemPoser for InversiveDistanceRegulator { if let Some(val) = set_pt.value { let [row, col] = self.subjects.each_ref().map( |subj| subj.column_index().expect( - "Subjects should be indexed before inversive distance regulator writes problem data" - ) + indexing_error("Subject", subj.id(), + "inversive distance regulator").as_str()) ); problem.gram.push_sym(row, col, val); } @@ -446,14 +464,14 @@ impl ProblemPoser for InversiveDistanceRegulator { } pub struct HalfCurvatureRegulator { - pub subject: Rc, + pub subject: Rc, pub measurement: ReadSignal, pub set_point: Signal, serial: u64, } impl HalfCurvatureRegulator { - pub fn new(subject: Rc) -> Self { + pub fn new(subject: Rc) -> Self { let measurement = subject.representation().map( |rep| rep[Sphere::CURVATURE_COMPONENT] ); @@ -490,14 +508,85 @@ impl ProblemPoser for HalfCurvatureRegulator { self.set_point.with_untracked(|set_pt| { if let Some(val) = set_pt.value { let col = self.subject.column_index().expect( - "Subject should be indexed before half-curvature regulator writes problem data" - ); + indexing_error("Subject", &self.subject.id, + "half-curvature regulator").as_str()); problem.frozen.push(Sphere::CURVATURE_COMPONENT, col, val); } }); } } +#[derive(Clone, Copy, Sequence)] +pub enum Axis { X = 0, Y = 1, Z = 2 } + +impl Axis { + fn name(&self) -> &'static str { + match self { Axis::X => "X", Axis::Y => "Y", Axis::Z => "Z" } + } +} + +impl Display for Axis { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.name()) + } +} + +pub struct PointCoordinateRegulator { + pub subject: Rc, + pub axis: Axis, + pub measurement: ReadSignal, + pub set_point: Signal, + serial: u64 +} + +impl PointCoordinateRegulator { + pub fn new(subject: Rc, axis: Axis) -> Self { + let measurement = subject.representation().map( + move |rep| rep[axis as usize] + ); + let set_point = create_signal(SpecifiedValue::from_empty_spec()); + Self { subject, axis, measurement, set_point, serial: Self::next_serial() } + } +} + +impl Serial for PointCoordinateRegulator { + fn serial(&self) -> u64 { self.serial } +} + +impl Regulator for PointCoordinateRegulator { + fn subjects(&self) -> Vec> { vec![self.subject.clone()] } + fn measurement(&self) -> ReadSignal { self.measurement } + fn set_point(&self) -> Signal { self.set_point } +} + +impl ProblemPoser for PointCoordinateRegulator { + fn pose(&self, problem: &mut ConstraintProblem) { + self.set_point.with_untracked(|set_pt| { + if let Some(val) = set_pt.value { + let col = self.subject.column_index().expect( + indexing_error("Subject", &self.subject.id, + "point-coordinate regulator").as_str()); + problem.frozen.push(self.axis as usize, col, val); + // If all three of the subject's spatial coordinates have been + // frozen, then freeze its norm component: + let mut coords = [0.0; Axis::CARDINALITY]; + let mut nset: usize = 0; + for &MatrixEntry {index, value} in &(problem.frozen) { + if index.1 == col && index.0 < Axis::CARDINALITY { + nset += 1; + coords[index.0] = value + } + } + if nset == Axis::CARDINALITY { + let [x, y, z] = coords; + problem.frozen.push( + Point::NORM_COMPONENT, col, point(x,y,z)[Point::NORM_COMPONENT]); + } + } + }); + } +} + // the velocity is expressed in uniform coordinates pub struct ElementMotion<'a> { pub element: Rc, @@ -698,6 +787,7 @@ impl Assembly { /* DEBUG */ // log the Gram matrix console_log!("Gram matrix:\n{}", problem.gram); + console_log!("Frozen entries:\n{}", problem.frozen); /* DEBUG */ // log the initial configuration matrix @@ -857,7 +947,8 @@ mod tests { use crate::engine; #[test] - #[should_panic(expected = "Sphere \"sphere\" should be indexed before writing problem data")] + #[should_panic(expected = + "Sphere \"sphere\" must be indexed before it writes problem data")] fn unindexed_element_test() { let _ = create_root(|| { let elt = Sphere::default("sphere".to_string(), 0); @@ -866,7 +957,8 @@ mod tests { } #[test] - #[should_panic(expected = "Subjects should be indexed before inversive distance regulator writes problem data")] + #[should_panic(expected = "Subject \"sphere1\" must be indexed before \ + inversive distance regulator writes problem data")] fn unindexed_subject_test_inversive_distance() { let _ = create_root(|| { let subjects = [0, 1].map( @@ -927,4 +1019,4 @@ mod tests { assert!((final_half_curv / INITIAL_HALF_CURV - 1.0).abs() < DRIFT_TOL); }); } -} \ No newline at end of file +} diff --git a/app-proto/src/components/outline.rs b/app-proto/src/components/outline.rs index 5355042..547b73b 100644 --- a/app-proto/src/components/outline.rs +++ b/app-proto/src/components/outline.rs @@ -9,6 +9,7 @@ use crate::{ Element, HalfCurvatureRegulator, InversiveDistanceRegulator, + PointCoordinateRegulator, Regulator, }, specified::SpecifiedValue @@ -119,6 +120,20 @@ impl OutlineItem for HalfCurvatureRegulator { } } +impl OutlineItem for PointCoordinateRegulator { + fn outline_item(self: Rc, _element: &Rc) -> View { + let name = format!("{} coordinate", self.axis); + view! { + li(class = "regulator") { + div(class = "regulator-label") // for spacing + div(class = "regulator-type") { (name) } + RegulatorInput(regulator = self) + div(class = "status") + } + } + } +} + // a list item that shows an element in an outline view of an assembly #[component(inline_props)] fn ElementOutlineItem(element: Rc) -> View { diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index ef150a0..0f26f02 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -52,8 +52,8 @@ pub fn project_point_to_normalized(rep: &mut DVector) { // --- partial matrices --- pub struct MatrixEntry { - index: (usize, usize), - value: f64, + pub index: (usize, usize), + pub value: f64, } pub struct PartialMatrix(Vec);