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:
parent
22870342f3
commit
03da831c9a
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user