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.
This commit is contained in:
Aaron Fenyes 2025-02-18 13:29:10 -08:00
parent bbd0835a8f
commit f2e84fb64a
3 changed files with 74 additions and 52 deletions

View file

@ -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

View file

@ -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<f64>,
pub set_point: Signal<Option<f64>>,
pub set_point_spec: Signal<String>
pub set_point: ReadSignal<Option<f64>>,
set_point_writable: Signal<Option<f64>>,
set_point_spec: Signal<String>
}
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::<f64>() {
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) {

View file

@ -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());
})
};