feat: Point coordinate regulators #118
2 changed files with 33 additions and 54 deletions
|
@ -31,14 +31,11 @@ The latest prototype is in the folder `app-proto`. It includes both a user inter
|
|||
3. Call `rustup target add wasm32-unknown-unknown` to add the [most generic 32-bit WebAssembly target](https://doc.rust-lang.org/nightly/rustc/platform-support/wasm32-unknown-unknown.html)
|
||||
4. Call `cargo install wasm-pack` to install the [WebAssembly toolchain](https://rustwasm.github.io/docs/wasm-pack/)
|
||||
5. Call `cargo install trunk` to install the [Trunk](https://trunkrs.dev/) web-build tool
|
||||
- In the future, `trunk` can be updated with the same command. You may
|
||||
need the `--locked` flag if your ambient version of `rustc` does not
|
||||
match that required by `trunk`.
|
||||
- In the future, `trunk` can be updated with the same command. You may need the `--locked` flag if your ambient version of `rustc` does not match that required by `trunk`.
|
||||
6. Add the `.cargo/bin` folder in your home directory to your executable search path
|
||||
- This lets you call Trunk, and other tools installed by Cargo, without specifying their paths
|
||||
- On POSIX systems, the search path is stored in the `PATH` environment variable
|
||||
- Alternatively, if you don't want to adjust your `PATH`, you can install
|
||||
`trunk` in another directory `DIR` via `cargo install --root DIR trunk`
|
||||
- Alternatively, if you don't want to adjust your `PATH`, you can install `trunk` in another directory `DIR` via `cargo install --root DIR trunk`
|
||||
|
||||
### Play with the prototype
|
||||
|
||||
|
|
|
@ -86,6 +86,14 @@ impl Ord for dyn Serial {
|
|||
}
|
||||
}
|
||||
|
||||
// Small helper function to generate consistent errors when there
|
||||
// are indexing issues in a ProblemPoser
|
||||
fn indexing_error(item: &str, name: &str, actor: &str) -> String {
|
||||
format!(
|
||||
"{item} \"{name}\" must be indexed before {actor} writes problem data"
|
||||
)
|
||||
}
|
||||
|
||||
pub trait ProblemPoser {
|
||||
fn pose(&self, problem: &mut ConstraintProblem);
|
||||
}
|
||||
|
@ -248,19 +256,12 @@ impl Serial for Sphere {
|
|||
}
|
||||
}
|
||||
|
||||
fn index_message(thing: &str, name: &str, actor: &str) -> String {
|
||||
format!(
|
||||
"{thing} \"{name}\" must be indexed before {actor} writes problem data"
|
||||
)
|
||||
}
|
||||
|
||||
impl ProblemPoser for Sphere {
|
||||
fn pose(&self, problem: &mut ConstraintProblem) {
|
||||
let index = self.column_index().expect(
|
||||
index_message("Sphere", &self.id, "it").as_str());
|
||||
indexing_error("Sphere", &self.id, "it").as_str());
|
||||
problem.gram.push_sym(index, index, 1.0);
|
||||
problem.guess.set_column(
|
||||
index, &self.representation.get_clone_untracked());
|
||||
problem.guess.set_column(index, &self.representation.get_clone_untracked());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -363,11 +364,10 @@ impl Serial for Point {
|
|||
impl ProblemPoser for Point {
|
||||
fn pose(&self, problem: &mut ConstraintProblem) {
|
||||
let index = self.column_index().expect(
|
||||
index_message("Point", &self.id, "it").as_str());
|
||||
indexing_error("Point", &self.id, "it").as_str());
|
||||
problem.gram.push_sym(index, index, 0.0);
|
||||
problem.frozen.push(Self::WEIGHT_COMPONENT, index, 0.5);
|
||||
problem.guess.set_column(
|
||||
index, &self.representation.get_clone_untracked());
|
||||
problem.guess.set_column(index, &self.representation.get_clone_untracked());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -412,8 +412,7 @@ pub struct InversiveDistanceRegulator {
|
|||
|
||||
impl InversiveDistanceRegulator {
|
||||
pub fn new(subjects: [Rc<dyn Element>; 2]) -> Self {
|
||||
let representations = subjects.each_ref().map(
|
||||
|subj| subj.representation());
|
||||
let representations = subjects.each_ref().map(|subj| subj.representation());
|
||||
let measurement = create_memo(move || {
|
||||
representations[0].with(|rep_0|
|
||||
representations[1].with(|rep_1|
|
||||
|
@ -455,7 +454,7 @@ impl ProblemPoser for InversiveDistanceRegulator {
|
|||
if let Some(val) = set_pt.value {
|
||||
let [row, col] = self.subjects.each_ref().map(
|
||||
|subj| subj.column_index().expect(
|
||||
index_message("Subject", subj.id(),
|
||||
indexing_error("Subject", subj.id(),
|
||||
"inversive distance regulator").as_str())
|
||||
);
|
||||
problem.gram.push_sym(row, col, val);
|
||||
|
@ -509,7 +508,7 @@ impl ProblemPoser for HalfCurvatureRegulator {
|
|||
self.set_point.with_untracked(|set_pt| {
|
||||
if let Some(val) = set_pt.value {
|
||||
let col = self.subject.column_index().expect(
|
||||
index_message("Subject", &self.subject.id,
|
||||
indexing_error("Subject", &self.subject.id,
|
||||
"half-curvature regulator").as_str());
|
||||
problem.frozen.push(Sphere::CURVATURE_COMPONENT, col, val);
|
||||
}
|
||||
|
@ -546,9 +545,7 @@ impl PointCoordinateRegulator {
|
|||
move |rep| rep[axis as usize]
|
||||
);
|
||||
let set_point = create_signal(SpecifiedValue::from_empty_spec());
|
||||
Self {
|
||||
subject, axis, measurement, set_point, serial: Self::next_serial()
|
||||
}
|
||||
Self { subject, axis, measurement, set_point, serial: Self::next_serial() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -567,7 +564,7 @@ impl ProblemPoser for PointCoordinateRegulator {
|
|||
self.set_point.with_untracked(|set_pt| {
|
||||
if let Some(val) = set_pt.value {
|
||||
let col = self.subject.column_index().expect(
|
||||
index_message("Subject", &self.subject.id,
|
||||
indexing_error("Subject", &self.subject.id,
|
||||
"point-coordinate regulator").as_str());
|
||||
problem.frozen.push(self.axis as usize, col, val);
|
||||
// If all three of the subject's spatial coordinates have been
|
||||
|
@ -583,9 +580,7 @@ impl ProblemPoser for PointCoordinateRegulator {
|
|||
if nset == Axis::CARDINALITY {
|
||||
let [x, y, z] = coords;
|
||||
problem.frozen.push(
|
||||
Point::NORM_COMPONENT,
|
||||
col,
|
||||
point(x,y,z)[Point::NORM_COMPONENT]);
|
||||
Point::NORM_COMPONENT, col, point(x,y,z)[Point::NORM_COMPONENT]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -684,8 +679,7 @@ impl Assembly {
|
|||
let id = elt.id().clone();
|
||||
let elt_rc = Rc::new(elt);
|
||||
self.elements.update(|elts| elts.insert(elt_rc.clone()));
|
||||
self.elements_by_id.update(
|
||||
|elts_by_id| elts_by_id.insert(id, elt_rc.clone()));
|
||||
self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, elt_rc.clone()));
|
||||
|
||||
// create and insert the element's default regulators
|
||||
for reg in elt_rc.default_regulators() {
|
||||
|
@ -694,9 +688,7 @@ impl Assembly {
|
|||
}
|
||||
|
||||
pub fn try_insert_element(&self, elt: impl Element + 'static) -> bool {
|
||||
let can_insert = self.elements_by_id.with_untracked(
|
||||
|elts_by_id| !elts_by_id.contains_key(elt.id())
|
||||
);
|
||||
let can_insert = self.elements_by_id.with_untracked(|elts_by_id| !elts_by_id.contains_key(elt.id()));
|
||||
if can_insert {
|
||||
self.insert_element_unchecked(elt);
|
||||
}
|
||||
|
@ -761,8 +753,7 @@ impl Assembly {
|
|||
pub fn load_config(&self, config: &DMatrix<f64>) {
|
||||
for elt in self.elements.get_clone_untracked() {
|
||||
elt.representation().update(
|
||||
|rep| rep.set_column(
|
||||
0, &config.column(elt.column_index().unwrap()))
|
||||
|rep| rep.set_column(0, &config.column(elt.column_index().unwrap()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -907,8 +898,7 @@ impl Assembly {
|
|||
if column_index < realized_dim {
|
||||
// this element had a column index when we started, so by
|
||||
// invariant (1), it's reflected in the tangent space
|
||||
let mut target_columns =
|
||||
motion_proj.columns_mut(0, realized_dim);
|
||||
let mut target_columns = motion_proj.columns_mut(0, realized_dim);
|
||||
target_columns += self.tangent.with(
|
||||
|tan| tan.proj(&elt_motion.velocity, column_index)
|
||||
);
|
||||
|
@ -916,10 +906,9 @@ impl Assembly {
|
|||
// this element didn't have a column index when we started, so
|
||||
// by invariant (2), it's unconstrained
|
||||
let mut target_column = motion_proj.column_mut(column_index);
|
||||
let unif_to_std = elt_motion
|
||||
.element.representation()
|
||||
.with_untracked(
|
||||
|rep| local_unif_to_std(rep.as_view()));
|
||||
let unif_to_std = elt_motion.element.representation().with_untracked(
|
||||
|rep| local_unif_to_std(rep.as_view())
|
||||
);
|
||||
target_column += unif_to_std * elt_motion.velocity;
|
||||
}
|
||||
}
|
||||
|
@ -936,9 +925,7 @@ impl Assembly {
|
|||
elt.project_to_normalized(rep);
|
||||
},
|
||||
None => {
|
||||
console_log!(
|
||||
"No velocity to unpack for fresh element \"{}\"",
|
||||
elt.id())
|
||||
console_log!("No velocity to unpack for fresh element \"{}\"", elt.id())
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -973,15 +960,13 @@ inversive distance regulator writes problem data")]
|
|||
fn unindexed_subject_test_inversive_distance() {
|
||||
let _ = create_root(|| {
|
||||
Vectornaut marked this conversation as resolved
|
||||
let subjects = [0, 1].map(
|
||||
|k| Rc::new(Sphere::default(
|
||||
format!("sphere{k}"), k)) as Rc<dyn Element>
|
||||
|k| Rc::new(Sphere::default(format!("sphere{k}"), k)) as Rc<dyn Element>
|
||||
);
|
||||
subjects[0].set_column_index(0);
|
||||
InversiveDistanceRegulator {
|
||||
subjects: subjects,
|
||||
measurement: create_memo(|| 0.0),
|
||||
set_point: create_signal(
|
||||
SpecifiedValue::try_from("0.0".to_string()).unwrap()),
|
||||
set_point: create_signal(SpecifiedValue::try_from("0.0".to_string()).unwrap()),
|
||||
serial: InversiveDistanceRegulator::next_serial()
|
||||
}.pose(&mut ConstraintProblem::new(2));
|
||||
});
|
||||
|
@ -1010,10 +995,8 @@ inversive distance regulator writes problem data")]
|
|||
// 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]);
|
||||
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![
|
||||
|
@ -1031,8 +1014,7 @@ inversive distance regulator writes problem data")]
|
|||
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);
|
||||
assert!((final_half_curv / INITIAL_HALF_CURV - 1.0).abs() < DRIFT_TOL);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue
When I originally wrote this test, it bothered me that the identifiers
sphere0
andsphere1
were assigned but never tested. The new, more informative indexing error message makes them part of the test, which is very satisfying.