Rewind through the descent history
All checks were successful
/ test (pull_request) Successful in 3m47s
All checks were successful
/ test (pull_request) Successful in 3m47s
This commit is contained in:
parent
af18a8e7d1
commit
36036141b5
6 changed files with 152 additions and 19 deletions
|
@ -184,6 +184,7 @@ details[open]:has(li) .element-switch::after {
|
|||
|
||||
#diagnostics-bar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#realization-status {
|
||||
|
@ -207,6 +208,14 @@ details[open]:has(li) .element-switch::after {
|
|||
content: '⚠';
|
||||
}
|
||||
|
||||
#step-input > label {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
#step-input > input {
|
||||
width: 45px;
|
||||
}
|
||||
|
||||
.diagnostics-panel {
|
||||
margin-top: 10px;
|
||||
min-height: 180px;
|
||||
|
|
|
@ -534,6 +534,7 @@ pub struct Assembly {
|
|||
// realization diagnostics
|
||||
pub realization_status: Signal<Result<(), String>>,
|
||||
pub descent_history: Signal<DescentHistory>,
|
||||
pub step: Signal<SpecifiedValue>,
|
||||
}
|
||||
|
||||
impl Assembly {
|
||||
|
@ -547,20 +548,33 @@ impl Assembly {
|
|||
realization_trigger: create_signal(()),
|
||||
realization_status: create_signal(Ok(())),
|
||||
descent_history: create_signal(DescentHistory::new()),
|
||||
step: create_signal(SpecifiedValue::from_empty_spec()),
|
||||
};
|
||||
|
||||
// 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();
|
||||
let assembly_for_realization = assembly.clone();
|
||||
create_effect(move || {
|
||||
assembly_for_effect.elements.track();
|
||||
assembly_for_effect.regulators.with(
|
||||
assembly_for_realization.elements.track();
|
||||
assembly_for_realization.regulators.with(
|
||||
|regs| for reg in regs {
|
||||
reg.set_point().track();
|
||||
}
|
||||
);
|
||||
assembly_for_effect.realization_trigger.track();
|
||||
assembly_for_effect.realize();
|
||||
assembly_for_realization.realization_trigger.track();
|
||||
assembly_for_realization.realize();
|
||||
});
|
||||
|
||||
// load a configuration from the descent history whenever the active
|
||||
// step is updated
|
||||
let assembly_for_step_selection = assembly.clone();
|
||||
create_effect(move || {
|
||||
if let Some(step) = assembly.step.with(|n| n.value) {
|
||||
let config = assembly.descent_history.with_untracked(
|
||||
|history| history.config[step as usize].clone()
|
||||
);
|
||||
assembly_for_step_selection.load_config(&config)
|
||||
}
|
||||
});
|
||||
|
||||
assembly
|
||||
|
@ -647,6 +661,16 @@ impl Assembly {
|
|||
});
|
||||
}
|
||||
|
||||
// --- updating the configuration ---
|
||||
|
||||
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()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- realization ---
|
||||
|
||||
pub fn realize(&self) {
|
||||
|
@ -696,11 +720,12 @@ impl Assembly {
|
|||
console_log!("Loss: {}", history.scaled_loss.last().unwrap());
|
||||
}
|
||||
|
||||
// report the loss history
|
||||
// report the descent history
|
||||
let step_cnt = history.config.len();
|
||||
self.descent_history.set(history);
|
||||
|
||||
match result {
|
||||
Ok(ConfigNeighborhood { config, nbhd: tangent }) => {
|
||||
Ok(ConfigNeighborhood { nbhd: tangent, .. }) => {
|
||||
/* DEBUG */
|
||||
// report the tangent dimension
|
||||
console_log!("Tangent dimension: {}", tangent.dim());
|
||||
|
@ -708,12 +733,15 @@ impl Assembly {
|
|||
// report the realization status
|
||||
self.realization_status.set(Ok(()));
|
||||
|
||||
// read out the solution
|
||||
for elt in self.elements.get_clone_untracked() {
|
||||
elt.representation().update(
|
||||
|rep| rep.set_column(0, &config.column(elt.column_index().unwrap()))
|
||||
);
|
||||
}
|
||||
// display the last realization step
|
||||
self.step.set(
|
||||
if step_cnt > 0 {
|
||||
let last_step = step_cnt - 1;
|
||||
SpecifiedValue::try_from(last_step.to_string()).unwrap()
|
||||
} else {
|
||||
SpecifiedValue::from_empty_spec()
|
||||
}
|
||||
);
|
||||
|
||||
// save the tangent space
|
||||
self.tangent.set_silent(tangent);
|
||||
|
@ -723,7 +751,10 @@ impl Assembly {
|
|||
// setting the status to has a different type than the
|
||||
// `Err(message)` we received from the match: we're changing the
|
||||
// `Ok` type from `Realization` to `()`
|
||||
self.realization_status.set(Err(message))
|
||||
self.realization_status.set(Err(message));
|
||||
|
||||
// display the initial guess
|
||||
self.step.set(SpecifiedValue::from(Some(0.0)));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use charming::{
|
|||
};
|
||||
use sycamore::prelude::*;
|
||||
|
||||
use crate::AppState;
|
||||
use crate::{AppState, specified::SpecifiedValue};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DiagnosticsState {
|
||||
|
@ -48,6 +48,69 @@ fn RealizationStatus() -> View {
|
|||
}
|
||||
}
|
||||
|
||||
// history step input
|
||||
#[component]
|
||||
fn StepInput() -> View {
|
||||
// get the assembly
|
||||
let state = use_context::<AppState>();
|
||||
let assembly = state.assembly;
|
||||
|
||||
// the `last_step` signal holds the index of the last step
|
||||
let last_step = assembly.descent_history.map(
|
||||
|history| match history.config.len() {
|
||||
0 => None,
|
||||
n => Some(n - 1),
|
||||
}
|
||||
);
|
||||
let input_max = last_step.map(|last| last.unwrap_or(0));
|
||||
|
||||
// these signals hold the entered step number
|
||||
let value = create_signal(String::new());
|
||||
let value_as_number = create_signal(0.0);
|
||||
|
||||
create_effect(move || {
|
||||
value.set(assembly.step.with(|n| n.spec.clone()));
|
||||
});
|
||||
|
||||
view! {
|
||||
div(id = "step-input") {
|
||||
label { "Step" }
|
||||
input(
|
||||
r#type = "number",
|
||||
min = "0",
|
||||
max = input_max.with(|max| max.to_string()),
|
||||
bind:value = value,
|
||||
bind:valueAsNumber = value_as_number,
|
||||
on:change = move |_| {
|
||||
if last_step.with(|last| last.is_some()) {
|
||||
// clamp the step within its allowed range. the lower
|
||||
// bound is redundant on browsers that make it
|
||||
// impossible to type negative values into a number
|
||||
// input with a non-negative `min`, but there's no harm
|
||||
// in being careful
|
||||
let step_raw = value.with(
|
||||
|val| SpecifiedValue::try_from(val.clone())
|
||||
.unwrap_or(SpecifiedValue::from_empty_spec()
|
||||
)
|
||||
);
|
||||
let step = SpecifiedValue::from(
|
||||
step_raw.value.map(
|
||||
|val| val.clamp(0.0, input_max.get() as f64)
|
||||
)
|
||||
);
|
||||
|
||||
// set the input string and the assembly's active step
|
||||
value.set(step.spec.clone());
|
||||
assembly.step.set(step);
|
||||
} else {
|
||||
value.set(String::new());
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_log10_time_point((step, value): (usize, f64)) -> Vec<Option<f64>> {
|
||||
vec![
|
||||
Some(step as f64),
|
||||
|
@ -248,6 +311,7 @@ pub fn Diagnostics() -> View {
|
|||
option(value = "loss") { "Loss" }
|
||||
option(value = "spectrum") { "Spectrum" }
|
||||
}
|
||||
StepInput {}
|
||||
}
|
||||
DiagnosticsPanel(name = "loss") { LossHistory {} }
|
||||
DiagnosticsPanel(name = "spectrum") { SpectrumHistory {} }
|
||||
|
|
|
@ -588,7 +588,25 @@ pub fn Display() -> View {
|
|||
location_z *= (time_step * ZOOM_SPEED * zoom).exp();
|
||||
|
||||
// manipulate the assembly
|
||||
if state.selection.with(|sel| sel.len() == 1) {
|
||||
/* KLUDGE */
|
||||
// to avoid the complexity of making tangent space projection
|
||||
// conditional and dealing with unnormalized representation vectors,
|
||||
// we only allow manipulation when we're looking at the last step of
|
||||
// a successful realization
|
||||
let realization_successful = state.assembly.realization_status.with(
|
||||
|status| status.is_ok()
|
||||
);
|
||||
let step_val = state.assembly.step.with_untracked(|step| step.value);
|
||||
let on_last_step = step_val.is_some_and(
|
||||
|n| state.assembly.descent_history.with_untracked(
|
||||
|history| n as usize + 1 == history.config.len().max(1)
|
||||
)
|
||||
);
|
||||
if
|
||||
state.selection.with(|sel| sel.len() == 1)
|
||||
&& realization_successful
|
||||
&& on_last_step
|
||||
{
|
||||
let sel = state.selection.with(
|
||||
|sel| sel.into_iter().next().unwrap().clone()
|
||||
);
|
||||
|
|
|
@ -353,7 +353,7 @@ fn seek_better_config(
|
|||
|
||||
// a first-order neighborhood of a configuration
|
||||
pub struct ConfigNeighborhood {
|
||||
pub config: DMatrix<f64>,
|
||||
#[cfg(feature = "dev")] pub config: DMatrix<f64>,
|
||||
pub nbhd: ConfigSubspace,
|
||||
}
|
||||
|
||||
|
@ -388,7 +388,7 @@ pub fn realize_gram(
|
|||
if assembly_dim == 0 {
|
||||
let result = Ok(
|
||||
ConfigNeighborhood {
|
||||
config: guess.clone(),
|
||||
#[cfg(feature = "dev")] config: guess.clone(),
|
||||
nbhd: ConfigSubspace::zero(0),
|
||||
}
|
||||
);
|
||||
|
@ -509,7 +509,7 @@ pub fn realize_gram(
|
|||
// find the kernel of the Hessian. give it the uniform inner product
|
||||
let tangent = ConfigSubspace::symmetric_kernel(hess, unif_to_std, assembly_dim);
|
||||
|
||||
Ok(ConfigNeighborhood { config: state.config, nbhd: tangent })
|
||||
Ok(ConfigNeighborhood { #[cfg(feature = "dev")] config: state.config, nbhd: tangent })
|
||||
} else {
|
||||
Err("Failed to reach target accuracy".to_string())
|
||||
};
|
||||
|
|
|
@ -26,6 +26,17 @@ impl SpecifiedValue {
|
|||
}
|
||||
}
|
||||
|
||||
// a `SpecifiedValue` can be constructed from a floating-point option, which is
|
||||
// given a canonical specification
|
||||
impl From<Option<f64>> for SpecifiedValue {
|
||||
fn from(value: Option<f64>) -> Self {
|
||||
match value {
|
||||
Some(x) => SpecifiedValue{ spec: x.to_string(), value },
|
||||
None => SpecifiedValue::from_empty_spec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a `SpecifiedValue` can be constructed from a specification string, formatted
|
||||
// as described in the comment on the structure definition. the result is `Ok`
|
||||
// if the specification is properly formatted, and `Error` if not
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue