feat: Engine diagnostics #92

Merged
glen merged 16 commits from Vectornaut/dyna3:diagnostics into main 2025-07-21 04:18:50 +00:00
4 changed files with 105 additions and 29 deletions
Showing only changes of commit 402f5609c0 - Show all commits

View file

@ -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!");
}

View file

@ -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;
}

View file

@ -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

Conceptually, does the descent history belong as a member of an Assembly?

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.

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.

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.
}
}
@ -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 }) => {

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...

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`...

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 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. 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 on a vector bundle and evaluate it at a point.
  • Something related to the notion of a first-order neighborhood used in algebraic geometry.

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);
}
}

View file

@ -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 {}
}
}
}