From 677d7707381821e5eaf6c91de363ddbcf3a25522 Mon Sep 17 00:00:00 2001
From: Aaron Fenyes <aaron.fenyes@fareycircles.ooo>
Date: Tue, 25 Mar 2025 02:15:03 -0700
Subject: [PATCH] Let the elements and regulators write the problem

When we realize an assembly, each element and regulator now writes its
own data into the constraint problem.
---
 app-proto/src/assembly.rs | 122 +++++++++++++++++++++++++++++---------
 app-proto/src/outline.rs  |   4 +-
 2 files changed, 95 insertions(+), 31 deletions(-)

diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs
index 289b271..293bf73 100644
--- a/app-proto/src/assembly.rs
+++ b/app-proto/src/assembly.rs
@@ -121,15 +121,43 @@ impl Element {
             None
         }
     }
+    
+    fn write_to_problem(&self, problem: &mut ConstraintProblem) {
+        if let Some(index) = self.column_index {
+            problem.gram.push_sym(index, index, 1.0);
+            problem.guess.set_column(index, &self.representation.get_clone_untracked());
+        } else {
+            panic!("Tried to write problem data from an unindexed element: \"{}\"", self.id);
+        }
+    }
 }
 
 #[derive(Clone, Copy)]
-pub struct Regulator {
+pub struct ProductRegulator {
     pub subjects: (ElementKey, ElementKey),
     pub measurement: ReadSignal<f64>,
     pub set_point: Signal<SpecifiedValue>
 }
 
+impl ProductRegulator {
+    fn write_to_problem(&self, problem: &mut ConstraintProblem, elts: &Slab<Element>) {
+        self.set_point.with_untracked(|set_pt| {
+            if let Some(val) = set_pt.value {
+                let subjects = self.subjects;
+                let subject_column_indices = (
+                    elts[subjects.0].column_index,
+                    elts[subjects.1].column_index
+                );
+                if let (Some(row), Some(col)) = subject_column_indices {
+                    problem.gram.push_sym(row, col, val);
+                } else {
+                    panic!("Tried to write problem data from a regulator with an unindexed subject");
+                }
+            }
+        });
+    }
+}
+
 // the velocity is expressed in uniform coordinates
 pub struct ElementMotion<'a> {
     pub key: ElementKey,
@@ -143,7 +171,7 @@ type AssemblyMotion<'a> = Vec<ElementMotion<'a>>;
 pub struct Assembly {
     // elements and regulators
     pub elements: Signal<Slab<Element>>,
-    pub regulators: Signal<Slab<Regulator>>,
+    pub regulators: Signal<Slab<ProductRegulator>>,
     
     // solution variety tangent space. the basis vectors are stored in
     // configuration matrix format, ordered according to the elements' column
@@ -214,7 +242,7 @@ impl Assembly {
         );
     }
     
-    fn insert_regulator(&self, regulator: Regulator) {
+    fn insert_regulator(&self, regulator: ProductRegulator) {
         let subjects = regulator.subjects;
         let key = self.regulators.update(|regs| regs.insert(regulator));
         let subject_regulators = self.elements.with(
@@ -236,7 +264,7 @@ impl Assembly {
             }
         );
         let set_point = create_signal(SpecifiedValue::from_empty_spec());
-        self.insert_regulator(Regulator {
+        self.insert_regulator(ProductRegulator {
             subjects: subjects,
             measurement: measurement,
             set_point: set_point
@@ -263,7 +291,7 @@ impl Assembly {
         // edited while acting as a constraint
         create_effect(move || {
             console::log_1(&JsValue::from(
-                format!("Updated constraint with subjects ({}, {})", subjects.0, subjects.1)
+                format!("Updated regulator with subjects {:?}", subjects)
             ));
             if set_point.with(|set_pt| set_pt.is_present()) {
                 self.realize();
@@ -274,11 +302,6 @@ impl Assembly {
     // --- realization ---
     
     pub fn realize(&self) {
-        // create a blank constraint problem
-        let mut problem = ConstraintProblem::new(
-            self.elements.with_untracked(|elts| elts.len())
-        );
-        
         // index the elements
         self.elements.update_silent(|elts| {
             for (index, (_, elt)) in elts.into_iter().enumerate() {
@@ -286,29 +309,18 @@ impl Assembly {
             }
         });
         
-        // set up the Gram matrix and the initial configuration matrix
-        self.elements.with_untracked(|elts| {
-            // set up the off-diagonal part of the Gram matrix
+        // set up the constraint problem
+        let problem = self.elements.with_untracked(|elts| {
+            let mut problem_to_be = ConstraintProblem::new(elts.len());
+            for (_, elt) in elts {
+                elt.write_to_problem(&mut problem_to_be);
+            }
             self.regulators.with_untracked(|regs| {
                 for (_, reg) in regs {
-                    reg.set_point.with_untracked(|set_pt| {
-                        if let Some(val) = set_pt.value {
-                            let subjects = reg.subjects;
-                            let row = elts[subjects.0].column_index.unwrap();
-                            let col = elts[subjects.1].column_index.unwrap();
-                            problem.gram.push_sym(row, col, val);
-                        }
-                    });
+                    reg.write_to_problem(&mut problem_to_be, elts);
                 }
             });
-            
-            // set up the initial configuration matrix and the diagonal of the
-            // Gram matrix
-            for (_, elt) in elts {
-                let index = elt.column_index.unwrap();
-                problem.gram.push_sym(index, index, 1.0);
-                problem.guess.set_column(index, &elt.representation.get_clone_untracked());
-            }
+            problem_to_be
         });
         
         /* DEBUG */
@@ -464,4 +476,56 @@ impl Assembly {
         // sync
         self.realize();
     }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::engine;
+    
+    use super::*;
+    
+    #[test]
+    #[should_panic(expected = "Tried to write problem data from an unindexed element: \"sphere\"")]
+    fn unindexed_element_test() {
+        let _ = create_root(|| {
+            Element::new(
+                "sphere".to_string(),
+                "Sphere".to_string(),
+                [1.0_f32, 1.0_f32, 1.0_f32],
+                engine::sphere(0.0, 0.0, 0.0, 1.0)
+            ).write_to_problem(&mut ConstraintProblem::new(1));
+        });
+    }
+    
+    #[test]
+    #[should_panic(expected = "Tried to write problem data from a regulator with an unindexed subject")]
+    fn unindexed_subject_test() {
+        let _ = create_root(|| {
+            let mut elts = Slab::new();
+            let subjects = (
+                elts.insert(
+                    Element::new(
+                        "sphere0".to_string(),
+                        "Sphere 0".to_string(),
+                        [1.0_f32, 1.0_f32, 1.0_f32],
+                        engine::sphere(0.0, 0.0, 0.0, 1.0)
+                    )
+                ),
+                elts.insert(
+                    Element::new(
+                        "sphere1".to_string(),
+                        "Sphere 1".to_string(),
+                        [1.0_f32, 1.0_f32, 1.0_f32],
+                        engine::sphere(0.0, 0.0, 0.0, 1.0)
+                    )
+                )
+            );
+            elts[subjects.0].column_index = Some(0);
+            ProductRegulator {
+                subjects: subjects,
+                measurement: create_memo(|| 0.0),
+                set_point: create_signal(SpecifiedValue::try_from("0.0".to_string()).unwrap())
+            }.write_to_problem(&mut ConstraintProblem::new(2), &elts);
+        });
+    }
 }
\ No newline at end of file
diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs
index 002baea..deede23 100644
--- a/app-proto/src/outline.rs
+++ b/app-proto/src/outline.rs
@@ -9,13 +9,13 @@ use web_sys::{
 use crate::{
     AppState,
     assembly,
-    assembly::{ElementKey, Regulator, RegulatorKey},
+    assembly::{ElementKey, ProductRegulator, RegulatorKey},
     specified::SpecifiedValue
 };
 
 // an editable view of a regulator
 #[component(inline_props)]
-fn RegulatorInput(regulator: Regulator) -> View {
+fn RegulatorInput(regulator: ProductRegulator) -> View {
     let valid = create_signal(true);
     let value = create_signal(
         regulator.set_point.with_untracked(|set_pt| set_pt.spec.clone())