feat: Engine diagnostics #92
|
@ -11,8 +11,8 @@ pub fn print_title(title: &str) {
|
|||
pub fn print_realization_diagnostics(realization_result: &RealizationResult) {
|
||||
let RealizationResult { result, history } = realization_result;
|
||||
println!();
|
||||
if let Err(ref msg) = result {
|
||||
println!("❌️ {msg}");
|
||||
if let Err(ref message) = result {
|
||||
println!("❌️ {message}");
|
||||
} else {
|
||||
println!("✅️ Target accuracy achieved!");
|
||||
}
|
||||
|
|
|
@ -18,6 +18,17 @@ body {
|
|||
font-family: 'Fira Sans', sans-serif;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
color: var(--text-invalid);
|
||||
}
|
||||
|
||||
.status {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
font-family: 'Noto Emoji';
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* sidebar */
|
||||
|
||||
#sidebar {
|
||||
|
@ -138,6 +149,7 @@ details[open]:has(li) .element-switch::after {
|
|||
}
|
||||
|
||||
.regulator-input {
|
||||
margin-right: 4px;
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
border: 1px solid var(--border);
|
||||
|
@ -159,14 +171,6 @@ details[open]:has(li) .element-switch::after {
|
|||
border-color: var(--border-invalid);
|
||||
}
|
||||
|
||||
.status {
|
||||
width: 20px;
|
||||
padding-left: 4px;
|
||||
text-align: center;
|
||||
font-family: 'Noto Emoji';
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.regulator-input.invalid + .status::after, details:has(.invalid):not([open]) .status::after {
|
||||
content: '⚠';
|
||||
color: var(--text-invalid);
|
||||
|
@ -174,8 +178,28 @@ details[open]:has(li) .element-switch::after {
|
|||
|
||||
/* diagnostics */
|
||||
|
||||
#loss-history {
|
||||
#diagnostics {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#realization-status {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#realization-status .status {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
#realization-status .status::after {
|
||||
content: '✓';
|
||||
}
|
||||
|
||||
#realization-status.invalid .status::after {
|
||||
content: '⚠';
|
||||
}
|
||||
|
||||
#loss-history {
|
||||
margin-top: 10px;
|
||||
background-color: var(--display-background);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
|
|
@ -553,6 +553,7 @@ pub struct Assembly {
|
|||
pub elements_by_id: Signal<BTreeMap<String, Rc<dyn Element>>>,
|
||||
|
||||
// realization diagnostics
|
||||
pub realization_status: Signal<Result<(), String>>,
|
||||
pub descent_history: Signal<DescentHistory>
|
||||
}
|
||||
|
||||
|
@ -563,6 +564,7 @@ impl Assembly {
|
|||
regulators: create_signal(BTreeSet::new()),
|
||||
tangent: create_signal(ConfigSubspace::zero(0)),
|
||||
elements_by_id: create_signal(BTreeMap::default()),
|
||||
realization_status: create_signal(Ok(())),
|
||||
descent_history: create_signal(DescentHistory::new())
|
||||
glen marked this conversation as resolved
|
||||
}
|
||||
}
|
||||
|
@ -699,32 +701,44 @@ impl Assembly {
|
|||
);
|
||||
|
||||
/* DEBUG */
|
||||
// report the outcome of the search
|
||||
if let Err(ref msg) = result {
|
||||
console_log!("❌️ {msg}");
|
||||
// report the outcome of the search in the browser console
|
||||
if let Err(ref message) = result {
|
||||
console_log!("❌️ {message}");
|
||||
} else {
|
||||
console_log!("✅️ Target accuracy achieved!");
|
||||
}
|
||||
console_log!("Steps: {}", history.scaled_loss.len() - 1);
|
||||
console_log!("Loss: {}", history.scaled_loss.last().unwrap());
|
||||
|
||||
// record realization diagnostics
|
||||
// report the loss history
|
||||
self.descent_history.set(history);
|
||||
|
||||
if let Ok(Realization { config, tangent }) = result {
|
||||
/* DEBUG */
|
||||
// report the tangent dimension
|
||||
console_log!("Tangent dimension: {}", tangent.dim());
|
||||
|
||||
// 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()))
|
||||
);
|
||||
match result {
|
||||
Ok(Realization { config, tangent }) => {
|
||||
glen
commented
Is Is `nbhd: tangent` just a syntax for in essence renaming the `nbhd` member of a ConfigNeighborhood to `tangent` for use as a local variable? Is the fact that you are inclined to do so an indication that perhaps `config` and `tangent` would be better names for the members of a ConfigNeighborhood rather than `config` and `nbhd`? In other words, you seem comfortable enough with config just to use it here, but felt motivated to use the `nbhd` under the name `tangent`...
Vectornaut
commented
Yes—this code destructures
That field was indeed called I'd be open to switching to a name like
However, I haven't been able to find one. > Is nbhd: tangent just a syntax for in essence renaming the nbhd member of a ConfigNeighborhood to tangent for use as a local variable?
Yes—this code [*destructures*](https://doc.rust-lang.org/rust-by-example/flow_control/match/destructuring/destructure_structures.html) `result` into local variables called `config` and `tangent`, which hold the values of `result.config` and `result.nbhd` respectively.
> Is the fact that you are inclined to do so an indication that perhaps `config` and `tangent` would be better names for the members of a ConfigNeighborhood rather than `config` and `nbhd`?
That field was indeed called `tangent` before the [naming improvement commit](https://code.studioinfinity.org/StudioInfinity/dyna3/commit/f1865f8#diff-1357a04457079bf2e3ec43e8436b5f77f70471c1). I renamed it to `nbhd` because the `ConfigNeighborhood` structure seems well-designed to represent any first-order neighborhood of a point in configuration space, regardless of whether we're thinking of it as the tangent space of a submanifold. To me, this aligns with our renaming of the `ConfigNeighborhood` structure itself, which recognized that this structure is well-designed to represent things other than the result of a realization.
I'd be open to switching to a name like `ConfigTangent` or `ConfigTangentPlane` for the structure and going back to `tangent` for the field in that context. I still feel like there must be a better name for the structure, like maybe:
- A name for what you get when you take a [distribution](https://en.wikipedia.org/wiki/Distribution_(differential_geometry)) on a vector bundle and evaluate it at a point.
- Something related to the notion of a [first-order neighborhood](https://mathoverflow.net/questions/78313/first-order-infinitesimal-neighborhood-of-a-point) used in algebraic geometry.
However, I haven't been able to find one.
|
||||
/* DEBUG */
|
||||
// report the tangent dimension
|
||||
console_log!("Tangent dimension: {}", tangent.dim());
|
||||
|
||||
// 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()))
|
||||
);
|
||||
}
|
||||
|
||||
// save the tangent space
|
||||
self.tangent.set_silent(tangent);
|
||||
},
|
||||
Err(message) => {
|
||||
// report the realization status. the `Err(message)` we're
|
||||
// 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))
|
||||
}
|
||||
|
||||
// save the tangent space
|
||||
self.tangent.set_silent(tangent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,9 +10,37 @@ use sycamore::prelude::*;
|
|||
|
||||
use crate::AppState;
|
||||
|
||||
// a realization status indicator
|
||||
#[component]
|
||||
pub 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()
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a plot of the loss history from the last realization
|
||||
#[component]
|
||||
pub fn Diagnostics() -> View {
|
||||
pub fn LossHistory() -> View {
|
||||
const CONTAINER_ID: &str = "loss-history";
|
||||
let state = use_context::<AppState>();
|
||||
let renderer = WasmRenderer::new_opt(None, Some(180)).theme(Theme::Walden);
|
||||
|
@ -62,4 +90,14 @@ pub fn Diagnostics() -> View {
|
|||
view! {
|
||||
div(id=CONTAINER_ID)
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Diagnostics() -> View {
|
||||
view! {
|
||||
div(id="diagnostics") {
|
||||
RealizationStatus {}
|
||||
LossHistory {}
|
||||
}
|
||||
}
|
||||
}
|
Conceptually, does the descent history belong as a member of an Assembly?
My perspective here is that realization happens within an assembly, so its results—including the descent history—belong to the assembly.
The upcoming history display pull feature will force us to rethink that, because we decided to implement that feature by turning the realization history into a whole stack of assemblies—one for each descent step.
I propose keeping the current organization for this pull request, because it's consistent with the way I've been thinking about realization, and then reorganizing while we write the history display feature.
Yes, this is fine -- I think something closer to my perspective is that an assembly is a geometric arrangement together with a list of properties it is intended to have (i.e., constraints). I think that will be consistent with the reorganization you propose.