From 40d665d8acc31c498506aef8cb789835d9e20f1e Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 2 Jul 2025 13:59:22 -0700 Subject: [PATCH 01/14] Pause realization while loading assemblies This avoids redundant realizations as we set an assembly's regulators during loading. Adding some regulators to the low-curvature assembly confirms that the feature is working as intended. --- app-proto/src/add_remove.rs | 60 ++++++++++++++++++++++++++++++++++++- app-proto/src/assembly.rs | 39 +++++++++++++++++++----- 2 files changed, 90 insertions(+), 9 deletions(-) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index d737c79..ea5267d 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -6,7 +6,8 @@ use crate::{ AppState, engine, engine::DescentHistory, - assembly::{Assembly, InversiveDistanceRegulator, Point, Sphere} + assembly::{Assembly, InversiveDistanceRegulator, Point, Sphere}, + specified::SpecifiedValue }; /* DEBUG */ @@ -67,6 +68,7 @@ fn load_gen_assemb(assembly: &Assembly) { // load an example assembly for testing. this code will be removed once we've // built a more formal test assembly system fn load_low_curv_assemb(assembly: &Assembly) { + // create the spheres let a = 0.75_f64.sqrt(); let _ = assembly.try_insert_element( Sphere::new( @@ -132,6 +134,56 @@ fn load_low_curv_assemb(assembly: &Assembly) { engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0) ) ); + + // impose the desired tangencies and make the sides planar + let index_range = 1..=3; + let [central, assemb_plane] = ["central", "assemb_plane"].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + let sides = index_range.clone().map( + |k| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("side{k}")].clone() + ) + ); + let corners = index_range.map( + |k| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("corner{k}")].clone() + ) + ); + for plane in [assemb_plane.clone()].into_iter().chain(sides.clone()) { + // fix the curvature of each plane + let curvature = plane.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature.set_point().set(SpecifiedValue::try_from("0".to_string()).unwrap()); + } + let all_perpendicular = [central.clone()].into_iter() + .chain(sides.clone()) + .chain(corners.clone()); + for sphere in all_perpendicular { + // make each side and packed sphere perpendicular to the assembly plane + let right_angle = InversiveDistanceRegulator::new([sphere, assemb_plane.clone()]); + right_angle.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(right_angle)); + } + for sphere in sides.clone().chain(corners.clone()) { + // make each side and corner sphere tangent to the central sphere + let tangency = InversiveDistanceRegulator::new([sphere.clone(), central.clone()]); + tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(tangency)); + } + for (side_index, side) in sides.enumerate() { + // make each side tangent to the two adjacent corner spheres + for (corner_index, corner) in corners.clone().enumerate() { + if side_index != corner_index { + let tangency = InversiveDistanceRegulator::new([side.clone(), corner]); + tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(tangency)); + } + } + } } fn load_pointed_assemb(assembly: &Assembly) { @@ -192,6 +244,9 @@ pub fn AddRemove() -> View { let state = use_context::(); let assembly = &state.assembly; + // pause realization + assembly.keep_realized.set(false); + // clear state assembly.regulators.update(|regs| regs.clear()); assembly.elements.update(|elts| elts.clear()); @@ -206,6 +261,9 @@ pub fn AddRemove() -> View { "pointed" => load_pointed_assemb(assembly), _ => () }; + + // resume realization + assembly.keep_realized.set(true); }); }); diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index c3b0c6b..e696845 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -552,6 +552,10 @@ pub struct Assembly { // indexing pub elements_by_id: Signal>>, + // realization control + pub keep_realized: Signal, + pub needs_realization: Signal, + // realization diagnostics pub realization_status: Signal>, pub descent_history: Signal @@ -559,14 +563,30 @@ pub struct Assembly { impl Assembly { pub fn new() -> Assembly { - Assembly { + // create an assembly + let assembly = Assembly { elements: create_signal(BTreeSet::new()), regulators: create_signal(BTreeSet::new()), tangent: create_signal(ConfigSubspace::zero(0)), elements_by_id: create_signal(BTreeMap::default()), + keep_realized: create_signal(true), + needs_realization: create_signal(false), realization_status: create_signal(Ok(())), descent_history: create_signal(DescentHistory::new()) - } + }; + + // realize the assembly whenever it becomes simultaneously true that + // we're trying to keep it realized and it needs realization + let assembly_for_effect = assembly.clone(); + create_effect(move || { + let should_realize = assembly_for_effect.keep_realized.get() + && assembly_for_effect.needs_realization.get(); + if should_realize { + assembly_for_effect.realize(); + } + }); + + assembly } // --- inserting elements and regulators --- @@ -627,7 +647,7 @@ impl Assembly { regulators.update(|regs| regs.insert(regulator.clone())); } - // update the realization when the regulator becomes a constraint, or is + // request a realization when the regulator becomes a constraint, or is // edited while acting as a constraint let self_for_effect = self.clone(); create_effect(move || { @@ -636,7 +656,7 @@ impl Assembly { console_log!("Updated regulator with subjects {:?}", regulator.subjects()); if regulator.try_activate() { - self_for_effect.realize(); + self_for_effect.needs_realization.set(true); } }); @@ -731,6 +751,9 @@ impl Assembly { // save the tangent space self.tangent.set_silent(tangent); + + // clear the realization request flag + self.needs_realization.set(false); }, Err(message) => { // report the realization status. the `Err(message)` we're @@ -826,10 +849,10 @@ impl Assembly { }); } - // bring the configuration back onto the solution variety. this also - // gets the elements' column indices and the saved tangent space back in - // sync - self.realize(); + // request a realization to bring the configuration back onto the + // solution variety. this also gets the elements' column indices and the + // saved tangent space back in sync + self.needs_realization.set(true); } } From 543bc4020fd0d5cc82a387c2589fd3fc88e869f0 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 2 Jul 2025 16:26:14 -0700 Subject: [PATCH 02/14] Add a tetrahedron radius ratio test assembly This partial setup of the tetrahedron radius ratio problem is from the summer 2025 tech demo. --- app-proto/src/add_remove.rs | 142 +++++++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index ea5267d..1419b02 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -1,4 +1,6 @@ +use itertools::izip; use std::{f64::consts::FRAC_1_SQRT_2, rc::Rc}; +use nalgebra::Vector3; use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; @@ -6,7 +8,14 @@ use crate::{ AppState, engine, engine::DescentHistory, - assembly::{Assembly, InversiveDistanceRegulator, Point, Sphere}, + assembly::{ + Assembly, + Element, + ElementColor, + InversiveDistanceRegulator, + Point, + Sphere + }, specified::SpecifiedValue }; @@ -229,6 +238,135 @@ fn load_pointed_assemb(assembly: &Assembly) { } } +// setting the inversive distances between the vertices to -2 gives a regular +// tetrahedron with side length 1, whose insphere and circumsphere have radii +// sqrt(1/6) and sqrt(3/2), respectively. to measure those radii, set an +// inversive distance of -1 between the insphere and each face, and then set an +// inversive distance of 0 between the circumsphere and each vertex +fn load_radius_ratio_assemb(assembly: &Assembly) { + let index_range = 1..=4; + + // create the spheres + const GRAY: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let spheres = [ + Sphere::new( + "sphere_faces".to_string(), + "Insphere".to_string(), + GRAY, + engine::sphere(0.0, 0.0, 0.0, 0.5) + ), + Sphere::new( + "sphere_vertices".to_string(), + "Circumsphere".to_string(), + GRAY, + engine::sphere(0.0, 0.0, 0.0, 0.25) + ) + ]; + for sphere in spheres { + let _ = assembly.try_insert_element(sphere); + } + + // create the vertices + let vertices = izip!( + index_range.clone(), + [ + [1.00_f32, 0.50_f32, 0.75_f32], + [1.00_f32, 0.75_f32, 0.50_f32], + [1.00_f32, 1.00_f32, 0.50_f32], + [0.75_f32, 0.50_f32, 1.00_f32] + ].into_iter(), + [ + engine::point(-0.6, -0.8, -0.6), + engine::point(-0.6, 0.8, 0.6), + engine::point(0.6, -0.8, 0.6), + engine::point(0.6, 0.8, -0.6) + ].into_iter() + ).map( + |(k, color, representation)| { + Point::new( + format!("v{k}"), + format!("Vertex {k}"), + color, + representation + ) + } + ); + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // create the faces + let base_dir = Vector3::new(1.0, 0.75, 1.0).normalize(); + let offset = base_dir.dot(&Vector3::new(-0.6, 0.8, 0.6)); + let faces = izip!( + index_range.clone(), + [ + [1.00_f32, 0.00_f32, 0.25_f32], + [1.00_f32, 0.25_f32, 0.00_f32], + [0.75_f32, 0.75_f32, 0.00_f32], + [0.25_f32, 0.00_f32, 1.00_f32] + ].into_iter(), + [ + engine::sphere_with_offset(base_dir[0], base_dir[1], base_dir[2], offset, 0.0), + engine::sphere_with_offset(base_dir[0], -base_dir[1], -base_dir[2], offset, 0.0), + engine::sphere_with_offset(-base_dir[0], base_dir[1], -base_dir[2], offset, 0.0), + engine::sphere_with_offset(-base_dir[0], -base_dir[1], base_dir[2], offset, 0.0) + ].into_iter() + ).map( + |(k, color, representation)| { + Sphere::new( + format!("f{k}"), + format!("Face {k}"), + color, + representation + ) + } + ); + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + // impose the constraints + for j in index_range.clone() { + let [face_j, vertex_j] = [ + format!("f{j}"), + format!("v{j}") + ].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id].clone() + ) + ); + + // make the faces planar + let curvature_regulator = face_j.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set( + SpecifiedValue::try_from("0".to_string()).unwrap() + ); + + for k in index_range.clone().filter(|&index| index != j) { + let vertex_k = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("v{k}")].clone() + ); + + // fix the distances between the vertices + if j < k { + let distance_regulator = InversiveDistanceRegulator::new( + [vertex_j.clone(), vertex_k.clone()] + ); + assembly.insert_regulator(Rc::new(distance_regulator)); + } + + // put the vertices on the faces + let incidence_regulator = InversiveDistanceRegulator::new([face_j.clone(), vertex_k.clone()]); + incidence_regulator.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(incidence_regulator)); + } + } +} + #[component] pub fn AddRemove() -> View { /* DEBUG */ @@ -259,6 +397,7 @@ pub fn AddRemove() -> View { "general" => load_gen_assemb(assembly), "low-curv" => load_low_curv_assemb(assembly), "pointed" => load_pointed_assemb(assembly), + "radius-ratio" => load_radius_ratio_assemb(assembly), _ => () }; @@ -310,6 +449,7 @@ pub fn AddRemove() -> View { option(value="general") { "General" } option(value="low-curv") { "Low-curvature" } option(value="pointed") { "Pointed" } + option(value="radius-ratio") { "Radius ratio" } option(value="empty") { "Empty" } } } From fae3f4531e7cbaa43c758d2c091ea6189257ea96 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 2 Jul 2025 16:53:08 -0700 Subject: [PATCH 03/14] Add an Irisawa hexlet test assembly This partial setup of the Irisawa hexlet problem is from the summer 2025 tech demo. --- app-proto/src/add_remove.rs | 114 +++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index 1419b02..b95eec2 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -1,5 +1,5 @@ use itertools::izip; -use std::{f64::consts::FRAC_1_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}; @@ -367,6 +367,116 @@ fn load_radius_ratio_assemb(assembly: &Assembly) { } } +// to finish setting up the problem, fix the following curvatures: +// sun 1 +// moon 5/3 = 1.666666666666666... +// chain1 2 +// a tiny `x` or `z` nudge of the outer sphere reliably prevents realization +// failures before they happen, or resolves them after they happen. the result +// depends sensitively on the translation direction, suggesting that realization +// is failing because the engine is having trouble breaking a symmetry +// /* TO DO */ +// the engine's performance on this problem is scale-dependent! with the current +// initial conditions, realization fails for any order of imposing the remaining +// curvature constraints. scaling everything up by a factor of ten, as done in +// the original problem, makes realization succeed reliably. one potentially +// relevant difference is that a lot of the numbers in the current initial +// conditions are exactly representable as floats, unlike the analogous numbers +// in the scaled-up problem. the inexact representations might break the +// symmetry that's getting the engine stuck +fn load_irisawa_hexlet_assemb(assembly: &Assembly) { + let index_range = 1..=6; + let colors = [ + [1.00_f32, 0.00_f32, 0.25_f32], + [1.00_f32, 0.25_f32, 0.00_f32], + [0.75_f32, 0.75_f32, 0.00_f32], + [0.25_f32, 1.00_f32, 0.00_f32], + [0.00_f32, 0.25_f32, 1.00_f32], + [0.25_f32, 0.00_f32, 1.00_f32] + ].into_iter(); + + // create the spheres + let spheres = [ + Sphere::new( + "outer".to_string(), + "Outer".to_string(), + [0.5_f32, 0.5_f32, 0.5_f32], + engine::sphere(0.0, 0.0, 0.0, 1.5) + ), + Sphere::new( + "sun".to_string(), + "Sun".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, -0.75, 0.0, 0.75) + ), + Sphere::new( + "moon".to_string(), + "Moon".to_string(), + [0.25_f32, 0.25_f32, 0.25_f32], + engine::sphere(0.0, 0.75, 0.0, 0.75) + ), + ].into_iter().chain( + index_range.clone().zip(colors).map( + |(k, color)| { + let ang = (k as f64) * PI/3.0; + Sphere::new( + format!("chain{k}"), + format!("Chain {k}"), + color, + engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5) + ) + } + ) + ); + for sphere in spheres { + let _ = assembly.try_insert_element(sphere); + } + + // put the outer sphere in ghost mode and fix its curvature + let outer = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id["outer"].clone() + ); + outer.ghost().set(true); + let outer_curvature_regulator = outer.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + outer_curvature_regulator.set_point().set( + SpecifiedValue::try_from((1.0 / 3.0).to_string()).unwrap() + ); + + // impose the desired tangencies + let [outer, sun, moon] = ["outer", "sun", "moon"].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + let chain = index_range.map( + |k| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("chain{k}")].clone() + ) + ); + for (chain_sphere, chain_sphere_next) in chain.clone().zip(chain.cycle().skip(1)) { + for (other_sphere, inversive_distance) in [ + (outer.clone(), "1"), + (sun.clone(), "-1"), + (moon.clone(), "-1"), + (chain_sphere_next.clone(), "-1") + ] { + let tangency = InversiveDistanceRegulator::new([chain_sphere.clone(), other_sphere]); + tangency.set_point.set(SpecifiedValue::try_from(inversive_distance.to_string()).unwrap()); + assembly.insert_regulator(Rc::new(tangency)); + } + } + + let outer_sun_tangency = InversiveDistanceRegulator::new([outer.clone(), sun]); + outer_sun_tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(outer_sun_tangency)); + + let outer_moon_tangency = InversiveDistanceRegulator::new([outer.clone(), moon]); + outer_moon_tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(outer_moon_tangency)); +} + #[component] pub fn AddRemove() -> View { /* DEBUG */ @@ -398,6 +508,7 @@ pub fn AddRemove() -> View { "low-curv" => load_low_curv_assemb(assembly), "pointed" => load_pointed_assemb(assembly), "radius-ratio" => load_radius_ratio_assemb(assembly), + "irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly), _ => () }; @@ -450,6 +561,7 @@ pub fn AddRemove() -> View { option(value="low-curv") { "Low-curvature" } option(value="pointed") { "Pointed" } option(value="radius-ratio") { "Radius ratio" } + option(value="irisawa-hexlet") { "Irisawa hexlet" } option(value="empty") { "Empty" } } } From 27a8cbfd6989dc6b6318d0b1660e2b37b7e8ddf6 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Thu, 3 Jul 2025 16:27:28 -0700 Subject: [PATCH 04/14] Add a balanced test assembly This test assembly reveals one way that the engine can stall, indicated by a long plateau in the loss history. You can make the plateau even longer by shrinking the inner spheres. --- app-proto/src/add_remove.rs | 64 +++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index b95eec2..d23a849 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -238,6 +238,68 @@ fn load_pointed_assemb(assembly: &Assembly) { } } +/* DEBUG */ +// the initial configuration of this test assembly deliberately violates the +// constraints, so loading the assembly will trigger a non-trivial realization +fn load_balanced_assemb(assembly: &Assembly) { + // create the spheres + const R_OUTER: f64 = 10.0; + const R_INNER: f64 = 4.0; + let spheres = [ + Sphere::new( + "outer".to_string(), + "Outer".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, 0.0, 0.0, R_OUTER) + ), + Sphere::new( + "a".to_string(), + "A".to_string(), + [1.00_f32, 0.00_f32, 0.25_f32], + engine::sphere(0.0, 4.0, 0.0, R_INNER) + ), + Sphere::new( + "b".to_string(), + "B".to_string(), + [0.00_f32, 0.25_f32, 1.00_f32], + engine::sphere(0.0, -4.0, 0.0, R_INNER) + ), + ]; + for sphere in spheres { + let _ = assembly.try_insert_element(sphere); + } + + // get references to the spheres + let [outer, a, b] = ["outer", "a", "b"].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + + // fix the diameters of the outer, sun, and moon spheres + for (sphere, radius) in [ + (outer.clone(), R_OUTER), + (a.clone(), R_INNER), + (b.clone(), R_INNER) + ] { + let curvature_regulator = sphere.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + let curvature = 0.5 / radius; + curvature_regulator.set_point().set( + SpecifiedValue::try_from(curvature.to_string()).unwrap() + ); + } + + // set the inversive distances between the spheres. as described above, the + // initial configuration deliberately violates these constraints + for inner in [a, b] { + let tangency = InversiveDistanceRegulator::new([outer.clone(), inner]); + tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(tangency)); + } +} + // setting the inversive distances between the vertices to -2 gives a regular // tetrahedron with side length 1, whose insphere and circumsphere have radii // sqrt(1/6) and sqrt(3/2), respectively. to measure those radii, set an @@ -507,6 +569,7 @@ pub fn AddRemove() -> View { "general" => load_gen_assemb(assembly), "low-curv" => load_low_curv_assemb(assembly), "pointed" => load_pointed_assemb(assembly), + "balanced" => load_balanced_assemb(assembly), "radius-ratio" => load_radius_ratio_assemb(assembly), "irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly), _ => () @@ -560,6 +623,7 @@ pub fn AddRemove() -> View { option(value="general") { "General" } option(value="low-curv") { "Low-curvature" } option(value="pointed") { "Pointed" } + option(value="balanced") { "Balanced" } option(value="radius-ratio") { "Radius ratio" } option(value="irisawa-hexlet") { "Irisawa hexlet" } option(value="empty") { "Empty" } From 9c22eebb46f9cf1b95ca2ac6239ce541ea293d4e Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 7 Jul 2025 14:10:53 -0700 Subject: [PATCH 05/14] Add an off-center test assembly This test assembly should provide a simple example where the loss function is saddle-shaped near its manifold of minima. --- app-proto/src/add_remove.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index d23a849..bf48546 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -300,6 +300,41 @@ fn load_balanced_assemb(assembly: &Assembly) { } } +/* DEBUG */ +// the initial configuration of this test assembly deliberately violates the +// constraints, so loading the assembly will trigger a non-trivial realization +fn load_off_center_assemb(assembly: &Assembly) { + // create a point almost at the origin and a sphere centered on the origin + let _ = assembly.try_insert_element( + Point::new( + "point".to_string(), + "Point".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::point(1e-9, 0.0, 0.0) + ), + ); + let _ = assembly.try_insert_element( + Sphere::new( + "sphere".to_string(), + "Sphere".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ), + ); + + // get references to the elements + let point_and_sphere = ["point", "sphere"].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + + // put the point on the sphere + let incidence = InversiveDistanceRegulator::new(point_and_sphere); + incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(incidence)); +} + // setting the inversive distances between the vertices to -2 gives a regular // tetrahedron with side length 1, whose insphere and circumsphere have radii // sqrt(1/6) and sqrt(3/2), respectively. to measure those radii, set an @@ -570,6 +605,7 @@ pub fn AddRemove() -> View { "low-curv" => load_low_curv_assemb(assembly), "pointed" => load_pointed_assemb(assembly), "balanced" => load_balanced_assemb(assembly), + "off-center" => load_off_center_assemb(assembly), "radius-ratio" => load_radius_ratio_assemb(assembly), "irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly), _ => () @@ -624,6 +660,7 @@ pub fn AddRemove() -> View { option(value="low-curv") { "Low-curvature" } option(value="pointed") { "Pointed" } option(value="balanced") { "Balanced" } + option(value="off-center") { "Off-center" } option(value="radius-ratio") { "Radius ratio" } option(value="irisawa-hexlet") { "Irisawa hexlet" } option(value="empty") { "Empty" } From 97d61e154a78bb56462b2d105fa92c099eaa470e Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Thu, 10 Jul 2025 11:30:16 -0700 Subject: [PATCH 06/14] Add a tridiminished icosahedron test assembly --- app-proto/src/add_remove.rs | 170 ++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index bf48546..cac7aa9 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -238,6 +238,174 @@ fn load_pointed_assemb(assembly: &Assembly) { } } +// to finish describing the tridiminished icosahedron, set the inversive +// distance regulators as follows: +// A-A -0.25 +// A-B " +// B-C " +// C-C " +// A-C -0.25 * φ^2 = -0.6545084971874737 +fn load_tridim_icosahedron_assemb(assembly: &Assembly) { + // create the vertices + const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.25_f32]; + const COLOR_B: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + const COLOR_C: ElementColor = [0.25_f32, 0.50_f32, 1.00_f32]; + let vertices = [ + Point::new( + "a1".to_string(), + "A₁".to_string(), + COLOR_A, + engine::point(0.25, 0.75, 0.75) + ), + Point::new( + "a2".to_string(), + "A₂".to_string(), + COLOR_A, + engine::point(0.75, 0.25, 0.75) + ), + Point::new( + "a3".to_string(), + "A₃".to_string(), + COLOR_A, + engine::point(0.75, 0.75, 0.25) + ), + Point::new( + "b1".to_string(), + "B₁".to_string(), + COLOR_B, + engine::point(0.75, -0.25, -0.25) + ), + Point::new( + "b2".to_string(), + "B₂".to_string(), + COLOR_B, + engine::point(-0.25, 0.75, -0.25) + ), + Point::new( + "b3".to_string(), + "B₃".to_string(), + COLOR_B, + engine::point(-0.25, -0.25, 0.75) + ), + Point::new( + "c1".to_string(), + "C₁".to_string(), + COLOR_C, + engine::point(0.0, -1.0, -1.0) + ), + Point::new( + "c2".to_string(), + "C₂".to_string(), + COLOR_C, + engine::point(-1.0, 0.0, -1.0) + ), + Point::new( + "c3".to_string(), + "C₃".to_string(), + COLOR_C, + engine::point(-1.0, -1.0, 0.0) + ) + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // create the faces + const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let frac_1_sqrt_6 = 1.0 / 6.0_f64.sqrt(); + let frac_2_sqrt_6 = 2.0 * frac_1_sqrt_6; + let faces = [ + Sphere::new( + "face1".to_string(), + "Face 1".to_string(), + COLOR_FACE, + engine::sphere_with_offset(frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0) + ), + Sphere::new( + "face2".to_string(), + "Face 2".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0) + ), + Sphere::new( + "face3".to_string(), + "Face 3".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-frac_1_sqrt_6, -frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, 0.0) + ) + ]; + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + let index_range = 1..=3; + for j in index_range.clone() { + // make each face planar + let face = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("face{j}")].clone() + ); + let curvature_regulator = face.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set( + SpecifiedValue::try_from("0".to_string()).unwrap() + ); + + // put each A vertex on the face it belongs to + let vertex_a = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("a{j}")].clone() + ); + let incidence_a = InversiveDistanceRegulator::new([face.clone(), vertex_a.clone()]); + incidence_a.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(incidence_a)); + + // regulate the B-C vertex distances + let vertices_bc = ["b", "c"].map( + |series| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("{series}{j}")].clone() + ) + ); + assembly.insert_regulator( + Rc::new(InversiveDistanceRegulator::new(vertices_bc)) + ); + + // get the pair of indices adjacent to `j` + let adjacent_indices = [j % 3 + 1, (j + 1) % 3 + 1]; + + for k in adjacent_indices.clone() { + for series in ["b", "c"] { + // put each B and C vertex on the faces it belongs to + let vertex = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("{series}{k}")].clone() + ); + let incidence = InversiveDistanceRegulator::new([face.clone(), vertex.clone()]); + incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(incidence)); + + // regulate the A-B and A-C vertex distances + assembly.insert_regulator( + Rc::new(InversiveDistanceRegulator::new([vertex_a.clone(), vertex])) + ); + } + } + + // regulate the A-A and C-C vertex distances + let adjacent_pairs = ["a", "c"].map( + |series| adjacent_indices.map( + |index| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("{series}{index}")].clone() + ) + ) + ); + for pair in adjacent_pairs { + assembly.insert_regulator( + Rc::new(InversiveDistanceRegulator::new(pair)) + ); + } + } +} + /* DEBUG */ // the initial configuration of this test assembly deliberately violates the // constraints, so loading the assembly will trigger a non-trivial realization @@ -604,6 +772,7 @@ pub fn AddRemove() -> View { "general" => load_gen_assemb(assembly), "low-curv" => load_low_curv_assemb(assembly), "pointed" => load_pointed_assemb(assembly), + "tridim-icosahedron" => load_tridim_icosahedron_assemb(assembly), "balanced" => load_balanced_assemb(assembly), "off-center" => load_off_center_assemb(assembly), "radius-ratio" => load_radius_ratio_assemb(assembly), @@ -659,6 +828,7 @@ pub fn AddRemove() -> View { option(value="general") { "General" } option(value="low-curv") { "Low-curvature" } option(value="pointed") { "Pointed" } + option(value="tridim-icosahedron") { "Tridiminished icosahedron" } option(value="balanced") { "Balanced" } option(value="off-center") { "Off-center" } option(value="radius-ratio") { "Radius ratio" } From 4e5cd6e4a434d185837a8519e399857a71000a9a Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 14 Jul 2025 23:18:35 -0700 Subject: [PATCH 07/14] Add a dodecahedral circle packing test assembly --- app-proto/src/add_remove.rs | 143 ++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index cac7aa9..fbe37c2 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -406,6 +406,147 @@ fn load_tridim_icosahedron_assemb(assembly: &Assembly) { } } +// to finish describing the dodecahedral circle packing, set the inversive +// distance regulators to -1. some of the regulators have already been set +fn load_dodeca_packing_assemb(assembly: &Assembly) { + // add the substrate + let _ = assembly.try_insert_element( + Sphere::new( + "substrate".to_string(), + "Substrate".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ) + ); + let substrate = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id["substrate"].clone() + ); + + // fix the substrate's curvature + substrate.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ).set_point().set( + SpecifiedValue::try_from("0.5".to_string()).unwrap() + ); + + // add the circles to be packed + const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.00_f32]; + const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32]; + const COLOR_C: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; + let phi = 0.5 + 1.25_f64.sqrt(); /* TO DO */ // replace with std::f64::consts::PHI when that gets stabilized + let phi_inv = 1.0 / phi; + let coord_scale = (phi + 2.0).sqrt(); + let face_scales = [phi_inv, (13.0 / 12.0) / coord_scale]; + let face_radii = [phi_inv, 5.0 / 12.0]; + let mut faces = Vec::>::new(); + let subscripts = ["₀", "₁"]; + for j in 0..2 { + for k in 0..2 { + let small_coord = face_scales[k] * (2.0*(j as f64) - 1.0); + let big_coord = face_scales[k] * (2.0*(k as f64) - 1.0) * phi; + + let id_num = format!("{j}{k}"); + let label_sub = format!("{}{}", subscripts[j], subscripts[k]); + + // add the A face + let id_a = format!("a{id_num}"); + let _ = assembly.try_insert_element( + Sphere::new( + id_a.clone(), + format!("A{label_sub}"), + COLOR_A, + engine::sphere(0.0, small_coord, big_coord, face_radii[k]) + ) + ); + faces.push( + assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id_a].clone() + ) + ); + + // add the B face + let id_b = format!("b{id_num}"); + let _ = assembly.try_insert_element( + Sphere::new( + id_b.clone(), + format!("B{label_sub}"), + COLOR_B, + engine::sphere(small_coord, big_coord, 0.0, face_radii[k]) + ) + ); + faces.push( + assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id_b].clone() + ) + ); + + // add the C face + let id_c = format!("c{id_num}"); + let _ = assembly.try_insert_element( + Sphere::new( + id_c.clone(), + format!("C{label_sub}"), + COLOR_C, + engine::sphere(big_coord, 0.0, small_coord, face_radii[k]) + ) + ); + faces.push( + assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id_c].clone() + ) + ); + } + } + + // make each face sphere perpendicular to the substrate + for face in faces { + let right_angle = InversiveDistanceRegulator::new([face, substrate.clone()]); + right_angle.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(right_angle)); + } + + // set up the tangencies that define the packing + for [long_edge_plane, short_edge_plane] in [["a", "b"], ["b", "c"], ["c", "a"]] { + for k in 0..2 { + let long_edge_ids = [ + format!("{long_edge_plane}{k}0"), + format!("{long_edge_plane}{k}1") + ]; + let short_edge_ids = [ + format!("{short_edge_plane}0{k}"), + format!("{short_edge_plane}1{k}") + ]; + let [long_edge, short_edge] = [long_edge_ids, short_edge_ids].map( + |edge_ids| edge_ids.map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id].clone() + ) + ) + ); + + // set up the short-edge tangency + let short_tangency = InversiveDistanceRegulator::new(short_edge.clone()); + if k == 0 { + short_tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); + } + assembly.insert_regulator(Rc::new(short_tangency)); + + // set up the side tangencies + for i in 0..2 { + for j in 0..2 { + let side_tangency = InversiveDistanceRegulator::new( + [long_edge[i].clone(), short_edge[j].clone()] + ); + if i == 0 && k == 0 { + side_tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); + } + assembly.insert_regulator(Rc::new(side_tangency)); + } + } + } + } +} + /* DEBUG */ // the initial configuration of this test assembly deliberately violates the // constraints, so loading the assembly will trigger a non-trivial realization @@ -773,6 +914,7 @@ pub fn AddRemove() -> View { "low-curv" => load_low_curv_assemb(assembly), "pointed" => load_pointed_assemb(assembly), "tridim-icosahedron" => load_tridim_icosahedron_assemb(assembly), + "dodeca-packing" => load_dodeca_packing_assemb(assembly), "balanced" => load_balanced_assemb(assembly), "off-center" => load_off_center_assemb(assembly), "radius-ratio" => load_radius_ratio_assemb(assembly), @@ -829,6 +971,7 @@ pub fn AddRemove() -> View { option(value="low-curv") { "Low-curvature" } option(value="pointed") { "Pointed" } option(value="tridim-icosahedron") { "Tridiminished icosahedron" } + option(value="dodeca-packing") { "Dodecahedral packing" } option(value="balanced") { "Balanced" } option(value="off-center") { "Off-center" } option(value="radius-ratio") { "Radius ratio" } From 91e4e1f4143a1632621ccce498ceb8f0e7d41d18 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 21 Jul 2025 12:16:16 -0700 Subject: [PATCH 08/14] Make the test assembly chooser its own component --- app-proto/src/add_remove.rs | 937 +----------------------- app-proto/src/main.rs | 1 + app-proto/src/test_assembly_chooser.rs | 947 +++++++++++++++++++++++++ 3 files changed, 952 insertions(+), 933 deletions(-) create mode 100644 app-proto/src/test_assembly_chooser.rs diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index fbe37c2..de9272e 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -1,932 +1,14 @@ -use itertools::izip; -use std::{f64::consts::{FRAC_1_SQRT_2, PI}, rc::Rc}; -use nalgebra::Vector3; +use std::rc::Rc; use sycamore::prelude::*; -use web_sys::{console, wasm_bindgen::JsValue}; use crate::{ AppState, - engine, - engine::DescentHistory, - assembly::{ - Assembly, - Element, - ElementColor, - InversiveDistanceRegulator, - Point, - Sphere - }, - specified::SpecifiedValue + assembly::{InversiveDistanceRegulator, Point, Sphere}, + test_assembly_chooser::TestAssemblyChooser }; -/* DEBUG */ -// load an example assembly for testing. this code will be removed once we've -// built a more formal test assembly system -fn load_gen_assemb(assembly: &Assembly) { - let _ = assembly.try_insert_element( - Sphere::new( - String::from("gemini_a"), - String::from("Castor"), - [1.00_f32, 0.25_f32, 0.00_f32], - engine::sphere(0.5, 0.5, 0.0, 1.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("gemini_b"), - String::from("Pollux"), - [0.00_f32, 0.25_f32, 1.00_f32], - engine::sphere(-0.5, -0.5, 0.0, 1.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("ursa_major"), - String::from("Ursa major"), - [0.25_f32, 0.00_f32, 1.00_f32], - engine::sphere(-0.5, 0.5, 0.0, 0.75) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("ursa_minor"), - String::from("Ursa minor"), - [0.25_f32, 1.00_f32, 0.00_f32], - engine::sphere(0.5, -0.5, 0.0, 0.5) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("moon_deimos"), - String::from("Deimos"), - [0.75_f32, 0.75_f32, 0.00_f32], - engine::sphere(0.0, 0.15, 1.0, 0.25) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("moon_phobos"), - String::from("Phobos"), - [0.00_f32, 0.75_f32, 0.50_f32], - engine::sphere(0.0, -0.15, -1.0, 0.25) - ) - ); -} - -/* DEBUG */ -// load an example assembly for testing. this code will be removed once we've -// built a more formal test assembly system -fn load_low_curv_assemb(assembly: &Assembly) { - // create the spheres - let a = 0.75_f64.sqrt(); - let _ = assembly.try_insert_element( - Sphere::new( - "central".to_string(), - "Central".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, 1.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "assemb_plane".to_string(), - "Assembly plane".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "side1".to_string(), - "Side 1".to_string(), - [1.00_f32, 0.00_f32, 0.25_f32], - engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "side2".to_string(), - "Side 2".to_string(), - [0.25_f32, 1.00_f32, 0.00_f32], - engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "side3".to_string(), - "Side 3".to_string(), - [0.00_f32, 0.25_f32, 1.00_f32], - engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "corner1".to_string(), - "Corner 1".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "corner2".to_string(), - "Corner 2".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("corner3"), - String::from("Corner 3"), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0) - ) - ); - - // impose the desired tangencies and make the sides planar - let index_range = 1..=3; - let [central, assemb_plane] = ["central", "assemb_plane"].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[id].clone() - ) - ); - let sides = index_range.clone().map( - |k| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("side{k}")].clone() - ) - ); - let corners = index_range.map( - |k| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("corner{k}")].clone() - ) - ); - for plane in [assemb_plane.clone()].into_iter().chain(sides.clone()) { - // fix the curvature of each plane - let curvature = plane.regulators().with_untracked( - |regs| regs.first().unwrap().clone() - ); - curvature.set_point().set(SpecifiedValue::try_from("0".to_string()).unwrap()); - } - let all_perpendicular = [central.clone()].into_iter() - .chain(sides.clone()) - .chain(corners.clone()); - for sphere in all_perpendicular { - // make each side and packed sphere perpendicular to the assembly plane - let right_angle = InversiveDistanceRegulator::new([sphere, assemb_plane.clone()]); - right_angle.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(right_angle)); - } - for sphere in sides.clone().chain(corners.clone()) { - // make each side and corner sphere tangent to the central sphere - let tangency = InversiveDistanceRegulator::new([sphere.clone(), central.clone()]); - tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(tangency)); - } - for (side_index, side) in sides.enumerate() { - // make each side tangent to the two adjacent corner spheres - for (corner_index, corner) in corners.clone().enumerate() { - if side_index != corner_index { - let tangency = InversiveDistanceRegulator::new([side.clone(), corner]); - tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(tangency)); - } - } - } -} - -fn load_pointed_assemb(assembly: &Assembly) { - let _ = assembly.try_insert_element( - Point::new( - format!("point_front"), - format!("Front point"), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::point(0.0, 0.0, FRAC_1_SQRT_2) - ) - ); - let _ = assembly.try_insert_element( - Point::new( - format!("point_back"), - format!("Back point"), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::point(0.0, 0.0, -FRAC_1_SQRT_2) - ) - ); - for index_x in 0..=1 { - for index_y in 0..=1 { - let x = index_x as f64 - 0.5; - let y = index_y as f64 - 0.5; - - let _ = assembly.try_insert_element( - Sphere::new( - format!("sphere{index_x}{index_y}"), - format!("Sphere {index_x}{index_y}"), - [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], - engine::sphere(x, y, 0.0, 1.0) - ) - ); - - let _ = assembly.try_insert_element( - Point::new( - format!("point{index_x}{index_y}"), - format!("Point {index_x}{index_y}"), - [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], - engine::point(x, y, 0.0) - ) - ); - } - } -} - -// to finish describing the tridiminished icosahedron, set the inversive -// distance regulators as follows: -// A-A -0.25 -// A-B " -// B-C " -// C-C " -// A-C -0.25 * φ^2 = -0.6545084971874737 -fn load_tridim_icosahedron_assemb(assembly: &Assembly) { - // create the vertices - const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.25_f32]; - const COLOR_B: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; - const COLOR_C: ElementColor = [0.25_f32, 0.50_f32, 1.00_f32]; - let vertices = [ - Point::new( - "a1".to_string(), - "A₁".to_string(), - COLOR_A, - engine::point(0.25, 0.75, 0.75) - ), - Point::new( - "a2".to_string(), - "A₂".to_string(), - COLOR_A, - engine::point(0.75, 0.25, 0.75) - ), - Point::new( - "a3".to_string(), - "A₃".to_string(), - COLOR_A, - engine::point(0.75, 0.75, 0.25) - ), - Point::new( - "b1".to_string(), - "B₁".to_string(), - COLOR_B, - engine::point(0.75, -0.25, -0.25) - ), - Point::new( - "b2".to_string(), - "B₂".to_string(), - COLOR_B, - engine::point(-0.25, 0.75, -0.25) - ), - Point::new( - "b3".to_string(), - "B₃".to_string(), - COLOR_B, - engine::point(-0.25, -0.25, 0.75) - ), - Point::new( - "c1".to_string(), - "C₁".to_string(), - COLOR_C, - engine::point(0.0, -1.0, -1.0) - ), - Point::new( - "c2".to_string(), - "C₂".to_string(), - COLOR_C, - engine::point(-1.0, 0.0, -1.0) - ), - Point::new( - "c3".to_string(), - "C₃".to_string(), - COLOR_C, - engine::point(-1.0, -1.0, 0.0) - ) - ]; - for vertex in vertices { - let _ = assembly.try_insert_element(vertex); - } - - // create the faces - const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; - let frac_1_sqrt_6 = 1.0 / 6.0_f64.sqrt(); - let frac_2_sqrt_6 = 2.0 * frac_1_sqrt_6; - let faces = [ - Sphere::new( - "face1".to_string(), - "Face 1".to_string(), - COLOR_FACE, - engine::sphere_with_offset(frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0) - ), - Sphere::new( - "face2".to_string(), - "Face 2".to_string(), - COLOR_FACE, - engine::sphere_with_offset(-frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0) - ), - Sphere::new( - "face3".to_string(), - "Face 3".to_string(), - COLOR_FACE, - engine::sphere_with_offset(-frac_1_sqrt_6, -frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, 0.0) - ) - ]; - for face in faces { - face.ghost().set(true); - let _ = assembly.try_insert_element(face); - } - - let index_range = 1..=3; - for j in index_range.clone() { - // make each face planar - let face = assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("face{j}")].clone() - ); - let curvature_regulator = face.regulators().with_untracked( - |regs| regs.first().unwrap().clone() - ); - curvature_regulator.set_point().set( - SpecifiedValue::try_from("0".to_string()).unwrap() - ); - - // put each A vertex on the face it belongs to - let vertex_a = assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("a{j}")].clone() - ); - let incidence_a = InversiveDistanceRegulator::new([face.clone(), vertex_a.clone()]); - incidence_a.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(incidence_a)); - - // regulate the B-C vertex distances - let vertices_bc = ["b", "c"].map( - |series| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("{series}{j}")].clone() - ) - ); - assembly.insert_regulator( - Rc::new(InversiveDistanceRegulator::new(vertices_bc)) - ); - - // get the pair of indices adjacent to `j` - let adjacent_indices = [j % 3 + 1, (j + 1) % 3 + 1]; - - for k in adjacent_indices.clone() { - for series in ["b", "c"] { - // put each B and C vertex on the faces it belongs to - let vertex = assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("{series}{k}")].clone() - ); - let incidence = InversiveDistanceRegulator::new([face.clone(), vertex.clone()]); - incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(incidence)); - - // regulate the A-B and A-C vertex distances - assembly.insert_regulator( - Rc::new(InversiveDistanceRegulator::new([vertex_a.clone(), vertex])) - ); - } - } - - // regulate the A-A and C-C vertex distances - let adjacent_pairs = ["a", "c"].map( - |series| adjacent_indices.map( - |index| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("{series}{index}")].clone() - ) - ) - ); - for pair in adjacent_pairs { - assembly.insert_regulator( - Rc::new(InversiveDistanceRegulator::new(pair)) - ); - } - } -} - -// to finish describing the dodecahedral circle packing, set the inversive -// distance regulators to -1. some of the regulators have already been set -fn load_dodeca_packing_assemb(assembly: &Assembly) { - // add the substrate - let _ = assembly.try_insert_element( - Sphere::new( - "substrate".to_string(), - "Substrate".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, 1.0) - ) - ); - let substrate = assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id["substrate"].clone() - ); - - // fix the substrate's curvature - substrate.regulators().with_untracked( - |regs| regs.first().unwrap().clone() - ).set_point().set( - SpecifiedValue::try_from("0.5".to_string()).unwrap() - ); - - // add the circles to be packed - const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.00_f32]; - const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32]; - const COLOR_C: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; - let phi = 0.5 + 1.25_f64.sqrt(); /* TO DO */ // replace with std::f64::consts::PHI when that gets stabilized - let phi_inv = 1.0 / phi; - let coord_scale = (phi + 2.0).sqrt(); - let face_scales = [phi_inv, (13.0 / 12.0) / coord_scale]; - let face_radii = [phi_inv, 5.0 / 12.0]; - let mut faces = Vec::>::new(); - let subscripts = ["₀", "₁"]; - for j in 0..2 { - for k in 0..2 { - let small_coord = face_scales[k] * (2.0*(j as f64) - 1.0); - let big_coord = face_scales[k] * (2.0*(k as f64) - 1.0) * phi; - - let id_num = format!("{j}{k}"); - let label_sub = format!("{}{}", subscripts[j], subscripts[k]); - - // add the A face - let id_a = format!("a{id_num}"); - let _ = assembly.try_insert_element( - Sphere::new( - id_a.clone(), - format!("A{label_sub}"), - COLOR_A, - engine::sphere(0.0, small_coord, big_coord, face_radii[k]) - ) - ); - faces.push( - assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&id_a].clone() - ) - ); - - // add the B face - let id_b = format!("b{id_num}"); - let _ = assembly.try_insert_element( - Sphere::new( - id_b.clone(), - format!("B{label_sub}"), - COLOR_B, - engine::sphere(small_coord, big_coord, 0.0, face_radii[k]) - ) - ); - faces.push( - assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&id_b].clone() - ) - ); - - // add the C face - let id_c = format!("c{id_num}"); - let _ = assembly.try_insert_element( - Sphere::new( - id_c.clone(), - format!("C{label_sub}"), - COLOR_C, - engine::sphere(big_coord, 0.0, small_coord, face_radii[k]) - ) - ); - faces.push( - assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&id_c].clone() - ) - ); - } - } - - // make each face sphere perpendicular to the substrate - for face in faces { - let right_angle = InversiveDistanceRegulator::new([face, substrate.clone()]); - right_angle.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(right_angle)); - } - - // set up the tangencies that define the packing - for [long_edge_plane, short_edge_plane] in [["a", "b"], ["b", "c"], ["c", "a"]] { - for k in 0..2 { - let long_edge_ids = [ - format!("{long_edge_plane}{k}0"), - format!("{long_edge_plane}{k}1") - ]; - let short_edge_ids = [ - format!("{short_edge_plane}0{k}"), - format!("{short_edge_plane}1{k}") - ]; - let [long_edge, short_edge] = [long_edge_ids, short_edge_ids].map( - |edge_ids| edge_ids.map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&id].clone() - ) - ) - ); - - // set up the short-edge tangency - let short_tangency = InversiveDistanceRegulator::new(short_edge.clone()); - if k == 0 { - short_tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); - } - assembly.insert_regulator(Rc::new(short_tangency)); - - // set up the side tangencies - for i in 0..2 { - for j in 0..2 { - let side_tangency = InversiveDistanceRegulator::new( - [long_edge[i].clone(), short_edge[j].clone()] - ); - if i == 0 && k == 0 { - side_tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); - } - assembly.insert_regulator(Rc::new(side_tangency)); - } - } - } - } -} - -/* DEBUG */ -// the initial configuration of this test assembly deliberately violates the -// constraints, so loading the assembly will trigger a non-trivial realization -fn load_balanced_assemb(assembly: &Assembly) { - // create the spheres - const R_OUTER: f64 = 10.0; - const R_INNER: f64 = 4.0; - let spheres = [ - Sphere::new( - "outer".to_string(), - "Outer".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, R_OUTER) - ), - Sphere::new( - "a".to_string(), - "A".to_string(), - [1.00_f32, 0.00_f32, 0.25_f32], - engine::sphere(0.0, 4.0, 0.0, R_INNER) - ), - Sphere::new( - "b".to_string(), - "B".to_string(), - [0.00_f32, 0.25_f32, 1.00_f32], - engine::sphere(0.0, -4.0, 0.0, R_INNER) - ), - ]; - for sphere in spheres { - let _ = assembly.try_insert_element(sphere); - } - - // get references to the spheres - let [outer, a, b] = ["outer", "a", "b"].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[id].clone() - ) - ); - - // fix the diameters of the outer, sun, and moon spheres - for (sphere, radius) in [ - (outer.clone(), R_OUTER), - (a.clone(), R_INNER), - (b.clone(), R_INNER) - ] { - let curvature_regulator = sphere.regulators().with_untracked( - |regs| regs.first().unwrap().clone() - ); - let curvature = 0.5 / radius; - curvature_regulator.set_point().set( - SpecifiedValue::try_from(curvature.to_string()).unwrap() - ); - } - - // set the inversive distances between the spheres. as described above, the - // initial configuration deliberately violates these constraints - for inner in [a, b] { - let tangency = InversiveDistanceRegulator::new([outer.clone(), inner]); - tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(tangency)); - } -} - -/* DEBUG */ -// the initial configuration of this test assembly deliberately violates the -// constraints, so loading the assembly will trigger a non-trivial realization -fn load_off_center_assemb(assembly: &Assembly) { - // create a point almost at the origin and a sphere centered on the origin - let _ = assembly.try_insert_element( - Point::new( - "point".to_string(), - "Point".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::point(1e-9, 0.0, 0.0) - ), - ); - let _ = assembly.try_insert_element( - Sphere::new( - "sphere".to_string(), - "Sphere".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, 1.0) - ), - ); - - // get references to the elements - let point_and_sphere = ["point", "sphere"].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[id].clone() - ) - ); - - // put the point on the sphere - let incidence = InversiveDistanceRegulator::new(point_and_sphere); - incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(incidence)); -} - -// setting the inversive distances between the vertices to -2 gives a regular -// tetrahedron with side length 1, whose insphere and circumsphere have radii -// sqrt(1/6) and sqrt(3/2), respectively. to measure those radii, set an -// inversive distance of -1 between the insphere and each face, and then set an -// inversive distance of 0 between the circumsphere and each vertex -fn load_radius_ratio_assemb(assembly: &Assembly) { - let index_range = 1..=4; - - // create the spheres - const GRAY: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; - let spheres = [ - Sphere::new( - "sphere_faces".to_string(), - "Insphere".to_string(), - GRAY, - engine::sphere(0.0, 0.0, 0.0, 0.5) - ), - Sphere::new( - "sphere_vertices".to_string(), - "Circumsphere".to_string(), - GRAY, - engine::sphere(0.0, 0.0, 0.0, 0.25) - ) - ]; - for sphere in spheres { - let _ = assembly.try_insert_element(sphere); - } - - // create the vertices - let vertices = izip!( - index_range.clone(), - [ - [1.00_f32, 0.50_f32, 0.75_f32], - [1.00_f32, 0.75_f32, 0.50_f32], - [1.00_f32, 1.00_f32, 0.50_f32], - [0.75_f32, 0.50_f32, 1.00_f32] - ].into_iter(), - [ - engine::point(-0.6, -0.8, -0.6), - engine::point(-0.6, 0.8, 0.6), - engine::point(0.6, -0.8, 0.6), - engine::point(0.6, 0.8, -0.6) - ].into_iter() - ).map( - |(k, color, representation)| { - Point::new( - format!("v{k}"), - format!("Vertex {k}"), - color, - representation - ) - } - ); - for vertex in vertices { - let _ = assembly.try_insert_element(vertex); - } - - // create the faces - let base_dir = Vector3::new(1.0, 0.75, 1.0).normalize(); - let offset = base_dir.dot(&Vector3::new(-0.6, 0.8, 0.6)); - let faces = izip!( - index_range.clone(), - [ - [1.00_f32, 0.00_f32, 0.25_f32], - [1.00_f32, 0.25_f32, 0.00_f32], - [0.75_f32, 0.75_f32, 0.00_f32], - [0.25_f32, 0.00_f32, 1.00_f32] - ].into_iter(), - [ - engine::sphere_with_offset(base_dir[0], base_dir[1], base_dir[2], offset, 0.0), - engine::sphere_with_offset(base_dir[0], -base_dir[1], -base_dir[2], offset, 0.0), - engine::sphere_with_offset(-base_dir[0], base_dir[1], -base_dir[2], offset, 0.0), - engine::sphere_with_offset(-base_dir[0], -base_dir[1], base_dir[2], offset, 0.0) - ].into_iter() - ).map( - |(k, color, representation)| { - Sphere::new( - format!("f{k}"), - format!("Face {k}"), - color, - representation - ) - } - ); - for face in faces { - face.ghost().set(true); - let _ = assembly.try_insert_element(face); - } - - // impose the constraints - for j in index_range.clone() { - let [face_j, vertex_j] = [ - format!("f{j}"), - format!("v{j}") - ].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&id].clone() - ) - ); - - // make the faces planar - let curvature_regulator = face_j.regulators().with_untracked( - |regs| regs.first().unwrap().clone() - ); - curvature_regulator.set_point().set( - SpecifiedValue::try_from("0".to_string()).unwrap() - ); - - for k in index_range.clone().filter(|&index| index != j) { - let vertex_k = assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("v{k}")].clone() - ); - - // fix the distances between the vertices - if j < k { - let distance_regulator = InversiveDistanceRegulator::new( - [vertex_j.clone(), vertex_k.clone()] - ); - assembly.insert_regulator(Rc::new(distance_regulator)); - } - - // put the vertices on the faces - let incidence_regulator = InversiveDistanceRegulator::new([face_j.clone(), vertex_k.clone()]); - incidence_regulator.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(incidence_regulator)); - } - } -} - -// to finish setting up the problem, fix the following curvatures: -// sun 1 -// moon 5/3 = 1.666666666666666... -// chain1 2 -// a tiny `x` or `z` nudge of the outer sphere reliably prevents realization -// failures before they happen, or resolves them after they happen. the result -// depends sensitively on the translation direction, suggesting that realization -// is failing because the engine is having trouble breaking a symmetry -// /* TO DO */ -// the engine's performance on this problem is scale-dependent! with the current -// initial conditions, realization fails for any order of imposing the remaining -// curvature constraints. scaling everything up by a factor of ten, as done in -// the original problem, makes realization succeed reliably. one potentially -// relevant difference is that a lot of the numbers in the current initial -// conditions are exactly representable as floats, unlike the analogous numbers -// in the scaled-up problem. the inexact representations might break the -// symmetry that's getting the engine stuck -fn load_irisawa_hexlet_assemb(assembly: &Assembly) { - let index_range = 1..=6; - let colors = [ - [1.00_f32, 0.00_f32, 0.25_f32], - [1.00_f32, 0.25_f32, 0.00_f32], - [0.75_f32, 0.75_f32, 0.00_f32], - [0.25_f32, 1.00_f32, 0.00_f32], - [0.00_f32, 0.25_f32, 1.00_f32], - [0.25_f32, 0.00_f32, 1.00_f32] - ].into_iter(); - - // create the spheres - let spheres = [ - Sphere::new( - "outer".to_string(), - "Outer".to_string(), - [0.5_f32, 0.5_f32, 0.5_f32], - engine::sphere(0.0, 0.0, 0.0, 1.5) - ), - Sphere::new( - "sun".to_string(), - "Sun".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, -0.75, 0.0, 0.75) - ), - Sphere::new( - "moon".to_string(), - "Moon".to_string(), - [0.25_f32, 0.25_f32, 0.25_f32], - engine::sphere(0.0, 0.75, 0.0, 0.75) - ), - ].into_iter().chain( - index_range.clone().zip(colors).map( - |(k, color)| { - let ang = (k as f64) * PI/3.0; - Sphere::new( - format!("chain{k}"), - format!("Chain {k}"), - color, - engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5) - ) - } - ) - ); - for sphere in spheres { - let _ = assembly.try_insert_element(sphere); - } - - // put the outer sphere in ghost mode and fix its curvature - let outer = assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id["outer"].clone() - ); - outer.ghost().set(true); - let outer_curvature_regulator = outer.regulators().with_untracked( - |regs| regs.first().unwrap().clone() - ); - outer_curvature_regulator.set_point().set( - SpecifiedValue::try_from((1.0 / 3.0).to_string()).unwrap() - ); - - // impose the desired tangencies - let [outer, sun, moon] = ["outer", "sun", "moon"].map( - |id| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[id].clone() - ) - ); - let chain = index_range.map( - |k| assembly.elements_by_id.with_untracked( - |elts_by_id| elts_by_id[&format!("chain{k}")].clone() - ) - ); - for (chain_sphere, chain_sphere_next) in chain.clone().zip(chain.cycle().skip(1)) { - for (other_sphere, inversive_distance) in [ - (outer.clone(), "1"), - (sun.clone(), "-1"), - (moon.clone(), "-1"), - (chain_sphere_next.clone(), "-1") - ] { - let tangency = InversiveDistanceRegulator::new([chain_sphere.clone(), other_sphere]); - tangency.set_point.set(SpecifiedValue::try_from(inversive_distance.to_string()).unwrap()); - assembly.insert_regulator(Rc::new(tangency)); - } - } - - let outer_sun_tangency = InversiveDistanceRegulator::new([outer.clone(), sun]); - outer_sun_tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(outer_sun_tangency)); - - let outer_moon_tangency = InversiveDistanceRegulator::new([outer.clone(), moon]); - outer_moon_tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); - assembly.insert_regulator(Rc::new(outer_moon_tangency)); -} - #[component] pub fn AddRemove() -> View { - /* DEBUG */ - let assembly_name = create_signal("general".to_string()); - create_effect(move || { - // get name of chosen assembly - let name = assembly_name.get_clone(); - console::log_1( - &JsValue::from(format!("Showing assembly \"{}\"", name.clone())) - ); - - batch(|| { - let state = use_context::(); - let assembly = &state.assembly; - - // pause realization - assembly.keep_realized.set(false); - - // clear state - assembly.regulators.update(|regs| regs.clear()); - assembly.elements.update(|elts| elts.clear()); - assembly.elements_by_id.update(|elts_by_id| elts_by_id.clear()); - assembly.descent_history.set(DescentHistory::new()); - state.selection.update(|sel| sel.clear()); - - // load assembly - match name.as_str() { - "general" => load_gen_assemb(assembly), - "low-curv" => load_low_curv_assemb(assembly), - "pointed" => load_pointed_assemb(assembly), - "tridim-icosahedron" => load_tridim_icosahedron_assemb(assembly), - "dodeca-packing" => load_dodeca_packing_assemb(assembly), - "balanced" => load_balanced_assemb(assembly), - "off-center" => load_off_center_assemb(assembly), - "radius-ratio" => load_radius_ratio_assemb(assembly), - "irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly), - _ => () - }; - - // resume realization - assembly.keep_realized.set(true); - }); - }); - view! { div(id="add-remove") { button( @@ -966,18 +48,7 @@ pub fn AddRemove() -> View { state.selection.update(|sel| sel.clear()); } ) { "🔗" } - select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser - option(value="general") { "General" } - option(value="low-curv") { "Low-curvature" } - option(value="pointed") { "Pointed" } - option(value="tridim-icosahedron") { "Tridiminished icosahedron" } - option(value="dodeca-packing") { "Dodecahedral packing" } - option(value="balanced") { "Balanced" } - option(value="off-center") { "Off-center" } - option(value="radius-ratio") { "Radius ratio" } - option(value="irisawa-hexlet") { "Irisawa hexlet" } - option(value="empty") { "Empty" } - } + TestAssemblyChooser {} } } } \ No newline at end of file diff --git a/app-proto/src/main.rs b/app-proto/src/main.rs index f905c46..f5b7cef 100644 --- a/app-proto/src/main.rs +++ b/app-proto/src/main.rs @@ -5,6 +5,7 @@ mod display; mod engine; mod outline; mod specified; +mod test_assembly_chooser; #[cfg(test)] mod tests; diff --git a/app-proto/src/test_assembly_chooser.rs b/app-proto/src/test_assembly_chooser.rs new file mode 100644 index 0000000..232cda3 --- /dev/null +++ b/app-proto/src/test_assembly_chooser.rs @@ -0,0 +1,947 @@ +use itertools::izip; +use std::{f64::consts::{FRAC_1_SQRT_2, PI}, rc::Rc}; +use nalgebra::Vector3; +use sycamore::prelude::*; +use web_sys::{console, wasm_bindgen::JsValue}; + +use crate::{ + AppState, + engine, + engine::DescentHistory, + assembly::{ + Assembly, + Element, + ElementColor, + InversiveDistanceRegulator, + Point, + Sphere + }, + specified::SpecifiedValue +}; + +// --- loaders --- + +/* DEBUG */ +// each of these functions loads an example assembly for testing. once we've +// done more work on saving and loading assemblies, we should come back to this +// code to see if it can be simplified + +fn load_gen_assemb(assembly: &Assembly) { + let _ = assembly.try_insert_element( + Sphere::new( + String::from("gemini_a"), + String::from("Castor"), + [1.00_f32, 0.25_f32, 0.00_f32], + engine::sphere(0.5, 0.5, 0.0, 1.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("gemini_b"), + String::from("Pollux"), + [0.00_f32, 0.25_f32, 1.00_f32], + engine::sphere(-0.5, -0.5, 0.0, 1.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("ursa_major"), + String::from("Ursa major"), + [0.25_f32, 0.00_f32, 1.00_f32], + engine::sphere(-0.5, 0.5, 0.0, 0.75) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("ursa_minor"), + String::from("Ursa minor"), + [0.25_f32, 1.00_f32, 0.00_f32], + engine::sphere(0.5, -0.5, 0.0, 0.5) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("moon_deimos"), + String::from("Deimos"), + [0.75_f32, 0.75_f32, 0.00_f32], + engine::sphere(0.0, 0.15, 1.0, 0.25) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("moon_phobos"), + String::from("Phobos"), + [0.00_f32, 0.75_f32, 0.50_f32], + engine::sphere(0.0, -0.15, -1.0, 0.25) + ) + ); +} + +fn load_low_curv_assemb(assembly: &Assembly) { + // create the spheres + let a = 0.75_f64.sqrt(); + let _ = assembly.try_insert_element( + Sphere::new( + "central".to_string(), + "Central".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "assemb_plane".to_string(), + "Assembly plane".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "side1".to_string(), + "Side 1".to_string(), + [1.00_f32, 0.00_f32, 0.25_f32], + engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "side2".to_string(), + "Side 2".to_string(), + [0.25_f32, 1.00_f32, 0.00_f32], + engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "side3".to_string(), + "Side 3".to_string(), + [0.00_f32, 0.25_f32, 1.00_f32], + engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "corner1".to_string(), + "Corner 1".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "corner2".to_string(), + "Corner 2".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("corner3"), + String::from("Corner 3"), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0) + ) + ); + + // impose the desired tangencies and make the sides planar + let index_range = 1..=3; + let [central, assemb_plane] = ["central", "assemb_plane"].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + let sides = index_range.clone().map( + |k| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("side{k}")].clone() + ) + ); + let corners = index_range.map( + |k| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("corner{k}")].clone() + ) + ); + for plane in [assemb_plane.clone()].into_iter().chain(sides.clone()) { + // fix the curvature of each plane + let curvature = plane.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature.set_point().set(SpecifiedValue::try_from("0".to_string()).unwrap()); + } + let all_perpendicular = [central.clone()].into_iter() + .chain(sides.clone()) + .chain(corners.clone()); + for sphere in all_perpendicular { + // make each side and packed sphere perpendicular to the assembly plane + let right_angle = InversiveDistanceRegulator::new([sphere, assemb_plane.clone()]); + right_angle.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(right_angle)); + } + for sphere in sides.clone().chain(corners.clone()) { + // make each side and corner sphere tangent to the central sphere + let tangency = InversiveDistanceRegulator::new([sphere.clone(), central.clone()]); + tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(tangency)); + } + for (side_index, side) in sides.enumerate() { + // make each side tangent to the two adjacent corner spheres + for (corner_index, corner) in corners.clone().enumerate() { + if side_index != corner_index { + let tangency = InversiveDistanceRegulator::new([side.clone(), corner]); + tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(tangency)); + } + } + } +} + +fn load_pointed_assemb(assembly: &Assembly) { + let _ = assembly.try_insert_element( + Point::new( + format!("point_front"), + format!("Front point"), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::point(0.0, 0.0, FRAC_1_SQRT_2) + ) + ); + let _ = assembly.try_insert_element( + Point::new( + format!("point_back"), + format!("Back point"), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::point(0.0, 0.0, -FRAC_1_SQRT_2) + ) + ); + for index_x in 0..=1 { + for index_y in 0..=1 { + let x = index_x as f64 - 0.5; + let y = index_y as f64 - 0.5; + + let _ = assembly.try_insert_element( + Sphere::new( + format!("sphere{index_x}{index_y}"), + format!("Sphere {index_x}{index_y}"), + [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], + engine::sphere(x, y, 0.0, 1.0) + ) + ); + + let _ = assembly.try_insert_element( + Point::new( + format!("point{index_x}{index_y}"), + format!("Point {index_x}{index_y}"), + [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], + engine::point(x, y, 0.0) + ) + ); + } + } +} + +// to finish describing the tridiminished icosahedron, set the inversive +// distance regulators as follows: +// A-A -0.25 +// A-B " +// B-C " +// C-C " +// A-C -0.25 * φ^2 = -0.6545084971874737 +fn load_tridim_icosahedron_assemb(assembly: &Assembly) { + // create the vertices + const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.25_f32]; + const COLOR_B: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + const COLOR_C: ElementColor = [0.25_f32, 0.50_f32, 1.00_f32]; + let vertices = [ + Point::new( + "a1".to_string(), + "A₁".to_string(), + COLOR_A, + engine::point(0.25, 0.75, 0.75) + ), + Point::new( + "a2".to_string(), + "A₂".to_string(), + COLOR_A, + engine::point(0.75, 0.25, 0.75) + ), + Point::new( + "a3".to_string(), + "A₃".to_string(), + COLOR_A, + engine::point(0.75, 0.75, 0.25) + ), + Point::new( + "b1".to_string(), + "B₁".to_string(), + COLOR_B, + engine::point(0.75, -0.25, -0.25) + ), + Point::new( + "b2".to_string(), + "B₂".to_string(), + COLOR_B, + engine::point(-0.25, 0.75, -0.25) + ), + Point::new( + "b3".to_string(), + "B₃".to_string(), + COLOR_B, + engine::point(-0.25, -0.25, 0.75) + ), + Point::new( + "c1".to_string(), + "C₁".to_string(), + COLOR_C, + engine::point(0.0, -1.0, -1.0) + ), + Point::new( + "c2".to_string(), + "C₂".to_string(), + COLOR_C, + engine::point(-1.0, 0.0, -1.0) + ), + Point::new( + "c3".to_string(), + "C₃".to_string(), + COLOR_C, + engine::point(-1.0, -1.0, 0.0) + ) + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // create the faces + const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let frac_1_sqrt_6 = 1.0 / 6.0_f64.sqrt(); + let frac_2_sqrt_6 = 2.0 * frac_1_sqrt_6; + let faces = [ + Sphere::new( + "face1".to_string(), + "Face 1".to_string(), + COLOR_FACE, + engine::sphere_with_offset(frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0) + ), + Sphere::new( + "face2".to_string(), + "Face 2".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0) + ), + Sphere::new( + "face3".to_string(), + "Face 3".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-frac_1_sqrt_6, -frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, 0.0) + ) + ]; + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + let index_range = 1..=3; + for j in index_range.clone() { + // make each face planar + let face = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("face{j}")].clone() + ); + let curvature_regulator = face.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set( + SpecifiedValue::try_from("0".to_string()).unwrap() + ); + + // put each A vertex on the face it belongs to + let vertex_a = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("a{j}")].clone() + ); + let incidence_a = InversiveDistanceRegulator::new([face.clone(), vertex_a.clone()]); + incidence_a.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(incidence_a)); + + // regulate the B-C vertex distances + let vertices_bc = ["b", "c"].map( + |series| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("{series}{j}")].clone() + ) + ); + assembly.insert_regulator( + Rc::new(InversiveDistanceRegulator::new(vertices_bc)) + ); + + // get the pair of indices adjacent to `j` + let adjacent_indices = [j % 3 + 1, (j + 1) % 3 + 1]; + + for k in adjacent_indices.clone() { + for series in ["b", "c"] { + // put each B and C vertex on the faces it belongs to + let vertex = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("{series}{k}")].clone() + ); + let incidence = InversiveDistanceRegulator::new([face.clone(), vertex.clone()]); + incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(incidence)); + + // regulate the A-B and A-C vertex distances + assembly.insert_regulator( + Rc::new(InversiveDistanceRegulator::new([vertex_a.clone(), vertex])) + ); + } + } + + // regulate the A-A and C-C vertex distances + let adjacent_pairs = ["a", "c"].map( + |series| adjacent_indices.map( + |index| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("{series}{index}")].clone() + ) + ) + ); + for pair in adjacent_pairs { + assembly.insert_regulator( + Rc::new(InversiveDistanceRegulator::new(pair)) + ); + } + } +} + +// to finish describing the dodecahedral circle packing, set the inversive +// distance regulators to -1. some of the regulators have already been set +fn load_dodeca_packing_assemb(assembly: &Assembly) { + // add the substrate + let _ = assembly.try_insert_element( + Sphere::new( + "substrate".to_string(), + "Substrate".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ) + ); + let substrate = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id["substrate"].clone() + ); + + // fix the substrate's curvature + substrate.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ).set_point().set( + SpecifiedValue::try_from("0.5".to_string()).unwrap() + ); + + // add the circles to be packed + const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.00_f32]; + const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32]; + const COLOR_C: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; + let phi = 0.5 + 1.25_f64.sqrt(); /* TO DO */ // replace with std::f64::consts::PHI when that gets stabilized + let phi_inv = 1.0 / phi; + let coord_scale = (phi + 2.0).sqrt(); + let face_scales = [phi_inv, (13.0 / 12.0) / coord_scale]; + let face_radii = [phi_inv, 5.0 / 12.0]; + let mut faces = Vec::>::new(); + let subscripts = ["₀", "₁"]; + for j in 0..2 { + for k in 0..2 { + let small_coord = face_scales[k] * (2.0*(j as f64) - 1.0); + let big_coord = face_scales[k] * (2.0*(k as f64) - 1.0) * phi; + + let id_num = format!("{j}{k}"); + let label_sub = format!("{}{}", subscripts[j], subscripts[k]); + + // add the A face + let id_a = format!("a{id_num}"); + let _ = assembly.try_insert_element( + Sphere::new( + id_a.clone(), + format!("A{label_sub}"), + COLOR_A, + engine::sphere(0.0, small_coord, big_coord, face_radii[k]) + ) + ); + faces.push( + assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id_a].clone() + ) + ); + + // add the B face + let id_b = format!("b{id_num}"); + let _ = assembly.try_insert_element( + Sphere::new( + id_b.clone(), + format!("B{label_sub}"), + COLOR_B, + engine::sphere(small_coord, big_coord, 0.0, face_radii[k]) + ) + ); + faces.push( + assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id_b].clone() + ) + ); + + // add the C face + let id_c = format!("c{id_num}"); + let _ = assembly.try_insert_element( + Sphere::new( + id_c.clone(), + format!("C{label_sub}"), + COLOR_C, + engine::sphere(big_coord, 0.0, small_coord, face_radii[k]) + ) + ); + faces.push( + assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id_c].clone() + ) + ); + } + } + + // make each face sphere perpendicular to the substrate + for face in faces { + let right_angle = InversiveDistanceRegulator::new([face, substrate.clone()]); + right_angle.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(right_angle)); + } + + // set up the tangencies that define the packing + for [long_edge_plane, short_edge_plane] in [["a", "b"], ["b", "c"], ["c", "a"]] { + for k in 0..2 { + let long_edge_ids = [ + format!("{long_edge_plane}{k}0"), + format!("{long_edge_plane}{k}1") + ]; + let short_edge_ids = [ + format!("{short_edge_plane}0{k}"), + format!("{short_edge_plane}1{k}") + ]; + let [long_edge, short_edge] = [long_edge_ids, short_edge_ids].map( + |edge_ids| edge_ids.map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id].clone() + ) + ) + ); + + // set up the short-edge tangency + let short_tangency = InversiveDistanceRegulator::new(short_edge.clone()); + if k == 0 { + short_tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); + } + assembly.insert_regulator(Rc::new(short_tangency)); + + // set up the side tangencies + for i in 0..2 { + for j in 0..2 { + let side_tangency = InversiveDistanceRegulator::new( + [long_edge[i].clone(), short_edge[j].clone()] + ); + if i == 0 && k == 0 { + side_tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); + } + assembly.insert_regulator(Rc::new(side_tangency)); + } + } + } + } +} + +// the initial configuration of this test assembly deliberately violates the +// constraints, so loading the assembly will trigger a non-trivial realization +fn load_balanced_assemb(assembly: &Assembly) { + // create the spheres + const R_OUTER: f64 = 10.0; + const R_INNER: f64 = 4.0; + let spheres = [ + Sphere::new( + "outer".to_string(), + "Outer".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, 0.0, 0.0, R_OUTER) + ), + Sphere::new( + "a".to_string(), + "A".to_string(), + [1.00_f32, 0.00_f32, 0.25_f32], + engine::sphere(0.0, 4.0, 0.0, R_INNER) + ), + Sphere::new( + "b".to_string(), + "B".to_string(), + [0.00_f32, 0.25_f32, 1.00_f32], + engine::sphere(0.0, -4.0, 0.0, R_INNER) + ), + ]; + for sphere in spheres { + let _ = assembly.try_insert_element(sphere); + } + + // get references to the spheres + let [outer, a, b] = ["outer", "a", "b"].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + + // fix the diameters of the outer, sun, and moon spheres + for (sphere, radius) in [ + (outer.clone(), R_OUTER), + (a.clone(), R_INNER), + (b.clone(), R_INNER) + ] { + let curvature_regulator = sphere.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + let curvature = 0.5 / radius; + curvature_regulator.set_point().set( + SpecifiedValue::try_from(curvature.to_string()).unwrap() + ); + } + + // set the inversive distances between the spheres. as described above, the + // initial configuration deliberately violates these constraints + for inner in [a, b] { + let tangency = InversiveDistanceRegulator::new([outer.clone(), inner]); + tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(tangency)); + } +} + +// the initial configuration of this test assembly deliberately violates the +// constraints, so loading the assembly will trigger a non-trivial realization +fn load_off_center_assemb(assembly: &Assembly) { + // create a point almost at the origin and a sphere centered on the origin + let _ = assembly.try_insert_element( + Point::new( + "point".to_string(), + "Point".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::point(1e-9, 0.0, 0.0) + ), + ); + let _ = assembly.try_insert_element( + Sphere::new( + "sphere".to_string(), + "Sphere".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ), + ); + + // get references to the elements + let point_and_sphere = ["point", "sphere"].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + + // put the point on the sphere + let incidence = InversiveDistanceRegulator::new(point_and_sphere); + incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(incidence)); +} + +// setting the inversive distances between the vertices to -2 gives a regular +// tetrahedron with side length 1, whose insphere and circumsphere have radii +// sqrt(1/6) and sqrt(3/2), respectively. to measure those radii, set an +// inversive distance of -1 between the insphere and each face, and then set an +// inversive distance of 0 between the circumsphere and each vertex +fn load_radius_ratio_assemb(assembly: &Assembly) { + let index_range = 1..=4; + + // create the spheres + const GRAY: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let spheres = [ + Sphere::new( + "sphere_faces".to_string(), + "Insphere".to_string(), + GRAY, + engine::sphere(0.0, 0.0, 0.0, 0.5) + ), + Sphere::new( + "sphere_vertices".to_string(), + "Circumsphere".to_string(), + GRAY, + engine::sphere(0.0, 0.0, 0.0, 0.25) + ) + ]; + for sphere in spheres { + let _ = assembly.try_insert_element(sphere); + } + + // create the vertices + let vertices = izip!( + index_range.clone(), + [ + [1.00_f32, 0.50_f32, 0.75_f32], + [1.00_f32, 0.75_f32, 0.50_f32], + [1.00_f32, 1.00_f32, 0.50_f32], + [0.75_f32, 0.50_f32, 1.00_f32] + ].into_iter(), + [ + engine::point(-0.6, -0.8, -0.6), + engine::point(-0.6, 0.8, 0.6), + engine::point(0.6, -0.8, 0.6), + engine::point(0.6, 0.8, -0.6) + ].into_iter() + ).map( + |(k, color, representation)| { + Point::new( + format!("v{k}"), + format!("Vertex {k}"), + color, + representation + ) + } + ); + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // create the faces + let base_dir = Vector3::new(1.0, 0.75, 1.0).normalize(); + let offset = base_dir.dot(&Vector3::new(-0.6, 0.8, 0.6)); + let faces = izip!( + index_range.clone(), + [ + [1.00_f32, 0.00_f32, 0.25_f32], + [1.00_f32, 0.25_f32, 0.00_f32], + [0.75_f32, 0.75_f32, 0.00_f32], + [0.25_f32, 0.00_f32, 1.00_f32] + ].into_iter(), + [ + engine::sphere_with_offset(base_dir[0], base_dir[1], base_dir[2], offset, 0.0), + engine::sphere_with_offset(base_dir[0], -base_dir[1], -base_dir[2], offset, 0.0), + engine::sphere_with_offset(-base_dir[0], base_dir[1], -base_dir[2], offset, 0.0), + engine::sphere_with_offset(-base_dir[0], -base_dir[1], base_dir[2], offset, 0.0) + ].into_iter() + ).map( + |(k, color, representation)| { + Sphere::new( + format!("f{k}"), + format!("Face {k}"), + color, + representation + ) + } + ); + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + // impose the constraints + for j in index_range.clone() { + let [face_j, vertex_j] = [ + format!("f{j}"), + format!("v{j}") + ].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id].clone() + ) + ); + + // make the faces planar + let curvature_regulator = face_j.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set( + SpecifiedValue::try_from("0".to_string()).unwrap() + ); + + for k in index_range.clone().filter(|&index| index != j) { + let vertex_k = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("v{k}")].clone() + ); + + // fix the distances between the vertices + if j < k { + let distance_regulator = InversiveDistanceRegulator::new( + [vertex_j.clone(), vertex_k.clone()] + ); + assembly.insert_regulator(Rc::new(distance_regulator)); + } + + // put the vertices on the faces + let incidence_regulator = InversiveDistanceRegulator::new([face_j.clone(), vertex_k.clone()]); + incidence_regulator.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(incidence_regulator)); + } + } +} + +// to finish setting up the problem, fix the following curvatures: +// sun 1 +// moon 5/3 = 1.666666666666666... +// chain1 2 +// a tiny `x` or `z` nudge of the outer sphere reliably prevents realization +// failures before they happen, or resolves them after they happen. the result +// depends sensitively on the translation direction, suggesting that realization +// is failing because the engine is having trouble breaking a symmetry +// /* TO DO */ +// the engine's performance on this problem is scale-dependent! with the current +// initial conditions, realization fails for any order of imposing the remaining +// curvature constraints. scaling everything up by a factor of ten, as done in +// the original problem, makes realization succeed reliably. one potentially +// relevant difference is that a lot of the numbers in the current initial +// conditions are exactly representable as floats, unlike the analogous numbers +// in the scaled-up problem. the inexact representations might break the +// symmetry that's getting the engine stuck +fn load_irisawa_hexlet_assemb(assembly: &Assembly) { + let index_range = 1..=6; + let colors = [ + [1.00_f32, 0.00_f32, 0.25_f32], + [1.00_f32, 0.25_f32, 0.00_f32], + [0.75_f32, 0.75_f32, 0.00_f32], + [0.25_f32, 1.00_f32, 0.00_f32], + [0.00_f32, 0.25_f32, 1.00_f32], + [0.25_f32, 0.00_f32, 1.00_f32] + ].into_iter(); + + // create the spheres + let spheres = [ + Sphere::new( + "outer".to_string(), + "Outer".to_string(), + [0.5_f32, 0.5_f32, 0.5_f32], + engine::sphere(0.0, 0.0, 0.0, 1.5) + ), + Sphere::new( + "sun".to_string(), + "Sun".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, -0.75, 0.0, 0.75) + ), + Sphere::new( + "moon".to_string(), + "Moon".to_string(), + [0.25_f32, 0.25_f32, 0.25_f32], + engine::sphere(0.0, 0.75, 0.0, 0.75) + ), + ].into_iter().chain( + index_range.clone().zip(colors).map( + |(k, color)| { + let ang = (k as f64) * PI/3.0; + Sphere::new( + format!("chain{k}"), + format!("Chain {k}"), + color, + engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5) + ) + } + ) + ); + for sphere in spheres { + let _ = assembly.try_insert_element(sphere); + } + + // put the outer sphere in ghost mode and fix its curvature + let outer = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id["outer"].clone() + ); + outer.ghost().set(true); + let outer_curvature_regulator = outer.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + outer_curvature_regulator.set_point().set( + SpecifiedValue::try_from((1.0 / 3.0).to_string()).unwrap() + ); + + // impose the desired tangencies + let [outer, sun, moon] = ["outer", "sun", "moon"].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + let chain = index_range.map( + |k| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("chain{k}")].clone() + ) + ); + for (chain_sphere, chain_sphere_next) in chain.clone().zip(chain.cycle().skip(1)) { + for (other_sphere, inversive_distance) in [ + (outer.clone(), "1"), + (sun.clone(), "-1"), + (moon.clone(), "-1"), + (chain_sphere_next.clone(), "-1") + ] { + let tangency = InversiveDistanceRegulator::new([chain_sphere.clone(), other_sphere]); + tangency.set_point.set(SpecifiedValue::try_from(inversive_distance.to_string()).unwrap()); + assembly.insert_regulator(Rc::new(tangency)); + } + } + + let outer_sun_tangency = InversiveDistanceRegulator::new([outer.clone(), sun]); + outer_sun_tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(outer_sun_tangency)); + + let outer_moon_tangency = InversiveDistanceRegulator::new([outer.clone(), moon]); + outer_moon_tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(outer_moon_tangency)); +} + +// --- chooser --- + +/* DEBUG */ +#[component] +pub fn TestAssemblyChooser() -> View { + // create an effect that loads the selected test assembly + let assembly_name = create_signal("general".to_string()); + create_effect(move || { + // get name of chosen assembly + let name = assembly_name.get_clone(); + console::log_1( + &JsValue::from(format!("Showing assembly \"{}\"", name.clone())) + ); + + batch(|| { + let state = use_context::(); + let assembly = &state.assembly; + + // pause realization + assembly.keep_realized.set(false); + + // clear state + assembly.regulators.update(|regs| regs.clear()); + assembly.elements.update(|elts| elts.clear()); + assembly.elements_by_id.update(|elts_by_id| elts_by_id.clear()); + assembly.descent_history.set(DescentHistory::new()); + state.selection.update(|sel| sel.clear()); + + // load assembly + match name.as_str() { + "general" => load_gen_assemb(assembly), + "low-curv" => load_low_curv_assemb(assembly), + "pointed" => load_pointed_assemb(assembly), + "tridim-icosahedron" => load_tridim_icosahedron_assemb(assembly), + "dodeca-packing" => load_dodeca_packing_assemb(assembly), + "balanced" => load_balanced_assemb(assembly), + "off-center" => load_off_center_assemb(assembly), + "radius-ratio" => load_radius_ratio_assemb(assembly), + "irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly), + _ => () + }; + + // resume realization + assembly.keep_realized.set(true); + }); + }); + + // build the chooser + view! { + select(bind:value=assembly_name) { + option(value="general") { "General" } + option(value="low-curv") { "Low-curvature" } + option(value="pointed") { "Pointed" } + option(value="tridim-icosahedron") { "Tridiminished icosahedron" } + option(value="dodeca-packing") { "Dodecahedral packing" } + option(value="balanced") { "Balanced" } + option(value="off-center") { "Off-center" } + option(value="radius-ratio") { "Radius ratio" } + option(value="irisawa-hexlet") { "Irisawa hexlet" } + option(value="empty") { "Empty" } + } + } +} \ No newline at end of file From 5233d8eb935db92c76de0366c241101e7b93741d Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 22 Jul 2025 13:28:48 -0700 Subject: [PATCH 09/14] Move the components into their own module This makes the module tree more reflective of module use patterns. In particular, it restores the condition that every top-level module is used in top-level code, which was broken by the addition of the `test_assembly_chooser` module in commit 91e4e1f. --- app-proto/src/assembly.rs | 3 +-- app-proto/src/components.rs | 5 +++++ app-proto/src/{ => components}/add_remove.rs | 4 ++-- app-proto/src/{ => components}/diagnostics.rs | 0 app-proto/src/{ => components}/display.rs | 0 app-proto/src/{ => components}/identity.vert | 0 app-proto/src/{ => components}/outline.rs | 0 app-proto/src/{ => components}/point.frag | 0 app-proto/src/{ => components}/point.vert | 0 app-proto/src/{ => components}/spheres.frag | 0 .../{ => components}/test_assembly_chooser.rs | 0 app-proto/src/main.rs | 16 +++++++--------- 12 files changed, 15 insertions(+), 13 deletions(-) create mode 100644 app-proto/src/components.rs rename app-proto/src/{ => components}/add_remove.rs (94%) rename app-proto/src/{ => components}/diagnostics.rs (100%) rename app-proto/src/{ => components}/display.rs (100%) rename app-proto/src/{ => components}/identity.vert (100%) rename app-proto/src/{ => components}/outline.rs (100%) rename app-proto/src/{ => components}/point.frag (100%) rename app-proto/src/{ => components}/point.vert (100%) rename app-proto/src/{ => components}/spheres.frag (100%) rename app-proto/src/{ => components}/test_assembly_chooser.rs (100%) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index e696845..68fcd8b 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -13,7 +13,7 @@ use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ use crate::{ - display::DisplayItem, + components::{display::DisplayItem, outline::OutlineItem}, engine::{ Q, change_half_curvature, @@ -29,7 +29,6 @@ use crate::{ DescentHistory, Realization }, - outline::OutlineItem, specified::SpecifiedValue }; diff --git a/app-proto/src/components.rs b/app-proto/src/components.rs new file mode 100644 index 0000000..7387d58 --- /dev/null +++ b/app-proto/src/components.rs @@ -0,0 +1,5 @@ +pub mod add_remove; +pub mod diagnostics; +pub mod display; +pub mod outline; +pub mod test_assembly_chooser; \ No newline at end of file diff --git a/app-proto/src/add_remove.rs b/app-proto/src/components/add_remove.rs similarity index 94% rename from app-proto/src/add_remove.rs rename to app-proto/src/components/add_remove.rs index de9272e..3b0f9e0 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/components/add_remove.rs @@ -1,10 +1,10 @@ use std::rc::Rc; use sycamore::prelude::*; +use super::test_assembly_chooser::TestAssemblyChooser; use crate::{ AppState, - assembly::{InversiveDistanceRegulator, Point, Sphere}, - test_assembly_chooser::TestAssemblyChooser + assembly::{InversiveDistanceRegulator, Point, Sphere} }; #[component] diff --git a/app-proto/src/diagnostics.rs b/app-proto/src/components/diagnostics.rs similarity index 100% rename from app-proto/src/diagnostics.rs rename to app-proto/src/components/diagnostics.rs diff --git a/app-proto/src/display.rs b/app-proto/src/components/display.rs similarity index 100% rename from app-proto/src/display.rs rename to app-proto/src/components/display.rs diff --git a/app-proto/src/identity.vert b/app-proto/src/components/identity.vert similarity index 100% rename from app-proto/src/identity.vert rename to app-proto/src/components/identity.vert diff --git a/app-proto/src/outline.rs b/app-proto/src/components/outline.rs similarity index 100% rename from app-proto/src/outline.rs rename to app-proto/src/components/outline.rs diff --git a/app-proto/src/point.frag b/app-proto/src/components/point.frag similarity index 100% rename from app-proto/src/point.frag rename to app-proto/src/components/point.frag diff --git a/app-proto/src/point.vert b/app-proto/src/components/point.vert similarity index 100% rename from app-proto/src/point.vert rename to app-proto/src/components/point.vert diff --git a/app-proto/src/spheres.frag b/app-proto/src/components/spheres.frag similarity index 100% rename from app-proto/src/spheres.frag rename to app-proto/src/components/spheres.frag diff --git a/app-proto/src/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs similarity index 100% rename from app-proto/src/test_assembly_chooser.rs rename to app-proto/src/components/test_assembly_chooser.rs diff --git a/app-proto/src/main.rs b/app-proto/src/main.rs index f5b7cef..152d11c 100644 --- a/app-proto/src/main.rs +++ b/app-proto/src/main.rs @@ -1,11 +1,7 @@ -mod add_remove; mod assembly; -mod diagnostics; -mod display; +mod components; mod engine; -mod outline; mod specified; -mod test_assembly_chooser; #[cfg(test)] mod tests; @@ -13,11 +9,13 @@ mod tests; use std::{collections::BTreeSet, rc::Rc}; use sycamore::prelude::*; -use add_remove::AddRemove; use assembly::{Assembly, Element}; -use diagnostics::Diagnostics; -use display::Display; -use outline::Outline; +use components::{ + add_remove::AddRemove, + diagnostics::Diagnostics, + display::Display, + outline::Outline +}; #[derive(Clone)] struct AppState { From 0801200210094b9514974d63bb09eaad88ba74de Mon Sep 17 00:00:00 2001 From: Vectornaut Date: Tue, 22 Jul 2025 22:01:37 +0000 Subject: [PATCH 10/14] Add more test assemblies (#103) This PR helps probe the capabilities of the engine. Also adjusts the realization triggering system to reduce redundant realizations as we set an assembly's regulators during loading. Specificially, consolidates all calls to `realize()` into a single effect, which is triggered by the `needs_realization` signal. Also introduces a `keep_realized` signal and use it to pause realization while loading assemblies, but this signal is planned for removal as ultimately we do not want a separate "mode" of interpreting commands during loading, for maximal reproducibility of results (and simplicity of system). Co-authored-by: Aaron Fenyes Reviewed-on: https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/103 Co-authored-by: Vectornaut Co-committed-by: Vectornaut --- app-proto/src/add_remove.rs | 259 ----- app-proto/src/assembly.rs | 42 +- app-proto/src/components.rs | 5 + app-proto/src/components/add_remove.rs | 54 + app-proto/src/{ => components}/diagnostics.rs | 0 app-proto/src/{ => components}/display.rs | 0 app-proto/src/{ => components}/identity.vert | 0 app-proto/src/{ => components}/outline.rs | 0 app-proto/src/{ => components}/point.frag | 0 app-proto/src/{ => components}/point.vert | 0 app-proto/src/{ => components}/spheres.frag | 0 .../src/components/test_assembly_chooser.rs | 947 ++++++++++++++++++ app-proto/src/main.rs | 15 +- 13 files changed, 1045 insertions(+), 277 deletions(-) delete mode 100644 app-proto/src/add_remove.rs create mode 100644 app-proto/src/components.rs create mode 100644 app-proto/src/components/add_remove.rs rename app-proto/src/{ => components}/diagnostics.rs (100%) rename app-proto/src/{ => components}/display.rs (100%) rename app-proto/src/{ => components}/identity.vert (100%) rename app-proto/src/{ => components}/outline.rs (100%) rename app-proto/src/{ => components}/point.frag (100%) rename app-proto/src/{ => components}/point.vert (100%) rename app-proto/src/{ => components}/spheres.frag (100%) create mode 100644 app-proto/src/components/test_assembly_chooser.rs diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs deleted file mode 100644 index d737c79..0000000 --- a/app-proto/src/add_remove.rs +++ /dev/null @@ -1,259 +0,0 @@ -use std::{f64::consts::FRAC_1_SQRT_2, rc::Rc}; -use sycamore::prelude::*; -use web_sys::{console, wasm_bindgen::JsValue}; - -use crate::{ - AppState, - engine, - engine::DescentHistory, - assembly::{Assembly, InversiveDistanceRegulator, Point, Sphere} -}; - -/* DEBUG */ -// load an example assembly for testing. this code will be removed once we've -// built a more formal test assembly system -fn load_gen_assemb(assembly: &Assembly) { - let _ = assembly.try_insert_element( - Sphere::new( - String::from("gemini_a"), - String::from("Castor"), - [1.00_f32, 0.25_f32, 0.00_f32], - engine::sphere(0.5, 0.5, 0.0, 1.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("gemini_b"), - String::from("Pollux"), - [0.00_f32, 0.25_f32, 1.00_f32], - engine::sphere(-0.5, -0.5, 0.0, 1.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("ursa_major"), - String::from("Ursa major"), - [0.25_f32, 0.00_f32, 1.00_f32], - engine::sphere(-0.5, 0.5, 0.0, 0.75) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("ursa_minor"), - String::from("Ursa minor"), - [0.25_f32, 1.00_f32, 0.00_f32], - engine::sphere(0.5, -0.5, 0.0, 0.5) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("moon_deimos"), - String::from("Deimos"), - [0.75_f32, 0.75_f32, 0.00_f32], - engine::sphere(0.0, 0.15, 1.0, 0.25) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("moon_phobos"), - String::from("Phobos"), - [0.00_f32, 0.75_f32, 0.50_f32], - engine::sphere(0.0, -0.15, -1.0, 0.25) - ) - ); -} - -/* DEBUG */ -// load an example assembly for testing. this code will be removed once we've -// built a more formal test assembly system -fn load_low_curv_assemb(assembly: &Assembly) { - let a = 0.75_f64.sqrt(); - let _ = assembly.try_insert_element( - Sphere::new( - "central".to_string(), - "Central".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, 1.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "assemb_plane".to_string(), - "Assembly plane".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "side1".to_string(), - "Side 1".to_string(), - [1.00_f32, 0.00_f32, 0.25_f32], - engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "side2".to_string(), - "Side 2".to_string(), - [0.25_f32, 1.00_f32, 0.00_f32], - engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "side3".to_string(), - "Side 3".to_string(), - [0.00_f32, 0.25_f32, 1.00_f32], - engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "corner1".to_string(), - "Corner 1".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - "corner2".to_string(), - "Corner 2".to_string(), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0) - ) - ); - let _ = assembly.try_insert_element( - Sphere::new( - String::from("corner3"), - String::from("Corner 3"), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0) - ) - ); -} - -fn load_pointed_assemb(assembly: &Assembly) { - let _ = assembly.try_insert_element( - Point::new( - format!("point_front"), - format!("Front point"), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::point(0.0, 0.0, FRAC_1_SQRT_2) - ) - ); - let _ = assembly.try_insert_element( - Point::new( - format!("point_back"), - format!("Back point"), - [0.75_f32, 0.75_f32, 0.75_f32], - engine::point(0.0, 0.0, -FRAC_1_SQRT_2) - ) - ); - for index_x in 0..=1 { - for index_y in 0..=1 { - let x = index_x as f64 - 0.5; - let y = index_y as f64 - 0.5; - - let _ = assembly.try_insert_element( - Sphere::new( - format!("sphere{index_x}{index_y}"), - format!("Sphere {index_x}{index_y}"), - [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], - engine::sphere(x, y, 0.0, 1.0) - ) - ); - - let _ = assembly.try_insert_element( - Point::new( - format!("point{index_x}{index_y}"), - format!("Point {index_x}{index_y}"), - [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], - engine::point(x, y, 0.0) - ) - ); - } - } -} - -#[component] -pub fn AddRemove() -> View { - /* DEBUG */ - let assembly_name = create_signal("general".to_string()); - create_effect(move || { - // get name of chosen assembly - let name = assembly_name.get_clone(); - console::log_1( - &JsValue::from(format!("Showing assembly \"{}\"", name.clone())) - ); - - batch(|| { - let state = use_context::(); - let assembly = &state.assembly; - - // clear state - assembly.regulators.update(|regs| regs.clear()); - assembly.elements.update(|elts| elts.clear()); - assembly.elements_by_id.update(|elts_by_id| elts_by_id.clear()); - assembly.descent_history.set(DescentHistory::new()); - state.selection.update(|sel| sel.clear()); - - // load assembly - match name.as_str() { - "general" => load_gen_assemb(assembly), - "low-curv" => load_low_curv_assemb(assembly), - "pointed" => load_pointed_assemb(assembly), - _ => () - }; - }); - }); - - view! { - div(id="add-remove") { - button( - on:click=|_| { - let state = use_context::(); - state.assembly.insert_element_default::(); - } - ) { "Add sphere" } - button( - on:click=|_| { - let state = use_context::(); - state.assembly.insert_element_default::(); - } - ) { "Add point" } - button( - class="emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button - disabled={ - let state = use_context::(); - state.selection.with(|sel| sel.len() != 2) - }, - on:click=|_| { - let state = use_context::(); - let subjects: [_; 2] = state.selection.with( - // the button is only enabled when two elements are - // selected, so we know the cast to a two-element array - // will succeed - |sel| sel - .clone() - .into_iter() - .collect::>() - .try_into() - .unwrap() - ); - state.assembly.insert_regulator( - Rc::new(InversiveDistanceRegulator::new(subjects)) - ); - state.selection.update(|sel| sel.clear()); - } - ) { "🔗" } - select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser - option(value="general") { "General" } - option(value="low-curv") { "Low-curvature" } - option(value="pointed") { "Pointed" } - option(value="empty") { "Empty" } - } - } - } -} \ No newline at end of file diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index c3b0c6b..68fcd8b 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -13,7 +13,7 @@ use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ use crate::{ - display::DisplayItem, + components::{display::DisplayItem, outline::OutlineItem}, engine::{ Q, change_half_curvature, @@ -29,7 +29,6 @@ use crate::{ DescentHistory, Realization }, - outline::OutlineItem, specified::SpecifiedValue }; @@ -552,6 +551,10 @@ pub struct Assembly { // indexing pub elements_by_id: Signal>>, + // realization control + pub keep_realized: Signal, + pub needs_realization: Signal, + // realization diagnostics pub realization_status: Signal>, pub descent_history: Signal @@ -559,14 +562,30 @@ pub struct Assembly { impl Assembly { pub fn new() -> Assembly { - Assembly { + // create an assembly + let assembly = Assembly { elements: create_signal(BTreeSet::new()), regulators: create_signal(BTreeSet::new()), tangent: create_signal(ConfigSubspace::zero(0)), elements_by_id: create_signal(BTreeMap::default()), + keep_realized: create_signal(true), + needs_realization: create_signal(false), realization_status: create_signal(Ok(())), descent_history: create_signal(DescentHistory::new()) - } + }; + + // realize the assembly whenever it becomes simultaneously true that + // we're trying to keep it realized and it needs realization + let assembly_for_effect = assembly.clone(); + create_effect(move || { + let should_realize = assembly_for_effect.keep_realized.get() + && assembly_for_effect.needs_realization.get(); + if should_realize { + assembly_for_effect.realize(); + } + }); + + assembly } // --- inserting elements and regulators --- @@ -627,7 +646,7 @@ impl Assembly { regulators.update(|regs| regs.insert(regulator.clone())); } - // update the realization when the regulator becomes a constraint, or is + // request a realization when the regulator becomes a constraint, or is // edited while acting as a constraint let self_for_effect = self.clone(); create_effect(move || { @@ -636,7 +655,7 @@ impl Assembly { console_log!("Updated regulator with subjects {:?}", regulator.subjects()); if regulator.try_activate() { - self_for_effect.realize(); + self_for_effect.needs_realization.set(true); } }); @@ -731,6 +750,9 @@ impl Assembly { // save the tangent space self.tangent.set_silent(tangent); + + // clear the realization request flag + self.needs_realization.set(false); }, Err(message) => { // report the realization status. the `Err(message)` we're @@ -826,10 +848,10 @@ impl Assembly { }); } - // bring the configuration back onto the solution variety. this also - // gets the elements' column indices and the saved tangent space back in - // sync - self.realize(); + // request a realization to bring the configuration back onto the + // solution variety. this also gets the elements' column indices and the + // saved tangent space back in sync + self.needs_realization.set(true); } } diff --git a/app-proto/src/components.rs b/app-proto/src/components.rs new file mode 100644 index 0000000..7387d58 --- /dev/null +++ b/app-proto/src/components.rs @@ -0,0 +1,5 @@ +pub mod add_remove; +pub mod diagnostics; +pub mod display; +pub mod outline; +pub mod test_assembly_chooser; \ No newline at end of file diff --git a/app-proto/src/components/add_remove.rs b/app-proto/src/components/add_remove.rs new file mode 100644 index 0000000..3b0f9e0 --- /dev/null +++ b/app-proto/src/components/add_remove.rs @@ -0,0 +1,54 @@ +use std::rc::Rc; +use sycamore::prelude::*; + +use super::test_assembly_chooser::TestAssemblyChooser; +use crate::{ + AppState, + assembly::{InversiveDistanceRegulator, Point, Sphere} +}; + +#[component] +pub fn AddRemove() -> View { + view! { + div(id="add-remove") { + button( + on:click=|_| { + let state = use_context::(); + state.assembly.insert_element_default::(); + } + ) { "Add sphere" } + button( + on:click=|_| { + let state = use_context::(); + state.assembly.insert_element_default::(); + } + ) { "Add point" } + button( + class="emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button + disabled={ + let state = use_context::(); + state.selection.with(|sel| sel.len() != 2) + }, + on:click=|_| { + let state = use_context::(); + let subjects: [_; 2] = state.selection.with( + // the button is only enabled when two elements are + // selected, so we know the cast to a two-element array + // will succeed + |sel| sel + .clone() + .into_iter() + .collect::>() + .try_into() + .unwrap() + ); + state.assembly.insert_regulator( + Rc::new(InversiveDistanceRegulator::new(subjects)) + ); + state.selection.update(|sel| sel.clear()); + } + ) { "🔗" } + TestAssemblyChooser {} + } + } +} \ No newline at end of file diff --git a/app-proto/src/diagnostics.rs b/app-proto/src/components/diagnostics.rs similarity index 100% rename from app-proto/src/diagnostics.rs rename to app-proto/src/components/diagnostics.rs diff --git a/app-proto/src/display.rs b/app-proto/src/components/display.rs similarity index 100% rename from app-proto/src/display.rs rename to app-proto/src/components/display.rs diff --git a/app-proto/src/identity.vert b/app-proto/src/components/identity.vert similarity index 100% rename from app-proto/src/identity.vert rename to app-proto/src/components/identity.vert diff --git a/app-proto/src/outline.rs b/app-proto/src/components/outline.rs similarity index 100% rename from app-proto/src/outline.rs rename to app-proto/src/components/outline.rs diff --git a/app-proto/src/point.frag b/app-proto/src/components/point.frag similarity index 100% rename from app-proto/src/point.frag rename to app-proto/src/components/point.frag diff --git a/app-proto/src/point.vert b/app-proto/src/components/point.vert similarity index 100% rename from app-proto/src/point.vert rename to app-proto/src/components/point.vert diff --git a/app-proto/src/spheres.frag b/app-proto/src/components/spheres.frag similarity index 100% rename from app-proto/src/spheres.frag rename to app-proto/src/components/spheres.frag diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs new file mode 100644 index 0000000..232cda3 --- /dev/null +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -0,0 +1,947 @@ +use itertools::izip; +use std::{f64::consts::{FRAC_1_SQRT_2, PI}, rc::Rc}; +use nalgebra::Vector3; +use sycamore::prelude::*; +use web_sys::{console, wasm_bindgen::JsValue}; + +use crate::{ + AppState, + engine, + engine::DescentHistory, + assembly::{ + Assembly, + Element, + ElementColor, + InversiveDistanceRegulator, + Point, + Sphere + }, + specified::SpecifiedValue +}; + +// --- loaders --- + +/* DEBUG */ +// each of these functions loads an example assembly for testing. once we've +// done more work on saving and loading assemblies, we should come back to this +// code to see if it can be simplified + +fn load_gen_assemb(assembly: &Assembly) { + let _ = assembly.try_insert_element( + Sphere::new( + String::from("gemini_a"), + String::from("Castor"), + [1.00_f32, 0.25_f32, 0.00_f32], + engine::sphere(0.5, 0.5, 0.0, 1.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("gemini_b"), + String::from("Pollux"), + [0.00_f32, 0.25_f32, 1.00_f32], + engine::sphere(-0.5, -0.5, 0.0, 1.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("ursa_major"), + String::from("Ursa major"), + [0.25_f32, 0.00_f32, 1.00_f32], + engine::sphere(-0.5, 0.5, 0.0, 0.75) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("ursa_minor"), + String::from("Ursa minor"), + [0.25_f32, 1.00_f32, 0.00_f32], + engine::sphere(0.5, -0.5, 0.0, 0.5) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("moon_deimos"), + String::from("Deimos"), + [0.75_f32, 0.75_f32, 0.00_f32], + engine::sphere(0.0, 0.15, 1.0, 0.25) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("moon_phobos"), + String::from("Phobos"), + [0.00_f32, 0.75_f32, 0.50_f32], + engine::sphere(0.0, -0.15, -1.0, 0.25) + ) + ); +} + +fn load_low_curv_assemb(assembly: &Assembly) { + // create the spheres + let a = 0.75_f64.sqrt(); + let _ = assembly.try_insert_element( + Sphere::new( + "central".to_string(), + "Central".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "assemb_plane".to_string(), + "Assembly plane".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "side1".to_string(), + "Side 1".to_string(), + [1.00_f32, 0.00_f32, 0.25_f32], + engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "side2".to_string(), + "Side 2".to_string(), + [0.25_f32, 1.00_f32, 0.00_f32], + engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "side3".to_string(), + "Side 3".to_string(), + [0.00_f32, 0.25_f32, 1.00_f32], + engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "corner1".to_string(), + "Corner 1".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + "corner2".to_string(), + "Corner 2".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0) + ) + ); + let _ = assembly.try_insert_element( + Sphere::new( + String::from("corner3"), + String::from("Corner 3"), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0) + ) + ); + + // impose the desired tangencies and make the sides planar + let index_range = 1..=3; + let [central, assemb_plane] = ["central", "assemb_plane"].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + let sides = index_range.clone().map( + |k| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("side{k}")].clone() + ) + ); + let corners = index_range.map( + |k| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("corner{k}")].clone() + ) + ); + for plane in [assemb_plane.clone()].into_iter().chain(sides.clone()) { + // fix the curvature of each plane + let curvature = plane.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature.set_point().set(SpecifiedValue::try_from("0".to_string()).unwrap()); + } + let all_perpendicular = [central.clone()].into_iter() + .chain(sides.clone()) + .chain(corners.clone()); + for sphere in all_perpendicular { + // make each side and packed sphere perpendicular to the assembly plane + let right_angle = InversiveDistanceRegulator::new([sphere, assemb_plane.clone()]); + right_angle.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(right_angle)); + } + for sphere in sides.clone().chain(corners.clone()) { + // make each side and corner sphere tangent to the central sphere + let tangency = InversiveDistanceRegulator::new([sphere.clone(), central.clone()]); + tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(tangency)); + } + for (side_index, side) in sides.enumerate() { + // make each side tangent to the two adjacent corner spheres + for (corner_index, corner) in corners.clone().enumerate() { + if side_index != corner_index { + let tangency = InversiveDistanceRegulator::new([side.clone(), corner]); + tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(tangency)); + } + } + } +} + +fn load_pointed_assemb(assembly: &Assembly) { + let _ = assembly.try_insert_element( + Point::new( + format!("point_front"), + format!("Front point"), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::point(0.0, 0.0, FRAC_1_SQRT_2) + ) + ); + let _ = assembly.try_insert_element( + Point::new( + format!("point_back"), + format!("Back point"), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::point(0.0, 0.0, -FRAC_1_SQRT_2) + ) + ); + for index_x in 0..=1 { + for index_y in 0..=1 { + let x = index_x as f64 - 0.5; + let y = index_y as f64 - 0.5; + + let _ = assembly.try_insert_element( + Sphere::new( + format!("sphere{index_x}{index_y}"), + format!("Sphere {index_x}{index_y}"), + [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], + engine::sphere(x, y, 0.0, 1.0) + ) + ); + + let _ = assembly.try_insert_element( + Point::new( + format!("point{index_x}{index_y}"), + format!("Point {index_x}{index_y}"), + [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], + engine::point(x, y, 0.0) + ) + ); + } + } +} + +// to finish describing the tridiminished icosahedron, set the inversive +// distance regulators as follows: +// A-A -0.25 +// A-B " +// B-C " +// C-C " +// A-C -0.25 * φ^2 = -0.6545084971874737 +fn load_tridim_icosahedron_assemb(assembly: &Assembly) { + // create the vertices + const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.25_f32]; + const COLOR_B: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + const COLOR_C: ElementColor = [0.25_f32, 0.50_f32, 1.00_f32]; + let vertices = [ + Point::new( + "a1".to_string(), + "A₁".to_string(), + COLOR_A, + engine::point(0.25, 0.75, 0.75) + ), + Point::new( + "a2".to_string(), + "A₂".to_string(), + COLOR_A, + engine::point(0.75, 0.25, 0.75) + ), + Point::new( + "a3".to_string(), + "A₃".to_string(), + COLOR_A, + engine::point(0.75, 0.75, 0.25) + ), + Point::new( + "b1".to_string(), + "B₁".to_string(), + COLOR_B, + engine::point(0.75, -0.25, -0.25) + ), + Point::new( + "b2".to_string(), + "B₂".to_string(), + COLOR_B, + engine::point(-0.25, 0.75, -0.25) + ), + Point::new( + "b3".to_string(), + "B₃".to_string(), + COLOR_B, + engine::point(-0.25, -0.25, 0.75) + ), + Point::new( + "c1".to_string(), + "C₁".to_string(), + COLOR_C, + engine::point(0.0, -1.0, -1.0) + ), + Point::new( + "c2".to_string(), + "C₂".to_string(), + COLOR_C, + engine::point(-1.0, 0.0, -1.0) + ), + Point::new( + "c3".to_string(), + "C₃".to_string(), + COLOR_C, + engine::point(-1.0, -1.0, 0.0) + ) + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // create the faces + const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let frac_1_sqrt_6 = 1.0 / 6.0_f64.sqrt(); + let frac_2_sqrt_6 = 2.0 * frac_1_sqrt_6; + let faces = [ + Sphere::new( + "face1".to_string(), + "Face 1".to_string(), + COLOR_FACE, + engine::sphere_with_offset(frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0) + ), + Sphere::new( + "face2".to_string(), + "Face 2".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0) + ), + Sphere::new( + "face3".to_string(), + "Face 3".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-frac_1_sqrt_6, -frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, 0.0) + ) + ]; + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + let index_range = 1..=3; + for j in index_range.clone() { + // make each face planar + let face = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("face{j}")].clone() + ); + let curvature_regulator = face.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set( + SpecifiedValue::try_from("0".to_string()).unwrap() + ); + + // put each A vertex on the face it belongs to + let vertex_a = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("a{j}")].clone() + ); + let incidence_a = InversiveDistanceRegulator::new([face.clone(), vertex_a.clone()]); + incidence_a.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(incidence_a)); + + // regulate the B-C vertex distances + let vertices_bc = ["b", "c"].map( + |series| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("{series}{j}")].clone() + ) + ); + assembly.insert_regulator( + Rc::new(InversiveDistanceRegulator::new(vertices_bc)) + ); + + // get the pair of indices adjacent to `j` + let adjacent_indices = [j % 3 + 1, (j + 1) % 3 + 1]; + + for k in adjacent_indices.clone() { + for series in ["b", "c"] { + // put each B and C vertex on the faces it belongs to + let vertex = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("{series}{k}")].clone() + ); + let incidence = InversiveDistanceRegulator::new([face.clone(), vertex.clone()]); + incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(incidence)); + + // regulate the A-B and A-C vertex distances + assembly.insert_regulator( + Rc::new(InversiveDistanceRegulator::new([vertex_a.clone(), vertex])) + ); + } + } + + // regulate the A-A and C-C vertex distances + let adjacent_pairs = ["a", "c"].map( + |series| adjacent_indices.map( + |index| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("{series}{index}")].clone() + ) + ) + ); + for pair in adjacent_pairs { + assembly.insert_regulator( + Rc::new(InversiveDistanceRegulator::new(pair)) + ); + } + } +} + +// to finish describing the dodecahedral circle packing, set the inversive +// distance regulators to -1. some of the regulators have already been set +fn load_dodeca_packing_assemb(assembly: &Assembly) { + // add the substrate + let _ = assembly.try_insert_element( + Sphere::new( + "substrate".to_string(), + "Substrate".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ) + ); + let substrate = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id["substrate"].clone() + ); + + // fix the substrate's curvature + substrate.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ).set_point().set( + SpecifiedValue::try_from("0.5".to_string()).unwrap() + ); + + // add the circles to be packed + const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.00_f32]; + const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32]; + const COLOR_C: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; + let phi = 0.5 + 1.25_f64.sqrt(); /* TO DO */ // replace with std::f64::consts::PHI when that gets stabilized + let phi_inv = 1.0 / phi; + let coord_scale = (phi + 2.0).sqrt(); + let face_scales = [phi_inv, (13.0 / 12.0) / coord_scale]; + let face_radii = [phi_inv, 5.0 / 12.0]; + let mut faces = Vec::>::new(); + let subscripts = ["₀", "₁"]; + for j in 0..2 { + for k in 0..2 { + let small_coord = face_scales[k] * (2.0*(j as f64) - 1.0); + let big_coord = face_scales[k] * (2.0*(k as f64) - 1.0) * phi; + + let id_num = format!("{j}{k}"); + let label_sub = format!("{}{}", subscripts[j], subscripts[k]); + + // add the A face + let id_a = format!("a{id_num}"); + let _ = assembly.try_insert_element( + Sphere::new( + id_a.clone(), + format!("A{label_sub}"), + COLOR_A, + engine::sphere(0.0, small_coord, big_coord, face_radii[k]) + ) + ); + faces.push( + assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id_a].clone() + ) + ); + + // add the B face + let id_b = format!("b{id_num}"); + let _ = assembly.try_insert_element( + Sphere::new( + id_b.clone(), + format!("B{label_sub}"), + COLOR_B, + engine::sphere(small_coord, big_coord, 0.0, face_radii[k]) + ) + ); + faces.push( + assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id_b].clone() + ) + ); + + // add the C face + let id_c = format!("c{id_num}"); + let _ = assembly.try_insert_element( + Sphere::new( + id_c.clone(), + format!("C{label_sub}"), + COLOR_C, + engine::sphere(big_coord, 0.0, small_coord, face_radii[k]) + ) + ); + faces.push( + assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id_c].clone() + ) + ); + } + } + + // make each face sphere perpendicular to the substrate + for face in faces { + let right_angle = InversiveDistanceRegulator::new([face, substrate.clone()]); + right_angle.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(right_angle)); + } + + // set up the tangencies that define the packing + for [long_edge_plane, short_edge_plane] in [["a", "b"], ["b", "c"], ["c", "a"]] { + for k in 0..2 { + let long_edge_ids = [ + format!("{long_edge_plane}{k}0"), + format!("{long_edge_plane}{k}1") + ]; + let short_edge_ids = [ + format!("{short_edge_plane}0{k}"), + format!("{short_edge_plane}1{k}") + ]; + let [long_edge, short_edge] = [long_edge_ids, short_edge_ids].map( + |edge_ids| edge_ids.map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id].clone() + ) + ) + ); + + // set up the short-edge tangency + let short_tangency = InversiveDistanceRegulator::new(short_edge.clone()); + if k == 0 { + short_tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); + } + assembly.insert_regulator(Rc::new(short_tangency)); + + // set up the side tangencies + for i in 0..2 { + for j in 0..2 { + let side_tangency = InversiveDistanceRegulator::new( + [long_edge[i].clone(), short_edge[j].clone()] + ); + if i == 0 && k == 0 { + side_tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap()); + } + assembly.insert_regulator(Rc::new(side_tangency)); + } + } + } + } +} + +// the initial configuration of this test assembly deliberately violates the +// constraints, so loading the assembly will trigger a non-trivial realization +fn load_balanced_assemb(assembly: &Assembly) { + // create the spheres + const R_OUTER: f64 = 10.0; + const R_INNER: f64 = 4.0; + let spheres = [ + Sphere::new( + "outer".to_string(), + "Outer".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, 0.0, 0.0, R_OUTER) + ), + Sphere::new( + "a".to_string(), + "A".to_string(), + [1.00_f32, 0.00_f32, 0.25_f32], + engine::sphere(0.0, 4.0, 0.0, R_INNER) + ), + Sphere::new( + "b".to_string(), + "B".to_string(), + [0.00_f32, 0.25_f32, 1.00_f32], + engine::sphere(0.0, -4.0, 0.0, R_INNER) + ), + ]; + for sphere in spheres { + let _ = assembly.try_insert_element(sphere); + } + + // get references to the spheres + let [outer, a, b] = ["outer", "a", "b"].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + + // fix the diameters of the outer, sun, and moon spheres + for (sphere, radius) in [ + (outer.clone(), R_OUTER), + (a.clone(), R_INNER), + (b.clone(), R_INNER) + ] { + let curvature_regulator = sphere.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + let curvature = 0.5 / radius; + curvature_regulator.set_point().set( + SpecifiedValue::try_from(curvature.to_string()).unwrap() + ); + } + + // set the inversive distances between the spheres. as described above, the + // initial configuration deliberately violates these constraints + for inner in [a, b] { + let tangency = InversiveDistanceRegulator::new([outer.clone(), inner]); + tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(tangency)); + } +} + +// the initial configuration of this test assembly deliberately violates the +// constraints, so loading the assembly will trigger a non-trivial realization +fn load_off_center_assemb(assembly: &Assembly) { + // create a point almost at the origin and a sphere centered on the origin + let _ = assembly.try_insert_element( + Point::new( + "point".to_string(), + "Point".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::point(1e-9, 0.0, 0.0) + ), + ); + let _ = assembly.try_insert_element( + Sphere::new( + "sphere".to_string(), + "Sphere".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ), + ); + + // get references to the elements + let point_and_sphere = ["point", "sphere"].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + + // put the point on the sphere + let incidence = InversiveDistanceRegulator::new(point_and_sphere); + incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(incidence)); +} + +// setting the inversive distances between the vertices to -2 gives a regular +// tetrahedron with side length 1, whose insphere and circumsphere have radii +// sqrt(1/6) and sqrt(3/2), respectively. to measure those radii, set an +// inversive distance of -1 between the insphere and each face, and then set an +// inversive distance of 0 between the circumsphere and each vertex +fn load_radius_ratio_assemb(assembly: &Assembly) { + let index_range = 1..=4; + + // create the spheres + const GRAY: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let spheres = [ + Sphere::new( + "sphere_faces".to_string(), + "Insphere".to_string(), + GRAY, + engine::sphere(0.0, 0.0, 0.0, 0.5) + ), + Sphere::new( + "sphere_vertices".to_string(), + "Circumsphere".to_string(), + GRAY, + engine::sphere(0.0, 0.0, 0.0, 0.25) + ) + ]; + for sphere in spheres { + let _ = assembly.try_insert_element(sphere); + } + + // create the vertices + let vertices = izip!( + index_range.clone(), + [ + [1.00_f32, 0.50_f32, 0.75_f32], + [1.00_f32, 0.75_f32, 0.50_f32], + [1.00_f32, 1.00_f32, 0.50_f32], + [0.75_f32, 0.50_f32, 1.00_f32] + ].into_iter(), + [ + engine::point(-0.6, -0.8, -0.6), + engine::point(-0.6, 0.8, 0.6), + engine::point(0.6, -0.8, 0.6), + engine::point(0.6, 0.8, -0.6) + ].into_iter() + ).map( + |(k, color, representation)| { + Point::new( + format!("v{k}"), + format!("Vertex {k}"), + color, + representation + ) + } + ); + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // create the faces + let base_dir = Vector3::new(1.0, 0.75, 1.0).normalize(); + let offset = base_dir.dot(&Vector3::new(-0.6, 0.8, 0.6)); + let faces = izip!( + index_range.clone(), + [ + [1.00_f32, 0.00_f32, 0.25_f32], + [1.00_f32, 0.25_f32, 0.00_f32], + [0.75_f32, 0.75_f32, 0.00_f32], + [0.25_f32, 0.00_f32, 1.00_f32] + ].into_iter(), + [ + engine::sphere_with_offset(base_dir[0], base_dir[1], base_dir[2], offset, 0.0), + engine::sphere_with_offset(base_dir[0], -base_dir[1], -base_dir[2], offset, 0.0), + engine::sphere_with_offset(-base_dir[0], base_dir[1], -base_dir[2], offset, 0.0), + engine::sphere_with_offset(-base_dir[0], -base_dir[1], base_dir[2], offset, 0.0) + ].into_iter() + ).map( + |(k, color, representation)| { + Sphere::new( + format!("f{k}"), + format!("Face {k}"), + color, + representation + ) + } + ); + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + // impose the constraints + for j in index_range.clone() { + let [face_j, vertex_j] = [ + format!("f{j}"), + format!("v{j}") + ].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&id].clone() + ) + ); + + // make the faces planar + let curvature_regulator = face_j.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set( + SpecifiedValue::try_from("0".to_string()).unwrap() + ); + + for k in index_range.clone().filter(|&index| index != j) { + let vertex_k = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("v{k}")].clone() + ); + + // fix the distances between the vertices + if j < k { + let distance_regulator = InversiveDistanceRegulator::new( + [vertex_j.clone(), vertex_k.clone()] + ); + assembly.insert_regulator(Rc::new(distance_regulator)); + } + + // put the vertices on the faces + let incidence_regulator = InversiveDistanceRegulator::new([face_j.clone(), vertex_k.clone()]); + incidence_regulator.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(incidence_regulator)); + } + } +} + +// to finish setting up the problem, fix the following curvatures: +// sun 1 +// moon 5/3 = 1.666666666666666... +// chain1 2 +// a tiny `x` or `z` nudge of the outer sphere reliably prevents realization +// failures before they happen, or resolves them after they happen. the result +// depends sensitively on the translation direction, suggesting that realization +// is failing because the engine is having trouble breaking a symmetry +// /* TO DO */ +// the engine's performance on this problem is scale-dependent! with the current +// initial conditions, realization fails for any order of imposing the remaining +// curvature constraints. scaling everything up by a factor of ten, as done in +// the original problem, makes realization succeed reliably. one potentially +// relevant difference is that a lot of the numbers in the current initial +// conditions are exactly representable as floats, unlike the analogous numbers +// in the scaled-up problem. the inexact representations might break the +// symmetry that's getting the engine stuck +fn load_irisawa_hexlet_assemb(assembly: &Assembly) { + let index_range = 1..=6; + let colors = [ + [1.00_f32, 0.00_f32, 0.25_f32], + [1.00_f32, 0.25_f32, 0.00_f32], + [0.75_f32, 0.75_f32, 0.00_f32], + [0.25_f32, 1.00_f32, 0.00_f32], + [0.00_f32, 0.25_f32, 1.00_f32], + [0.25_f32, 0.00_f32, 1.00_f32] + ].into_iter(); + + // create the spheres + let spheres = [ + Sphere::new( + "outer".to_string(), + "Outer".to_string(), + [0.5_f32, 0.5_f32, 0.5_f32], + engine::sphere(0.0, 0.0, 0.0, 1.5) + ), + Sphere::new( + "sun".to_string(), + "Sun".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, -0.75, 0.0, 0.75) + ), + Sphere::new( + "moon".to_string(), + "Moon".to_string(), + [0.25_f32, 0.25_f32, 0.25_f32], + engine::sphere(0.0, 0.75, 0.0, 0.75) + ), + ].into_iter().chain( + index_range.clone().zip(colors).map( + |(k, color)| { + let ang = (k as f64) * PI/3.0; + Sphere::new( + format!("chain{k}"), + format!("Chain {k}"), + color, + engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5) + ) + } + ) + ); + for sphere in spheres { + let _ = assembly.try_insert_element(sphere); + } + + // put the outer sphere in ghost mode and fix its curvature + let outer = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id["outer"].clone() + ); + outer.ghost().set(true); + let outer_curvature_regulator = outer.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + outer_curvature_regulator.set_point().set( + SpecifiedValue::try_from((1.0 / 3.0).to_string()).unwrap() + ); + + // impose the desired tangencies + let [outer, sun, moon] = ["outer", "sun", "moon"].map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + let chain = index_range.map( + |k| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[&format!("chain{k}")].clone() + ) + ); + for (chain_sphere, chain_sphere_next) in chain.clone().zip(chain.cycle().skip(1)) { + for (other_sphere, inversive_distance) in [ + (outer.clone(), "1"), + (sun.clone(), "-1"), + (moon.clone(), "-1"), + (chain_sphere_next.clone(), "-1") + ] { + let tangency = InversiveDistanceRegulator::new([chain_sphere.clone(), other_sphere]); + tangency.set_point.set(SpecifiedValue::try_from(inversive_distance.to_string()).unwrap()); + assembly.insert_regulator(Rc::new(tangency)); + } + } + + let outer_sun_tangency = InversiveDistanceRegulator::new([outer.clone(), sun]); + outer_sun_tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(outer_sun_tangency)); + + let outer_moon_tangency = InversiveDistanceRegulator::new([outer.clone(), moon]); + outer_moon_tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap()); + assembly.insert_regulator(Rc::new(outer_moon_tangency)); +} + +// --- chooser --- + +/* DEBUG */ +#[component] +pub fn TestAssemblyChooser() -> View { + // create an effect that loads the selected test assembly + let assembly_name = create_signal("general".to_string()); + create_effect(move || { + // get name of chosen assembly + let name = assembly_name.get_clone(); + console::log_1( + &JsValue::from(format!("Showing assembly \"{}\"", name.clone())) + ); + + batch(|| { + let state = use_context::(); + let assembly = &state.assembly; + + // pause realization + assembly.keep_realized.set(false); + + // clear state + assembly.regulators.update(|regs| regs.clear()); + assembly.elements.update(|elts| elts.clear()); + assembly.elements_by_id.update(|elts_by_id| elts_by_id.clear()); + assembly.descent_history.set(DescentHistory::new()); + state.selection.update(|sel| sel.clear()); + + // load assembly + match name.as_str() { + "general" => load_gen_assemb(assembly), + "low-curv" => load_low_curv_assemb(assembly), + "pointed" => load_pointed_assemb(assembly), + "tridim-icosahedron" => load_tridim_icosahedron_assemb(assembly), + "dodeca-packing" => load_dodeca_packing_assemb(assembly), + "balanced" => load_balanced_assemb(assembly), + "off-center" => load_off_center_assemb(assembly), + "radius-ratio" => load_radius_ratio_assemb(assembly), + "irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly), + _ => () + }; + + // resume realization + assembly.keep_realized.set(true); + }); + }); + + // build the chooser + view! { + select(bind:value=assembly_name) { + option(value="general") { "General" } + option(value="low-curv") { "Low-curvature" } + option(value="pointed") { "Pointed" } + option(value="tridim-icosahedron") { "Tridiminished icosahedron" } + option(value="dodeca-packing") { "Dodecahedral packing" } + option(value="balanced") { "Balanced" } + option(value="off-center") { "Off-center" } + option(value="radius-ratio") { "Radius ratio" } + option(value="irisawa-hexlet") { "Irisawa hexlet" } + option(value="empty") { "Empty" } + } + } +} \ No newline at end of file diff --git a/app-proto/src/main.rs b/app-proto/src/main.rs index f905c46..152d11c 100644 --- a/app-proto/src/main.rs +++ b/app-proto/src/main.rs @@ -1,9 +1,6 @@ -mod add_remove; mod assembly; -mod diagnostics; -mod display; +mod components; mod engine; -mod outline; mod specified; #[cfg(test)] @@ -12,11 +9,13 @@ mod tests; use std::{collections::BTreeSet, rc::Rc}; use sycamore::prelude::*; -use add_remove::AddRemove; use assembly::{Assembly, Element}; -use diagnostics::Diagnostics; -use display::Display; -use outline::Outline; +use components::{ + add_remove::AddRemove, + diagnostics::Diagnostics, + display::Display, + outline::Outline +}; #[derive(Clone)] struct AppState { From 2eba80fb69318ae78337c012b71909bba0c13461 Mon Sep 17 00:00:00 2001 From: Vectornaut Date: Thu, 31 Jul 2025 22:21:32 +0000 Subject: [PATCH 11/14] Simplify the realization triggering system (#105) Simplifies the system that reactively triggers realizations, at the cost of removing the preconditioning step described in issue #101 and doing unnecessary realizations after certain kinds of updates. Co-authored-by: Aaron Fenyes Reviewed-on: https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/105 Co-authored-by: Vectornaut Co-committed-by: Vectornaut --- app-proto/src/assembly.rs | 72 +++++-------------- app-proto/src/components/add_remove.rs | 17 ++++- .../src/components/test_assembly_chooser.rs | 6 -- app-proto/src/engine.rs | 57 ++++----------- 4 files changed, 48 insertions(+), 104 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 68fcd8b..26fb4aa 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -16,7 +16,6 @@ use crate::{ components::{display::DisplayItem, outline::OutlineItem}, engine::{ Q, - change_half_curvature, local_unif_to_std, point, project_point_to_normalized, @@ -358,16 +357,6 @@ pub trait Regulator: Serial + ProblemPoser + OutlineItem { fn subjects(&self) -> Vec>; fn measurement(&self) -> ReadSignal; fn set_point(&self) -> Signal; - - // this method is used to responsively precondition the assembly for - // realization when the regulator becomes a constraint, or is edited while - // acting as a constraint. it should track the set point, do any desired - // preconditioning when the set point is present, and use its return value - // to report whether the set is present. the default implementation does no - // preconditioning - fn try_activate(&self) -> bool { - self.set_point().with(|set_pt| set_pt.is_present()) - } } impl Hash for dyn Regulator { @@ -488,18 +477,6 @@ impl Regulator for HalfCurvatureRegulator { fn set_point(&self) -> Signal { self.set_point } - - fn try_activate(&self) -> bool { - match self.set_point.with(|set_pt| set_pt.value) { - Some(half_curv) => { - self.subject.representation().update( - |rep| change_half_curvature(rep, half_curv) - ); - true - } - None => false - } - } } impl Serial for HalfCurvatureRegulator { @@ -552,8 +529,7 @@ pub struct Assembly { pub elements_by_id: Signal>>, // realization control - pub keep_realized: Signal, - pub needs_realization: Signal, + pub realization_trigger: Signal<()>, // realization diagnostics pub realization_status: Signal>, @@ -568,21 +544,23 @@ impl Assembly { regulators: create_signal(BTreeSet::new()), tangent: create_signal(ConfigSubspace::zero(0)), elements_by_id: create_signal(BTreeMap::default()), - keep_realized: create_signal(true), - needs_realization: create_signal(false), + realization_trigger: create_signal(()), realization_status: create_signal(Ok(())), descent_history: create_signal(DescentHistory::new()) }; - // realize the assembly whenever it becomes simultaneously true that - // we're trying to keep it realized and it needs realization + // realize the assembly whenever the element list, the regulator list, + // a regulator's set point, or the realization trigger is updated let assembly_for_effect = assembly.clone(); create_effect(move || { - let should_realize = assembly_for_effect.keep_realized.get() - && assembly_for_effect.needs_realization.get(); - if should_realize { - assembly_for_effect.realize(); - } + assembly_for_effect.elements.track(); + assembly_for_effect.regulators.with( + |regs| for reg in regs { + reg.set_point().track(); + } + ); + assembly_for_effect.realization_trigger.track(); + assembly_for_effect.realize(); }); assembly @@ -646,19 +624,6 @@ impl Assembly { regulators.update(|regs| regs.insert(regulator.clone())); } - // request a realization when the regulator becomes a constraint, or is - // edited while acting as a constraint - let self_for_effect = self.clone(); - create_effect(move || { - /* DEBUG */ - // log the regulator update - console_log!("Updated regulator with subjects {:?}", regulator.subjects()); - - if regulator.try_activate() { - self_for_effect.needs_realization.set(true); - } - }); - /* DEBUG */ // print an updated list of regulators console_log!("Regulators:"); @@ -726,8 +691,10 @@ impl Assembly { } else { console_log!("✅️ Target accuracy achieved!"); } - console_log!("Steps: {}", history.scaled_loss.len() - 1); - console_log!("Loss: {}", history.scaled_loss.last().unwrap()); + if history.scaled_loss.len() > 0 { + console_log!("Steps: {}", history.scaled_loss.len() - 1); + console_log!("Loss: {}", history.scaled_loss.last().unwrap()); + } // report the loss history self.descent_history.set(history); @@ -750,9 +717,6 @@ impl Assembly { // save the tangent space self.tangent.set_silent(tangent); - - // clear the realization request flag - self.needs_realization.set(false); }, Err(message) => { // report the realization status. the `Err(message)` we're @@ -848,10 +812,10 @@ impl Assembly { }); } - // request a realization to bring the configuration back onto the + // trigger a realization to bring the configuration back onto the // solution variety. this also gets the elements' column indices and the // saved tangent space back in sync - self.needs_realization.set(true); + self.realization_trigger.set(()); } } diff --git a/app-proto/src/components/add_remove.rs b/app-proto/src/components/add_remove.rs index 3b0f9e0..a685482 100644 --- a/app-proto/src/components/add_remove.rs +++ b/app-proto/src/components/add_remove.rs @@ -14,7 +14,22 @@ pub fn AddRemove() -> View { button( on:click=|_| { let state = use_context::(); - state.assembly.insert_element_default::(); + batch(|| { + // this call is batched to avoid redundant realizations. + // it updates the element list and the regulator list, + // which are both tracked by the realization effect + /* TO DO */ + // it would make more to do the batching inside + // `insert_element_default`, but that will have to wait + // until Sycamore handles nested batches correctly. + // + // https://github.com/sycamore-rs/sycamore/issues/802 + // + // the nested batch issue is relevant here because the + // assembly loaders in the test assembly chooser use + // `insert_element_default` within larger batches + state.assembly.insert_element_default::(); + }); } ) { "Add sphere" } button( diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 232cda3..b58dd1a 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -900,9 +900,6 @@ pub fn TestAssemblyChooser() -> View { let state = use_context::(); let assembly = &state.assembly; - // pause realization - assembly.keep_realized.set(false); - // clear state assembly.regulators.update(|regs| regs.clear()); assembly.elements.update(|elts| elts.clear()); @@ -923,9 +920,6 @@ pub fn TestAssemblyChooser() -> View { "irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly), _ => () }; - - // resume realization - assembly.keep_realized.set(true); }); }); diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index e6ffa25..602fc57 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -1,7 +1,6 @@ use lazy_static::lazy_static; use nalgebra::{Const, DMatrix, DVector, DVectorView, Dyn, SymmetricEigen}; use std::fmt::{Display, Error, Formatter}; -use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ // --- elements --- @@ -50,40 +49,6 @@ pub fn project_point_to_normalized(rep: &mut DVector) { rep.scale_mut(0.5 / rep[3]); } -// given a sphere's representation vector, change the sphere's half-curvature to -// `half-curv` and then restore normalization by contracting the representation -// vector toward the curvature axis -pub fn change_half_curvature(rep: &mut DVector, half_curv: f64) { - // set the sphere's half-curvature to the desired value - rep[3] = half_curv; - - // restore normalization by contracting toward the curvature axis - const SIZE_THRESHOLD: f64 = 1e-9; - let half_q_lt = -2.0 * half_curv * rep[4]; - let half_q_lt_sq = half_q_lt * half_q_lt; - let mut spatial = rep.fixed_rows_mut::<3>(0); - let q_sp = spatial.norm_squared(); - if q_sp < SIZE_THRESHOLD && half_q_lt_sq < SIZE_THRESHOLD { - spatial.copy_from_slice( - &[0.0, 0.0, (1.0 - 2.0 * half_q_lt).sqrt()] - ); - } else { - let scaling = half_q_lt + (q_sp + half_q_lt_sq).sqrt(); - spatial.scale_mut(1.0 / scaling); - rep[4] /= scaling; - } - - /* DEBUG */ - // verify normalization - let rep_for_debug = rep.clone(); - console::log_1(&JsValue::from( - format!( - "Sphere self-product after curvature change: {}", - rep_for_debug.dot(&(&*Q * &rep_for_debug)) - ) - )); -} - // --- partial matrices --- pub struct MatrixEntry { @@ -199,13 +164,6 @@ impl ConfigSubspace { ).collect::>().as_slice() ); - /* DEBUG */ - // print the eigenvalues - #[cfg(all(target_family = "wasm", target_os = "unknown"))] - console::log_1(&JsValue::from( - format!("Eigenvalues used to find kernel:{}", eig.eigenvalues) - )); - // express the basis in the standard coordinates let basis_std = proj_to_std * &basis_proj; @@ -425,9 +383,22 @@ pub fn realize_gram( // start the descent history let mut history = DescentHistory::new(); + // handle the case where the assembly is empty. our general realization + // routine can't handle this case because it builds the Hessian using + // `DMatrix::from_columns`, which panics when the list of columns is empty + let assembly_dim = guess.ncols(); + if assembly_dim == 0 { + let result = Ok( + ConfigNeighborhood { + config: guess.clone(), + nbhd: ConfigSubspace::zero(0) + } + ); + return Realization { result, history } + } + // find the dimension of the search space let element_dim = guess.nrows(); - let assembly_dim = guess.ncols(); let total_dim = element_dim * assembly_dim; // scale the tolerance From ef1a579ac0893a150c37f570729a4ba77d62b6e8 Mon Sep 17 00:00:00 2001 From: Vectornaut Date: Mon, 4 Aug 2025 23:34:33 +0000 Subject: [PATCH 12/14] refactor: Code formatting (#108) Primarily, switch to using trailing commas. Also uniformizes commas with respect to switch branches, makes function call layout more consistent, line breaking more consistent, alphabetizes imports, uses the field init shorthand when possible, etc. Resolves #99. Co-authored-by: Aaron Fenyes Reviewed-on: https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/108 Co-authored-by: Vectornaut Co-committed-by: Vectornaut --- app-proto/examples/kaleidocycle.rs | 2 +- app-proto/examples/point-on-sphere.rs | 4 +- app-proto/examples/three-spheres.rs | 6 +- app-proto/src/assembly.rs | 48 +++--- app-proto/src/components/add_remove.rs | 14 +- app-proto/src/components/diagnostics.rs | 44 +++--- app-proto/src/components/display.rs | 117 ++++++++------ app-proto/src/components/outline.rs | 94 ++++++----- .../src/components/test_assembly_chooser.rs | 148 +++++++++--------- app-proto/src/engine.rs | 118 +++++++------- app-proto/src/main.rs | 8 +- app-proto/src/specified.rs | 4 +- 12 files changed, 310 insertions(+), 297 deletions(-) diff --git a/app-proto/examples/kaleidocycle.rs b/app-proto/examples/kaleidocycle.rs index 7ca1f97..ae4eb07 100644 --- a/app-proto/examples/kaleidocycle.rs +++ b/app-proto/examples/kaleidocycle.rs @@ -23,7 +23,7 @@ fn main() { let twist_motion: DMatrix<_> = (0..N_POINTS).step_by(4).flat_map( |n| [ tangent.proj(&up.as_view(), n), - tangent.proj(&down.as_view(), n+1) + tangent.proj(&down.as_view(), n+1), ] ).sum(); let normalization = 5.0 / twist_motion[(2, 0)]; diff --git a/app-proto/examples/point-on-sphere.rs b/app-proto/examples/point-on-sphere.rs index 89dee76..a73490e 100644 --- a/app-proto/examples/point-on-sphere.rs +++ b/app-proto/examples/point-on-sphere.rs @@ -6,7 +6,7 @@ use dyna3::engine::{ realize_gram, sphere, ConfigNeighborhood, - ConstraintProblem + ConstraintProblem, }; fn main() { @@ -25,7 +25,7 @@ fn main() { ); print::title("Point on a sphere"); print::realization_diagnostics(&realization); - if let Ok(ConfigNeighborhood{ config, .. }) = realization.result { + if let Ok(ConfigNeighborhood { config, .. }) = realization.result { print::gram_matrix(&config); print::config(&config); } diff --git a/app-proto/examples/three-spheres.rs b/app-proto/examples/three-spheres.rs index aa5a105..7901e31 100644 --- a/app-proto/examples/three-spheres.rs +++ b/app-proto/examples/three-spheres.rs @@ -5,7 +5,7 @@ use dyna3::engine::{ realize_gram, sphere, ConfigNeighborhood, - ConstraintProblem + ConstraintProblem, }; fn main() { @@ -14,7 +14,7 @@ fn main() { &[ sphere(1.0, 0.0, 0.0, 1.0), sphere(-0.5, a, 0.0, 1.0), - sphere(-0.5, -a, 0.0, 1.0) + sphere(-0.5, -a, 0.0, 1.0), ] }); for j in 0..3 { @@ -27,7 +27,7 @@ fn main() { ); print::title("Three spheres"); print::realization_diagnostics(&realization); - if let Ok(ConfigNeighborhood{ config, .. }) = realization.result { + if let Ok(ConfigNeighborhood { config, .. }) = realization.result { print::gram_matrix(&config); } print::loss_history(&realization.history); diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 26fb4aa..43066fd 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -1,13 +1,13 @@ use nalgebra::{DMatrix, DVector, DVectorView}; use std::{ cell::Cell, - collections::{BTreeMap, BTreeSet}, cmp::Ordering, + collections::{BTreeMap, BTreeSet}, fmt, fmt::{Debug, Formatter}, hash::{Hash, Hasher}, rc::Rc, - sync::{atomic, atomic::AtomicU64} + sync::{atomic, atomic::AtomicU64}, }; use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ @@ -26,9 +26,9 @@ use crate::{ ConfigSubspace, ConstraintProblem, DescentHistory, - Realization + Realization, }, - specified::SpecifiedValue + specified::SpecifiedValue, }; pub type ElementColor = [f32; 3]; @@ -164,7 +164,7 @@ pub struct Sphere { pub ghost: Signal, pub regulators: Signal>>, serial: u64, - column_index: Cell> + column_index: Cell>, } impl Sphere { @@ -174,17 +174,17 @@ impl Sphere { id: String, label: String, color: ElementColor, - representation: DVector + representation: DVector, ) -> Sphere { Sphere { - id: id, - label: label, - color: color, + id, + label, + color, representation: create_signal(representation), ghost: create_signal(false), regulators: create_signal(BTreeSet::new()), serial: Self::next_serial(), - column_index: None.into() + column_index: None.into(), } } } @@ -199,7 +199,7 @@ impl Element for Sphere { id, format!("Sphere {id_num}"), [0.75_f32, 0.75_f32, 0.75_f32], - sphere(0.0, 0.0, 0.0, 1.0) + sphere(0.0, 0.0, 0.0, 1.0), ) } @@ -264,7 +264,7 @@ pub struct Point { pub ghost: Signal, pub regulators: Signal>>, serial: u64, - column_index: Cell> + column_index: Cell>, } impl Point { @@ -274,7 +274,7 @@ impl Point { id: String, label: String, color: ElementColor, - representation: DVector + representation: DVector, ) -> Point { Point { id, @@ -284,7 +284,7 @@ impl Point { ghost: create_signal(false), regulators: create_signal(BTreeSet::new()), serial: Self::next_serial(), - column_index: None.into() + column_index: None.into(), } } } @@ -299,7 +299,7 @@ impl Element for Point { id, format!("Point {id_num}"), [0.75_f32, 0.75_f32, 0.75_f32], - point(0.0, 0.0, 0.0) + point(0.0, 0.0, 0.0), ) } @@ -389,7 +389,7 @@ pub struct InversiveDistanceRegulator { pub subjects: [Rc; 2], pub measurement: ReadSignal, pub set_point: Signal, - serial: u64 + serial: u64, } impl InversiveDistanceRegulator { @@ -449,7 +449,7 @@ pub struct HalfCurvatureRegulator { pub subject: Rc, pub measurement: ReadSignal, pub set_point: Signal, - serial: u64 + serial: u64, } impl HalfCurvatureRegulator { @@ -501,7 +501,7 @@ impl ProblemPoser for HalfCurvatureRegulator { // the velocity is expressed in uniform coordinates pub struct ElementMotion<'a> { pub element: Rc, - pub velocity: DVectorView<'a, f64> + pub velocity: DVectorView<'a, f64>, } type AssemblyMotion<'a> = Vec>; @@ -533,7 +533,7 @@ pub struct Assembly { // realization diagnostics pub realization_status: Signal>, - pub descent_history: Signal + pub descent_history: Signal, } impl Assembly { @@ -546,7 +546,7 @@ impl Assembly { elements_by_id: create_signal(BTreeMap::default()), realization_trigger: create_signal(()), realization_status: create_signal(Ok(())), - descent_history: create_signal(DescentHistory::new()) + descent_history: create_signal(DescentHistory::new()), }; // realize the assembly whenever the element list, the regulator list, @@ -724,7 +724,7 @@ impl Assembly { // `Err(message)` we received from the match: we're changing the // `Ok` type from `Realization` to `()` self.realization_status.set(Err(message)) - } + }, } } @@ -807,7 +807,7 @@ impl Assembly { }, None => { console_log!("No velocity to unpack for fresh element \"{}\"", elt.id()) - } + }, }; }); } @@ -867,7 +867,7 @@ mod tests { String::from(sphere_id), String::from("Sphere 0"), [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, INITIAL_RADIUS) + engine::sphere(0.0, 0.0, 0.0, INITIAL_RADIUS), ) ); @@ -881,7 +881,7 @@ mod tests { vec![ ElementMotion { element: sphere.clone(), - velocity: velocity.as_view() + velocity: velocity.as_view(), } ] ); diff --git a/app-proto/src/components/add_remove.rs b/app-proto/src/components/add_remove.rs index a685482..4196640 100644 --- a/app-proto/src/components/add_remove.rs +++ b/app-proto/src/components/add_remove.rs @@ -4,15 +4,15 @@ use sycamore::prelude::*; use super::test_assembly_chooser::TestAssemblyChooser; use crate::{ AppState, - assembly::{InversiveDistanceRegulator, Point, Sphere} + assembly::{InversiveDistanceRegulator, Point, Sphere}, }; #[component] pub fn AddRemove() -> View { view! { - div(id="add-remove") { + div(id = "add-remove") { button( - on:click=|_| { + on:click = |_| { let state = use_context::(); batch(|| { // this call is batched to avoid redundant realizations. @@ -33,18 +33,18 @@ pub fn AddRemove() -> View { } ) { "Add sphere" } button( - on:click=|_| { + on:click = |_| { let state = use_context::(); state.assembly.insert_element_default::(); } ) { "Add point" } button( - class="emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button - disabled={ + class = "emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button + disabled = { let state = use_context::(); state.selection.with(|sel| sel.len() != 2) }, - on:click=|_| { + on:click = |_| { let state = use_context::(); let subjects: [_; 2] = state.selection.with( // the button is only enabled when two elements are diff --git a/app-proto/src/components/diagnostics.rs b/app-proto/src/components/diagnostics.rs index a2f090a..b274dca 100644 --- a/app-proto/src/components/diagnostics.rs +++ b/app-proto/src/components/diagnostics.rs @@ -11,14 +11,12 @@ use crate::AppState; #[derive(Clone)] struct DiagnosticsState { - active_tab: Signal + active_tab: Signal, } impl DiagnosticsState { fn new(initial_tab: String) -> DiagnosticsState { - DiagnosticsState { - active_tab: create_signal(initial_tab) - } + DiagnosticsState { active_tab: create_signal(initial_tab) } } } @@ -29,20 +27,20 @@ fn RealizationStatus() -> View { let realization_status = state.assembly.realization_status; view! { div( - id="realization-status", - class=realization_status.with( + id = "realization-status", + class = realization_status.with( |status| match status { Ok(_) => "", - Err(_) => "invalid" + Err(_) => "invalid", } ) ) { - div(class="status") + div(class = "status") div { (realization_status.with( |status| match status { Ok(_) => "Target accuracy achieved".to_string(), - Err(message) => message.clone() + Err(message) => message.clone(), } )) } @@ -53,7 +51,7 @@ fn RealizationStatus() -> View { fn into_log10_time_point((step, value): (usize, f64)) -> Vec> { vec![ Some(step as f64), - if value == 0.0 { None } else { Some(value.abs().log10()) } + if value == 0.0 { None } else { Some(value.abs().log10()) }, ] } @@ -105,7 +103,7 @@ fn LossHistory() -> View { }); view! { - div(id=CONTAINER_ID, class="diagnostics-chart") + div(id = CONTAINER_ID, class = "diagnostics-chart") } } @@ -122,7 +120,7 @@ fn SpectrumHistory() -> View { // positive, negative, and strictly-zero parts let ( hess_eigvals_zero, - hess_eigvals_nonzero + hess_eigvals_nonzero, ): (Vec<_>, Vec<_>) = state.assembly.descent_history.with( |history| history.hess_eigvals .iter() @@ -143,7 +141,7 @@ fn SpectrumHistory() -> View { .unwrap_or(1.0); let ( hess_eigvals_pos, - hess_eigvals_neg + hess_eigvals_neg, ): (Vec<_>, Vec<_>) = hess_eigvals_nonzero .into_iter() .partition(|&(_, val)| val > 0.0); @@ -211,7 +209,7 @@ fn SpectrumHistory() -> View { }); view! { - div(id=CONTAINER_ID, class="diagnostics-chart") + div(id = CONTAINER_ID, class = "diagnostics-chart") } } @@ -220,8 +218,8 @@ fn DiagnosticsPanel(name: &'static str, children: Children) -> View { let diagnostics_state = use_context::(); view! { div( - class="diagnostics-panel", - "hidden"=diagnostics_state.active_tab.with( + class = "diagnostics-panel", + "hidden" = diagnostics_state.active_tab.with( |active_tab| { if active_tab == name { None @@ -243,16 +241,16 @@ pub fn Diagnostics() -> View { provide_context(diagnostics_state); view! { - div(id="diagnostics") { - div(id="diagnostics-bar") { + div(id = "diagnostics") { + div(id = "diagnostics-bar") { RealizationStatus {} - select(bind:value=active_tab) { - option(value="loss") { "Loss" } - option(value="spectrum") { "Spectrum" } + select(bind:value = active_tab) { + option(value = "loss") { "Loss" } + option(value = "spectrum") { "Spectrum" } } } - DiagnosticsPanel(name="loss") { LossHistory {} } - DiagnosticsPanel(name="spectrum") { SpectrumHistory {} } + DiagnosticsPanel(name = "loss") { LossHistory {} } + DiagnosticsPanel(name = "spectrum") { SpectrumHistory {} } } } } \ No newline at end of file diff --git a/app-proto/src/components/display.rs b/app-proto/src/components/display.rs index 1646c4e..a0cdba6 100644 --- a/app-proto/src/components/display.rs +++ b/app-proto/src/components/display.rs @@ -12,12 +12,12 @@ use web_sys::{ WebGlProgram, WebGlShader, WebGlUniformLocation, - wasm_bindgen::{JsCast, JsValue} + wasm_bindgen::{JsCast, JsValue}, }; use crate::{ AppState, - assembly::{Element, ElementColor, ElementMotion, Point, Sphere} + assembly::{Element, ElementColor, ElementMotion, Point, Sphere}, }; // --- color --- @@ -37,15 +37,15 @@ fn combine_channels(color: ElementColor, opacity: f32) -> ColorWithOpacity { struct SceneSpheres { representations: Vec>, colors_with_opacity: Vec, - highlights: Vec + highlights: Vec, } impl SceneSpheres { - fn new() -> SceneSpheres{ + fn new() -> SceneSpheres { SceneSpheres { representations: Vec::new(), colors_with_opacity: Vec::new(), - highlights: Vec::new() + highlights: Vec::new(), } } @@ -53,7 +53,10 @@ impl SceneSpheres { self.representations.len().try_into().expect("Number of spheres must fit in a 32-bit integer") } - fn push(&mut self, representation: DVector, color: ElementColor, opacity: f32, highlight: f32) { + fn push( + &mut self, representation: DVector, + color: ElementColor, opacity: f32, highlight: f32, + ) { self.representations.push(representation); self.colors_with_opacity.push(combine_channels(color, opacity)); self.highlights.push(highlight); @@ -64,7 +67,7 @@ struct ScenePoints { representations: Vec>, colors_with_opacity: Vec, highlights: Vec, - selections: Vec + selections: Vec, } impl ScenePoints { @@ -73,11 +76,14 @@ impl ScenePoints { representations: Vec::new(), colors_with_opacity: Vec::new(), highlights: Vec::new(), - selections: Vec::new() + selections: Vec::new(), } } - fn push(&mut self, representation: DVector, color: ElementColor, opacity: f32, highlight: f32, selected: bool) { + fn push( + &mut self, representation: DVector, + color: ElementColor, opacity: f32, highlight: f32, selected: bool, + ) { self.representations.push(representation); self.colors_with_opacity.push(combine_channels(color, opacity)); self.highlights.push(highlight); @@ -87,14 +93,14 @@ impl ScenePoints { pub struct Scene { spheres: SceneSpheres, - points: ScenePoints + points: ScenePoints, } impl Scene { fn new() -> Scene { Scene { spheres: SceneSpheres::new(), - points: ScenePoints::new() + points: ScenePoints::new(), } } } @@ -105,7 +111,12 @@ pub trait DisplayItem { // the smallest positive depth, represented as a multiple of `dir`, where // the line generated by `dir` hits the element. returns `None` if the line // misses the element - fn cast(&self, dir: Vector3, assembly_to_world: &DMatrix, pixel_size: f64) -> Option; + fn cast( + &self, + dir: Vector3, + assembly_to_world: &DMatrix, + pixel_size: f64, + ) -> Option; } impl DisplayItem for Sphere { @@ -124,7 +135,12 @@ impl DisplayItem for Sphere { // this method should be kept synchronized with `sphere_cast` in // `spheres.frag`, which does essentially the same thing on the GPU side - fn cast(&self, dir: Vector3, assembly_to_world: &DMatrix, _pixel_size: f64) -> Option { + fn cast( + &self, + dir: Vector3, + assembly_to_world: &DMatrix, + _pixel_size: f64, + ) -> Option { // if `a/b` is less than this threshold, we approximate // `a*u^2 + b*u + c` by the linear function `b*u + c` const DEG_THRESHOLD: f64 = 1e-9; @@ -177,7 +193,12 @@ impl DisplayItem for Point { } /* SCAFFOLDING */ - fn cast(&self, dir: Vector3, assembly_to_world: &DMatrix, pixel_size: f64) -> Option { + fn cast( + &self, + dir: Vector3, + assembly_to_world: &DMatrix, + pixel_size: f64, + ) -> Option { let rep = self.representation.with_untracked(|rep| assembly_to_world * rep); if rep[2] < 0.0 { // this constant should be kept synchronized with `point.frag` @@ -220,7 +241,7 @@ fn compile_shader( fn set_up_program( context: &WebGl2RenderingContext, vertex_shader_source: &str, - fragment_shader_source: &str + fragment_shader_source: &str, ) -> WebGlProgram { // compile the shaders let vertex_shader = compile_shader( @@ -260,12 +281,12 @@ fn get_uniform_array_locations( context: &WebGl2RenderingContext, program: &WebGlProgram, var_name: &str, - member_name_opt: Option<&str> + member_name_opt: Option<&str>, ) -> [Option; N] { array::from_fn(|n| { let name = match member_name_opt { Some(member_name) => format!("{var_name}[{n}].{member_name}"), - None => format!("{var_name}[{n}]") + None => format!("{var_name}[{n}]"), }; context.get_uniform_location(&program, name.as_str()) }) @@ -276,7 +297,7 @@ fn bind_to_attribute( context: &WebGl2RenderingContext, attr_index: u32, attr_size: i32, - buffer: &Option + buffer: &Option, ) { context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, buffer.as_ref()); context.vertex_attrib_pointer_with_i32( @@ -292,7 +313,7 @@ fn bind_to_attribute( // load the given data into a new vertex buffer object fn load_new_buffer( context: &WebGl2RenderingContext, - data: &[f32] + data: &[f32], ) -> Option { // create a buffer and bind it to ARRAY_BUFFER let buffer = context.create_buffer(); @@ -319,7 +340,7 @@ fn bind_new_buffer_to_attribute( context: &WebGl2RenderingContext, attr_index: u32, attr_size: i32, - data: &[f32] + data: &[f32], ) { let buffer = load_new_buffer(context, data); bind_to_attribute(context, attr_index, attr_size, &buffer); @@ -341,9 +362,9 @@ fn event_dir(event: &MouseEvent) -> (Vector3, f64) { Vector3::new( FOCAL_SLOPE * (2.0*(f64::from(event.client_x()) - rect.left()) - width) / shortdim, FOCAL_SLOPE * (2.0*(rect.bottom() - f64::from(event.client_y())) - height) / shortdim, - -1.0 + -1.0, ), - FOCAL_SLOPE * 2.0 / shortdim + FOCAL_SLOPE * 2.0 / shortdim, ) } @@ -443,14 +464,14 @@ pub fn Display() -> View { let sphere_program = set_up_program( &ctx, include_str!("identity.vert"), - include_str!("spheres.frag") + include_str!("spheres.frag"), ); // set up the point rendering program let point_program = set_up_program( &ctx, include_str!("point.vert"), - include_str!("point.frag") + include_str!("point.frag"), ); /* DEBUG */ @@ -467,7 +488,7 @@ pub fn Display() -> View { // capped at 1024 elements console::log_2( &ctx.get_parameter(WebGl2RenderingContext::MAX_FRAGMENT_UNIFORM_VECTORS).unwrap(), - &JsValue::from("uniform vectors available") + &JsValue::from("uniform vectors available"), ); // find the sphere program's vertex attribute @@ -503,7 +524,7 @@ pub fn Display() -> View { // southeast triangle -1.0, -1.0, 0.0, 1.0, 1.0, 0.0, - 1.0, -1.0, 0.0 + 1.0, -1.0, 0.0, ]; let viewport_position_buffer = load_new_buffer(&ctx, &viewport_positions); @@ -596,7 +617,7 @@ pub fn Display() -> View { vec![ ElementMotion { element: sel, - velocity: elt_motion.as_view() + velocity: elt_motion.as_view(), } ] ); @@ -629,7 +650,7 @@ pub fn Display() -> View { 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, u, 0.0, 0.0, 2.0*u, 1.0, u*u, - 0.0, 0.0, 0.0, 0.0, 1.0 + 0.0, 0.0, 0.0, 0.0, 1.0, ]) }; let asm_to_world = &location * &orientation; @@ -668,19 +689,19 @@ pub fn Display() -> View { let v = &sphere_reps_world[n]; ctx.uniform3fv_with_f32_array( sphere_sp_locs[n].as_ref(), - v.rows(0, 3).as_slice() + v.rows(0, 3).as_slice(), ); ctx.uniform2fv_with_f32_array( sphere_lt_locs[n].as_ref(), - v.rows(3, 2).as_slice() + v.rows(3, 2).as_slice(), ); ctx.uniform4fv_with_f32_array( sphere_color_locs[n].as_ref(), - &scene.spheres.colors_with_opacity[n] + &scene.spheres.colors_with_opacity[n], ); ctx.uniform1f( sphere_highlight_locs[n].as_ref(), - scene.spheres.highlights[n] + scene.spheres.highlights[n], ); } @@ -773,7 +794,7 @@ pub fn Display() -> View { "ArrowLeft" if shift => roll_ccw.set(value), "ArrowRight" => yaw_right.set(value), "ArrowLeft" => yaw_left.set(value), - _ => navigating = false + _ => navigating = false, }; if navigating { scene_changed.set(true); @@ -793,7 +814,7 @@ pub fn Display() -> View { "s" | "S" => translate_neg_y.set(value), "]" | "}" => shrink_neg.set(value), "[" | "{" => shrink_pos.set(value), - _ => manipulating = false + _ => manipulating = false, }; if manipulating { event.prevent_default(); @@ -805,12 +826,12 @@ pub fn Display() -> View { // switch back to integer-valued parameters when that becomes possible // again canvas( - ref=display, - id="display", - width="600", - height="600", - tabindex="0", - on:keydown=move |event: KeyboardEvent| { + ref = display, + id = "display", + width = "600", + height = "600", + tabindex = "0", + on:keydown = move |event: KeyboardEvent| { if event.key() == "Shift" { // swap navigation inputs roll_cw.set(yaw_right.get()); @@ -836,7 +857,7 @@ pub fn Display() -> View { set_manip_signal(&event, 1.0); } }, - on:keyup=move |event: KeyboardEvent| { + on:keyup = move |event: KeyboardEvent| { if event.key() == "Shift" { // swap navigation inputs yaw_right.set(roll_cw.get()); @@ -858,7 +879,7 @@ pub fn Display() -> View { set_manip_signal(&event, 0.0); } }, - on:blur=move |_| { + on:blur = move |_| { pitch_up.set(0.0); pitch_down.set(0.0); yaw_right.set(0.0); @@ -866,7 +887,7 @@ pub fn Display() -> View { roll_ccw.set(0.0); roll_cw.set(0.0); }, - on:click=move |event: MouseEvent| { + on:click = move |event: MouseEvent| { // find the nearest element along the pointer direction let (dir, pixel_size) = event_dir(&event); console::log_1(&JsValue::from(dir.to_string())); @@ -883,18 +904,18 @@ pub fn Display() -> View { clicked = Some((elt, depth)) } }, - None => clicked = Some((elt, depth)) - } - None => () + None => clicked = Some((elt, depth)), + }, + None => (), }; } // if we clicked something, select it match clicked { Some((elt, _)) => state.select(&elt, event.shift_key()), - None => state.selection.update(|sel| sel.clear()) + None => state.selection.update(|sel| sel.clear()), }; - } + }, ) } } \ No newline at end of file diff --git a/app-proto/src/components/outline.rs b/app-proto/src/components/outline.rs index 77d8575..5355042 100644 --- a/app-proto/src/components/outline.rs +++ b/app-proto/src/components/outline.rs @@ -1,11 +1,7 @@ use itertools::Itertools; use std::rc::Rc; use sycamore::prelude::*; -use web_sys::{ - KeyboardEvent, - MouseEvent, - wasm_bindgen::JsCast -}; +use web_sys::{KeyboardEvent, MouseEvent, wasm_bindgen::JsCast}; use crate::{ AppState, @@ -13,7 +9,7 @@ use crate::{ Element, HalfCurvatureRegulator, InversiveDistanceRegulator, - Regulator + Regulator, }, specified::SpecifiedValue }; @@ -49,8 +45,8 @@ fn RegulatorInput(regulator: Rc) -> View { view! { input( - r#type="text", - class=move || { + r#type = "text", + class = move || { if valid.get() { set_point.with(|set_pt| { if set_pt.is_present() { @@ -63,27 +59,27 @@ fn RegulatorInput(regulator: Rc) -> View { "regulator-input invalid" } }, - placeholder=measurement.with(|result| result.to_string()), - bind:value=value, - on:change=move |_| { + placeholder = measurement.with(|result| result.to_string()), + bind:value = value, + on:change = move |_| { valid.set( match SpecifiedValue::try_from(value.get_clone_untracked()) { Ok(set_pt) => { set_point.set(set_pt); true - } - Err(_) => false + }, + Err(_) => false, } ) }, - on:keydown={ + on:keydown = { move |event: KeyboardEvent| { match event.key().as_str() { "Escape" => reset_value(), - _ => () + _ => (), } } - } + }, ) } } @@ -100,11 +96,11 @@ impl OutlineItem for InversiveDistanceRegulator { self.subjects[0].label() }.clone(); view! { - li(class="regulator") { - div(class="regulator-label") { (other_subject_label) } - div(class="regulator-type") { "Inversive distance" } - RegulatorInput(regulator=self) - div(class="status") + li(class = "regulator") { + div(class = "regulator-label") { (other_subject_label) } + div(class = "regulator-type") { "Inversive distance" } + RegulatorInput(regulator = self) + div(class = "status") } } } @@ -113,11 +109,11 @@ impl OutlineItem for InversiveDistanceRegulator { impl OutlineItem for HalfCurvatureRegulator { fn outline_item(self: Rc, _element: &Rc) -> View { view! { - li(class="regulator") { - div(class="regulator-label") // for spacing - div(class="regulator-type") { "Half-curvature" } - RegulatorInput(regulator=self) - div(class="status") + li(class = "regulator") { + div(class = "regulator-label") // for spacing + div(class = "regulator-type") { "Half-curvature" } + RegulatorInput(regulator = self) + div(class = "status") } } } @@ -156,10 +152,10 @@ fn ElementOutlineItem(element: Rc) -> View { let details_node = create_node_ref(); view! { li { - details(ref=details_node) { + details(ref = details_node) { summary( - class=class.get(), - on:keydown={ + class = class.get(), + on:keydown = { let element_for_handler = element.clone(); move |event: KeyboardEvent| { match event.key().as_str() { @@ -179,18 +175,18 @@ fn ElementOutlineItem(element: Rc) -> View { .unchecked_into::() .remove_attribute("open"); }, - _ => () + _ => (), } } } ) { div( - class="element-switch", - on:click=|event: MouseEvent| event.stop_propagation() + class = "element-switch", + on:click = |event: MouseEvent| event.stop_propagation() ) div( - class="element", - on:click={ + class = "element", + on:click = { let state_for_handler = state.clone(); let element_for_handler = element.clone(); move |event: MouseEvent| { @@ -200,20 +196,20 @@ fn ElementOutlineItem(element: Rc) -> View { } } ) { - div(class="element-label") { (label) } - div(class="element-representation") { (rep_components) } + div(class = "element-label") { (label) } + div(class = "element-representation") { (rep_components) } input( - r#type="checkbox", - bind:checked=element.ghost(), - on:click=|event: MouseEvent| event.stop_propagation() + r#type = "checkbox", + bind:checked = element.ghost(), + on:click = |event: MouseEvent| event.stop_propagation() ) } } - ul(class="regulators") { + ul(class = "regulators") { Keyed( - list=regulator_list, - view=move |reg| reg.outline_item(&element), - key=|reg| reg.serial() + list = regulator_list, + view = move |reg| reg.outline_item(&element), + key = |reg| reg.serial() ) } } @@ -246,18 +242,18 @@ pub fn Outline() -> View { view! { ul( - id="outline", - on:click={ + id = "outline", + on:click = { let state = use_context::(); move |_| state.selection.update(|sel| sel.clear()) } ) { Keyed( - list=element_list, - view=|elt| view! { - ElementOutlineItem(element=elt) + list = element_list, + view = |elt| view! { + ElementOutlineItem(element = elt) }, - key=|elt| elt.serial() + key = |elt| elt.serial() ) } } diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index b58dd1a..5ed94ad 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -6,17 +6,17 @@ use web_sys::{console, wasm_bindgen::JsValue}; use crate::{ AppState, - engine, - engine::DescentHistory, assembly::{ Assembly, Element, ElementColor, InversiveDistanceRegulator, Point, - Sphere + Sphere, }, - specified::SpecifiedValue + engine, + engine::DescentHistory, + specified::SpecifiedValue, }; // --- loaders --- @@ -32,7 +32,7 @@ fn load_gen_assemb(assembly: &Assembly) { String::from("gemini_a"), String::from("Castor"), [1.00_f32, 0.25_f32, 0.00_f32], - engine::sphere(0.5, 0.5, 0.0, 1.0) + engine::sphere(0.5, 0.5, 0.0, 1.0), ) ); let _ = assembly.try_insert_element( @@ -40,7 +40,7 @@ fn load_gen_assemb(assembly: &Assembly) { String::from("gemini_b"), String::from("Pollux"), [0.00_f32, 0.25_f32, 1.00_f32], - engine::sphere(-0.5, -0.5, 0.0, 1.0) + engine::sphere(-0.5, -0.5, 0.0, 1.0), ) ); let _ = assembly.try_insert_element( @@ -48,7 +48,7 @@ fn load_gen_assemb(assembly: &Assembly) { String::from("ursa_major"), String::from("Ursa major"), [0.25_f32, 0.00_f32, 1.00_f32], - engine::sphere(-0.5, 0.5, 0.0, 0.75) + engine::sphere(-0.5, 0.5, 0.0, 0.75), ) ); let _ = assembly.try_insert_element( @@ -56,7 +56,7 @@ fn load_gen_assemb(assembly: &Assembly) { String::from("ursa_minor"), String::from("Ursa minor"), [0.25_f32, 1.00_f32, 0.00_f32], - engine::sphere(0.5, -0.5, 0.0, 0.5) + engine::sphere(0.5, -0.5, 0.0, 0.5), ) ); let _ = assembly.try_insert_element( @@ -64,7 +64,7 @@ fn load_gen_assemb(assembly: &Assembly) { String::from("moon_deimos"), String::from("Deimos"), [0.75_f32, 0.75_f32, 0.00_f32], - engine::sphere(0.0, 0.15, 1.0, 0.25) + engine::sphere(0.0, 0.15, 1.0, 0.25), ) ); let _ = assembly.try_insert_element( @@ -72,7 +72,7 @@ fn load_gen_assemb(assembly: &Assembly) { String::from("moon_phobos"), String::from("Phobos"), [0.00_f32, 0.75_f32, 0.50_f32], - engine::sphere(0.0, -0.15, -1.0, 0.25) + engine::sphere(0.0, -0.15, -1.0, 0.25), ) ); } @@ -85,7 +85,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { "central".to_string(), "Central".to_string(), [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, 1.0) + engine::sphere(0.0, 0.0, 0.0, 1.0), ) ); let _ = assembly.try_insert_element( @@ -93,7 +93,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { "assemb_plane".to_string(), "Assembly plane".to_string(), [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0) + engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0), ) ); let _ = assembly.try_insert_element( @@ -101,7 +101,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { "side1".to_string(), "Side 1".to_string(), [1.00_f32, 0.00_f32, 0.25_f32], - engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0) + engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0), ) ); let _ = assembly.try_insert_element( @@ -109,7 +109,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { "side2".to_string(), "Side 2".to_string(), [0.25_f32, 1.00_f32, 0.00_f32], - engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0) + engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0), ) ); let _ = assembly.try_insert_element( @@ -117,7 +117,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { "side3".to_string(), "Side 3".to_string(), [0.00_f32, 0.25_f32, 1.00_f32], - engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0) + engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0), ) ); let _ = assembly.try_insert_element( @@ -125,7 +125,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { "corner1".to_string(), "Corner 1".to_string(), [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0) + engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0), ) ); let _ = assembly.try_insert_element( @@ -133,7 +133,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { "corner2".to_string(), "Corner 2".to_string(), [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0) + engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0), ) ); let _ = assembly.try_insert_element( @@ -141,7 +141,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { String::from("corner3"), String::from("Corner 3"), [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0) + engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0), ) ); @@ -202,7 +202,7 @@ fn load_pointed_assemb(assembly: &Assembly) { format!("point_front"), format!("Front point"), [0.75_f32, 0.75_f32, 0.75_f32], - engine::point(0.0, 0.0, FRAC_1_SQRT_2) + engine::point(0.0, 0.0, FRAC_1_SQRT_2), ) ); let _ = assembly.try_insert_element( @@ -210,7 +210,7 @@ fn load_pointed_assemb(assembly: &Assembly) { format!("point_back"), format!("Back point"), [0.75_f32, 0.75_f32, 0.75_f32], - engine::point(0.0, 0.0, -FRAC_1_SQRT_2) + engine::point(0.0, 0.0, -FRAC_1_SQRT_2), ) ); for index_x in 0..=1 { @@ -223,7 +223,7 @@ fn load_pointed_assemb(assembly: &Assembly) { format!("sphere{index_x}{index_y}"), format!("Sphere {index_x}{index_y}"), [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], - engine::sphere(x, y, 0.0, 1.0) + engine::sphere(x, y, 0.0, 1.0), ) ); @@ -232,7 +232,7 @@ fn load_pointed_assemb(assembly: &Assembly) { format!("point{index_x}{index_y}"), format!("Point {index_x}{index_y}"), [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], - engine::point(x, y, 0.0) + engine::point(x, y, 0.0), ) ); } @@ -256,56 +256,56 @@ fn load_tridim_icosahedron_assemb(assembly: &Assembly) { "a1".to_string(), "A₁".to_string(), COLOR_A, - engine::point(0.25, 0.75, 0.75) + engine::point(0.25, 0.75, 0.75), ), Point::new( "a2".to_string(), "A₂".to_string(), COLOR_A, - engine::point(0.75, 0.25, 0.75) + engine::point(0.75, 0.25, 0.75), ), Point::new( "a3".to_string(), "A₃".to_string(), COLOR_A, - engine::point(0.75, 0.75, 0.25) + engine::point(0.75, 0.75, 0.25), ), Point::new( "b1".to_string(), "B₁".to_string(), COLOR_B, - engine::point(0.75, -0.25, -0.25) + engine::point(0.75, -0.25, -0.25), ), Point::new( "b2".to_string(), "B₂".to_string(), COLOR_B, - engine::point(-0.25, 0.75, -0.25) + engine::point(-0.25, 0.75, -0.25), ), Point::new( "b3".to_string(), "B₃".to_string(), COLOR_B, - engine::point(-0.25, -0.25, 0.75) + engine::point(-0.25, -0.25, 0.75), ), Point::new( "c1".to_string(), "C₁".to_string(), COLOR_C, - engine::point(0.0, -1.0, -1.0) + engine::point(0.0, -1.0, -1.0), ), Point::new( "c2".to_string(), "C₂".to_string(), COLOR_C, - engine::point(-1.0, 0.0, -1.0) + engine::point(-1.0, 0.0, -1.0), ), Point::new( "c3".to_string(), "C₃".to_string(), COLOR_C, - engine::point(-1.0, -1.0, 0.0) - ) + engine::point(-1.0, -1.0, 0.0), + ), ]; for vertex in vertices { let _ = assembly.try_insert_element(vertex); @@ -320,20 +320,20 @@ fn load_tridim_icosahedron_assemb(assembly: &Assembly) { "face1".to_string(), "Face 1".to_string(), COLOR_FACE, - engine::sphere_with_offset(frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0) + engine::sphere_with_offset(frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0), ), Sphere::new( "face2".to_string(), "Face 2".to_string(), COLOR_FACE, - engine::sphere_with_offset(-frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0) + engine::sphere_with_offset(-frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0), ), Sphere::new( "face3".to_string(), "Face 3".to_string(), COLOR_FACE, - engine::sphere_with_offset(-frac_1_sqrt_6, -frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, 0.0) - ) + engine::sphere_with_offset(-frac_1_sqrt_6, -frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, 0.0), + ), ]; for face in faces { face.ghost().set(true); @@ -416,7 +416,7 @@ fn load_dodeca_packing_assemb(assembly: &Assembly) { "substrate".to_string(), "Substrate".to_string(), [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, 1.0) + engine::sphere(0.0, 0.0, 0.0, 1.0), ) ); let substrate = assembly.elements_by_id.with_untracked( @@ -456,7 +456,7 @@ fn load_dodeca_packing_assemb(assembly: &Assembly) { id_a.clone(), format!("A{label_sub}"), COLOR_A, - engine::sphere(0.0, small_coord, big_coord, face_radii[k]) + engine::sphere(0.0, small_coord, big_coord, face_radii[k]), ) ); faces.push( @@ -472,7 +472,7 @@ fn load_dodeca_packing_assemb(assembly: &Assembly) { id_b.clone(), format!("B{label_sub}"), COLOR_B, - engine::sphere(small_coord, big_coord, 0.0, face_radii[k]) + engine::sphere(small_coord, big_coord, 0.0, face_radii[k]), ) ); faces.push( @@ -488,7 +488,7 @@ fn load_dodeca_packing_assemb(assembly: &Assembly) { id_c.clone(), format!("C{label_sub}"), COLOR_C, - engine::sphere(big_coord, 0.0, small_coord, face_radii[k]) + engine::sphere(big_coord, 0.0, small_coord, face_radii[k]), ) ); faces.push( @@ -559,19 +559,19 @@ fn load_balanced_assemb(assembly: &Assembly) { "outer".to_string(), "Outer".to_string(), [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, R_OUTER) + engine::sphere(0.0, 0.0, 0.0, R_OUTER), ), Sphere::new( "a".to_string(), "A".to_string(), [1.00_f32, 0.00_f32, 0.25_f32], - engine::sphere(0.0, 4.0, 0.0, R_INNER) + engine::sphere(0.0, 4.0, 0.0, R_INNER), ), Sphere::new( "b".to_string(), "B".to_string(), [0.00_f32, 0.25_f32, 1.00_f32], - engine::sphere(0.0, -4.0, 0.0, R_INNER) + engine::sphere(0.0, -4.0, 0.0, R_INNER), ), ]; for sphere in spheres { @@ -589,7 +589,7 @@ fn load_balanced_assemb(assembly: &Assembly) { for (sphere, radius) in [ (outer.clone(), R_OUTER), (a.clone(), R_INNER), - (b.clone(), R_INNER) + (b.clone(), R_INNER), ] { let curvature_regulator = sphere.regulators().with_untracked( |regs| regs.first().unwrap().clone() @@ -618,7 +618,7 @@ fn load_off_center_assemb(assembly: &Assembly) { "point".to_string(), "Point".to_string(), [0.75_f32, 0.75_f32, 0.75_f32], - engine::point(1e-9, 0.0, 0.0) + engine::point(1e-9, 0.0, 0.0), ), ); let _ = assembly.try_insert_element( @@ -626,7 +626,7 @@ fn load_off_center_assemb(assembly: &Assembly) { "sphere".to_string(), "Sphere".to_string(), [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, 0.0, 0.0, 1.0) + engine::sphere(0.0, 0.0, 0.0, 1.0), ), ); @@ -658,14 +658,14 @@ fn load_radius_ratio_assemb(assembly: &Assembly) { "sphere_faces".to_string(), "Insphere".to_string(), GRAY, - engine::sphere(0.0, 0.0, 0.0, 0.5) + engine::sphere(0.0, 0.0, 0.0, 0.5), ), Sphere::new( "sphere_vertices".to_string(), "Circumsphere".to_string(), GRAY, - engine::sphere(0.0, 0.0, 0.0, 0.25) - ) + engine::sphere(0.0, 0.0, 0.0, 0.25), + ), ]; for sphere in spheres { let _ = assembly.try_insert_element(sphere); @@ -678,13 +678,13 @@ fn load_radius_ratio_assemb(assembly: &Assembly) { [1.00_f32, 0.50_f32, 0.75_f32], [1.00_f32, 0.75_f32, 0.50_f32], [1.00_f32, 1.00_f32, 0.50_f32], - [0.75_f32, 0.50_f32, 1.00_f32] + [0.75_f32, 0.50_f32, 1.00_f32], ].into_iter(), [ engine::point(-0.6, -0.8, -0.6), engine::point(-0.6, 0.8, 0.6), engine::point(0.6, -0.8, 0.6), - engine::point(0.6, 0.8, -0.6) + engine::point(0.6, 0.8, -0.6), ].into_iter() ).map( |(k, color, representation)| { @@ -692,7 +692,7 @@ fn load_radius_ratio_assemb(assembly: &Assembly) { format!("v{k}"), format!("Vertex {k}"), color, - representation + representation, ) } ); @@ -709,13 +709,13 @@ fn load_radius_ratio_assemb(assembly: &Assembly) { [1.00_f32, 0.00_f32, 0.25_f32], [1.00_f32, 0.25_f32, 0.00_f32], [0.75_f32, 0.75_f32, 0.00_f32], - [0.25_f32, 0.00_f32, 1.00_f32] + [0.25_f32, 0.00_f32, 1.00_f32], ].into_iter(), [ engine::sphere_with_offset(base_dir[0], base_dir[1], base_dir[2], offset, 0.0), engine::sphere_with_offset(base_dir[0], -base_dir[1], -base_dir[2], offset, 0.0), engine::sphere_with_offset(-base_dir[0], base_dir[1], -base_dir[2], offset, 0.0), - engine::sphere_with_offset(-base_dir[0], -base_dir[1], base_dir[2], offset, 0.0) + engine::sphere_with_offset(-base_dir[0], -base_dir[1], base_dir[2], offset, 0.0), ].into_iter() ).map( |(k, color, representation)| { @@ -723,7 +723,7 @@ fn load_radius_ratio_assemb(assembly: &Assembly) { format!("f{k}"), format!("Face {k}"), color, - representation + representation, ) } ); @@ -736,7 +736,7 @@ fn load_radius_ratio_assemb(assembly: &Assembly) { for j in index_range.clone() { let [face_j, vertex_j] = [ format!("f{j}"), - format!("v{j}") + format!("v{j}"), ].map( |id| assembly.elements_by_id.with_untracked( |elts_by_id| elts_by_id[&id].clone() @@ -797,7 +797,7 @@ fn load_irisawa_hexlet_assemb(assembly: &Assembly) { [0.75_f32, 0.75_f32, 0.00_f32], [0.25_f32, 1.00_f32, 0.00_f32], [0.00_f32, 0.25_f32, 1.00_f32], - [0.25_f32, 0.00_f32, 1.00_f32] + [0.25_f32, 0.00_f32, 1.00_f32], ].into_iter(); // create the spheres @@ -806,19 +806,19 @@ fn load_irisawa_hexlet_assemb(assembly: &Assembly) { "outer".to_string(), "Outer".to_string(), [0.5_f32, 0.5_f32, 0.5_f32], - engine::sphere(0.0, 0.0, 0.0, 1.5) + engine::sphere(0.0, 0.0, 0.0, 1.5), ), Sphere::new( "sun".to_string(), "Sun".to_string(), [0.75_f32, 0.75_f32, 0.75_f32], - engine::sphere(0.0, -0.75, 0.0, 0.75) + engine::sphere(0.0, -0.75, 0.0, 0.75), ), Sphere::new( "moon".to_string(), "Moon".to_string(), [0.25_f32, 0.25_f32, 0.25_f32], - engine::sphere(0.0, 0.75, 0.0, 0.75) + engine::sphere(0.0, 0.75, 0.0, 0.75), ), ].into_iter().chain( index_range.clone().zip(colors).map( @@ -828,7 +828,7 @@ fn load_irisawa_hexlet_assemb(assembly: &Assembly) { format!("chain{k}"), format!("Chain {k}"), color, - engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5) + engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5), ) } ) @@ -865,7 +865,7 @@ fn load_irisawa_hexlet_assemb(assembly: &Assembly) { (outer.clone(), "1"), (sun.clone(), "-1"), (moon.clone(), "-1"), - (chain_sphere_next.clone(), "-1") + (chain_sphere_next.clone(), "-1"), ] { let tangency = InversiveDistanceRegulator::new([chain_sphere.clone(), other_sphere]); tangency.set_point.set(SpecifiedValue::try_from(inversive_distance.to_string()).unwrap()); @@ -918,24 +918,24 @@ pub fn TestAssemblyChooser() -> View { "off-center" => load_off_center_assemb(assembly), "radius-ratio" => load_radius_ratio_assemb(assembly), "irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly), - _ => () + _ => (), }; }); }); // build the chooser view! { - select(bind:value=assembly_name) { - option(value="general") { "General" } - option(value="low-curv") { "Low-curvature" } - option(value="pointed") { "Pointed" } - option(value="tridim-icosahedron") { "Tridiminished icosahedron" } - option(value="dodeca-packing") { "Dodecahedral packing" } - option(value="balanced") { "Balanced" } - option(value="off-center") { "Off-center" } - option(value="radius-ratio") { "Radius ratio" } - option(value="irisawa-hexlet") { "Irisawa hexlet" } - option(value="empty") { "Empty" } + select(bind:value = assembly_name) { + option(value = "general") { "General" } + option(value = "low-curv") { "Low-curvature" } + option(value = "pointed") { "Pointed" } + option(value = "tridim-icosahedron") { "Tridiminished icosahedron" } + option(value = "dodeca-packing") { "Dodecahedral packing" } + option(value = "balanced") { "Balanced" } + option(value = "off-center") { "Off-center" } + option(value = "radius-ratio") { "Radius ratio" } + option(value = "irisawa-hexlet") { "Irisawa hexlet" } + option(value = "empty") { "Empty" } } } } \ No newline at end of file diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index 602fc57..dc6b470 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -16,7 +16,7 @@ pub fn sphere(center_x: f64, center_y: f64, center_z: f64, radius: f64) -> DVect center_y / radius, center_z / radius, 0.5 / radius, - 0.5 * (center_norm_sq / radius - radius) + 0.5 * (center_norm_sq / radius - radius), ]) } @@ -30,7 +30,7 @@ pub fn sphere_with_offset(dir_x: f64, dir_y: f64, dir_z: f64, off: f64, curv: f6 norm_sp * dir_y, norm_sp * dir_z, 0.5 * curv, - off * (1.0 + 0.5 * off * curv) + off * (1.0 + 0.5 * off * curv), ]) } @@ -53,7 +53,7 @@ pub fn project_point_to_normalized(rep: &mut DVector) { pub struct MatrixEntry { index: (usize, usize), - value: f64 + value: f64, } pub struct PartialMatrix(Vec); @@ -65,7 +65,7 @@ impl PartialMatrix { pub fn push(&mut self, row: usize, col: usize, value: f64) { let PartialMatrix(entries) = self; - entries.push(MatrixEntry { index: (row, col), value: value }); + entries.push(MatrixEntry { index: (row, col), value }); } pub fn push_sym(&mut self, row: usize, col: usize, value: f64) { @@ -135,22 +135,26 @@ impl<'a> IntoIterator for &'a PartialMatrix { pub struct ConfigSubspace { assembly_dim: usize, basis_std: Vec>, - basis_proj: Vec> + basis_proj: Vec>, } impl ConfigSubspace { pub fn zero(assembly_dim: usize) -> ConfigSubspace { ConfigSubspace { - assembly_dim: assembly_dim, + assembly_dim, basis_proj: Vec::new(), - basis_std: Vec::new() + basis_std: Vec::new(), } } // approximate the kernel of a symmetric endomorphism of the configuration // space for `assembly_dim` elements. we consider an eigenvector to be part // of the kernel if its eigenvalue is smaller than the constant `THRESHOLD` - fn symmetric_kernel(a: DMatrix, proj_to_std: DMatrix, assembly_dim: usize) -> ConfigSubspace { + fn symmetric_kernel( + a: DMatrix, + proj_to_std: DMatrix, + assembly_dim: usize, + ) -> ConfigSubspace { // find a basis for the kernel. the basis is expressed in the projection // coordinates, and it's orthonormal with respect to the projection // inner product @@ -170,7 +174,7 @@ impl ConfigSubspace { const ELEMENT_DIM: usize = 5; const UNIFORM_DIM: usize = 4; ConfigSubspace { - assembly_dim: assembly_dim, + assembly_dim, basis_std: basis_std.column_iter().map( |v| Into::>::into( v.reshape_generic(Dyn(ELEMENT_DIM), Dyn(assembly_dim)) @@ -180,7 +184,7 @@ impl ConfigSubspace { |v| Into::>::into( v.reshape_generic(Dyn(UNIFORM_DIM), Dyn(assembly_dim)) ) - ).collect() + ).collect(), } } @@ -214,9 +218,9 @@ pub struct DescentHistory { pub config: Vec>, pub scaled_loss: Vec, pub neg_grad: Vec>, - pub hess_eigvals: Vec::>, + pub hess_eigvals: Vec>, pub base_step: Vec>, - pub backoff_steps: Vec + pub backoff_steps: Vec, } impl DescentHistory { @@ -246,7 +250,7 @@ impl ConstraintProblem { ConstraintProblem { gram: PartialMatrix::new(), frozen: PartialMatrix::new(), - guess: DMatrix::::zeros(ELEMENT_DIM, element_count) + guess: DMatrix::::zeros(ELEMENT_DIM, element_count), } } @@ -255,7 +259,7 @@ impl ConstraintProblem { ConstraintProblem { gram: PartialMatrix::new(), frozen: PartialMatrix::new(), - guess: DMatrix::from_columns(guess_columns) + guess: DMatrix::from_columns(guess_columns), } } } @@ -269,25 +273,21 @@ lazy_static! { 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.0, - 0.0, 0.0, 0.0, -2.0, 0.0 + 0.0, 0.0, 0.0, -2.0, 0.0, ]); } struct SearchState { config: DMatrix, err_proj: DMatrix, - loss: f64 + loss: f64, } impl SearchState { fn from_config(gram: &PartialMatrix, config: DMatrix) -> SearchState { let err_proj = gram.sub_proj(&(config.tr_mul(&*Q) * &config)); let loss = err_proj.norm_squared(); - SearchState { - config: config, - err_proj: err_proj, - loss: loss - } + SearchState { config, err_proj, loss } } } @@ -314,7 +314,7 @@ pub fn local_unif_to_std(v: DVectorView) -> DMatrix { curv, 0.0, 0.0, 0.0, v[0], 0.0, curv, 0.0, 0.0, v[1], 0.0, 0.0, curv, 0.0, v[2], - 0.0, 0.0, 0.0, 0.0, 1.0 + 0.0, 0.0, 0.0, 0.0, 1.0, ]) } else { // `v` represents a sphere. the normalization condition says that the @@ -323,7 +323,7 @@ pub fn local_unif_to_std(v: DVectorView) -> DMatrix { curv, 0.0, 0.0, 0.0, v[0], 0.0, curv, 0.0, 0.0, v[1], 0.0, 0.0, curv, 0.0, v[2], - curv*v[0], curv*v[1], curv*v[2], curv*v[3], curv*v[4] + 1.0 + curv*v[0], curv*v[1], curv*v[2], curv*v[3], curv*v[4] + 1.0, ]) } } @@ -336,7 +336,7 @@ fn seek_better_config( base_target_improvement: f64, min_efficiency: f64, backoff: f64, - max_backoff_steps: i32 + max_backoff_steps: i32, ) -> Option<(SearchState, i32)> { let mut rate = 1.0; for backoff_steps in 0..max_backoff_steps { @@ -354,12 +354,12 @@ fn seek_better_config( // a first-order neighborhood of a configuration pub struct ConfigNeighborhood { pub config: DMatrix, - pub nbhd: ConfigSubspace + pub nbhd: ConfigSubspace, } pub struct Realization { pub result: Result, - pub history: DescentHistory + pub history: DescentHistory, } // seek a matrix `config` that matches the partial matrix `problem.frozen` and @@ -373,12 +373,10 @@ pub fn realize_gram( backoff: f64, reg_scale: f64, max_descent_steps: i32, - max_backoff_steps: i32 + max_backoff_steps: i32, ) -> Realization { // destructure the problem data - let ConstraintProblem { - gram, guess, frozen - } = problem; + let ConstraintProblem { gram, guess, frozen } = problem; // start the descent history let mut history = DescentHistory::new(); @@ -391,10 +389,10 @@ pub fn realize_gram( let result = Ok( ConfigNeighborhood { config: guess.clone(), - nbhd: ConfigSubspace::zero(0) + nbhd: ConfigSubspace::zero(0), } ); - return Realization { result, history } + return Realization { result, history }; } // find the dimension of the search space @@ -475,8 +473,8 @@ pub fn realize_gram( Some(cholesky) => cholesky, None => return Realization { result: Err("Cholesky decomposition failed".to_string()), - history - } + history, + }, }; let base_step_stacked = hess_cholesky.solve(&neg_grad_stacked); let base_step = base_step_stacked.reshape_generic(Dyn(element_dim), Dyn(assembly_dim)); @@ -485,16 +483,16 @@ 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), - min_efficiency, backoff, max_backoff_steps + min_efficiency, backoff, max_backoff_steps, ) { state = better_state; history.backoff_steps.push(backoff_steps); } else { return Realization { result: Err("Line search failed".to_string()), - history - } - }; + history, + }; + } } let result = if state.loss < tol { // express the uniform basis in the standard basis @@ -539,7 +537,7 @@ pub mod examples { [ sphere(0.0, 0.0, 0.0, 15.0), sphere(0.0, 0.0, -9.0, 5.0), - sphere(0.0, 0.0, 11.0, 3.0) + sphere(0.0, 0.0, 11.0, 3.0), ].into_iter().chain( (1..=6).map( |k| { @@ -598,7 +596,7 @@ pub mod examples { point(0.0, 0.0, 0.0), point(ang_hor.cos(), ang_hor.sin(), 0.0), point(x_vert, y_vert, -0.5), - point(x_vert, y_vert, 0.5) + point(x_vert, y_vert, 0.5), ] } ).collect::>().as_slice() @@ -641,15 +639,15 @@ mod tests { MatrixEntry { index: (0, 0), value: 14.0 }, MatrixEntry { index: (0, 2), value: 28.0 }, MatrixEntry { index: (1, 1), value: 42.0 }, - MatrixEntry { index: (1, 2), value: 49.0 } + MatrixEntry { index: (1, 2), value: 49.0 }, ]); let config = DMatrix::::from_row_slice(2, 3, &[ 1.0, 2.0, 3.0, - 4.0, 5.0, 6.0 + 4.0, 5.0, 6.0, ]); let expected_result = DMatrix::::from_row_slice(2, 3, &[ 14.0, 2.0, 28.0, - 4.0, 42.0, 49.0 + 4.0, 42.0, 49.0, ]); assert_eq!(frozen.freeze(&config), expected_result); } @@ -660,15 +658,15 @@ mod tests { MatrixEntry { index: (0, 0), value: 19.0 }, MatrixEntry { index: (0, 2), value: 39.0 }, MatrixEntry { index: (1, 1), value: 59.0 }, - MatrixEntry { index: (1, 2), value: 69.0 } + MatrixEntry { index: (1, 2), value: 69.0 }, ]); let attempt = DMatrix::::from_row_slice(2, 3, &[ 1.0, 2.0, 3.0, - 4.0, 5.0, 6.0 + 4.0, 5.0, 6.0, ]); let expected_result = DMatrix::::from_row_slice(2, 3, &[ 18.0, 0.0, 36.0, - 0.0, 54.0, 63.0 + 0.0, 54.0, 63.0, ]); assert_eq!(target.sub_proj(&attempt), expected_result); } @@ -686,7 +684,7 @@ mod tests { DMatrix::from_columns(&[ sphere(1.0, 0.0, 0.0, a), sphere(-0.5, a, 0.0, a), - sphere(-0.5, -a, 0.0, a) + sphere(-0.5, -a, 0.0, a), ]) }; let state = SearchState::from_config(&gram, config); @@ -700,7 +698,7 @@ mod tests { fn frozen_entry_test() { let mut problem = ConstraintProblem::from_guess(&[ point(0.0, 0.0, 2.0), - sphere(0.0, 0.0, 0.0, 0.95) + sphere(0.0, 0.0, 0.0, 0.95), ]); for j in 0..2 { for k in j..2 { @@ -744,7 +742,7 @@ mod tests { let mut problem = ConstraintProblem::from_guess(&[ sphere(0.0, 0.0, 0.0, -2.0), sphere(0.0, 0.0, 1.0, 1.0), - sphere(0.0, 0.0, -1.0, 1.0) + sphere(0.0, 0.0, -1.0, 1.0), ]); for j in 0..3 { for k in j..3 { @@ -774,8 +772,8 @@ mod tests { DMatrix::::from_column_slice(UNIFORM_DIM, assembly_dim, &[ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, -0.5, - 0.0, 0.0, -0.5, 0.5 - ]) + 0.0, 0.0, -0.5, 0.5, + ]), ]; let tangent_motions_std = vec![ basis_matrix((0, 1), element_dim, assembly_dim), @@ -785,8 +783,8 @@ mod tests { DMatrix::::from_column_slice(element_dim, assembly_dim, &[ 0.0, 0.0, 0.0, 0.00, 0.0, 0.0, 0.0, -1.0, -0.25, -1.0, - 0.0, 0.0, -1.0, 0.25, 1.0 - ]) + 0.0, 0.0, -1.0, 0.25, 1.0, + ]), ]; // confirm that the dimension of the tangent space is no greater than @@ -862,10 +860,10 @@ mod tests { DVector::from_column_slice(&[0.0, 0.0, 5.0, 0.0]), DVector::from_column_slice(&[0.0, 0.0, 1.0, 0.0]), DVector::from_column_slice(&[-vel_vert_x, -vel_vert_y, -3.0, 0.0]), - DVector::from_column_slice(&[vel_vert_x, vel_vert_y, -3.0, 0.0]) + DVector::from_column_slice(&[vel_vert_x, vel_vert_y, -3.0, 0.0]), ] } - ).collect::>() + ).collect::>(), ]; let tangent_motions_std = tangent_motions_unif.iter().map( |motion| DMatrix::from_columns( @@ -898,7 +896,7 @@ mod tests { 0.0, 1.0, 0.0, 0.0, dis[1], 0.0, 0.0, 1.0, 0.0, dis[2], 2.0*dis[0], 2.0*dis[1], 2.0*dis[2], 1.0, dis.norm_squared(), - 0.0, 0.0, 0.0, 0.0, 1.0 + 0.0, 0.0, 0.0, 0.0, 1.0, ]) } @@ -910,7 +908,7 @@ mod tests { const SCALED_TOL: f64 = 1.0e-12; let mut problem_orig = ConstraintProblem::from_guess(&[ sphere(0.0, 0.0, 0.5, 1.0), - sphere(0.0, 0.0, -0.5, 1.0) + sphere(0.0, 0.0, -0.5, 1.0), ]); problem_orig.gram.push_sym(0, 0, 1.0); problem_orig.gram.push_sym(1, 1, 1.0); @@ -928,13 +926,13 @@ mod tests { let a = 0.5 * FRAC_1_SQRT_2; DMatrix::from_columns(&[ sphere(a, 0.0, 7.0 + a, 1.0), - sphere(-a, 0.0, 7.0 - a, 1.0) + sphere(-a, 0.0, 7.0 - a, 1.0), ]) }; let problem_tfm = ConstraintProblem { gram: problem_orig.gram, + frozen: problem_orig.frozen, guess: guess_tfm, - frozen: problem_orig.frozen }; let Realization { result: result_tfm, history: history_tfm } = realize_gram( &problem_tfm, SCALED_TOL, 0.5, 0.9, 1.1, 200, 110 @@ -962,7 +960,7 @@ mod tests { 0.0, 1.0, 0.0, 0.0, 0.0, FRAC_1_SQRT_2, 0.0, FRAC_1_SQRT_2, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 1.0 + 0.0, 0.0, 0.0, 0.0, 1.0, ]); let transl = translation(Vector3::new(0.0, 0.0, 7.0)); let motion_proj_tfm = transl * rot * motion_orig_proj; diff --git a/app-proto/src/main.rs b/app-proto/src/main.rs index 152d11c..7ca0731 100644 --- a/app-proto/src/main.rs +++ b/app-proto/src/main.rs @@ -14,20 +14,20 @@ use components::{ add_remove::AddRemove, diagnostics::Diagnostics, display::Display, - outline::Outline + outline::Outline, }; #[derive(Clone)] struct AppState { assembly: Assembly, - selection: Signal>> + selection: Signal>>, } impl AppState { fn new() -> AppState { AppState { assembly: Assembly::new(), - selection: create_signal(BTreeSet::default()) + selection: create_signal(BTreeSet::default()), } } @@ -58,7 +58,7 @@ fn main() { provide_context(AppState::new()); view! { - div(id="sidebar") { + div(id = "sidebar") { AddRemove {} Outline {} Diagnostics {} diff --git a/app-proto/src/specified.rs b/app-proto/src/specified.rs index cfe7fc3..ea1731c 100644 --- a/app-proto/src/specified.rs +++ b/app-proto/src/specified.rs @@ -13,7 +13,7 @@ use std::num::ParseFloatError; #[readonly::make] pub struct SpecifiedValue { pub spec: String, - pub value: Option + pub value: Option, } impl SpecifiedValue { @@ -37,7 +37,7 @@ impl TryFrom for SpecifiedValue { Ok(SpecifiedValue::from_empty_spec()) } else { spec.parse::().map( - |value| SpecifiedValue { spec: spec, value: Some(value) } + |value| SpecifiedValue { spec, value: Some(value) } ) } } From a4565281d5c3e7042ab9e1a2bdaa22bc52126b08 Mon Sep 17 00:00:00 2001 From: Vectornaut Date: Thu, 7 Aug 2025 23:24:07 +0000 Subject: [PATCH 13/14] Refactor: rename loaders and adopt 'Self' type convention (#111) Resolves #109. Resolves #110. Co-authored-by: Aaron Fenyes Reviewed-on: https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/111 Co-authored-by: Vectornaut Co-committed-by: Vectornaut --- app-proto/src/assembly.rs | 26 ++++++------ app-proto/src/components/diagnostics.rs | 4 +- app-proto/src/components/display.rs | 12 +++--- .../src/components/test_assembly_chooser.rs | 42 +++++++++---------- app-proto/src/engine.rs | 32 +++++++------- app-proto/src/main.rs | 4 +- app-proto/src/specified.rs | 8 ++-- 7 files changed, 64 insertions(+), 64 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 43066fd..94e7b3c 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -175,8 +175,8 @@ impl Sphere { label: String, color: ElementColor, representation: DVector, - ) -> Sphere { - Sphere { + ) -> Self { + Self { id, label, color, @@ -194,8 +194,8 @@ impl Element for Sphere { "sphere".to_string() } - fn default(id: String, id_num: u64) -> Sphere { - Sphere::new( + fn default(id: String, id_num: u64) -> Self { + Self::new( id, format!("Sphere {id_num}"), [0.75_f32, 0.75_f32, 0.75_f32], @@ -275,8 +275,8 @@ impl Point { label: String, color: ElementColor, representation: DVector, - ) -> Point { - Point { + ) -> Self { + Self { id, label, color, @@ -294,8 +294,8 @@ impl Element for Point { "point".to_string() } - fn default(id: String, id_num: u64) -> Point { - Point::new( + fn default(id: String, id_num: u64) -> Self { + Self::new( id, format!("Point {id_num}"), [0.75_f32, 0.75_f32, 0.75_f32], @@ -348,7 +348,7 @@ impl ProblemPoser for Point { format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str() ); problem.gram.push_sym(index, index, 0.0); - problem.frozen.push(Point::WEIGHT_COMPONENT, index, 0.5); + problem.frozen.push(Self::WEIGHT_COMPONENT, index, 0.5); problem.guess.set_column(index, &self.representation.get_clone_untracked()); } } @@ -393,7 +393,7 @@ pub struct InversiveDistanceRegulator { } impl InversiveDistanceRegulator { - pub fn new(subjects: [Rc; 2]) -> InversiveDistanceRegulator { + pub fn new(subjects: [Rc; 2]) -> Self { let representations = subjects.each_ref().map(|subj| subj.representation()); let measurement = create_memo(move || { representations[0].with(|rep_0| @@ -406,7 +406,7 @@ impl InversiveDistanceRegulator { let set_point = create_signal(SpecifiedValue::from_empty_spec()); let serial = Self::next_serial(); - InversiveDistanceRegulator { subjects, measurement, set_point, serial } + Self { subjects, measurement, set_point, serial } } } @@ -453,7 +453,7 @@ pub struct HalfCurvatureRegulator { } impl HalfCurvatureRegulator { - pub fn new(subject: Rc) -> HalfCurvatureRegulator { + pub fn new(subject: Rc) -> Self { let measurement = subject.representation().map( |rep| rep[Sphere::CURVATURE_COMPONENT] ); @@ -461,7 +461,7 @@ impl HalfCurvatureRegulator { let set_point = create_signal(SpecifiedValue::from_empty_spec()); let serial = Self::next_serial(); - HalfCurvatureRegulator { subject, measurement, set_point, serial } + Self { subject, measurement, set_point, serial } } } diff --git a/app-proto/src/components/diagnostics.rs b/app-proto/src/components/diagnostics.rs index b274dca..e265982 100644 --- a/app-proto/src/components/diagnostics.rs +++ b/app-proto/src/components/diagnostics.rs @@ -15,8 +15,8 @@ struct DiagnosticsState { } impl DiagnosticsState { - fn new(initial_tab: String) -> DiagnosticsState { - DiagnosticsState { active_tab: create_signal(initial_tab) } + fn new(initial_tab: String) -> Self { + Self { active_tab: create_signal(initial_tab) } } } diff --git a/app-proto/src/components/display.rs b/app-proto/src/components/display.rs index a0cdba6..da921dd 100644 --- a/app-proto/src/components/display.rs +++ b/app-proto/src/components/display.rs @@ -41,8 +41,8 @@ struct SceneSpheres { } impl SceneSpheres { - fn new() -> SceneSpheres { - SceneSpheres { + fn new() -> Self { + Self { representations: Vec::new(), colors_with_opacity: Vec::new(), highlights: Vec::new(), @@ -71,8 +71,8 @@ struct ScenePoints { } impl ScenePoints { - fn new() -> ScenePoints { - ScenePoints { + fn new() -> Self { + Self { representations: Vec::new(), colors_with_opacity: Vec::new(), highlights: Vec::new(), @@ -97,8 +97,8 @@ pub struct Scene { } impl Scene { - fn new() -> Scene { - Scene { + fn new() -> Self { + Self { spheres: SceneSpheres::new(), points: ScenePoints::new(), } diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 5ed94ad..0d387d3 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -26,7 +26,7 @@ use crate::{ // done more work on saving and loading assemblies, we should come back to this // code to see if it can be simplified -fn load_gen_assemb(assembly: &Assembly) { +fn load_general(assembly: &Assembly) { let _ = assembly.try_insert_element( Sphere::new( String::from("gemini_a"), @@ -77,7 +77,7 @@ fn load_gen_assemb(assembly: &Assembly) { ); } -fn load_low_curv_assemb(assembly: &Assembly) { +fn load_low_curvature(assembly: &Assembly) { // create the spheres let a = 0.75_f64.sqrt(); let _ = assembly.try_insert_element( @@ -196,7 +196,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { } } -fn load_pointed_assemb(assembly: &Assembly) { +fn load_pointed(assembly: &Assembly) { let _ = assembly.try_insert_element( Point::new( format!("point_front"), @@ -246,7 +246,7 @@ fn load_pointed_assemb(assembly: &Assembly) { // B-C " // C-C " // A-C -0.25 * φ^2 = -0.6545084971874737 -fn load_tridim_icosahedron_assemb(assembly: &Assembly) { +fn load_tridiminished_icosahedron(assembly: &Assembly) { // create the vertices const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.25_f32]; const COLOR_B: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; @@ -409,7 +409,7 @@ fn load_tridim_icosahedron_assemb(assembly: &Assembly) { // to finish describing the dodecahedral circle packing, set the inversive // distance regulators to -1. some of the regulators have already been set -fn load_dodeca_packing_assemb(assembly: &Assembly) { +fn load_dodecahedral_packing(assembly: &Assembly) { // add the substrate let _ = assembly.try_insert_element( Sphere::new( @@ -550,7 +550,7 @@ fn load_dodeca_packing_assemb(assembly: &Assembly) { // the initial configuration of this test assembly deliberately violates the // constraints, so loading the assembly will trigger a non-trivial realization -fn load_balanced_assemb(assembly: &Assembly) { +fn load_balanced(assembly: &Assembly) { // create the spheres const R_OUTER: f64 = 10.0; const R_INNER: f64 = 4.0; @@ -611,7 +611,7 @@ fn load_balanced_assemb(assembly: &Assembly) { // the initial configuration of this test assembly deliberately violates the // constraints, so loading the assembly will trigger a non-trivial realization -fn load_off_center_assemb(assembly: &Assembly) { +fn load_off_center(assembly: &Assembly) { // create a point almost at the origin and a sphere centered on the origin let _ = assembly.try_insert_element( Point::new( @@ -648,7 +648,7 @@ fn load_off_center_assemb(assembly: &Assembly) { // sqrt(1/6) and sqrt(3/2), respectively. to measure those radii, set an // inversive distance of -1 between the insphere and each face, and then set an // inversive distance of 0 between the circumsphere and each vertex -fn load_radius_ratio_assemb(assembly: &Assembly) { +fn load_radius_ratio(assembly: &Assembly) { let index_range = 1..=4; // create the spheres @@ -789,7 +789,7 @@ fn load_radius_ratio_assemb(assembly: &Assembly) { // conditions are exactly representable as floats, unlike the analogous numbers // in the scaled-up problem. the inexact representations might break the // symmetry that's getting the engine stuck -fn load_irisawa_hexlet_assemb(assembly: &Assembly) { +fn load_irisawa_hexlet(assembly: &Assembly) { let index_range = 1..=6; let colors = [ [1.00_f32, 0.00_f32, 0.25_f32], @@ -909,15 +909,15 @@ pub fn TestAssemblyChooser() -> View { // load assembly match name.as_str() { - "general" => load_gen_assemb(assembly), - "low-curv" => load_low_curv_assemb(assembly), - "pointed" => load_pointed_assemb(assembly), - "tridim-icosahedron" => load_tridim_icosahedron_assemb(assembly), - "dodeca-packing" => load_dodeca_packing_assemb(assembly), - "balanced" => load_balanced_assemb(assembly), - "off-center" => load_off_center_assemb(assembly), - "radius-ratio" => load_radius_ratio_assemb(assembly), - "irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly), + "general" => load_general(assembly), + "low-curvature" => load_low_curvature(assembly), + "pointed" => load_pointed(assembly), + "tridiminished-icosahedron" => load_tridiminished_icosahedron(assembly), + "dodecahedral-packing" => load_dodecahedral_packing(assembly), + "balanced" => load_balanced(assembly), + "off-center" => load_off_center(assembly), + "radius-ratio" => load_radius_ratio(assembly), + "irisawa-hexlet" => load_irisawa_hexlet(assembly), _ => (), }; }); @@ -927,10 +927,10 @@ pub fn TestAssemblyChooser() -> View { view! { select(bind:value = assembly_name) { option(value = "general") { "General" } - option(value = "low-curv") { "Low-curvature" } + option(value = "low-curvature") { "Low-curvature" } option(value = "pointed") { "Pointed" } - option(value = "tridim-icosahedron") { "Tridiminished icosahedron" } - option(value = "dodeca-packing") { "Dodecahedral packing" } + option(value = "tridiminished-icosahedron") { "Tridiminished icosahedron" } + option(value = "dodecahedral-packing") { "Dodecahedral packing" } option(value = "balanced") { "Balanced" } option(value = "off-center") { "Off-center" } option(value = "radius-ratio") { "Radius ratio" } diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index dc6b470..d033c01 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -59,12 +59,12 @@ pub struct MatrixEntry { pub struct PartialMatrix(Vec); impl PartialMatrix { - pub fn new() -> PartialMatrix { - PartialMatrix(Vec::::new()) + pub fn new() -> Self { + Self(Vec::::new()) } pub fn push(&mut self, row: usize, col: usize, value: f64) { - let PartialMatrix(entries) = self; + let Self(entries) = self; entries.push(MatrixEntry { index: (row, col), value }); } @@ -114,7 +114,7 @@ impl IntoIterator for PartialMatrix { type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { - let PartialMatrix(entries) = self; + let Self(entries) = self; entries.into_iter() } } @@ -139,8 +139,8 @@ pub struct ConfigSubspace { } impl ConfigSubspace { - pub fn zero(assembly_dim: usize) -> ConfigSubspace { - ConfigSubspace { + pub fn zero(assembly_dim: usize) -> Self { + Self { assembly_dim, basis_proj: Vec::new(), basis_std: Vec::new(), @@ -154,7 +154,7 @@ impl ConfigSubspace { a: DMatrix, proj_to_std: DMatrix, assembly_dim: usize, - ) -> ConfigSubspace { + ) -> Self { // find a basis for the kernel. the basis is expressed in the projection // coordinates, and it's orthonormal with respect to the projection // inner product @@ -173,7 +173,7 @@ impl ConfigSubspace { const ELEMENT_DIM: usize = 5; const UNIFORM_DIM: usize = 4; - ConfigSubspace { + Self { assembly_dim, basis_std: basis_std.column_iter().map( |v| Into::>::into( @@ -224,8 +224,8 @@ pub struct DescentHistory { } impl DescentHistory { - pub fn new() -> DescentHistory { - DescentHistory { + pub fn new() -> Self { + Self { config: Vec::>::new(), scaled_loss: Vec::::new(), neg_grad: Vec::>::new(), @@ -245,9 +245,9 @@ pub struct ConstraintProblem { } impl ConstraintProblem { - pub fn new(element_count: usize) -> ConstraintProblem { + pub fn new(element_count: usize) -> Self { const ELEMENT_DIM: usize = 5; - ConstraintProblem { + Self { gram: PartialMatrix::new(), frozen: PartialMatrix::new(), guess: DMatrix::::zeros(ELEMENT_DIM, element_count), @@ -255,8 +255,8 @@ impl ConstraintProblem { } #[cfg(feature = "dev")] - pub fn from_guess(guess_columns: &[DVector]) -> ConstraintProblem { - ConstraintProblem { + pub fn from_guess(guess_columns: &[DVector]) -> Self { + Self { gram: PartialMatrix::new(), frozen: PartialMatrix::new(), guess: DMatrix::from_columns(guess_columns), @@ -284,10 +284,10 @@ struct SearchState { } impl SearchState { - fn from_config(gram: &PartialMatrix, config: DMatrix) -> SearchState { + fn from_config(gram: &PartialMatrix, config: DMatrix) -> Self { let err_proj = gram.sub_proj(&(config.tr_mul(&*Q) * &config)); let loss = err_proj.norm_squared(); - SearchState { config, err_proj, loss } + Self { config, err_proj, loss } } } diff --git a/app-proto/src/main.rs b/app-proto/src/main.rs index 7ca0731..a03b026 100644 --- a/app-proto/src/main.rs +++ b/app-proto/src/main.rs @@ -24,8 +24,8 @@ struct AppState { } impl AppState { - fn new() -> AppState { - AppState { + fn new() -> Self { + Self { assembly: Assembly::new(), selection: create_signal(BTreeSet::default()), } diff --git a/app-proto/src/specified.rs b/app-proto/src/specified.rs index ea1731c..788460b 100644 --- a/app-proto/src/specified.rs +++ b/app-proto/src/specified.rs @@ -17,8 +17,8 @@ pub struct SpecifiedValue { } impl SpecifiedValue { - pub fn from_empty_spec() -> SpecifiedValue { - SpecifiedValue { spec: String::new(), value: None } + pub fn from_empty_spec() -> Self { + Self { spec: String::new(), value: None } } pub fn is_present(&self) -> bool { @@ -34,10 +34,10 @@ impl TryFrom for SpecifiedValue { fn try_from(spec: String) -> Result { if spec.is_empty() { - Ok(SpecifiedValue::from_empty_spec()) + Ok(Self::from_empty_spec()) } else { spec.parse::().map( - |value| SpecifiedValue { spec, value: Some(value) } + |value| Self { spec, value: Some(value) } ) } } From af18a8e7d19067523de7bb4c8555c397a6f13d87 Mon Sep 17 00:00:00 2001 From: Vectornaut Date: Mon, 11 Aug 2025 03:33:19 +0000 Subject: [PATCH 14/14] Write a deployment packaging script (#113) Adds a packaging script to help automate deployment. Documents the deployment process in `README.md`. Also, moves `run-examples.sh` into the tools folder that we created for the packaging script. Co-authored-by: Aaron Fenyes Reviewed-on: https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/113 Co-authored-by: Vectornaut Co-committed-by: Vectornaut --- README.md | 46 ++++++++++++++++++++-------- app-proto/Trunk.toml | 2 ++ deploy/.gitignore | 5 +++ tools/package-for-deployment.sh | 16 ++++++++++ {app-proto => tools}/run-examples.sh | 2 +- 5 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 app-proto/Trunk.toml create mode 100644 deploy/.gitignore create mode 100644 tools/package-for-deployment.sh rename {app-proto => tools}/run-examples.sh (89%) diff --git a/README.md b/README.md index 3a29eb0..cf3e589 100644 --- a/README.md +++ b/README.md @@ -25,32 +25,37 @@ 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) + - 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 + - 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 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 ### 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* - * *If you want to stay in the top-level folder, you can call `trunk serve --config app-proto [--release]`* from there instead. + - 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* + - 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. Go into the `app-proto` folder -2. Call `./run-examples` - * *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* +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 ```julia include("irisawa-hexlet.jl") @@ -59,9 +64,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` + +### 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 +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 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` +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 diff --git a/app-proto/Trunk.toml b/app-proto/Trunk.toml new file mode 100644 index 0000000..017deba --- /dev/null +++ b/app-proto/Trunk.toml @@ -0,0 +1,2 @@ +[build] +public_url = "./" \ No newline at end of file diff --git a/deploy/.gitignore b/deploy/.gitignore new file mode 100644 index 0000000..192f529 --- /dev/null +++ b/deploy/.gitignore @@ -0,0 +1,5 @@ +/dyna3.zip +/dyna3/index.html +/dyna3/dyna3-*.js +/dyna3/dyna3-*.wasm +/dyna3/main-*.css \ No newline at end of file diff --git a/tools/package-for-deployment.sh b/tools/package-for-deployment.sh new file mode 100644 index 0000000..fdda434 --- /dev/null +++ b/tools/package-for-deployment.sh @@ -0,0 +1,16 @@ +# set paths. this technique for getting the script location comes from +# `mklement0` on Stack Overflow +# +# https://stackoverflow.com/a/24114056 +# +TOOLS=$(dirname -- $0) +SRC="$TOOLS/../app-proto/dist" +DEST="$TOOLS/../deploy/dyna3" + +# remove the old hash-named files +[ -e "$DEST"/dyna3-*.js ] && rm "$DEST"/dyna3-*.js +[ -e "$DEST"/dyna3-*.wasm ] && rm "$DEST"/dyna3-*.wasm +[ -e "$DEST"/main-*.css ] && rm "$DEST"/main-*.css + +# copy the distribution +cp -r "$SRC/." "$DEST" diff --git a/app-proto/run-examples.sh b/tools/run-examples.sh similarity index 89% rename from app-proto/run-examples.sh rename to tools/run-examples.sh index 861addf..0946d92 100644 --- a/app-proto/run-examples.sh +++ b/tools/run-examples.sh @@ -8,7 +8,7 @@ # the application prototype # find the manifest file for the application prototype -MANIFEST="$(dirname -- $0)/Cargo.toml" +MANIFEST="$(dirname -- $0)/../app-proto/Cargo.toml" # set up the command that runs each example RUN_EXAMPLE="cargo run --manifest-path $MANIFEST --example"