Compare commits

..

7 commits

Author SHA1 Message Date
Aaron Fenyes
b3e4e902f3 Rename Regulator fields 2025-02-12 11:55:45 -08:00
Aaron Fenyes
24139ad5e9 Rename observables to regulators 2025-02-12 11:35:07 -08:00
Aaron Fenyes
de7122d871 Label observable type
Right now, there's only one type of observable, so the label can be
hard-coded.
2025-02-12 10:37:48 -08:00
Aaron Fenyes
dc8330df6a Revise observable styling
Distinguish constraints from observables using dark background rather
than marker. Customize focus highlighting. Drop the input type selector
that used to make styling apply to the Lorentz product field but not the
constraint activation check box.
2025-02-10 00:16:36 -08:00
Aaron Fenyes
af2724f934 Rename ObservableRole variants
Also rename corresponding CSS classes and add methods to check roles.
2025-02-10 00:16:36 -08:00
Aaron Fenyes
677ef47544 Rename constraints to observables 2025-02-10 00:16:36 -08:00
Aaron Fenyes
fb8e391587 Generalize constraints to observables 2025-02-10 00:08:32 -08:00
4 changed files with 171 additions and 96 deletions

View file

@ -3,7 +3,8 @@
--text-bright: white;
--text-invalid: #f58fc2; /* bright pink */
--border: #555; /* light gray */
--border-focus: #aaa; /* bright gray */
--border-focus-dark: #aaa; /* bright gray */
--border-focus-light: white;
--border-invalid: #70495c; /* dusky pink */
--selection-highlight: #444; /* medium gray */
--page-background: #222; /* dark gray */
@ -23,7 +24,7 @@ body {
display: flex;
flex-direction: column;
float: left;
width: 450px;
width: 500px;
height: 100vh;
margin: 0px;
padding: 0px;
@ -77,12 +78,12 @@ summary.selected {
background-color: var(--selection-highlight);
}
summary > div, .constraint {
summary > div, .regulator {
padding-top: 4px;
padding-bottom: 4px;
}
.element, .constraint {
.element, .regulator {
display: flex;
flex-grow: 1;
padding-left: 8px;
@ -107,7 +108,7 @@ details[open]:has(li) .element-switch::after {
flex-grow: 1;
}
.constraint-label {
.regulator-label {
flex-grow: 1;
}
@ -123,26 +124,37 @@ details[open]:has(li) .element-switch::after {
width: 56px;
}
.constraint {
.regulator {
font-style: italic;
}
.constraint.invalid {
.regulator-type {
padding: 2px 8px 0px 8px;
font-size: 10pt;
}
.regulator.invalid-constraint {
color: var(--text-invalid);
}
.constraint > input[type=checkbox] {
margin: 0px 8px 0px 0px;
}
.constraint > input[type=text] {
.regulator > input {
color: inherit;
background-color: inherit;
border: 1px solid var(--border);
border-radius: 2px;
}
.constraint.invalid > input[type=text] {
.regulator > input::placeholder {
color: inherit;
opacity: 54%;
font-style: italic;
}
.regulator.valid-constraint > input {
background-color: var(--display-background);
}
.regulator.invalid-constraint > input {
border-color: var(--border-invalid);
}
@ -154,7 +166,7 @@ details[open]:has(li) .element-switch::after {
font-style: normal;
}
.invalid > .status::after, details:has(.invalid):not([open]) .status::after {
.invalid-constraint > .status::after, details:has(.invalid-constraint):not([open]) .status::after {
content: '⚠';
color: var(--text-invalid);
}
@ -171,5 +183,11 @@ canvas {
}
canvas:focus {
border-color: var(--border-focus);
border-color: var(--border-focus-dark);
outline: none;
}
input:focus {
border-color: var(--border-focus-light);
outline: none;
}

View file

@ -1,7 +1,17 @@
use sycamore::prelude::*;
use web_sys::{console, wasm_bindgen::JsValue};
use crate::{engine, AppState, assembly::{Assembly, Constraint, Element}};
use crate::{
engine,
AppState,
assembly::{
Assembly,
Regulator,
RegulatorRole,
Element
},
engine::Q
};
/* DEBUG */
// load an example assembly for testing. this code will be removed once we've
@ -190,41 +200,49 @@ pub fn AddRemove() -> View {
(subject_vec[0].clone(), subject_vec[1].clone())
}
);
let lorentz_prod = create_signal(0.0);
let lorentz_prod_valid = create_signal(false);
let active = create_signal(true);
state.assembly.insert_constraint(Constraint {
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(0.0);
let role = create_signal(RegulatorRole::Measurement);
state.assembly.insert_regulator(Regulator {
subjects: subjects,
lorentz_prod: lorentz_prod,
lorentz_prod_text: create_signal(String::new()),
lorentz_prod_valid: lorentz_prod_valid,
active: active,
measurement: measurement,
set_point: set_point,
set_point_text: create_signal(String::new()),
role: role,
});
state.selection.update(|sel| sel.clear());
/* DEBUG */
// print updated constraint list
console::log_1(&JsValue::from("Constraints:"));
state.assembly.constraints.with(|csts| {
for (_, cst) in csts.into_iter() {
// 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(cst.subjects.0),
&JsValue::from(cst.subjects.1),
&JsValue::from(reg.subjects.0),
&JsValue::from(reg.subjects.1),
&JsValue::from(":"),
&JsValue::from(cst.lorentz_prod.get_untracked())
&JsValue::from(reg.set_point.get_untracked())
);
}
});
// update the realization when the constraint becomes active
// and valid, or is edited while active and valid
// 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!("Constraint ({}, {}) updated", subjects.0, subjects.1)
format!("Updated constraint with subjects ({}, {})", subjects.0, subjects.1)
));
lorentz_prod.track();
if active.get() && lorentz_prod_valid.get() {
set_point.track();
if role.with(|rl| rl.is_valid_constraint()) {
state.assembly.realize();
}
});

View file

@ -7,9 +7,9 @@ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
use crate::engine::{realize_gram, local_unif_to_std, ConfigSubspace, PartialMatrix};
// the types of the keys we use to access an assembly's elements and constraints
// the types of the keys we use to access an assembly's elements and regulators
pub type ElementKey = usize;
pub type ConstraintKey = usize;
pub type RegulatorKey = usize;
pub type ElementColor = [f32; 3];
@ -26,7 +26,7 @@ pub struct Element {
pub label: String,
pub color: ElementColor,
pub representation: Signal<DVector<f64>>,
pub constraints: Signal<BTreeSet<ConstraintKey>>,
pub regulators: Signal<BTreeSet<RegulatorKey>>,
// a serial number, assigned by `Element::new`, that uniquely identifies
// each element
@ -61,7 +61,7 @@ impl Element {
label: label,
color: color,
representation: create_signal(representation),
constraints: create_signal(BTreeSet::default()),
regulators: create_signal(BTreeSet::default()),
serial: serial,
column_index: None
}
@ -111,13 +111,33 @@ impl Element {
}
}
pub enum RegulatorRole {
Measurement,
Constraint(bool)
}
impl RegulatorRole {
pub fn is_valid_constraint(&self) -> bool {
match self {
RegulatorRole::Measurement => false,
RegulatorRole::Constraint(valid) => *valid
}
}
}
#[derive(Clone)]
pub struct Constraint {
pub struct Regulator {
pub subjects: (ElementKey, ElementKey),
pub lorentz_prod: Signal<f64>,
pub lorentz_prod_text: Signal<String>,
pub lorentz_prod_valid: Signal<bool>,
pub active: Signal<bool>
pub measurement: ReadSignal<f64>,
pub set_point: Signal<f64>,
pub set_point_text: Signal<String>,
pub role: Signal<RegulatorRole>
}
impl Regulator {
fn role_is_valid_constraint_untracked(&self) -> bool {
self.role.with_untracked(|role| role.is_valid_constraint())
}
}
// the velocity is expressed in uniform coordinates
@ -131,9 +151,9 @@ type AssemblyMotion<'a> = Vec<ElementMotion<'a>>;
// a complete, view-independent description of an assembly
#[derive(Clone)]
pub struct Assembly {
// elements and constraints
// elements and regulators
pub elements: Signal<Slab<Element>>,
pub constraints: Signal<Slab<Constraint>>,
pub regulators: Signal<Slab<Regulator>>,
// solution variety tangent space. the basis vectors are stored in
// configuration matrix format, ordered according to the elements' column
@ -155,13 +175,13 @@ impl Assembly {
pub fn new() -> Assembly {
Assembly {
elements: create_signal(Slab::new()),
constraints: create_signal(Slab::new()),
regulators: create_signal(Slab::new()),
tangent: create_signal(ConfigSubspace::zero(0)),
elements_by_id: create_signal(FxHashMap::default())
}
}
// --- inserting elements and constraints ---
// --- inserting elements and regulators ---
// insert an element into the assembly without checking whether we already
// have an element with the same identifier. any element that does have the
@ -204,14 +224,14 @@ impl Assembly {
);
}
pub fn insert_constraint(&self, constraint: Constraint) {
let subjects = constraint.subjects;
let key = self.constraints.update(|csts| csts.insert(constraint));
let subject_constraints = self.elements.with(
|elts| (elts[subjects.0].constraints, elts[subjects.1].constraints)
pub fn insert_regulator(&self, regulator: Regulator) {
let subjects = regulator.subjects;
let key = self.regulators.update(|regs| regs.insert(regulator));
let subject_regulators = self.elements.with(
|elts| (elts[subjects.0].regulators, elts[subjects.1].regulators)
);
subject_constraints.0.update(|csts| csts.insert(key));
subject_constraints.1.update(|csts| csts.insert(key));
subject_regulators.0.update(|regs| regs.insert(key));
subject_regulators.1.update(|regs| regs.insert(key));
}
// --- realization ---
@ -228,13 +248,13 @@ impl Assembly {
let (gram, guess) = self.elements.with_untracked(|elts| {
// set up the off-diagonal part of the Gram matrix
let mut gram_to_be = PartialMatrix::new();
self.constraints.with_untracked(|csts| {
for (_, cst) in csts {
if cst.active.get_untracked() && cst.lorentz_prod_valid.get_untracked() {
let subjects = cst.subjects;
self.regulators.with_untracked(|regs| {
for (_, reg) in regs {
if reg.role_is_valid_constraint_untracked() {
let subjects = reg.subjects;
let row = elts[subjects.0].column_index.unwrap();
let col = elts[subjects.1].column_index.unwrap();
gram_to_be.push_sym(row, col, cst.lorentz_prod.get_untracked());
gram_to_be.push_sym(row, col, reg.set_point.get_untracked());
}
}
});

View file

@ -8,49 +8,68 @@ use web_sys::{
wasm_bindgen::JsCast
};
use crate::{AppState, assembly, assembly::{Constraint, ConstraintKey, ElementKey}};
use crate::{
AppState,
assembly,
assembly::{
Regulator,
RegulatorKey,
RegulatorRole::*,
ElementKey
}
};
// an editable view of the Lorentz product representing a constraint
// an editable view of a regulator
#[component(inline_props)]
fn LorentzProductInput(constraint: Constraint) -> View {
fn RegulatorInput(regulator: Regulator) -> View {
view! {
input(
r#type="text",
bind:value=constraint.lorentz_prod_text,
placeholder=regulator.measurement.with(|result| result.to_string()),
bind:value=regulator.set_point_text,
on:change=move |event: Event| {
let target: HtmlInputElement = event.target().unwrap().unchecked_into();
let value = target.value();
if value.is_empty() {
regulator.role.set(Measurement);
} else {
match target.value().parse::<f64>() {
Ok(lorentz_prod) => batch(|| {
constraint.lorentz_prod.set(lorentz_prod);
constraint.lorentz_prod_valid.set(true);
Ok(set_pt) => batch(|| {
regulator.set_point.set(set_pt);
regulator.role.set(Constraint(true));
}),
Err(_) => constraint.lorentz_prod_valid.set(false)
Err(_) => regulator.role.set(Constraint(false))
};
}
}
)
}
}
// a list item that shows a constraint in an outline view of an element
// a list item that shows a regulator in an outline view of an element
#[component(inline_props)]
fn ConstraintOutlineItem(constraint_key: ConstraintKey, element_key: ElementKey) -> View {
fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) -> View {
let state = use_context::<AppState>();
let assembly = &state.assembly;
let constraint = assembly.constraints.with(|csts| csts[constraint_key].clone());
let other_subject = if constraint.subjects.0 == element_key {
constraint.subjects.1
let regulator = assembly.regulators.with(|regs| regs[regulator_key].clone());
let other_subject = if regulator.subjects.0 == element_key {
regulator.subjects.1
} else {
constraint.subjects.0
regulator.subjects.0
};
let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone());
let class = constraint.lorentz_prod_valid.map(
|&lorentz_prod_valid| if lorentz_prod_valid { "constraint" } else { "constraint invalid" }
let class = regulator.role.map(
|role| match role {
Measurement => "regulator",
Constraint(true) => "regulator valid-constraint",
Constraint(false) => "regulator invalid-constraint"
}
);
view! {
li(class=class.get()) {
input(r#type="checkbox", bind:checked=constraint.active)
div(class="constraint-label") { (other_subject_label) }
LorentzProductInput(constraint=constraint)
div(class="regulator-label") { (other_subject_label) }
div(class="regulator-type") { "Inversive distance" }
RegulatorInput(regulator=regulator)
div(class="status")
}
}
@ -74,9 +93,9 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
).collect::<Vec<_>>()
)
};
let constrained = element.constraints.map(|csts| csts.len() > 0);
let constraint_list = element.constraints.map(
|csts| csts.clone().into_iter().collect()
let regulated = element.regulators.map(|regs| regs.len() > 0);
let regulator_list = element.regulators.map(
|regs| regs.clone().into_iter().collect()
);
let details_node = create_node_ref();
view! {
@ -91,7 +110,7 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
state.select(key, event.shift_key());
event.prevent_default();
},
"ArrowRight" if constrained.get() => {
"ArrowRight" if regulated.get() => {
let _ = details_node
.get()
.unchecked_into::<web_sys::Element>()
@ -138,16 +157,16 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
div(class="status")
}
}
ul(class="constraints") {
ul(class="regulators") {
Keyed(
list=constraint_list,
view=move |cst_key| view! {
ConstraintOutlineItem(
constraint_key=cst_key,
list=regulator_list,
view=move |reg_key| view! {
RegulatorOutlineItem(
regulator_key=reg_key,
element_key=key
)
},
key=|cst_key| cst_key.clone()
key=|reg_key| reg_key.clone()
)
}
}
@ -155,9 +174,9 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
}
}
// a component that lists the elements of the current assembly, showing the
// constraints on each element as a collapsible sub-list. its implementation
// is based on Kate Morley's HTML + CSS tree views:
// a component that lists the elements of the current assembly, showing each
// element's regulators in a collapsible sub-list. its implementation is based
// on Kate Morley's HTML + CSS tree views:
//
// https://iamkate.com/code/tree-views/
//