feat: Engine diagnostics #92
1 changed files with 70 additions and 37 deletions
|
@ -2,7 +2,7 @@ use charming::{
|
||||||
Chart,
|
Chart,
|
||||||
WasmRenderer,
|
WasmRenderer,
|
||||||
component::{Axis, DataZoom, Grid},
|
component::{Axis, DataZoom, Grid},
|
||||||
element::AxisType,
|
element::{AxisType, Symbol},
|
||||||
series::{Line, Scatter},
|
series::{Line, Scatter},
|
||||||
};
|
};
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
|
@ -50,6 +50,10 @@ fn RealizationStatus() -> View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn into_time_point((step, value): (usize, f64)) -> Vec<Option<f64>> {
|
||||||
|
vec![Some(step as f64), Some(value)]
|
||||||
|
}
|
||||||
|
|
||||||
// the loss history from the last realization
|
// the loss history from the last realization
|
||||||
#[component]
|
#[component]
|
||||||
fn LossHistory() -> View {
|
fn LossHistory() -> View {
|
||||||
|
@ -60,38 +64,35 @@ fn LossHistory() -> View {
|
||||||
on_mount(move || {
|
on_mount(move || {
|
||||||
create_effect(move || {
|
create_effect(move || {
|
||||||
// get the loss history
|
// get the loss history
|
||||||
let scaled_loss = state.assembly.descent_history.with(
|
let scaled_loss: Vec<_> = state.assembly.descent_history.with(
|
||||||
|history| history.scaled_loss.clone()
|
|history| history.scaled_loss
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(step, &loss)| (step, loss))
|
||||||
|
.map(into_time_point)
|
||||||
|
.collect()
|
||||||
);
|
);
|
||||||
let step_cnt = scaled_loss.len();
|
|
||||||
|
|
||||||
// initialize the chart axes and series
|
// initialize the chart axes and series
|
||||||
const MIN_INTERVAL: f64 = 0.01;
|
let step_axis = Axis::new()
|
||||||
let mut step_axis = Axis::new()
|
|
||||||
.type_(AxisType::Category)
|
.type_(AxisType::Category)
|
||||||
.boundary_gap(false);
|
.boundary_gap(false);
|
||||||
let scaled_loss_axis = Axis::new()
|
let scaled_loss_axis = Axis::new().type_(AxisType::Log);
|
||||||
.type_(AxisType::Value)
|
|
||||||
.min(0)
|
|
||||||
.min_interval(MIN_INTERVAL);
|
|
||||||
|
|
||||||
// load the chart data. when there's no history, we load the data
|
// 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
|
// 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
|
// load empty data vectors, but that turns out not to clear the
|
||||||
// chart: it instead leads to previous data being re-used
|
// chart: it instead leads to previous data being re-used
|
||||||
let mut scaled_loss_series = Line::new();
|
let scaled_loss_series = Line::new().data(
|
||||||
if step_cnt > 0 {
|
if scaled_loss.len() > 0 {
|
||||||
step_axis = step_axis.data(
|
scaled_loss
|
||||||
(0..step_cnt).map(|step| step.to_string()).collect()
|
} else {
|
||||||
);
|
vec![vec![Some(0.0), None::<f64>]]
|
||||||
scaled_loss_series = scaled_loss_series.data(scaled_loss);
|
}
|
||||||
} else {
|
);
|
||||||
step_axis = step_axis.data(vec![0.to_string()]);
|
|
||||||
scaled_loss_series = scaled_loss_series.data(vec![None::<f64>]);
|
|
||||||
}
|
|
||||||
let chart = Chart::new()
|
let chart = Chart::new()
|
||||||
.animation(false)
|
.animation(false)
|
||||||
.data_zoom(DataZoom::new().y_axis_index(0).right(40).start(0).end(100))
|
.data_zoom(DataZoom::new().y_axis_index(0).right(40))
|
||||||
.x_axis(step_axis)
|
.x_axis(step_axis)
|
||||||
.y_axis(scaled_loss_axis)
|
.y_axis(scaled_loss_axis)
|
||||||
.grid(Grid::new().top(20).right(80).bottom(30).left(60))
|
.grid(Grid::new().top(20).right(80).bottom(30).left(60))
|
||||||
|
@ -114,41 +115,73 @@ fn SpectrumHistory() -> View {
|
||||||
|
|
||||||
on_mount(move || {
|
on_mount(move || {
|
||||||
create_effect(move || {
|
create_effect(move || {
|
||||||
// get the spectrum of the Hessian at each step
|
// get the spectrum of the Hessian at each step, split into its
|
||||||
let hess_eigvals = state.assembly.descent_history.with(
|
// positive and negative parts. throw away eigenvalues that are
|
||||||
|
// close to zero
|
||||||
|
const ZERO_THRESHOLD: f64 = 1e-6;
|
||||||
|
let (
|
||||||
|
hess_eigvals_pos,
|
||||||
|
hess_eigvals_neg
|
||||||
|
): (Vec<_>, Vec<_>) = state.assembly.descent_history.with(
|
||||||
|history| history.hess_eigvals
|
|history| history.hess_eigvals
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(
|
.map(
|
||||||
|(step, eigvals)| eigvals.iter().map(
|
|(step, eigvals)| eigvals
|
||||||
move |val| vec![step as f64, *val]
|
.iter()
|
||||||
)
|
.filter(|&&val| val.abs() > ZERO_THRESHOLD)
|
||||||
|
.map(
|
||||||
|
move |&val| (step, val)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect::<Vec<_>>()
|
.partition(|&(_, val)| val > 0.0)
|
||||||
);
|
);
|
||||||
|
|
||||||
// initialize the chart axes and series
|
// initialize the chart axes and series
|
||||||
let step_axis = Axis::new();
|
let step_axis = Axis::new()
|
||||||
let eigval_axis = Axis::new();
|
.type_(AxisType::Category)
|
||||||
|
.boundary_gap(false);
|
||||||
|
let eigval_axis = Axis::new().type_(AxisType::Log);
|
||||||
|
|
||||||
// load the chart data. when there's no history, we load the data
|
// 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
|
// 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
|
// load empty data vectors, but that turns out not to clear the
|
||||||
// chart: it instead leads to previous data being re-used
|
// chart: it instead leads to previous data being re-used
|
||||||
let mut eigval_series = Scatter::new().symbol_size(7);
|
let eigval_series_pos = Scatter::new()
|
||||||
if hess_eigvals.len() > 0 {
|
.symbol_size(4.5)
|
||||||
eigval_series = eigval_series.data(hess_eigvals);
|
.data(
|
||||||
} else {
|
if hess_eigvals_pos.len() > 0 {
|
||||||
eigval_series = eigval_series.data(vec![None::<f64>, None::<f64>]);
|
hess_eigvals_pos
|
||||||
}
|
.into_iter()
|
||||||
|
.map(into_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(|(step, val)| (step, -val))
|
||||||
|
.map(into_time_point)
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
vec![vec![Some(0.0), None::<f64>]]
|
||||||
|
}
|
||||||
|
);
|
||||||
let chart = Chart::new()
|
let chart = Chart::new()
|
||||||
.animation(false)
|
.animation(false)
|
||||||
.data_zoom(DataZoom::new().y_axis_index(0).right(40).start(0).end(100))
|
.data_zoom(DataZoom::new().y_axis_index(0).right(40))
|
||||||
.x_axis(step_axis)
|
.x_axis(step_axis)
|
||||||
.y_axis(eigval_axis)
|
.y_axis(eigval_axis)
|
||||||
.grid(Grid::new().top(20).right(80).bottom(30).left(60))
|
.grid(Grid::new().top(20).right(80).bottom(30).left(60))
|
||||||
.series(eigval_series);
|
.series(eigval_series_pos)
|
||||||
|
.series(eigval_series_neg);
|
||||||
renderer.render(CONTAINER_ID, &chart).unwrap();
|
renderer.render(CONTAINER_ID, &chart).unwrap();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue