Rewind through the descent history #114
6 changed files with 152 additions and 19 deletions
|
@ -184,6 +184,7 @@ details[open]:has(li) .element-switch::after {
|
||||||
|
|
||||||
#diagnostics-bar {
|
#diagnostics-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#realization-status {
|
#realization-status {
|
||||||
|
@ -207,6 +208,14 @@ details[open]:has(li) .element-switch::after {
|
||||||
content: '⚠';
|
content: '⚠';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#step-input > label {
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#step-input > input {
|
||||||
|
width: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
.diagnostics-panel {
|
.diagnostics-panel {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
min-height: 180px;
|
min-height: 180px;
|
||||||
|
|
|
@ -534,6 +534,7 @@ pub struct Assembly {
|
||||||
// realization diagnostics
|
// realization diagnostics
|
||||||
pub realization_status: Signal<Result<(), String>>,
|
pub realization_status: Signal<Result<(), String>>,
|
||||||
pub descent_history: Signal<DescentHistory>,
|
pub descent_history: Signal<DescentHistory>,
|
||||||
|
pub step: Signal<SpecifiedValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assembly {
|
impl Assembly {
|
||||||
|
@ -547,20 +548,33 @@ impl Assembly {
|
||||||
realization_trigger: create_signal(()),
|
realization_trigger: create_signal(()),
|
||||||
realization_status: create_signal(Ok(())),
|
realization_status: create_signal(Ok(())),
|
||||||
descent_history: create_signal(DescentHistory::new()),
|
descent_history: create_signal(DescentHistory::new()),
|
||||||
|
step: create_signal(SpecifiedValue::from_empty_spec()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// realize the assembly whenever the element list, the regulator list,
|
// realize the assembly whenever the element list, the regulator list,
|
||||||
// a regulator's set point, or the realization trigger is updated
|
// 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 || {
|
create_effect(move || {
|
||||||
assembly_for_effect.elements.track();
|
assembly_for_realization.elements.track();
|
||||||
assembly_for_effect.regulators.with(
|
assembly_for_realization.regulators.with(
|
||||||
|regs| for reg in regs {
|
|regs| for reg in regs {
|
||||||
reg.set_point().track();
|
reg.set_point().track();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assembly_for_effect.realization_trigger.track();
|
assembly_for_realization.realization_trigger.track();
|
||||||
assembly_for_effect.realize();
|
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
|
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 ---
|
// --- realization ---
|
||||||
|
|
||||||
pub fn realize(&self) {
|
pub fn realize(&self) {
|
||||||
|
@ -696,11 +720,12 @@ impl Assembly {
|
||||||
console_log!("Loss: {}", history.scaled_loss.last().unwrap());
|
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);
|
self.descent_history.set(history);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(ConfigNeighborhood { config, nbhd: tangent }) => {
|
Ok(ConfigNeighborhood { nbhd: tangent, .. }) => {
|
||||||
/* DEBUG */
|
/* DEBUG */
|
||||||
// report the tangent dimension
|
// report the tangent dimension
|
||||||
console_log!("Tangent dimension: {}", tangent.dim());
|
console_log!("Tangent dimension: {}", tangent.dim());
|
||||||
|
@ -708,12 +733,15 @@ impl Assembly {
|
||||||
// report the realization status
|
// report the realization status
|
||||||
self.realization_status.set(Ok(()));
|
self.realization_status.set(Ok(()));
|
||||||
|
|
||||||
// read out the solution
|
// display the last realization step
|
||||||
for elt in self.elements.get_clone_untracked() {
|
self.step.set(
|
||||||
elt.representation().update(
|
if step_cnt > 0 {
|
||||||
|rep| rep.set_column(0, &config.column(elt.column_index().unwrap()))
|
let last_step = step_cnt - 1;
|
||||||
);
|
SpecifiedValue::try_from(last_step.to_string()).unwrap()
|
||||||
|
} else {
|
||||||
|
SpecifiedValue::from_empty_spec()
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// save the tangent space
|
// save the tangent space
|
||||||
self.tangent.set_silent(tangent);
|
self.tangent.set_silent(tangent);
|
||||||
|
@ -723,7 +751,10 @@ impl Assembly {
|
||||||
// setting the status to has a different type than the
|
// setting the status to has a different type than the
|
||||||
// `Err(message)` we received from the match: we're changing the
|
// `Err(message)` we received from the match: we're changing the
|
||||||
// `Ok` type from `Realization` to `()`
|
// `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 sycamore::prelude::*;
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::{AppState, specified::SpecifiedValue};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct DiagnosticsState {
|
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>> {
|
fn into_log10_time_point((step, value): (usize, f64)) -> Vec<Option<f64>> {
|
||||||
vec![
|
vec![
|
||||||
Some(step as f64),
|
Some(step as f64),
|
||||||
|
@ -248,6 +311,7 @@ pub fn Diagnostics() -> View {
|
||||||
option(value = "loss") { "Loss" }
|
option(value = "loss") { "Loss" }
|
||||||
option(value = "spectrum") { "Spectrum" }
|
option(value = "spectrum") { "Spectrum" }
|
||||||
}
|
}
|
||||||
|
StepInput {}
|
||||||
}
|
}
|
||||||
DiagnosticsPanel(name = "loss") { LossHistory {} }
|
DiagnosticsPanel(name = "loss") { LossHistory {} }
|
||||||
DiagnosticsPanel(name = "spectrum") { SpectrumHistory {} }
|
DiagnosticsPanel(name = "spectrum") { SpectrumHistory {} }
|
||||||
|
|
|
@ -588,7 +588,25 @@ pub fn Display() -> View {
|
||||||
location_z *= (time_step * ZOOM_SPEED * zoom).exp();
|
location_z *= (time_step * ZOOM_SPEED * zoom).exp();
|
||||||
|
|
||||||
// manipulate the assembly
|
// 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_init_step = step_val.is_some_and(|n| n == 0.0);
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let on_manipulable_step =
|
||||||
|
!realization_successful && on_init_step
|
||||||
|
|| realization_successful && on_last_step;
|
||||||
|
if on_manipulable_step && state.selection.with(|sel| sel.len() == 1) {
|
||||||
let sel = state.selection.with(
|
let sel = state.selection.with(
|
||||||
|sel| sel.into_iter().next().unwrap().clone()
|
|sel| sel.into_iter().next().unwrap().clone()
|
||||||
);
|
);
|
||||||
|
|
|
@ -353,7 +353,7 @@ fn seek_better_config(
|
||||||
|
|
||||||
// a first-order neighborhood of a configuration
|
// a first-order neighborhood of a configuration
|
||||||
pub struct ConfigNeighborhood {
|
pub struct ConfigNeighborhood {
|
||||||
pub config: DMatrix<f64>,
|
#[cfg(feature = "dev")] pub config: DMatrix<f64>,
|
||||||
pub nbhd: ConfigSubspace,
|
pub nbhd: ConfigSubspace,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,7 +388,7 @@ pub fn realize_gram(
|
||||||
if assembly_dim == 0 {
|
if assembly_dim == 0 {
|
||||||
let result = Ok(
|
let result = Ok(
|
||||||
ConfigNeighborhood {
|
ConfigNeighborhood {
|
||||||
config: guess.clone(),
|
#[cfg(feature = "dev")] config: guess.clone(),
|
||||||
nbhd: ConfigSubspace::zero(0),
|
nbhd: ConfigSubspace::zero(0),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -509,7 +509,7 @@ pub fn realize_gram(
|
||||||
// find the kernel of the Hessian. give it the uniform inner product
|
// find the kernel of the Hessian. give it the uniform inner product
|
||||||
let tangent = ConfigSubspace::symmetric_kernel(hess, unif_to_std, assembly_dim);
|
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 {
|
} else {
|
||||||
Err("Failed to reach target accuracy".to_string())
|
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
|
// a `SpecifiedValue` can be constructed from a specification string, formatted
|
||||||
// as described in the comment on the structure definition. the result is `Ok`
|
// as described in the comment on the structure definition. the result is `Ok`
|
||||||
// if the specification is properly formatted, and `Error` if not
|
// if the specification is properly formatted, and `Error` if not
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue