forked from StudioInfinity/dyna3
Simplify the realization triggering system (#105)
Simplifies the system that reactively triggers realizations, at the cost of removing the preconditioning step described in issue #101 and doing unnecessary realizations after certain kinds of updates. Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: StudioInfinity/dyna3#105 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
This commit is contained in:
parent
0801200210
commit
2eba80fb69
4 changed files with 48 additions and 104 deletions
|
@ -16,7 +16,6 @@ use crate::{
|
|||
components::{display::DisplayItem, outline::OutlineItem},
|
||||
engine::{
|
||||
Q,
|
||||
change_half_curvature,
|
||||
local_unif_to_std,
|
||||
point,
|
||||
project_point_to_normalized,
|
||||
|
@ -358,16 +357,6 @@ pub trait Regulator: Serial + ProblemPoser + OutlineItem {
|
|||
fn subjects(&self) -> Vec<Rc<dyn Element>>;
|
||||
fn measurement(&self) -> ReadSignal<f64>;
|
||||
fn set_point(&self) -> Signal<SpecifiedValue>;
|
||||
|
||||
// this method is used to responsively precondition the assembly for
|
||||
// realization when the regulator becomes a constraint, or is edited while
|
||||
// acting as a constraint. it should track the set point, do any desired
|
||||
// preconditioning when the set point is present, and use its return value
|
||||
// to report whether the set is present. the default implementation does no
|
||||
// preconditioning
|
||||
fn try_activate(&self) -> bool {
|
||||
self.set_point().with(|set_pt| set_pt.is_present())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for dyn Regulator {
|
||||
|
@ -488,18 +477,6 @@ impl Regulator for HalfCurvatureRegulator {
|
|||
fn set_point(&self) -> Signal<SpecifiedValue> {
|
||||
self.set_point
|
||||
}
|
||||
|
||||
fn try_activate(&self) -> bool {
|
||||
match self.set_point.with(|set_pt| set_pt.value) {
|
||||
Some(half_curv) => {
|
||||
self.subject.representation().update(
|
||||
|rep| change_half_curvature(rep, half_curv)
|
||||
);
|
||||
true
|
||||
}
|
||||
None => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serial for HalfCurvatureRegulator {
|
||||
|
@ -552,8 +529,7 @@ pub struct Assembly {
|
|||
pub elements_by_id: Signal<BTreeMap<String, Rc<dyn Element>>>,
|
||||
|
||||
// realization control
|
||||
pub keep_realized: Signal<bool>,
|
||||
pub needs_realization: Signal<bool>,
|
||||
pub realization_trigger: Signal<()>,
|
||||
|
||||
// realization diagnostics
|
||||
pub realization_status: Signal<Result<(), String>>,
|
||||
|
@ -568,21 +544,23 @@ impl Assembly {
|
|||
regulators: create_signal(BTreeSet::new()),
|
||||
tangent: create_signal(ConfigSubspace::zero(0)),
|
||||
elements_by_id: create_signal(BTreeMap::default()),
|
||||
keep_realized: create_signal(true),
|
||||
needs_realization: create_signal(false),
|
||||
realization_trigger: create_signal(()),
|
||||
realization_status: create_signal(Ok(())),
|
||||
descent_history: create_signal(DescentHistory::new())
|
||||
};
|
||||
|
||||
// realize the assembly whenever it becomes simultaneously true that
|
||||
// we're trying to keep it realized and it needs realization
|
||||
// realize the assembly whenever the element list, the regulator list,
|
||||
// a regulator's set point, or the realization trigger is updated
|
||||
let assembly_for_effect = assembly.clone();
|
||||
create_effect(move || {
|
||||
let should_realize = assembly_for_effect.keep_realized.get()
|
||||
&& assembly_for_effect.needs_realization.get();
|
||||
if should_realize {
|
||||
assembly_for_effect.realize();
|
||||
}
|
||||
assembly_for_effect.elements.track();
|
||||
assembly_for_effect.regulators.with(
|
||||
|regs| for reg in regs {
|
||||
reg.set_point().track();
|
||||
}
|
||||
);
|
||||
assembly_for_effect.realization_trigger.track();
|
||||
assembly_for_effect.realize();
|
||||
});
|
||||
|
||||
assembly
|
||||
|
@ -646,19 +624,6 @@ impl Assembly {
|
|||
regulators.update(|regs| regs.insert(regulator.clone()));
|
||||
}
|
||||
|
||||
// request a realization when the regulator becomes a constraint, or is
|
||||
// edited while acting as a constraint
|
||||
let self_for_effect = self.clone();
|
||||
create_effect(move || {
|
||||
/* DEBUG */
|
||||
// log the regulator update
|
||||
console_log!("Updated regulator with subjects {:?}", regulator.subjects());
|
||||
|
||||
if regulator.try_activate() {
|
||||
self_for_effect.needs_realization.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
/* DEBUG */
|
||||
// print an updated list of regulators
|
||||
console_log!("Regulators:");
|
||||
|
@ -726,8 +691,10 @@ impl Assembly {
|
|||
} else {
|
||||
console_log!("✅️ Target accuracy achieved!");
|
||||
}
|
||||
console_log!("Steps: {}", history.scaled_loss.len() - 1);
|
||||
console_log!("Loss: {}", history.scaled_loss.last().unwrap());
|
||||
if history.scaled_loss.len() > 0 {
|
||||
console_log!("Steps: {}", history.scaled_loss.len() - 1);
|
||||
console_log!("Loss: {}", history.scaled_loss.last().unwrap());
|
||||
}
|
||||
|
||||
// report the loss history
|
||||
self.descent_history.set(history);
|
||||
|
@ -750,9 +717,6 @@ impl Assembly {
|
|||
|
||||
// save the tangent space
|
||||
self.tangent.set_silent(tangent);
|
||||
|
||||
// clear the realization request flag
|
||||
self.needs_realization.set(false);
|
||||
},
|
||||
Err(message) => {
|
||||
// report the realization status. the `Err(message)` we're
|
||||
|
@ -848,10 +812,10 @@ impl Assembly {
|
|||
});
|
||||
}
|
||||
|
||||
// request a realization to bring the configuration back onto the
|
||||
// trigger a realization to bring the configuration back onto the
|
||||
// solution variety. this also gets the elements' column indices and the
|
||||
// saved tangent space back in sync
|
||||
self.needs_realization.set(true);
|
||||
self.realization_trigger.set(());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,22 @@ pub fn AddRemove() -> View {
|
|||
button(
|
||||
on:click=|_| {
|
||||
let state = use_context::<AppState>();
|
||||
state.assembly.insert_element_default::<Sphere>();
|
||||
batch(|| {
|
||||
// this call is batched to avoid redundant realizations.
|
||||
// it updates the element list and the regulator list,
|
||||
// which are both tracked by the realization effect
|
||||
/* TO DO */
|
||||
// it would make more to do the batching inside
|
||||
// `insert_element_default`, but that will have to wait
|
||||
// until Sycamore handles nested batches correctly.
|
||||
//
|
||||
// https://github.com/sycamore-rs/sycamore/issues/802
|
||||
//
|
||||
// the nested batch issue is relevant here because the
|
||||
// assembly loaders in the test assembly chooser use
|
||||
// `insert_element_default` within larger batches
|
||||
state.assembly.insert_element_default::<Sphere>();
|
||||
});
|
||||
}
|
||||
) { "Add sphere" }
|
||||
button(
|
||||
|
|
|
@ -900,9 +900,6 @@ pub fn TestAssemblyChooser() -> View {
|
|||
let state = use_context::<AppState>();
|
||||
let assembly = &state.assembly;
|
||||
|
||||
// pause realization
|
||||
assembly.keep_realized.set(false);
|
||||
|
||||
// clear state
|
||||
assembly.regulators.update(|regs| regs.clear());
|
||||
assembly.elements.update(|elts| elts.clear());
|
||||
|
@ -923,9 +920,6 @@ pub fn TestAssemblyChooser() -> View {
|
|||
"irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly),
|
||||
_ => ()
|
||||
};
|
||||
|
||||
// resume realization
|
||||
assembly.keep_realized.set(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use lazy_static::lazy_static;
|
||||
use nalgebra::{Const, DMatrix, DVector, DVectorView, Dyn, SymmetricEigen};
|
||||
use std::fmt::{Display, Error, Formatter};
|
||||
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
|
||||
|
||||
// --- elements ---
|
||||
|
||||
|
@ -50,40 +49,6 @@ pub fn project_point_to_normalized(rep: &mut DVector<f64>) {
|
|||
rep.scale_mut(0.5 / rep[3]);
|
||||
}
|
||||
|
||||
// given a sphere's representation vector, change the sphere's half-curvature to
|
||||
// `half-curv` and then restore normalization by contracting the representation
|
||||
// vector toward the curvature axis
|
||||
pub fn change_half_curvature(rep: &mut DVector<f64>, half_curv: f64) {
|
||||
// set the sphere's half-curvature to the desired value
|
||||
rep[3] = half_curv;
|
||||
|
||||
// restore normalization by contracting toward the curvature axis
|
||||
const SIZE_THRESHOLD: f64 = 1e-9;
|
||||
let half_q_lt = -2.0 * half_curv * rep[4];
|
||||
let half_q_lt_sq = half_q_lt * half_q_lt;
|
||||
let mut spatial = rep.fixed_rows_mut::<3>(0);
|
||||
let q_sp = spatial.norm_squared();
|
||||
if q_sp < SIZE_THRESHOLD && half_q_lt_sq < SIZE_THRESHOLD {
|
||||
spatial.copy_from_slice(
|
||||
&[0.0, 0.0, (1.0 - 2.0 * half_q_lt).sqrt()]
|
||||
);
|
||||
} else {
|
||||
let scaling = half_q_lt + (q_sp + half_q_lt_sq).sqrt();
|
||||
spatial.scale_mut(1.0 / scaling);
|
||||
rep[4] /= scaling;
|
||||
}
|
||||
|
||||
/* DEBUG */
|
||||
// verify normalization
|
||||
let rep_for_debug = rep.clone();
|
||||
console::log_1(&JsValue::from(
|
||||
format!(
|
||||
"Sphere self-product after curvature change: {}",
|
||||
rep_for_debug.dot(&(&*Q * &rep_for_debug))
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
// --- partial matrices ---
|
||||
|
||||
pub struct MatrixEntry {
|
||||
|
@ -199,13 +164,6 @@ impl ConfigSubspace {
|
|||
).collect::<Vec<_>>().as_slice()
|
||||
);
|
||||
|
||||
/* DEBUG */
|
||||
// print the eigenvalues
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
console::log_1(&JsValue::from(
|
||||
format!("Eigenvalues used to find kernel:{}", eig.eigenvalues)
|
||||
));
|
||||
|
||||
// express the basis in the standard coordinates
|
||||
let basis_std = proj_to_std * &basis_proj;
|
||||
|
||||
|
@ -425,9 +383,22 @@ pub fn realize_gram(
|
|||
// start the descent history
|
||||
let mut history = DescentHistory::new();
|
||||
|
||||
// handle the case where the assembly is empty. our general realization
|
||||
// routine can't handle this case because it builds the Hessian using
|
||||
// `DMatrix::from_columns`, which panics when the list of columns is empty
|
||||
let assembly_dim = guess.ncols();
|
||||
if assembly_dim == 0 {
|
||||
let result = Ok(
|
||||
ConfigNeighborhood {
|
||||
config: guess.clone(),
|
||||
nbhd: ConfigSubspace::zero(0)
|
||||
}
|
||||
);
|
||||
return Realization { result, history }
|
||||
}
|
||||
|
||||
// find the dimension of the search space
|
||||
let element_dim = guess.nrows();
|
||||
let assembly_dim = guess.ncols();
|
||||
let total_dim = element_dim * assembly_dim;
|
||||
|
||||
// scale the tolerance
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue