Compare commits
29 Commits
main
...
outline-cl
Author | SHA1 | Date | |
---|---|---|---|
|
fc39f2a5f3 | ||
|
6e42681b71 | ||
|
327a1267d5 | ||
|
e12f4332fe | ||
|
5ce5f855d5 | ||
|
e42b8da897 | ||
|
bbeebe4464 | ||
|
fb292d8b5b | ||
|
a3fce9d298 | ||
|
5b522c12ee | ||
|
1f3a6eea3b | ||
|
35d3e4a6f8 | ||
|
0a13c062f4 | ||
|
9555d8f784 | ||
|
df6db983ba | ||
|
7f595ff27a | ||
|
9c191ae586 | ||
|
9e31037e17 | ||
|
c2e3c64d4a | ||
|
76ad4245d5 | ||
|
a46ef2c8d6 | ||
|
e0880d2ad2 | ||
|
e5f4d523f9 | ||
|
a37c71153d | ||
|
ce33bbf418 | ||
|
9f8632efb3 | ||
|
9fe03264ab | ||
|
e59d60bf77 | ||
|
16df161fe7 |
@ -1,19 +1,7 @@
|
|||||||
:root {
|
|
||||||
--text: #fcfcfc; /* almost white */
|
|
||||||
--text-bright: white;
|
|
||||||
--text-invalid: #f58fc2; /* bright pink */
|
|
||||||
--border: #555; /* light gray */
|
|
||||||
--border-focus: #aaa; /* bright gray */
|
|
||||||
--border-invalid: #70495c; /* dusky pink */
|
|
||||||
--selection-highlight: #444; /* medium gray */
|
|
||||||
--page-background: #222; /* dark gray */
|
|
||||||
--display-background: #020202; /* almost black */
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
color: var(--text);
|
color: #fcfcfc;
|
||||||
background-color: var(--page-background);
|
background-color: #222;
|
||||||
font-family: 'Fira Sans', sans-serif;
|
font-family: 'Fira Sans', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +17,7 @@ body {
|
|||||||
padding: 0px;
|
padding: 0px;
|
||||||
border-width: 0px 1px 0px 0px;
|
border-width: 0px 1px 0px 0px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: var(--border);
|
border-color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* add-remove */
|
/* add-remove */
|
||||||
@ -47,10 +35,6 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* KLUDGE */
|
/* KLUDGE */
|
||||||
/*
|
|
||||||
for convenience, we're using emoji as temporary icons for some buttons. these
|
|
||||||
buttons need to be displayed in an emoji font
|
|
||||||
*/
|
|
||||||
#add-remove > button.emoji {
|
#add-remove > button.emoji {
|
||||||
font-family: 'Noto Emoji', sans-serif;
|
font-family: 'Noto Emoji', sans-serif;
|
||||||
}
|
}
|
||||||
@ -73,49 +57,49 @@ summary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
summary.selected {
|
summary.selected {
|
||||||
color: var(--text-bright);
|
color: #fff;
|
||||||
background-color: var(--selection-highlight);
|
background-color: #444;
|
||||||
}
|
}
|
||||||
|
|
||||||
summary > div, .constraint {
|
summary > div, .cst {
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.element, .constraint {
|
.elt, .cst {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.element-switch {
|
.elt-switch {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
details:has(li) .element-switch::after {
|
details:has(li) .elt-switch::after {
|
||||||
content: '▸';
|
content: '▸';
|
||||||
}
|
}
|
||||||
|
|
||||||
details[open]:has(li) .element-switch::after {
|
details[open]:has(li) .elt-switch::after {
|
||||||
content: '▾';
|
content: '▾';
|
||||||
}
|
}
|
||||||
|
|
||||||
.element-label {
|
.elt-label {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.constraint-label {
|
.cst-label {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.element-representation {
|
.elt-rep {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.element-representation > div {
|
.elt-rep > div {
|
||||||
padding: 2px 0px 0px 0px;
|
padding: 2px 0px 0px 0px;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
@ -123,27 +107,27 @@ details[open]:has(li) .element-switch::after {
|
|||||||
width: 56px;
|
width: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.constraint {
|
.cst {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.constraint.invalid {
|
.cst.invalid {
|
||||||
color: var(--text-invalid);
|
color: #f58fc2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.constraint > input[type=checkbox] {
|
.cst > input[type=checkbox] {
|
||||||
margin: 0px 8px 0px 0px;
|
margin: 0px 8px 0px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.constraint > input[type=text] {
|
.cst > input[type=text] {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid #555;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.constraint.invalid > input[type=text] {
|
.cst.invalid > input[type=text] {
|
||||||
border-color: var(--border-invalid);
|
border-color: #70495c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
@ -156,7 +140,7 @@ details[open]:has(li) .element-switch::after {
|
|||||||
|
|
||||||
.invalid > .status::after, details:has(.invalid):not([open]) .status::after {
|
.invalid > .status::after, details:has(.invalid):not([open]) .status::after {
|
||||||
content: '⚠';
|
content: '⚠';
|
||||||
color: var(--text-invalid);
|
color: #f58fc2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* display */
|
/* display */
|
||||||
@ -165,11 +149,11 @@ canvas {
|
|||||||
float: left;
|
float: left;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
background-color: var(--display-background);
|
background-color: #020202;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid #555;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas:focus {
|
canvas:focus {
|
||||||
border-color: var(--border-focus);
|
border-color: #aaa;
|
||||||
}
|
}
|
@ -4,8 +4,6 @@ use web_sys::{console, wasm_bindgen::JsValue};
|
|||||||
use crate::{engine, AppState, assembly::{Assembly, Constraint, Element}};
|
use crate::{engine, AppState, assembly::{Assembly, Constraint, Element}};
|
||||||
|
|
||||||
/* DEBUG */
|
/* 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) {
|
fn load_gen_assemb(assembly: &Assembly) {
|
||||||
let _ = assembly.try_insert_element(
|
let _ = assembly.try_insert_element(
|
||||||
Element::new(
|
Element::new(
|
||||||
@ -58,8 +56,6 @@ fn load_gen_assemb(assembly: &Assembly) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* DEBUG */
|
/* 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) {
|
fn load_low_curv_assemb(assembly: &Assembly) {
|
||||||
let a = 0.75_f64.sqrt();
|
let a = 0.75_f64.sqrt();
|
||||||
let _ = assembly.try_insert_element(
|
let _ = assembly.try_insert_element(
|
||||||
@ -177,27 +173,27 @@ pub fn AddRemove() -> View {
|
|||||||
}
|
}
|
||||||
) { "+" }
|
) { "+" }
|
||||||
button(
|
button(
|
||||||
class="emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button
|
class="emoji", /* KLUDGE */
|
||||||
disabled={
|
disabled={
|
||||||
let state = use_context::<AppState>();
|
let state = use_context::<AppState>();
|
||||||
state.selection.with(|sel| sel.len() != 2)
|
state.selection.with(|sel| sel.len() != 2)
|
||||||
},
|
},
|
||||||
on:click=|_| {
|
on:click=|_| {
|
||||||
let state = use_context::<AppState>();
|
let state = use_context::<AppState>();
|
||||||
let subjects = state.selection.with(
|
let args = state.selection.with(
|
||||||
|sel| {
|
|sel| {
|
||||||
let subject_vec: Vec<_> = sel.into_iter().collect();
|
let arg_vec: Vec<_> = sel.into_iter().collect();
|
||||||
(subject_vec[0].clone(), subject_vec[1].clone())
|
(arg_vec[0].clone(), arg_vec[1].clone())
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
let lorentz_prod = create_signal(0.0);
|
let rep = create_signal(0.0);
|
||||||
let lorentz_prod_valid = create_signal(false);
|
let rep_valid = create_signal(false);
|
||||||
let active = create_signal(true);
|
let active = create_signal(true);
|
||||||
state.assembly.insert_constraint(Constraint {
|
state.assembly.insert_constraint(Constraint {
|
||||||
subjects: subjects,
|
args: args,
|
||||||
lorentz_prod: lorentz_prod,
|
rep: rep,
|
||||||
lorentz_prod_text: create_signal(String::new()),
|
rep_text: create_signal(String::new()),
|
||||||
lorentz_prod_valid: lorentz_prod_valid,
|
rep_valid: rep_valid,
|
||||||
active: active,
|
active: active,
|
||||||
});
|
});
|
||||||
state.selection.update(|sel| sel.clear());
|
state.selection.update(|sel| sel.clear());
|
||||||
@ -209,10 +205,10 @@ pub fn AddRemove() -> View {
|
|||||||
for (_, cst) in csts.into_iter() {
|
for (_, cst) in csts.into_iter() {
|
||||||
console::log_5(
|
console::log_5(
|
||||||
&JsValue::from(" "),
|
&JsValue::from(" "),
|
||||||
&JsValue::from(cst.subjects.0),
|
&JsValue::from(cst.args.0),
|
||||||
&JsValue::from(cst.subjects.1),
|
&JsValue::from(cst.args.1),
|
||||||
&JsValue::from(":"),
|
&JsValue::from(":"),
|
||||||
&JsValue::from(cst.lorentz_prod.get_untracked())
|
&JsValue::from(cst.rep.get_untracked())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -221,19 +217,18 @@ pub fn AddRemove() -> View {
|
|||||||
// and valid, or is edited while active and valid
|
// and valid, or is edited while active and valid
|
||||||
create_effect(move || {
|
create_effect(move || {
|
||||||
console::log_1(&JsValue::from(
|
console::log_1(&JsValue::from(
|
||||||
format!("Constraint ({}, {}) updated", subjects.0, subjects.1)
|
format!("Constraint ({}, {}) updated", args.0, args.1)
|
||||||
));
|
));
|
||||||
lorentz_prod.track();
|
rep.track();
|
||||||
if active.get() && lorentz_prod_valid.get() {
|
if active.get() && rep_valid.get() {
|
||||||
state.assembly.realize();
|
state.assembly.realize();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
) { "🔗" }
|
) { "🔗" }
|
||||||
select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser
|
select(bind:value=assembly_name) { /* DEBUG */
|
||||||
option(value="general") { "General" }
|
option(value="general") { "General" }
|
||||||
option(value="low-curv") { "Low-curvature" }
|
option(value="low-curv") { "Low-curvature" }
|
||||||
option(value="empty") { "Empty" }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,39 +7,32 @@ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
|
|||||||
|
|
||||||
use crate::engine::{realize_gram, PartialMatrix};
|
use crate::engine::{realize_gram, PartialMatrix};
|
||||||
|
|
||||||
// the types of the keys we use to access an assembly's elements and constraints
|
|
||||||
pub type ElementKey = usize;
|
|
||||||
pub type ConstraintKey = usize;
|
|
||||||
|
|
||||||
pub type ElementColor = [f32; 3];
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct Element {
|
pub struct Element {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub color: ElementColor,
|
pub color: [f32; 3],
|
||||||
pub representation: Signal<DVector<f64>>,
|
pub rep: Signal<DVector<f64>>,
|
||||||
pub constraints: Signal<BTreeSet<ConstraintKey>>,
|
pub constraints: Signal<BTreeSet<usize>>,
|
||||||
|
|
||||||
// the configuration matrix column index that was assigned to this element
|
// internal properties, not reflected in any view
|
||||||
// last time the assembly was realized
|
pub index: usize
|
||||||
column_index: usize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element {
|
impl Element {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
id: String,
|
id: String,
|
||||||
label: String,
|
label: String,
|
||||||
color: ElementColor,
|
color: [f32; 3],
|
||||||
representation: DVector<f64>
|
rep: DVector<f64>
|
||||||
) -> Element {
|
) -> Element {
|
||||||
Element {
|
Element {
|
||||||
id: id,
|
id: id,
|
||||||
label: label,
|
label: label,
|
||||||
color: color,
|
color: color,
|
||||||
representation: create_signal(representation),
|
rep: create_signal(rep),
|
||||||
constraints: create_signal(BTreeSet::default()),
|
constraints: create_signal(BTreeSet::default()),
|
||||||
column_index: 0
|
index: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,10 +40,10 @@ impl Element {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Constraint {
|
pub struct Constraint {
|
||||||
pub subjects: (ElementKey, ElementKey),
|
pub args: (usize, usize),
|
||||||
pub lorentz_prod: Signal<f64>,
|
pub rep: Signal<f64>,
|
||||||
pub lorentz_prod_text: Signal<String>,
|
pub rep_text: Signal<String>,
|
||||||
pub lorentz_prod_valid: Signal<bool>,
|
pub rep_valid: Signal<bool>,
|
||||||
pub active: Signal<bool>
|
pub active: Signal<bool>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +55,7 @@ pub struct Assembly {
|
|||||||
pub constraints: Signal<Slab<Constraint>>,
|
pub constraints: Signal<Slab<Constraint>>,
|
||||||
|
|
||||||
// indexing
|
// indexing
|
||||||
pub elements_by_id: Signal<FxHashMap<String, ElementKey>>
|
pub elements_by_id: Signal<FxHashMap<String, usize>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assembly {
|
impl Assembly {
|
||||||
@ -118,13 +111,13 @@ impl Assembly {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_constraint(&self, constraint: Constraint) {
|
pub fn insert_constraint(&self, constraint: Constraint) {
|
||||||
let subjects = constraint.subjects;
|
let args = constraint.args;
|
||||||
let key = self.constraints.update(|csts| csts.insert(constraint));
|
let key = self.constraints.update(|csts| csts.insert(constraint));
|
||||||
let subject_constraints = self.elements.with(
|
let arg_constraints = self.elements.with(
|
||||||
|elts| (elts[subjects.0].constraints, elts[subjects.1].constraints)
|
|elts| (elts[args.0].constraints, elts[args.1].constraints)
|
||||||
);
|
);
|
||||||
subject_constraints.0.update(|csts| csts.insert(key));
|
arg_constraints.0.update(|csts| csts.insert(key));
|
||||||
subject_constraints.1.update(|csts| csts.insert(key));
|
arg_constraints.1.update(|csts| csts.insert(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- realization ---
|
// --- realization ---
|
||||||
@ -133,7 +126,7 @@ impl Assembly {
|
|||||||
// index the elements
|
// index the elements
|
||||||
self.elements.update_silent(|elts| {
|
self.elements.update_silent(|elts| {
|
||||||
for (index, (_, elt)) in elts.into_iter().enumerate() {
|
for (index, (_, elt)) in elts.into_iter().enumerate() {
|
||||||
elt.column_index = index;
|
elt.index = index;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -143,11 +136,11 @@ impl Assembly {
|
|||||||
let mut gram_to_be = PartialMatrix::new();
|
let mut gram_to_be = PartialMatrix::new();
|
||||||
self.constraints.with_untracked(|csts| {
|
self.constraints.with_untracked(|csts| {
|
||||||
for (_, cst) in csts {
|
for (_, cst) in csts {
|
||||||
if cst.active.get_untracked() && cst.lorentz_prod_valid.get_untracked() {
|
if cst.active.get_untracked() && cst.rep_valid.get_untracked() {
|
||||||
let subjects = cst.subjects;
|
let args = cst.args;
|
||||||
let row = elts[subjects.0].column_index;
|
let row = elts[args.0].index;
|
||||||
let col = elts[subjects.1].column_index;
|
let col = elts[args.1].index;
|
||||||
gram_to_be.push_sym(row, col, cst.lorentz_prod.get_untracked());
|
gram_to_be.push_sym(row, col, cst.rep.get_untracked());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -156,9 +149,9 @@ impl Assembly {
|
|||||||
// Gram matrix
|
// Gram matrix
|
||||||
let mut guess_to_be = DMatrix::<f64>::zeros(5, elts.len());
|
let mut guess_to_be = DMatrix::<f64>::zeros(5, elts.len());
|
||||||
for (_, elt) in elts {
|
for (_, elt) in elts {
|
||||||
let index = elt.column_index;
|
let index = elt.index;
|
||||||
gram_to_be.push_sym(index, index, 1.0);
|
gram_to_be.push_sym(index, index, 1.0);
|
||||||
guess_to_be.set_column(index, &elt.representation.get_clone_untracked());
|
guess_to_be.set_column(index, &elt.rep.get_clone_untracked());
|
||||||
}
|
}
|
||||||
|
|
||||||
(gram_to_be, guess_to_be)
|
(gram_to_be, guess_to_be)
|
||||||
@ -201,8 +194,8 @@ impl Assembly {
|
|||||||
if success {
|
if success {
|
||||||
// read out the solution
|
// read out the solution
|
||||||
for (_, elt) in self.elements.get_clone_untracked() {
|
for (_, elt) in self.elements.get_clone_untracked() {
|
||||||
elt.representation.update(
|
elt.rep.update(
|
||||||
|rep| rep.set_column(0, &config.column(elt.column_index))
|
|rep| rep.set_column(0, &config.column(elt.index))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ pub fn Display() -> View {
|
|||||||
create_effect(move || {
|
create_effect(move || {
|
||||||
state.assembly.elements.with(|elts| {
|
state.assembly.elements.with(|elts| {
|
||||||
for (_, elt) in elts {
|
for (_, elt) in elts {
|
||||||
elt.representation.track();
|
elt.rep.track();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
state.selection.track();
|
state.selection.track();
|
||||||
@ -311,7 +311,7 @@ pub fn Display() -> View {
|
|||||||
|
|
||||||
// representation vectors in world coordinates
|
// representation vectors in world coordinates
|
||||||
elts.iter().map(
|
elts.iter().map(
|
||||||
|(_, elt)| elt.representation.with(|rep| &assembly_to_world * rep)
|
|(_, elt)| elt.rep.with(|rep| &assembly_to_world * rep)
|
||||||
).collect::<Vec<_>>(),
|
).collect::<Vec<_>>(),
|
||||||
|
|
||||||
// colors
|
// colors
|
||||||
|
@ -4,7 +4,6 @@ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
|
|||||||
|
|
||||||
// --- elements ---
|
// --- elements ---
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn point(x: f64, y: f64, z: f64) -> DVector<f64> {
|
pub fn point(x: f64, y: f64, z: f64) -> DVector<f64> {
|
||||||
DVector::from_column_slice(&[x, y, z, 0.5, 0.5*(x*x + y*y + z*z)])
|
DVector::from_column_slice(&[x, y, z, 0.5, 0.5*(x*x + y*y + z*z)])
|
||||||
}
|
}
|
||||||
@ -500,41 +499,4 @@ mod tests {
|
|||||||
println!("{:<4} │ {}", step, scaled_loss);
|
println!("{:<4} │ {}", step, scaled_loss);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TO DO */
|
|
||||||
// --- new test placed here to avoid merge conflict ---
|
|
||||||
|
|
||||||
// at the frozen indices, the optimization steps should have exact zeros,
|
|
||||||
// and the realized configuration should match the initial guess
|
|
||||||
#[test]
|
|
||||||
fn frozen_entry_test() {
|
|
||||||
let gram = {
|
|
||||||
let mut gram_to_be = PartialMatrix::new();
|
|
||||||
for j in 0..2 {
|
|
||||||
for k in j..2 {
|
|
||||||
gram_to_be.push_sym(j, k, if (j, k) == (1, 1) { 1.0 } else { 0.0 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gram_to_be
|
|
||||||
};
|
|
||||||
let guess = DMatrix::from_columns(&[
|
|
||||||
point(0.0, 0.0, 2.0),
|
|
||||||
sphere(0.0, 0.0, 0.0, 1.0)
|
|
||||||
]);
|
|
||||||
let frozen = [(3, 0), (3, 1)];
|
|
||||||
println!();
|
|
||||||
let (config, success, history) = realize_gram(
|
|
||||||
&gram, guess.clone(), &frozen,
|
|
||||||
1.0e-12, 0.5, 0.9, 1.1, 200, 110
|
|
||||||
);
|
|
||||||
assert_eq!(success, true);
|
|
||||||
for base_step in history.base_step.into_iter() {
|
|
||||||
for index in frozen {
|
|
||||||
assert_eq!(base_step[index], 0.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for index in frozen {
|
|
||||||
assert_eq!(config[index], guess[index]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -8,14 +8,14 @@ use rustc_hash::FxHashSet;
|
|||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
use add_remove::AddRemove;
|
use add_remove::AddRemove;
|
||||||
use assembly::{Assembly, ElementKey};
|
use assembly::Assembly;
|
||||||
use display::Display;
|
use display::Display;
|
||||||
use outline::Outline;
|
use outline::Outline;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
assembly: Assembly,
|
assembly: Assembly,
|
||||||
selection: Signal<FxHashSet<ElementKey>>
|
selection: Signal<FxHashSet<usize>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
|
@ -8,7 +8,7 @@ use web_sys::{
|
|||||||
wasm_bindgen::JsCast
|
wasm_bindgen::JsCast
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{AppState, assembly, assembly::{Constraint, ConstraintKey, ElementKey}};
|
use crate::{AppState, assembly, assembly::Constraint};
|
||||||
|
|
||||||
// an editable view of the Lorentz product representing a constraint
|
// an editable view of the Lorentz product representing a constraint
|
||||||
#[component(inline_props)]
|
#[component(inline_props)]
|
||||||
@ -16,15 +16,15 @@ fn LorentzProductInput(constraint: Constraint) -> View {
|
|||||||
view! {
|
view! {
|
||||||
input(
|
input(
|
||||||
r#type="text",
|
r#type="text",
|
||||||
bind:value=constraint.lorentz_prod_text,
|
bind:value=constraint.rep_text,
|
||||||
on:change=move |event: Event| {
|
on:change=move |event: Event| {
|
||||||
let target: HtmlInputElement = event.target().unwrap().unchecked_into();
|
let target: HtmlInputElement = event.target().unwrap().unchecked_into();
|
||||||
match target.value().parse::<f64>() {
|
match target.value().parse::<f64>() {
|
||||||
Ok(lorentz_prod) => batch(|| {
|
Ok(rep) => batch(|| {
|
||||||
constraint.lorentz_prod.set(lorentz_prod);
|
constraint.rep.set(rep);
|
||||||
constraint.lorentz_prod_valid.set(true);
|
constraint.rep_valid.set(true);
|
||||||
}),
|
}),
|
||||||
Err(_) => constraint.lorentz_prod_valid.set(false)
|
Err(_) => constraint.rep_valid.set(false)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -33,23 +33,23 @@ fn LorentzProductInput(constraint: Constraint) -> View {
|
|||||||
|
|
||||||
// a list item that shows a constraint in an outline view of an element
|
// a list item that shows a constraint in an outline view of an element
|
||||||
#[component(inline_props)]
|
#[component(inline_props)]
|
||||||
fn ConstraintOutlineItem(constraint_key: ConstraintKey, element_key: ElementKey) -> View {
|
fn ConstraintOutlineItem(constraint_key: usize, element_key: usize) -> View {
|
||||||
let state = use_context::<AppState>();
|
let state = use_context::<AppState>();
|
||||||
let assembly = &state.assembly;
|
let assembly = &state.assembly;
|
||||||
let constraint = assembly.constraints.with(|csts| csts[constraint_key].clone());
|
let constraint = assembly.constraints.with(|csts| csts[constraint_key].clone());
|
||||||
let other_subject = if constraint.subjects.0 == element_key {
|
let other_arg = if constraint.args.0 == element_key {
|
||||||
constraint.subjects.1
|
constraint.args.1
|
||||||
} else {
|
} else {
|
||||||
constraint.subjects.0
|
constraint.args.0
|
||||||
};
|
};
|
||||||
let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone());
|
let other_arg_label = assembly.elements.with(|elts| elts[other_arg].label.clone());
|
||||||
let class = constraint.lorentz_prod_valid.map(
|
let class = constraint.rep_valid.map(
|
||||||
|&lorentz_prod_valid| if lorentz_prod_valid { "constraint" } else { "constraint invalid" }
|
|&rep_valid| if rep_valid { "cst" } else { "cst invalid" }
|
||||||
);
|
);
|
||||||
view! {
|
view! {
|
||||||
li(class=class.get()) {
|
li(class=class.get()) {
|
||||||
input(r#type="checkbox", bind:checked=constraint.active)
|
input(r#type="checkbox", bind:checked=constraint.active)
|
||||||
div(class="constraint-label") { (other_subject_label) }
|
div(class="cst-label") { (other_arg_label) }
|
||||||
LorentzProductInput(constraint=constraint)
|
LorentzProductInput(constraint=constraint)
|
||||||
div(class="status")
|
div(class="status")
|
||||||
}
|
}
|
||||||
@ -58,13 +58,13 @@ fn ConstraintOutlineItem(constraint_key: ConstraintKey, element_key: ElementKey)
|
|||||||
|
|
||||||
// a list item that shows an element in an outline view of an assembly
|
// a list item that shows an element in an outline view of an assembly
|
||||||
#[component(inline_props)]
|
#[component(inline_props)]
|
||||||
fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
|
fn ElementOutlineItem(key: usize, element: assembly::Element) -> View {
|
||||||
let state = use_context::<AppState>();
|
let state = use_context::<AppState>();
|
||||||
let class = state.selection.map(
|
let class = state.selection.map(
|
||||||
move |sel| if sel.contains(&key) { "selected" } else { "" }
|
move |sel| if sel.contains(&key) { "selected" } else { "" }
|
||||||
);
|
);
|
||||||
let label = element.label.clone();
|
let label = element.label.clone();
|
||||||
let rep_components = element.representation.map(
|
let rep_components = element.rep.map(
|
||||||
|rep| rep.iter().map(
|
|rep| rep.iter().map(
|
||||||
|u| format!("{:.3}", u).replace("-", "\u{2212}")
|
|u| format!("{:.3}", u).replace("-", "\u{2212}")
|
||||||
).collect()
|
).collect()
|
||||||
@ -115,11 +115,11 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
div(
|
div(
|
||||||
class="element-switch",
|
class="elt-switch",
|
||||||
on:click=|event: MouseEvent| event.stop_propagation()
|
on:click=|event: MouseEvent| event.stop_propagation()
|
||||||
)
|
)
|
||||||
div(
|
div(
|
||||||
class="element",
|
class="elt",
|
||||||
on:click={
|
on:click={
|
||||||
move |event: MouseEvent| {
|
move |event: MouseEvent| {
|
||||||
if event.shift_key() {
|
if event.shift_key() {
|
||||||
@ -139,8 +139,8 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
div(class="element-label") { (label) }
|
div(class="elt-label") { (label) }
|
||||||
div(class="element-representation") {
|
div(class="elt-rep") {
|
||||||
Indexed(
|
Indexed(
|
||||||
list=rep_components,
|
list=rep_components,
|
||||||
view=|coord_str| view! {
|
view=|coord_str| view! {
|
||||||
|
@ -41,25 +41,3 @@ I will have to work out formulas for the Euclidean distance between two entities
|
|||||||
In this vein, it seems as though if J1 and J2 are the reps of two points, then Q(J1,J2) = d^2/2. So then the sphere centered at J1 through J2 is (J1-(2Q(J1,J2),0,0,0,0))/sqrt(2Q(J1,J2)). Ugh has a sqrt in it. Similarly for sphere centered at J3 through J2, (J3-(2Q(J3,J2),0000))/sqrt(2Q(J3,J2)). J1,J2,J3 are collinear if these spheres are tangent, i.e. if those vectors have Q-inner-product 1, which is to say Q(J1,J3) - Q(J1,J2) - Q(J3,J2) = 2sqrt(Q(J1,J2)Q(J2,J3)). But maybe that's not the simplest way of putting it. After all, we can just say that the cross-product of the two differences is 0; that has no square roots in it.
|
In this vein, it seems as though if J1 and J2 are the reps of two points, then Q(J1,J2) = d^2/2. So then the sphere centered at J1 through J2 is (J1-(2Q(J1,J2),0,0,0,0))/sqrt(2Q(J1,J2)). Ugh has a sqrt in it. Similarly for sphere centered at J3 through J2, (J3-(2Q(J3,J2),0000))/sqrt(2Q(J3,J2)). J1,J2,J3 are collinear if these spheres are tangent, i.e. if those vectors have Q-inner-product 1, which is to say Q(J1,J3) - Q(J1,J2) - Q(J3,J2) = 2sqrt(Q(J1,J2)Q(J2,J3)). But maybe that's not the simplest way of putting it. After all, we can just say that the cross-product of the two differences is 0; that has no square roots in it.
|
||||||
|
|
||||||
One conceivable way to canonicalize lines is to use the *perpendicular* plane that goes through the origin, that's uniquely defined, and anyway just amounts to I = (0,0,d) where d is the ordinary direction vector of the line; and a point J in that plane that the line goes through, which just amounts to J=(r^2,1,E) with Q(I,J) = 0, i.e. E\dot d = 0. It's also the point on the line closest to the origin. The reason that we don't usually use that point as the companion to the direction vector is that the resulting set of six coordinates is not homogeneous. But here that's not an issue, since we have our standard point coordinates and plane coordinates; and for a plane through the origin, only two of the direction coordinates are really free, and then we have the one dot-product relation, so only two of the point coordinates are really free, giving us the correct dimensionality of 4 for the set of lines. So in some sense this says that we could take naively as coordinates for a line the projection of the unit direction vector to the xy plane and the projection of the line's closest point to the origin to the xy plane. That doesn't seem to have any weird gimbal locks or discontinuities or anything. And with these coordinates, you can test if the point E=x,y,z is on the line (dx,dy,cx,cy) by extending (dx,dy) to d via dz = sqrt(1-dx^2 - dy^2), extending (cx,cy) to c by determining cz via d\dot c = 0, and then checking if d\cross(E-c) = 0. And you can see if two lines are parallel just by checking if they have the same direction vector, and if not, you can see if they are coplanar by projecting both of their closest points perpendicularly onto the line in the direction of the cross product of their directions, and if the projections match they are coplanar.
|
One conceivable way to canonicalize lines is to use the *perpendicular* plane that goes through the origin, that's uniquely defined, and anyway just amounts to I = (0,0,d) where d is the ordinary direction vector of the line; and a point J in that plane that the line goes through, which just amounts to J=(r^2,1,E) with Q(I,J) = 0, i.e. E\dot d = 0. It's also the point on the line closest to the origin. The reason that we don't usually use that point as the companion to the direction vector is that the resulting set of six coordinates is not homogeneous. But here that's not an issue, since we have our standard point coordinates and plane coordinates; and for a plane through the origin, only two of the direction coordinates are really free, and then we have the one dot-product relation, so only two of the point coordinates are really free, giving us the correct dimensionality of 4 for the set of lines. So in some sense this says that we could take naively as coordinates for a line the projection of the unit direction vector to the xy plane and the projection of the line's closest point to the origin to the xy plane. That doesn't seem to have any weird gimbal locks or discontinuities or anything. And with these coordinates, you can test if the point E=x,y,z is on the line (dx,dy,cx,cy) by extending (dx,dy) to d via dz = sqrt(1-dx^2 - dy^2), extending (cx,cy) to c by determining cz via d\dot c = 0, and then checking if d\cross(E-c) = 0. And you can see if two lines are parallel just by checking if they have the same direction vector, and if not, you can see if they are coplanar by projecting both of their closest points perpendicularly onto the line in the direction of the cross product of their directions, and if the projections match they are coplanar.
|
||||||
|
|
||||||
#### Engine Conventions
|
|
||||||
|
|
||||||
The coordinate conventions used in the engine are different from the ones used in these notes. Marking the engine vectors and coordinates with $'$, we have
|
|
||||||
$$I' = (x', y', z', b', c'),$$
|
|
||||||
where
|
|
||||||
$$
|
|
||||||
\begin{align*}
|
|
||||||
x' & = x & b' & = b/2 \\
|
|
||||||
y' & = y & c' & = c/2. \\
|
|
||||||
z' & = z
|
|
||||||
\end{align*}
|
|
||||||
$$
|
|
||||||
The engine uses the quadratic form $Q' = -Q$, which is expressed in engine coordinates as
|
|
||||||
$$Q'(I'_1, I'_2) = x'_1 x'_2 + y'_1 y'_2 + z'_1 z'_2 - 2(b'_1c'_2 + c'_1 b'_2).$$
|
|
||||||
In the `engine` module, the matrix of $Q'$ is encoded in the lazy static variable `Q`.
|
|
||||||
|
|
||||||
In the engine's coordinate conventions, a sphere with radius $r > 0$ centered on $P = (P_x, P_y, P_z)$ is represented by the vector
|
|
||||||
$$I'_s = \left(\frac{P_x}{r}, \frac{P_y}{r}, \frac{P_z}{r}, \frac1{2r}, \frac{\|P\|^2 - r^2}{2r}\right),$$
|
|
||||||
which has the normalization $Q'(I'_s, I'_s) = 1$. The point $P$ is represented by the vector
|
|
||||||
$$I'_P = \left(P_x, P_y, P_z, \frac{1}{2}, \frac{\|P\|^2}{2}\right).$$
|
|
||||||
In the `engine` module, these formulas are encoded in the `sphere` and `point` functions.
|
|
Loading…
Reference in New Issue
Block a user