Try projecting with the uniform inner product

This projection should be invariant under Euclidean motions. For now, we
assume that all of the elements are spheres.
This commit is contained in:
Aaron Fenyes 2025-01-17 00:58:35 -08:00
parent 22870342f3
commit 03da831c9a
3 changed files with 97 additions and 33 deletions

View File

@ -5,7 +5,7 @@ use std::{collections::BTreeSet, sync::atomic::{AtomicU64, Ordering}};
use sycamore::prelude::*; use sycamore::prelude::*;
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
use crate::engine::{realize_gram, ConfigSubspace, PartialMatrix, Q}; use crate::engine::{realize_gram, local_unif_to_std, ConfigSubspace, PartialMatrix, Q};
// 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 constraints
pub type ElementKey = usize; pub type ElementKey = usize;
@ -120,6 +120,7 @@ pub struct Constraint {
pub active: Signal<bool> pub active: Signal<bool>
} }
// the velocity is expressed in uniform coordinates
pub struct ElementMotion<'a> { pub struct ElementMotion<'a> {
pub key: ElementKey, pub key: ElementKey,
pub velocity: DVectorView<'a, f64> pub velocity: DVectorView<'a, f64>
@ -359,7 +360,14 @@ impl Assembly {
// this element didn't have a column index when we started, so // this element didn't have a column index when we started, so
// by invariant (2), it's unconstrained // by invariant (2), it's unconstrained
let mut target_column = motion_proj.column_mut(column_index); let mut target_column = motion_proj.column_mut(column_index);
target_column += elt_motion.velocity; let unif_to_std = self.elements.with_untracked(
|elts| {
elts[elt_motion.key].representation.with_untracked(
|rep| local_unif_to_std(rep.as_view())
)
}
);
target_column += unif_to_std * elt_motion.velocity;
} }
} }

View File

@ -130,6 +130,8 @@ pub fn Display() -> View {
let translate_pos_y = create_signal(0.0); let translate_pos_y = create_signal(0.0);
let translate_neg_z = create_signal(0.0); let translate_neg_z = create_signal(0.0);
let translate_pos_z = create_signal(0.0); let translate_pos_z = create_signal(0.0);
let shrink_neg = create_signal(0.0);
let shrink_pos = create_signal(0.0);
// change listener // change listener
let scene_changed = create_signal(true); let scene_changed = create_signal(true);
@ -164,6 +166,7 @@ pub fn Display() -> View {
// manipulation // manipulation
const TRANSLATION_SPEED: f64 = 0.15; // in length units per second const TRANSLATION_SPEED: f64 = 0.15; // in length units per second
const SHRINKING_SPEED: f64 = 0.15; // in length units per second
// display parameters // display parameters
const OPACITY: f32 = 0.5; /* SCAFFOLDING */ const OPACITY: f32 = 0.5; /* SCAFFOLDING */
@ -292,6 +295,8 @@ pub fn Display() -> View {
let translate_pos_y_val = translate_pos_y.get(); let translate_pos_y_val = translate_pos_y.get();
let translate_neg_z_val = translate_neg_z.get(); let translate_neg_z_val = translate_neg_z.get();
let translate_pos_z_val = translate_pos_z.get(); let translate_pos_z_val = translate_pos_z.get();
let shrink_neg_val = shrink_neg.get();
let shrink_pos_val = shrink_pos.get();
// update the assembly's orientation // update the assembly's orientation
let ang_vel = { let ang_vel = {
@ -323,24 +328,27 @@ pub fn Display() -> View {
let sel = state.selection.with( let sel = state.selection.with(
|sel| *sel.into_iter().next().unwrap() |sel| *sel.into_iter().next().unwrap()
); );
let rep = state.assembly.elements.with_untracked(
|elts| elts[sel].representation.get_clone_untracked()
);
let translate_x = translate_pos_x_val - translate_neg_x_val; let translate_x = translate_pos_x_val - translate_neg_x_val;
let translate_y = translate_pos_y_val - translate_neg_y_val; let translate_y = translate_pos_y_val - translate_neg_y_val;
let translate_z = translate_pos_z_val - translate_neg_z_val; let translate_z = translate_pos_z_val - translate_neg_z_val;
if translate_x != 0.0 || translate_y != 0.0 || translate_z != 0.0 { let shrink = shrink_pos_val - shrink_neg_val;
let vel_field = { let translating =
let u = Vector3::new(translate_x, translate_y, translate_z).normalize(); translate_x != 0.0
DMatrix::from_column_slice(5, 5, &[ || translate_y != 0.0
0.0, 0.0, 0.0, 0.0, u[0], || translate_z != 0.0;
0.0, 0.0, 0.0, 0.0, u[1], if translating || shrink != 0.0 {
0.0, 0.0, 0.0, 0.0, u[2], let elt_motion = {
2.0*u[0], 2.0*u[1], 2.0*u[2], 0.0, 0.0, let u = if translating {
0.0, 0.0, 0.0, 0.0, 0.0 TRANSLATION_SPEED * Vector3::new(
]) translate_x, translate_y, translate_z
).normalize()
} else {
Vector3::zeros()
};
time_step * DVector::from_column_slice(
&[u[0], u[1], u[2], SHRINKING_SPEED * shrink, 0.0]
)
}; };
let elt_motion: DVector<f64> = time_step * TRANSLATION_SPEED * vel_field * rep;
assembly_for_raf.deform( assembly_for_raf.deform(
vec![ vec![
ElementMotion { ElementMotion {
@ -501,6 +509,8 @@ pub fn Display() -> View {
"s" | "S" if shift => translate_pos_z.set(value), "s" | "S" if shift => translate_pos_z.set(value),
"w" | "W" => translate_pos_y.set(value), "w" | "W" => translate_pos_y.set(value),
"s" | "S" => translate_neg_y.set(value), "s" | "S" => translate_neg_y.set(value),
"]" | "}" => shrink_neg.set(value),
"[" | "{" => shrink_pos.set(value),
_ => manipulating = false _ => manipulating = false
}; };
if manipulating { if manipulating {

View File

@ -90,32 +90,33 @@ impl PartialMatrix {
#[derive(Clone)] #[derive(Clone)]
pub struct ConfigSubspace { pub struct ConfigSubspace {
assembly_dim: usize, assembly_dim: usize,
basis: Vec<DMatrix<f64>> basis_std: Vec<DMatrix<f64>>,
basis_proj: Vec<DMatrix<f64>>
} }
impl ConfigSubspace { impl ConfigSubspace {
pub fn zero(assembly_dim: usize) -> ConfigSubspace { pub fn zero(assembly_dim: usize) -> ConfigSubspace {
ConfigSubspace { ConfigSubspace {
assembly_dim: assembly_dim, assembly_dim: assembly_dim,
basis: Vec::new() basis_proj: Vec::new(),
basis_std: Vec::new()
} }
} }
// approximate the kernel of a symmetric endomorphism of the configuration // approximate the kernel of a symmetric endomorphism of the configuration
// space for `assembly_dim` elements. we consider an eigenvector to be part // space for `assembly_dim` elements. we consider an eigenvector to be part
// of the kernel if its eigenvalue is smaller than the constant `THRESHOLD` // of the kernel if its eigenvalue is smaller than the constant `THRESHOLD`
fn symmetric_kernel(a: DMatrix<f64>, assembly_dim: usize) -> ConfigSubspace { fn symmetric_kernel(a: DMatrix<f64>, proj_to_std: DMatrix<f64>, assembly_dim: usize) -> ConfigSubspace {
// find a basis for the kernel, expressed in the standard coordinates
const ELEMENT_DIM: usize = 5; const ELEMENT_DIM: usize = 5;
const THRESHOLD: f64 = 1.0e-4; const THRESHOLD: f64 = 1.0e-4;
let eig = SymmetricEigen::new(a); let eig = SymmetricEigen::new(a);
let eig_vecs = eig.eigenvectors.column_iter(); let eig_vecs = eig.eigenvectors.column_iter();
let eig_pairs = eig.eigenvalues.iter().zip(eig_vecs); let eig_pairs = eig.eigenvalues.iter().zip(eig_vecs);
let basis = eig_pairs.filter_map( let basis_std = DMatrix::from_columns(
|(λ, v)| (λ.abs() < THRESHOLD).then_some( eig_pairs.filter_map(
Into::<DMatrix<f64>>::into( |(λ, v)| (λ.abs() < THRESHOLD).then_some(v)
v.reshape_generic(Dyn(ELEMENT_DIM), Dyn(assembly_dim)) ).collect::<Vec<_>>().as_slice()
)
)
); );
/* DEBUG */ /* DEBUG */
@ -125,30 +126,50 @@ impl ConfigSubspace {
format!("Eigenvalues used to find kernel:{}", eig.eigenvalues) format!("Eigenvalues used to find kernel:{}", eig.eigenvalues)
)); ));
// express the basis in the projection coordinates
let basis_proj = proj_to_std.clone().qr().solve(&basis_std).unwrap();
// orthonormalize the basis with respect to the projection inner product
let basis_proj_orth = basis_proj.qr().q();
let basis_std_orth = proj_to_std * &basis_proj_orth;
console::log_1(&JsValue::from(
format!("Basis in projection coordinates:{}", basis_proj_orth)
));
ConfigSubspace { ConfigSubspace {
assembly_dim: assembly_dim, assembly_dim: assembly_dim,
basis: basis.collect() basis_std: basis_std_orth.column_iter().map(
|v| Into::<DMatrix<f64>>::into(
v.reshape_generic(Dyn(ELEMENT_DIM), Dyn(assembly_dim))
)
).collect(),
basis_proj: basis_proj_orth.column_iter().map(
|v| Into::<DMatrix<f64>>::into(
v.reshape_generic(Dyn(ELEMENT_DIM), Dyn(assembly_dim))
)
).collect()
} }
} }
pub fn dim(&self) -> usize { pub fn dim(&self) -> usize {
self.basis.len() self.basis_std.len()
} }
pub fn assembly_dim(&self) -> usize { pub fn assembly_dim(&self) -> usize {
self.assembly_dim self.assembly_dim
} }
// find the projection onto this subspace, with respect to the Euclidean // find the projection onto this subspace of the motion where the element
// inner product, of the motion where the element with the given column // with the given column index has velocity `v`. the velocity is given in
// index has velocity `v` // projection coordinates, and the projection is done with respect to the
// projection inner product
pub fn proj(&self, v: &DVectorView<f64>, column_index: usize) -> DMatrix<f64> { pub fn proj(&self, v: &DVectorView<f64>, column_index: usize) -> DMatrix<f64> {
if self.dim() == 0 { if self.dim() == 0 {
const ELEMENT_DIM: usize = 5; const ELEMENT_DIM: usize = 5;
DMatrix::zeros(ELEMENT_DIM, self.assembly_dim) DMatrix::zeros(ELEMENT_DIM, self.assembly_dim)
} else { } else {
self.basis.iter().map( self.basis_proj.iter().zip(self.basis_std.iter()).map(
|b| b.column(column_index).dot(&v) * b |(b_proj, b_std)| b_proj.column(column_index).dot(&v) * b_std
).sum() ).sum()
} }
} }
@ -215,6 +236,21 @@ fn basis_matrix(index: (usize, usize), nrows: usize, ncols: usize) -> DMatrix<f6
result result
} }
// given a spacelike unit vector `v`, which represents a sphere, build the basis
// for the configuration space given by the three unit translation motions of
// the sphere, the unit shrinking motion of the sphere, and `v`
pub fn local_unif_to_std(v: DVectorView<f64>) -> DMatrix<f64> {
const ELEMENT_DIM: usize = 5;
let curv = 2.0*v[3];
DMatrix::from_column_slice(ELEMENT_DIM, ELEMENT_DIM, &[
curv, 0.0, 0.0, 0.0, v[0],
0.0, curv, 0.0, 0.0, v[1],
0.0, 0.0, curv, 0.0, v[2],
curv*v[0], curv*v[1], curv*v[2], curv*v[3], curv*v[4] + 1.0,
v[0], v[1], v[2], v[3], v[4]
])
}
// use backtracking line search to find a better configuration // use backtracking line search to find a better configuration
fn seek_better_config( fn seek_better_config(
gram: &PartialMatrix, gram: &PartialMatrix,
@ -344,7 +380,17 @@ pub fn realize_gram(
} }
let success = state.loss < tol; let success = state.loss < tol;
let tangent = if success { let tangent = if success {
ConfigSubspace::symmetric_kernel(hess, assembly_dim) // express the uniform basis in the standard basis
let mut unif_to_std = DMatrix::<f64>::zeros(total_dim, total_dim);
for n in 0..assembly_dim {
let block_start = element_dim * n;
unif_to_std
.view_mut((block_start, block_start), (element_dim, element_dim))
.copy_from(&local_unif_to_std(state.config.column(n)));
}
// find the kernel of the Hessian. give it the uniform inner product
ConfigSubspace::symmetric_kernel(hess, unif_to_std, assembly_dim)
} else { } else {
ConfigSubspace::zero(assembly_dim) ConfigSubspace::zero(assembly_dim)
}; };