forked from StudioInfinity/dyna3
Primarily, switch to using trailing commas. Also uniformizes commas with respect to switch branches, makes function call layout more consistent, line breaking more consistent, alphabetizes imports, uses the field init shorthand when possible, etc. Resolves #99. Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: StudioInfinity/dyna3#108 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
256 lines
No EOL
8.5 KiB
Rust
256 lines
No EOL
8.5 KiB
Rust
use charming::{
|
|
Chart,
|
|
WasmRenderer,
|
|
component::{Axis, DataZoom, Grid},
|
|
element::{AxisType, Symbol},
|
|
series::{Line, Scatter},
|
|
};
|
|
use sycamore::prelude::*;
|
|
|
|
use crate::AppState;
|
|
|
|
#[derive(Clone)]
|
|
struct DiagnosticsState {
|
|
active_tab: Signal<String>,
|
|
}
|
|
|
|
impl DiagnosticsState {
|
|
fn new(initial_tab: String) -> DiagnosticsState {
|
|
DiagnosticsState { active_tab: create_signal(initial_tab) }
|
|
}
|
|
}
|
|
|
|
// a realization status indicator
|
|
#[component]
|
|
fn RealizationStatus() -> View {
|
|
let state = use_context::<AppState>();
|
|
let realization_status = state.assembly.realization_status;
|
|
view! {
|
|
div(
|
|
id = "realization-status",
|
|
class = realization_status.with(
|
|
|status| match status {
|
|
Ok(_) => "",
|
|
Err(_) => "invalid",
|
|
}
|
|
)
|
|
) {
|
|
div(class = "status")
|
|
div {
|
|
(realization_status.with(
|
|
|status| match status {
|
|
Ok(_) => "Target accuracy achieved".to_string(),
|
|
Err(message) => message.clone(),
|
|
}
|
|
))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn into_log10_time_point((step, value): (usize, f64)) -> Vec<Option<f64>> {
|
|
vec![
|
|
Some(step as f64),
|
|
if value == 0.0 { None } else { Some(value.abs().log10()) },
|
|
]
|
|
}
|
|
|
|
// the loss history from the last realization
|
|
#[component]
|
|
fn LossHistory() -> View {
|
|
const CONTAINER_ID: &str = "loss-history";
|
|
let state = use_context::<AppState>();
|
|
let renderer = WasmRenderer::new_opt(None, Some(178));
|
|
|
|
on_mount(move || {
|
|
create_effect(move || {
|
|
// get the loss history
|
|
let scaled_loss: Vec<_> = state.assembly.descent_history.with(
|
|
|history| history.scaled_loss
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(step, &loss)| (step, loss))
|
|
.map(into_log10_time_point)
|
|
.collect()
|
|
);
|
|
|
|
// initialize the chart axes
|
|
let step_axis = Axis::new()
|
|
.type_(AxisType::Category)
|
|
.boundary_gap(false);
|
|
let scaled_loss_axis = Axis::new();
|
|
|
|
// load the chart data. when there's no history, we load the data
|
|
// point (0, None) to clear the chart. it would feel more natural to
|
|
// load empty data vectors, but that turns out not to clear the
|
|
// chart: it instead leads to previous data being re-used
|
|
let scaled_loss_series = Line::new().data(
|
|
if scaled_loss.len() > 0 {
|
|
scaled_loss
|
|
} else {
|
|
vec![vec![Some(0.0), None::<f64>]]
|
|
}
|
|
);
|
|
let chart = Chart::new()
|
|
.animation(false)
|
|
.data_zoom(DataZoom::new().y_axis_index(0).right(40))
|
|
.x_axis(step_axis)
|
|
.y_axis(scaled_loss_axis)
|
|
.grid(Grid::new().top(20).right(80).bottom(30).left(60))
|
|
.series(scaled_loss_series);
|
|
renderer.render(CONTAINER_ID, &chart).unwrap();
|
|
});
|
|
});
|
|
|
|
view! {
|
|
div(id = CONTAINER_ID, class = "diagnostics-chart")
|
|
}
|
|
}
|
|
|
|
// the spectrum of the Hessian during the last realization
|
|
#[component]
|
|
fn SpectrumHistory() -> View {
|
|
const CONTAINER_ID: &str = "spectrum-history";
|
|
let state = use_context::<AppState>();
|
|
let renderer = WasmRenderer::new(478, 178);
|
|
|
|
on_mount(move || {
|
|
create_effect(move || {
|
|
// get the spectrum of the Hessian at each step, split into its
|
|
// positive, negative, and strictly-zero parts
|
|
let (
|
|
hess_eigvals_zero,
|
|
hess_eigvals_nonzero,
|
|
): (Vec<_>, Vec<_>) = state.assembly.descent_history.with(
|
|
|history| history.hess_eigvals
|
|
.iter()
|
|
.enumerate()
|
|
.map(
|
|
|(step, eigvals)| eigvals.iter().map(
|
|
move |&val| (step, val)
|
|
)
|
|
)
|
|
.flatten()
|
|
.partition(|&(_, val)| val == 0.0)
|
|
);
|
|
let zero_level = hess_eigvals_nonzero
|
|
.iter()
|
|
.map(|(_, val)| val.abs())
|
|
.reduce(f64::min)
|
|
.map(|val| 0.1 * val)
|
|
.unwrap_or(1.0);
|
|
let (
|
|
hess_eigvals_pos,
|
|
hess_eigvals_neg,
|
|
): (Vec<_>, Vec<_>) = hess_eigvals_nonzero
|
|
.into_iter()
|
|
.partition(|&(_, val)| val > 0.0);
|
|
|
|
// initialize the chart axes
|
|
let step_axis = Axis::new()
|
|
.type_(AxisType::Category)
|
|
.boundary_gap(false);
|
|
let eigval_axis = Axis::new();
|
|
|
|
// load the chart data. when there's no history, we load the data
|
|
// point (0, None) to clear the chart. it would feel more natural to
|
|
// load empty data vectors, but that turns out not to clear the
|
|
// chart: it instead leads to previous data being re-used
|
|
let eigval_series_pos = Scatter::new()
|
|
.symbol_size(4.5)
|
|
.data(
|
|
if hess_eigvals_pos.len() > 0 {
|
|
hess_eigvals_pos
|
|
.into_iter()
|
|
.map(into_log10_time_point)
|
|
.collect()
|
|
} else {
|
|
vec![vec![Some(0.0), None::<f64>]]
|
|
}
|
|
);
|
|
let eigval_series_neg = Scatter::new()
|
|
.symbol(Symbol::Diamond)
|
|
.symbol_size(6.0)
|
|
.data(
|
|
if hess_eigvals_neg.len() > 0 {
|
|
hess_eigvals_neg
|
|
.into_iter()
|
|
.map(into_log10_time_point)
|
|
.collect()
|
|
} else {
|
|
vec![vec![Some(0.0), None::<f64>]]
|
|
}
|
|
);
|
|
let eigval_series_zero = Scatter::new()
|
|
.symbol(Symbol::Triangle)
|
|
.symbol_size(5.0)
|
|
.data(
|
|
if hess_eigvals_zero.len() > 0 {
|
|
hess_eigvals_zero
|
|
.into_iter()
|
|
.map(|(step, _)| (step, zero_level))
|
|
.map(into_log10_time_point)
|
|
.collect()
|
|
} else {
|
|
vec![vec![Some(0.0), None::<f64>]]
|
|
}
|
|
);
|
|
let chart = Chart::new()
|
|
.animation(false)
|
|
.data_zoom(DataZoom::new().y_axis_index(0).right(40))
|
|
.x_axis(step_axis)
|
|
.y_axis(eigval_axis)
|
|
.grid(Grid::new().top(20).right(80).bottom(30).left(60))
|
|
.series(eigval_series_pos)
|
|
.series(eigval_series_neg)
|
|
.series(eigval_series_zero);
|
|
renderer.render(CONTAINER_ID, &chart).unwrap();
|
|
});
|
|
});
|
|
|
|
view! {
|
|
div(id = CONTAINER_ID, class = "diagnostics-chart")
|
|
}
|
|
}
|
|
|
|
#[component(inline_props)]
|
|
fn DiagnosticsPanel(name: &'static str, children: Children) -> View {
|
|
let diagnostics_state = use_context::<DiagnosticsState>();
|
|
view! {
|
|
div(
|
|
class = "diagnostics-panel",
|
|
"hidden" = diagnostics_state.active_tab.with(
|
|
|active_tab| {
|
|
if active_tab == name {
|
|
None
|
|
} else {
|
|
Some("")
|
|
}
|
|
}
|
|
)
|
|
) {
|
|
(children)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
pub fn Diagnostics() -> View {
|
|
let diagnostics_state = DiagnosticsState::new("loss".to_string());
|
|
let active_tab = diagnostics_state.active_tab.clone();
|
|
provide_context(diagnostics_state);
|
|
|
|
view! {
|
|
div(id = "diagnostics") {
|
|
div(id = "diagnostics-bar") {
|
|
RealizationStatus {}
|
|
select(bind:value = active_tab) {
|
|
option(value = "loss") { "Loss" }
|
|
option(value = "spectrum") { "Spectrum" }
|
|
}
|
|
}
|
|
DiagnosticsPanel(name = "loss") { LossHistory {} }
|
|
DiagnosticsPanel(name = "spectrum") { SpectrumHistory {} }
|
|
}
|
|
}
|
|
} |