Test curvature drift during position nudging

The test fails, as expected, if you disable sphere normalization by
making `Sphere::normalize_mut_rep` do nothing. Since the tests aren't
built for WebAssembly, we have to replace `console::log` with
`console_log!` in all of the functions they use. We'll eventually want
to do this replacement everywhere.
This commit is contained in:
Aaron Fenyes 2025-05-20 17:26:23 -07:00
parent f4e5c34fde
commit 0cfdd59e23
3 changed files with 76 additions and 38 deletions

View file

@ -46,6 +46,9 @@ features = [
dyna3 = { path = ".", default-features = false, features = ["dev"] } dyna3 = { path = ".", default-features = false, features = ["dev"] }
wasm-bindgen-test = "0.3.34" wasm-bindgen-test = "0.3.34"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(sycamore_force_ssr)"] }
[profile.release] [profile.release]
opt-level = "s" # optimize for small code size opt-level = "s" # optimize for small code size
debug = true # include debug symbols debug = true # include debug symbols

View file

@ -623,9 +623,7 @@ impl Assembly {
create_effect(move || { create_effect(move || {
/* DEBUG */ /* DEBUG */
// log the regulator update // log the regulator update
console::log_1(&JsValue::from( console_log!("Updated regulator with subjects {:?}", regulator.subjects());
format!("Updated regulator with subjects {:?}", regulator.subjects())
));
if regulator.try_activate() { if regulator.try_activate() {
self_for_effect.realize(); self_for_effect.realize();
@ -634,10 +632,10 @@ impl Assembly {
/* DEBUG */ /* DEBUG */
// print an updated list of regulators // print an updated list of regulators
console::log_1(&JsValue::from("Regulators:")); console_log!("Regulators:");
self.regulators.with_untracked(|regs| { self.regulators.with_untracked(|regs| {
for reg in regs.into_iter() { for reg in regs.into_iter() {
console::log_1(&JsValue::from(format!( console_log!(
" {:?}: {}", " {:?}: {}",
reg.subjects(), reg.subjects(),
reg.set_point().with_untracked( reg.set_point().with_untracked(
@ -650,7 +648,7 @@ impl Assembly {
} }
} }
) )
))); );
} }
}); });
} }
@ -681,19 +679,11 @@ impl Assembly {
/* DEBUG */ /* DEBUG */
// log the Gram matrix // log the Gram matrix
console::log_1(&JsValue::from("Gram matrix:")); console_log!("Gram matrix:\n{}", problem.gram);
problem.gram.log_to_console();
/* DEBUG */ /* DEBUG */
// log the initial configuration matrix // log the initial configuration matrix
console::log_1(&JsValue::from("Old configuration:")); console_log!("Old configuration:{:>8.3}", problem.guess);
for j in 0..problem.guess.nrows() {
let mut row_str = String::new();
for k in 0..problem.guess.ncols() {
row_str.push_str(format!(" {:>8.3}", problem.guess[(j, k)]).as_str());
}
console::log_1(&JsValue::from(row_str));
}
// look for a configuration with the given Gram matrix // look for a configuration with the given Gram matrix
let (config, tangent, success, history) = realize_gram( let (config, tangent, success, history) = realize_gram(
@ -702,16 +692,14 @@ impl Assembly {
/* DEBUG */ /* DEBUG */
// report the outcome of the search // report the outcome of the search
console::log_1(&JsValue::from( if success {
if success { console_log!("Target accuracy achieved!")
"Target accuracy achieved!" } else {
} else { console_log!("Failed to reach target accuracy")
"Failed to reach target accuracy" }
} console_log!("Steps: {}", history.scaled_loss.len() - 1);
)); console_log!("Loss: {}", *history.scaled_loss.last().unwrap());
console::log_2(&JsValue::from("Steps:"), &JsValue::from(history.scaled_loss.len() - 1)); console_log!("Tangent dimension: {}", tangent.dim());
console::log_2(&JsValue::from("Loss:"), &JsValue::from(*history.scaled_loss.last().unwrap()));
console::log_2(&JsValue::from("Tangent dimension:"), &JsValue::from(tangent.dim()));
if success { if success {
// read out the solution // read out the solution
@ -804,9 +792,7 @@ impl Assembly {
elt.normalize_mut_rep(rep); elt.normalize_mut_rep(rep);
}, },
None => { None => {
console::log_1(&JsValue::from( console_log!("No velocity to unpack for fresh element \"{}\"", elt.id())
format!("No velocity to unpack for fresh element \"{}\"", elt.id())
))
} }
}; };
}); });
@ -823,6 +809,8 @@ impl Assembly {
mod tests { mod tests {
use super::*; use super::*;
use crate::engine;
#[test] #[test]
#[should_panic(expected = "Sphere \"sphere\" should be indexed before writing problem data")] #[should_panic(expected = "Sphere \"sphere\" should be indexed before writing problem data")]
fn unindexed_element_test() { fn unindexed_element_test() {
@ -848,4 +836,50 @@ mod tests {
}.pose(&mut ConstraintProblem::new(2)); }.pose(&mut ConstraintProblem::new(2));
}); });
} }
#[test]
fn curvature_drift_test() {
const INITIAL_RADIUS: f64 = 0.25;
let _ = create_root(|| {
// set up an assembly containing a single sphere centered at the
// origin
let assembly = Assembly::new();
let sphere_id = "sphere0";
let _ = assembly.try_insert_element(
// we create the sphere by hand for two reasons: to choose the
// curvature (which can affect drift rate) and to make the test
// independent of `Sphere::default`
Sphere::new(
String::from(sphere_id),
String::from("Sphere 0"),
[0.75_f32, 0.75_f32, 0.75_f32],
engine::sphere(0.0, 0.0, 0.0, INITIAL_RADIUS)
)
);
// nudge the sphere repeatedly along the `z` axis
const STEP_SIZE: f64 = 0.0025;
const STEP_CNT: usize = 400;
let sphere = assembly.elements_by_id.with(|elts_by_id| elts_by_id[sphere_id].clone());
let velocity = DVector::from_column_slice(&[0.0, 0.0, STEP_SIZE, 0.0]);
for _ in 0..STEP_CNT {
assembly.deform(
vec![
ElementMotion {
element: sphere.clone(),
velocity: velocity.as_view()
}
]
);
}
// check how much the sphere's curvature has drifted
const INITIAL_HALF_CURV: f64 = 0.5 / INITIAL_RADIUS;
const DRIFT_TOL: f64 = 0.015;
let final_half_curv = sphere.representation().with_untracked(
|rep| rep[Sphere::CURVATURE_COMPONENT]
);
assert!((final_half_curv / INITIAL_HALF_CURV - 1.0).abs() < DRIFT_TOL);
});
}
} }

View file

@ -1,5 +1,6 @@
use lazy_static::lazy_static; use lazy_static::lazy_static;
use nalgebra::{Const, DMatrix, DVector, DVectorView, Dyn, SymmetricEigen}; use nalgebra::{Const, DMatrix, DVector, DVectorView, Dyn, SymmetricEigen};
use std::fmt::{Display, Error, Formatter};
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
// --- elements --- // --- elements ---
@ -109,15 +110,6 @@ impl PartialMatrix {
} }
} }
/* DEBUG */
pub fn log_to_console(&self) {
for &MatrixEntry { index: (row, col), value } in self {
console::log_1(&JsValue::from(
format!(" {} {} {}", row, col, value)
));
}
}
fn freeze(&self, a: &DMatrix<f64>) -> DMatrix<f64> { fn freeze(&self, a: &DMatrix<f64>) -> DMatrix<f64> {
let mut result = a.clone(); let mut result = a.clone();
for &MatrixEntry { index, value } in self { for &MatrixEntry { index, value } in self {
@ -143,6 +135,15 @@ impl PartialMatrix {
} }
} }
impl Display for PartialMatrix {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
for &MatrixEntry { index: (row, col), value } in self {
writeln!(f, " {row} {col} {value}")?;
}
Ok(())
}
}
impl IntoIterator for PartialMatrix { impl IntoIterator for PartialMatrix {
type Item = MatrixEntry; type Item = MatrixEntry;
type IntoIter = std::vec::IntoIter<Self::Item>; type IntoIter = std::vec::IntoIter<Self::Item>;