From f2e84fb64a87356e7ad43927dc3d2b7bd46bcd7b Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 18 Feb 2025 13:29:10 -0800 Subject: [PATCH] Enforce the validity of set point specifications Make a regulator's set point specification private, and split the set point into a private writable signal and a public read-only signal. The set point can now be initialized only through the factory method `insert_new_regulator` and changed only through the setter method `try_specify_set_point`, which both ensure that the set point specification is valid and consistent with the set point. --- app-proto/src/add_remove.rs | 47 +---------------------- app-proto/src/assembly.rs | 75 ++++++++++++++++++++++++++++++++++--- app-proto/src/outline.rs | 4 +- 3 files changed, 74 insertions(+), 52 deletions(-) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index c96d5ab..cdc35e6 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -6,10 +6,8 @@ use crate::{ AppState, assembly::{ Assembly, - Regulator, Element - }, - engine::Q + } }; /* DEBUG */ @@ -199,49 +197,8 @@ pub fn AddRemove() -> View { (subject_vec[0].clone(), subject_vec[1].clone()) } ); - let measurement = state.assembly.elements.map( - move |elts| { - let reps = ( - elts[subjects.0].representation.get_clone(), - elts[subjects.1].representation.get_clone() - ); - reps.0.dot(&(&*Q * reps.1)) - } - ); - let set_point = create_signal(None); - state.assembly.insert_regulator(Regulator { - subjects: subjects, - measurement: measurement, - set_point: set_point, - set_point_spec: create_signal(String::new()) - }); + state.assembly.insert_new_regulator(subjects); state.selection.update(|sel| sel.clear()); - - /* DEBUG */ - // print updated regulator list - console::log_1(&JsValue::from("Regulators:")); - state.assembly.regulators.with(|regs| { - for (_, reg) in regs.into_iter() { - console::log_5( - &JsValue::from(" "), - &JsValue::from(reg.subjects.0), - &JsValue::from(reg.subjects.1), - &JsValue::from(":"), - &JsValue::from(reg.set_point.get_untracked()) - ); - } - }); - - // update the realization when the regulator becomes - // a constraint, or is edited while acting as a constraint - create_effect(move || { - console::log_1(&JsValue::from( - format!("Updated constraint with subjects ({}, {})", subjects.0, subjects.1) - )); - if set_point.with(|set_pt| set_pt.is_some()) { - state.assembly.realize(); - } - }); } ) { "🔗" } select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 20b893d..21421b6 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -5,7 +5,7 @@ use std::{collections::BTreeSet, sync::atomic::{AtomicU64, Ordering}}; use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ -use crate::engine::{realize_gram, local_unif_to_std, ConfigSubspace, PartialMatrix}; +use crate::engine::{Q, local_unif_to_std, realize_gram, ConfigSubspace, PartialMatrix}; // the types of the keys we use to access an assembly's elements and regulators pub type ElementKey = usize; @@ -111,21 +111,38 @@ impl Element { } } -// `set_point_spec` must always be a valid specification of `set_point` +// `set_point_spec` is always a valid specification of `set_point` +// ┌────────────┬─────────────────────────────────────────────────────┐ +// │`set_point` │ `set_point_spec` │ +// ┝━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥ +// │`Some(x)` │ a string that parses to the floating-point value `x`│ +// ├────────────┼─────────────────────────────────────────────────────┤ +// │`None` │ the empty string │ +// └────────────┴─────────────────────────────────────────────────────┘ #[derive(Clone, Copy)] pub struct Regulator { pub subjects: (ElementKey, ElementKey), pub measurement: ReadSignal, - pub set_point: Signal>, - pub set_point_spec: Signal + pub set_point: ReadSignal>, + + set_point_writable: Signal>, + set_point_spec: Signal } impl Regulator { + pub fn get_set_point_spec_clone(&self) -> String { + self.set_point_spec.get_clone() + } + + pub fn get_set_point_spec_clone_untracked(&self) -> String { + self.set_point_spec.get_clone_untracked() + } + pub fn try_specify_set_point(&self, spec: String) -> bool { match spec.parse::() { Err(_) if !spec.is_empty() => false, set_pt => { - self.set_point.set(set_pt.ok()); + self.set_point_writable.set(set_pt.ok()); self.set_point_spec.set(spec); true } @@ -227,6 +244,54 @@ impl Assembly { subject_regulators.1.update(|regs| regs.insert(key)); } + pub fn insert_new_regulator(self, subjects: (ElementKey, ElementKey)) { + // create and insert a new regulator + let measurement = self.elements.map( + move |elts| { + let reps = ( + elts[subjects.0].representation.get_clone(), + elts[subjects.1].representation.get_clone() + ); + reps.0.dot(&(&*Q * reps.1)) + } + ); + let set_point_writable = create_signal(None); + let set_point = set_point_writable.split().0; + self.insert_regulator(Regulator { + subjects: subjects, + measurement: measurement, + set_point: set_point, + set_point_writable: set_point_writable, + set_point_spec: create_signal(String::new()) + }); + + /* DEBUG */ + // print updated regulator list + console::log_1(&JsValue::from("Regulators:")); + self.regulators.with(|regs| { + for (_, reg) in regs.into_iter() { + console::log_5( + &JsValue::from(" "), + &JsValue::from(reg.subjects.0), + &JsValue::from(reg.subjects.1), + &JsValue::from(":"), + &JsValue::from(reg.set_point.get_untracked()) + ); + } + }); + + // update the realization when the regulator becomes a constraint, or is + // edited while acting as a constraint + create_effect(move || { + console::log_1(&JsValue::from( + format!("Updated constraint with subjects ({}, {})", subjects.0, subjects.1) + )); + if set_point.with(|set_pt| set_pt.is_some()) { + self.realize(); + } + }); + } + // --- realization --- pub fn realize(&self) { diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index a48377b..4898aba 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -20,14 +20,14 @@ use crate::{ #[component(inline_props)] fn RegulatorInput(regulator: Regulator) -> View { let valid = create_signal(true); - let value = create_signal(regulator.set_point_spec.get_clone_untracked()); + let value = create_signal(regulator.get_set_point_spec_clone_untracked()); // this closure resets the input value to the regulator's set point // specification, which is always a valid specification let reset_value = move || { batch(|| { valid.set(true); - value.set(regulator.set_point_spec.get_clone()); + value.set(regulator.get_set_point_spec_clone()); }) };