feat: Engine diagnostics #92

Merged
glen merged 16 commits from Vectornaut/dyna3:diagnostics into main 2025-07-21 04:18:50 +00:00
Member

Built on top of pull request #91.

Primary changes

To facilitate engine development in the application prototype, add a Diagnostics component that shows the following diagnostics from the last realization:

  • Confirmation of success or a short description of what failed.
  • The value of the loss function at each step.
  • The spectrum of the Hessian at each step.

The loss and spectrum plots are shown on switchable panels, which are designed with extensibility in mind. When we're tinkering with the engine, we should be able to add or swap out diagnostics panels as needed.

Secondary changes

Encapsulate realization results and diagnostics in the new RealizationResult structure. In the process, spruce up our realization diagnostics logging and factor out some of the repetitive code in the examples, because we're already changing those parts of the code to adapt them to the new encapsulation.

This change, carried out in commit d4302d2, affects the output format of the Cargo examples. I've checked by hand that the output is rearranged but not meaningfully changed.

*Built on top of pull request #91.* ## Primary changes To facilitate engine development in the application prototype, add a `Diagnostics` component that shows the following diagnostics from the last realization: - Confirmation of success or a short description of what failed. - The value of the loss function at each step. - The spectrum of the Hessian at each step. The loss and spectrum plots are shown on switchable panels, which are designed with extensibility in mind. When we're tinkering with the engine, we should be able to add or swap out diagnostics panels as needed. ## Secondary changes Encapsulate realization results and diagnostics in the new `RealizationResult` structure. In the process, spruce up our realization diagnostics logging and factor out some of the repetitive code in the examples, because we're already changing those parts of the code to adapt them to the new encapsulation. This change, carried out in commit d4302d2, affects the output format of the Cargo examples. I've checked by hand that the output is rearranged but not meaningfully changed.
Vectornaut added 8 commits 2025-06-11 19:17:09 +00:00
Update Sycamore to 0.9.1
All checks were successful
/ test (pull_request) Successful in 2m30s
a4d081f684
In the process, spruce up our realization diagnostics logging and factor
out some of the repetitive code in the examples, because we're already
changing those parts of the code to adapt them to the new encapsulation.

This commit changes the example output format. I've checked by hand that
the output is rearranged but not meaningfully changed.
This introduces a dependency on the Charming crate, which we use to plot
the loss history, and the ECharts JavaScript library, which Charming
depends on.

Now that there's more than one canvas on the page, we have to pick out
the display by ID rather than by element type in our style sheet.
Add a spectrum history panel
All checks were successful
/ test (pull_request) Successful in 3m36s
0be7448e24
This introduces a framework for adding more diagnostics panels.
Vectornaut added 1 commit 2025-06-17 21:08:21 +00:00
Add data zoom controls to the diagnostics charts
All checks were successful
/ test (pull_request) Successful in 3m50s
99136498c7
Vectornaut changed title from feat: Engine diagnostics to WIP: feat: Engine diagnostics 2025-06-25 17:02:59 +00:00
Vectornaut added 1 commit 2025-06-25 19:57:39 +00:00
Switch to log scale for diagnostics charts
All checks were successful
/ test (pull_request) Successful in 3m40s
fa39a9a97d
Split the spectrum of the Hessian into its positive and negative parts
and plot them on the same logarithmic axis. The data zoom controls are
still linearly scaled, as discussed in ECharts issue #20927

  https://github.com/apache/echarts/issues/20927
Author
Member

I decided to include the scale change we discussed on Monday in this pull request. It's implemented in commit 2688b76 (formerly fa39a9a). As proposed, the Spectrum chart now splits the spectrum of the Hessian into its positive and negative parts and plots them on the same logarithmic axis, throwing away eigenvalues whose sizes are below a certain threshold (currently 10^{-6}). The data zoom controls are still linearly scaled, as discussed in ECharts issue #20927.

I decided to include the scale change we discussed on Monday in this pull request. It's implemented in commit 2688b76 (formerly fa39a9a). As proposed, the *Spectrum* chart now splits the spectrum of the Hessian into its positive and negative parts and plots them on the same logarithmic axis, throwing away eigenvalues whose sizes are below a certain threshold (currently $10^{-6}$). The data zoom controls are still linearly scaled, as discussed in [ECharts issue #20927](https://github.com/apache/echarts/issues/20927).
Vectornaut changed title from WIP: feat: Engine diagnostics to feat: Engine diagnostics 2025-06-25 20:03:50 +00:00
Owner

@Vectornaut wrote in #92 (comment):

throwing away eigenvalues whose sizes are below a certain threshold (currently 10^{-6} ).

This is to protect us from trying to display exactly-zero eigenvalues, that have no log? Should you in some way display the cutoff and the number of elided eigenvalues? (We are interested in the dimensionality of the solution variety, which is connected with that number, at least at a true solution, if I am not mistaken.)

@Vectornaut wrote in https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/92#issuecomment-2989: > throwing away eigenvalues whose sizes are below a certain threshold (currently \(10^{-6}\) ). This is to protect us from trying to display exactly-zero eigenvalues, that have no log? Should you in some way display the cutoff and the number of elided eigenvalues? (We are interested in the dimensionality of the solution variety, which is connected with that number, at least at a true solution, if I am not mistaken.)
Author
Member

This is to protect us from trying to display exactly-zero eigenvalues, that have no log?

Yes. Trying to display eigenvalue which are too close to zero seems to throw off the scaling procedure that ECharts uses, leading to a lot of eigenvalues being cut out of the chart window.

Should you in some way display the cutoff and the number of elided eigenvalues?

I was considering adding a component that would count the too-close-to-zero eigenvalues, and I could try implementing it if you'd like.

(We are interested in the dimensionality of the solution variety, which is connected with that number, at least at a true solution, if I am not mistaken.)

True, but the rule we use to classify eigenvalues as zero when computing the tangent space is different from the one I'm using here to avoid chart scaling problems, and I could imagine them becoming even more different in the future. For example, if we switch to a rule that involves looking for a gap in the eigenvalue distribution, that would probably allow eigenvalues to be declared nonzero while still being small enough to throw off the chart scaling.

> This is to protect us from trying to display exactly-zero eigenvalues, that have no log? Yes. Trying to display eigenvalue which are too close to zero seems to throw off the scaling procedure that ECharts uses, leading to a lot of eigenvalues being cut out of the chart window. > Should you in some way display the cutoff and the number of elided eigenvalues? I was considering adding a component that would count the too-close-to-zero eigenvalues, and I could try implementing it if you'd like. > (We are interested in the dimensionality of the solution variety, which is connected with that number, at least at a true solution, if I am not mistaken.) True, but the rule we use to classify eigenvalues as zero when computing the tangent space is different from the one I'm using here to avoid chart scaling problems, and I could imagine them becoming even more different in the future. For example, if we switch to a rule that involves looking for a gap in the eigenvalue distribution, that would probably allow eigenvalues to be declared nonzero while still being small enough to throw off the chart scaling.
Owner

@Vectornaut wrote in #92 (comment):

Should you in some way display the cutoff and the number of elided eigenvalues?

I was considering adding a component that would count the too-close-to-zero eigenvalues, and I could try implementing it if you'd like.

I guess I had imagined something like plotting a numeral at or just below the cutoff spot in the eigenvalue display, rather than a separate component. If that's relatively easy with the current code, then sure, go ahead and put it in this PR. If it would be involved and you'd rather defer, let me know and I will file an issue after this is merged.

As for the difference between a "too-small-to-plot" eigenvalue and a "inferred-to-be-zero" eigenvalue (for the sake of the tangent space), it would be nice at some point to coalesce them. I don't see any reason that we couldn't other than shortcomings to the plotting software, given that we are on a log scale anyway. (E.g., if we're using a wonky log-scale feature of the plotting software at the moment, maybe we could just take the logs ourselves and give the data to the plotter as log values to be plotted linearly?) Anyhow, I am not fussed by this discrepancy at the moment -- happy to just file a low-priority issue about it once this is merged.

@Vectornaut wrote in https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/92#issuecomment-2992: > > Should you in some way display the cutoff and the number of elided eigenvalues? > > I was considering adding a component that would count the too-close-to-zero eigenvalues, and I could try implementing it if you'd like. I guess I had imagined something like plotting a numeral at or just below the cutoff spot in the eigenvalue display, rather than a separate component. If that's relatively easy with the current code, then sure, go ahead and put it in this PR. If it would be involved and you'd rather defer, let me know and I will file an issue after this is merged. As for the difference between a "too-small-to-plot" eigenvalue and a "inferred-to-be-zero" eigenvalue (for the sake of the tangent space), it would be nice at some point to coalesce them. I don't see any reason that we couldn't other than shortcomings to the plotting software, given that we are on a log scale anyway. (E.g., if we're using a wonky log-scale feature of the plotting software at the moment, maybe we could just take the logs ourselves and give the data to the plotter as log values to be plotted linearly?) Anyhow, I am not fussed by this discrepancy at the moment -- happy to just file a low-priority issue about it once this is merged.
Vectornaut added 1 commit 2025-06-25 22:45:48 +00:00
Map diagnostics chart series to log scale by hand
All checks were successful
/ test (pull_request) Successful in 3m38s
c2c1e4a06d
This works around problems with the way ECharts does scaling and data
zoom for logarithmic axes.
Author
Member

(E.g., if we're using a wonky log-scale feature of the plotting software at the moment, maybe we could just take the logs ourselves and give the data to the plotter as log values to be plotted linearly?)

This turns out to be a nice workaround for both the scaling problem and the problem with data zoom controls being linear. I've implemented it in commit e877985 (formerly c2c1e4a). Now all eigenvalues are displayed except ones that are exactly zero (which I do think I've seen occasionally).

Is it worth trying to show the presence of exact zero eigenvalues? I could do it by adding an extra axis to the chart, but I'm not convinced it'd be worth the area it would take up.

> (E.g., if we're using a wonky log-scale feature of the plotting software at the moment, maybe we could just take the logs ourselves and give the data to the plotter as log values to be plotted linearly?) This turns out to be a nice workaround for both the scaling problem and the problem with data zoom controls being linear. I've implemented it in commit e877985 (formerly c2c1e4a). Now all eigenvalues are displayed except ones that are exactly zero (which I do think I've seen occasionally). Is it worth trying to show the presence of exact zero eigenvalues? I could do it by adding an extra axis to the chart, but I'm not convinced it'd be worth the area it would take up.
Owner

@Vectornaut wrote in #92 (comment):

Is it worth trying to show the presence of exact zero eigenvalues? I could do it by adding an extra axis to the chart, but I'm not convinced it'd be worth the area it would take up.

Well, the easiest way would be to show a symbol (or numeral giving multiplicity) in a different color or something like that, at some reasonable offset below the smallest nonzero eigenvalue. In other words, if the logs of the nonzero eigenvalues ranged from 3 to -7, you could put them at something like -10 but displayed in a way that distinguishes them. Or you could put a small text caption somewhere like "And 2 machine-zero values". I will leave it up to you whether to bother with something like that in this PR. I agree it's not worth an extra axis.

@Vectornaut wrote in https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/92#issuecomment-2995: > Is it worth trying to show the presence of exact zero eigenvalues? I could do it by adding an extra axis to the chart, but I'm not convinced it'd be worth the area it would take up. Well, the easiest way would be to show a symbol (or numeral giving multiplicity) in a different color or something like that, at some reasonable offset below the smallest nonzero eigenvalue. In other words, if the logs of the nonzero eigenvalues ranged from 3 to -7, you could put them at something like -10 but displayed in a way that distinguishes them. Or you could put a small text caption somewhere like "And 2 machine-zero values". I will leave it up to you whether to bother with something like that in this PR. I agree it's not worth an extra axis.
Owner

I guess we don't need an on-screen or README/other doc-file reminder that the positive eigenvalues are the blue dots and the negative ones the green squares? (I guessed from the distribution of the problem I tried, but then looked it up in the code to be sure...)

I guess we don't need an on-screen or README/other doc-file reminder that the positive eigenvalues are the blue dots and the negative ones the green squares? (I guessed from the distribution of the problem I tried, but then looked it up in the code to be sure...)
Vectornaut added 1 commit 2025-06-26 01:58:15 +00:00
Add a strictly-zero series to the spectrum history
All checks were successful
/ test (pull_request) Successful in 3m36s
4765acf6b9
Author
Member

Well, the easiest way would be to show a symbol (or numeral giving multiplicity) in a different color or something like that, at some reasonable offset below the smallest nonzero eigenvalue.

Done in commit 68d6cc1 (formerly 4765acf).

> Well, the easiest way would be to show a symbol (or numeral giving multiplicity) in a different color or something like that, at some reasonable offset below the smallest nonzero eigenvalue. Done in commit 68d6cc1 (formerly 4765acf).
Author
Member

I guess we don't need an on-screen or README/other doc-file reminder that the positive eigenvalues are the blue dots and the negative ones the green squares?

I could start a wiki page that briefly explains the diagnostics. (Right now, the series colors are chosen automatically, so it might be best to refer only to the series shapes.)

> I guess we don't need an on-screen or README/other doc-file reminder that the positive eigenvalues are the blue dots and the negative ones the green squares? I could start a wiki page that briefly explains the diagnostics. (Right now, the series colors are chosen automatically, so it might be best to refer only to the series shapes.)
Owner

@Vectornaut wrote in #92 (comment):

Well, the easiest way would be to show a symbol (or numeral giving multiplicity) in a different color or something like that, at some reasonable offset below the smallest nonzero eigenvalue.

Done (commit 4765acf).

Do you by any chance have an assembly I can set up that exercises this?

@Vectornaut wrote in https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/92#issuecomment-2999: > > Well, the easiest way would be to show a symbol (or numeral giving multiplicity) in a different color or something like that, at some reasonable offset below the smallest nonzero eigenvalue. > > Done (commit [`4765acf`](/StudioInfinity/dyna3/commit/4765acf)). Do you by any chance have an assembly I can set up that exercises this?
Author
Member

Do you by any chance have an assembly I can set up that exercises this?

Yes—strictly-zero eigenvalues actually seem to turn up often. As an example, go to the General test assembly, set the inversive distance between Castor and Pollux to 0.5, and look at the spectrum history. You'll see strictly-zero eigenvalues at every step of the realization process. I've confirmed that this is accurate by printing the eigenvalues at each step.

> Do you by any chance have an assembly I can set up that exercises this? Yes—strictly-zero eigenvalues actually seem to turn up often. As an example, go to the *General* test assembly, set the inversive distance between Castor and Pollux to 0.5, and look at the spectrum history. You'll see strictly-zero eigenvalues at every step of the realization process. I've confirmed that this is accurate by printing the eigenvalues at each step.
Vectornaut force-pushed diagnostics from 4765acf6b9 to 68d6cc1645 2025-06-27 05:46:08 +00:00 Compare
Author
Member

The incoming branch is now rebased onto the newly merged #91, so it's ready to merge, pending review!

The incoming branch is now rebased onto the newly merged #91, so it's ready to merge, pending review!
Owner

The file name app-proto/examples/common/mod.rs (well, the "mod.rs" part specifically) is inscrutable to me. Please could you rename it to something more descriptive? It looks something like "common/diagnostic.rs" or "common/log.rs" or "common/display.rs" or something, but perhaps I've misunderstood -- I really couldn't think what word "mod" was supposed to be short for. Thanks! (I just started reading the code, so there may be other comments.)

The file name `app-proto/examples/common/mod.rs` (well, the "mod.rs" part specifically) is inscrutable to me. Please could you rename it to something more descriptive? It looks something like "common/diagnostic.rs" or "common/log.rs" or "common/display.rs" or something, but perhaps I've misunderstood -- I really couldn't think what word "mod" was supposed to be short for. Thanks! (I just started reading the code, so there may be other comments.)
Owner

The file
app-proto/run-examples has gotten more code-y in this PR, begging the question as to what command interpreter is intended to run it. Please rename it to make that clear from the file name, e.g. "run-examples.sh" or "run-examples.bash" or something. Thanks!

The file `app-proto/run-examples` has gotten more code-y in this PR, begging the question as to what command interpreter is intended to run it. Please rename it to make that clear from the file name, e.g. "run-examples.sh" or "run-examples.bash" or something. Thanks!
Author
Member

@glen wrote in #92 (comment):

The file name app-proto/examples/common/mod.rs (well, the "mod.rs" part specifically) is inscrutable to me. Please could you rename it to something more descriptive? It looks something like "common/diagnostic.rs" or "common/log.rs" or "common/display.rs" or something, but perhaps I've misunderstood -- I really couldn't think what word "mod" was supposed to be short for. Thanks! (I just started reading the code, so there may be other comments.)

Putting a file called mod.rs in a source directory is one way to create a module. However, Rust has apparently introduced a new module filename system which is more readable, so I'll switch to that!

@glen wrote in https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/92#issuecomment-3014: > The file name `app-proto/examples/common/mod.rs` (well, the "mod.rs" part specifically) is inscrutable to me. Please could you rename it to something more descriptive? It looks something like "common/diagnostic.rs" or "common/log.rs" or "common/display.rs" or something, but perhaps I've misunderstood -- I really couldn't think what word "mod" was supposed to be short for. Thanks! (I just started reading the code, so there may be other comments.) Putting a file called `mod.rs` in a source directory is [one way to create a module](https://doc.rust-lang.org/reference/items/modules.html#r-items.mod.outlined.search-mod). However, Rust has apparently introduced a [new module filename system](https://doc.rust-lang.org/reference/items/modules.html#module-source-filenames) which is more readable, so I'll switch to that!
glen reviewed 2025-07-15 19:32:48 +00:00
glen left a comment
Owner

Otherwise not much, as this is mostly new display code with a bit of bookkeeping. Just some naming questions.

Otherwise not much, as this is mostly new display code with a bit of bookkeeping. Just some naming questions.
@ -28,0 +27,4 @@
ConstraintProblem,
DescentHistory,
Realization,
RealizationResult
Owner

Does Rust allow a final comma here? If so, please include one, to avoid in the future the semantically spurious first line of diff ("ConstraintProblem "-> "ConstraintProblem,")

Does Rust allow a final comma here? If so, please include one, to avoid in the future the semantically spurious first line of diff ("ConstraintProblem "-> "ConstraintProblem,")
Author
Member

Yes, the MIT comma is allowed in Rust, and recommended in the Rust style guide. I started writing the app prototype without trailing commas, with the intention of evaluating how annoying that style is, and by now I'm convinced that trailing commas would be nicer. Could we keep this pull request consistent with the current style and then do a small pull request that adds trailing commas throughout the code?

Yes, the [MIT comma](https://xkcd.com/2995/) is allowed in Rust, and [recommended](https://doc.rust-lang.org/beta/style-guide/index.html#trailing-commas) in the Rust style guide. I started writing the app prototype without trailing commas, with the intention of evaluating how annoying that style is, and by now I'm convinced that trailing commas would be nicer. Could we keep this pull request consistent with the current style and then do a small pull request that adds trailing commas throughout the code?
Owner

OK, fine, file an issue to do it (label "maintenance").

OK, fine, file an issue to do it (label "maintenance").
glen marked this conversation as resolved
@ -559,1 +566,3 @@
elements_by_id: create_signal(BTreeMap::default())
elements_by_id: create_signal(BTreeMap::default()),
realization_status: create_signal(Ok(())),
descent_history: create_signal(DescentHistory::new())
Owner

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

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

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

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.
glen marked this conversation as resolved
@ -396,0 +398,4 @@
pub tangent: ConfigSubspace
}
pub struct RealizationResult {
Owner

The difference between a "Realization" and a "RealizationResult" is not at all clear to me. I guess that this is some Rust-ism, in that a Result is some customary wrapper around a value, in this case a Realization -- is that right? Do you think the two names are completely idiomatic in a Rust world? Or do you think there might be some plain-english names that would better convey what's going on? (Intuitively to me, it would seem a Realization would be the natural outcome of trying to realize an Assembly...)

The difference between a "Realization" and a "RealizationResult" is not at all clear to me. I guess that this is some Rust-ism, in that a Result is some customary wrapper around a value, in this case a Realization -- is that right? Do you think the two names are completely idiomatic in a Rust world? Or do you think there might be some plain-english names that would better convey what's going on? (Intuitively to me, it would seem a Realization would be the natural outcome of trying to realize an Assembly...)
Author
Member

Intuitively to me, it would seem a Realization would be the natural outcome of trying to realize an Assembly...

Trying to realize an assembly doesn't always produce a realization (because it can fail), and it also produces additional data (the descent history and other diagnostic information). Are you hoping for a better way to organize this data, or just a more descriptive name for the data structure? If the latter, what do you think of RealizationOutcome or RealizationOutput?

I guess that this is some Rust-ism, in that a Result is some customary wrapper around a value, in this case a Realization -- is that right? Do you think the two names are completely idiomatic in a Rust world?

No, I don't think the current naming is idiomatic. In fact, I find it a little misleading, because I'd expect something called RealizationResult to just be or contain a Result<Realization, _>, with no additional data.

> Intuitively to me, it would seem a Realization would be the natural outcome of trying to realize an Assembly... Trying to realize an assembly doesn't always produce a realization (because it can fail), and it also produces additional data (the descent history and other diagnostic information). Are you hoping for a better way to organize this data, or just a more descriptive name for the data structure? If the latter, what do you think of `RealizationOutcome` or `RealizationOutput`? > I guess that this is some Rust-ism, in that a Result is some customary wrapper around a value, in this case a Realization -- is that right? Do you think the two names are completely idiomatic in a Rust world? No, I don't think the current naming is idiomatic. In fact, I find it a little misleading, because I'd expect something called `RealizationResult` to just be or contain a `Result<Realization, _>`, with no additional data.
Owner

OK, so currently a Realization is just a set of coordinates for everything, is that right? The other gadget is all the stuff that can be produced from a call to a "realize" function that tries to create a Realization that satisfies all the constraints, but (a) might fail, and (b) produces additional info, correct?

So yes, I guess all I am looking for are better names for these two things. Here are some options:

(a) What is currently called a Realization should perhaps just be called a Configuration -- if it is just coordinates, there's no guarantee that this data structure "realizes" any particular set of constraints. That would free up "Realization" to be, quite naturally, the result of calling realize() -- and such a thing would naturally include a Configuration if it managed to find one. (But again, I don't quite understand why it wouldn't always include a Configuration (the best it could do) and a flag to tell you whether this configuration actually realized all the constraints.)

(b) If you feel strongly that the collection of coordinates thing should absolutely be called a Realization (although to me that only makes sense if there is something inherent in the data structure that ensures its contents realize some specific constraings), then the gadget returned by having the engine try to realize() could be named something altogether different, like "EngineRun" or "EngineData."

I guess what I am mostly saying is to choose quite distinct stem words for the two structures, to reflect the fact that this is a containment relationship, rather than an is-a or flavor-of relationship: an output-of-realize gadget optionally contains one of the collection-of-coordinates things, but it is an altogether different entity.

Hopefully I am making sense.

OK, so currently a Realization is just a set of coordinates for everything, is that right? The other gadget is all the stuff that can be produced from a call to a "realize" function that tries to create a Realization that satisfies all the constraints, but (a) might fail, and (b) produces additional info, correct? So yes, I guess all I am looking for are better names for these two things. Here are some options: (a) What is currently called a Realization should perhaps just be called a Configuration -- if it is just coordinates, there's no guarantee that this data structure "realizes" any particular set of constraints. That would free up "Realization" to be, quite naturally, the result of calling `realize()` -- and such a thing would naturally include a Configuration if it managed to find one. (But again, I don't quite understand why it wouldn't _always_ include a Configuration (the best it could do) and a flag to tell you whether this configuration actually realized all the constraints.) (b) If you feel strongly that the collection of coordinates thing should absolutely be called a Realization (although to me that only makes sense if there is something inherent in the data structure that ensures its contents realize some specific constraings), then the gadget returned by having the engine try to realize() could be named something altogether different, like "EngineRun" or "EngineData." I guess what I am mostly saying is to choose quite distinct stem words for the two structures, to reflect the fact that this is a containment relationship, rather than an is-a or flavor-of relationship: an output-of-realize gadget optionally contains one of the collection-of-coordinates things, but it is an altogether different entity. Hopefully I am making sense.
Author
Member

What is currently called a Realization should perhaps just be called a Configuration -- if it is just coordinates, there's no guarantee that this data structure "realizes" any particular set of constraints. That would free up "Realization" to be, quite naturally, the result of calling realize() -- and such a thing would naturally include a Configuration if it managed to find one.

That makes the most sense to me! The structures would become something like this:

pub struct Configuration {
    pub config: DMatrix<f64>, /* rename? */
    pub tangent: ConfigSubspace
}

pub struct Realization {
    pub config: Result<Configuration, String>,
    pub history: DescentHistory
}
> What is currently called a Realization should perhaps just be called a Configuration -- if it is just coordinates, there's no guarantee that this data structure "realizes" any particular set of constraints. That would free up "Realization" to be, quite naturally, the result of calling `realize()` -- and such a thing would naturally include a Configuration if it managed to find one. That makes the most sense to me! The structures would become something like this: ```rust pub struct Configuration { pub config: DMatrix<f64>, /* rename? */ pub tangent: ConfigSubspace } pub struct Realization { pub config: Result<Configuration, String>, pub history: DescentHistory } ```
Author
Member

During our meeting, we decided to name the structures like this:

pub type Configuration = DMatrix<f64>;

// a first-order neighborhood of a configuration
pub struct ConfigNeighborhood {
    pub config: Configuration
    pub neighborhood: ConfigSubspace
}

pub struct Realization {
    pub config_neighborhood: Result<ConfigNeighborhood, String>,
    pub history: DescentHistory
}

In the future, we might want the engine to return a best guess even if realization fails. Ways to do that include:

  • Splitting config into a ConfigNeighborhood and a success indicator.
  • Changing the type of config to something like this:
    Result<ConfigNeighborhood, (ConfigNeighborhood, String)>
    
    The ConfigNeighborhood in the Err variant would be the best guess.
During our meeting, we decided to name the structures like this: ```rust pub type Configuration = DMatrix<f64>; // a first-order neighborhood of a configuration pub struct ConfigNeighborhood { pub config: Configuration pub neighborhood: ConfigSubspace } pub struct Realization { pub config_neighborhood: Result<ConfigNeighborhood, String>, pub history: DescentHistory } ``` In the future, we might want the engine to return a best guess even if realization fails. Ways to do that include: - Splitting `config` into a `ConfigNeighborhood` and a success indicator. - Changing the type of `config` to something like this: ```rust Result<ConfigNeighborhood, (ConfigNeighborhood, String)> ``` The `ConfigNeighborhood` in the `Err` variant would be the best guess.
Owner

Two thoughts:

  • In the new Realization structure, the ConfigNeighborhood member could be called "outcome" or "output" or something -- it's name needn't just reiterate its type.

  • In the possible "best guess" future world, we may still want to allow the engine sometimes to throw up its hands completely. So that would mean the two ways you suggest would become having the outcome be an Option<ConfigNeighboorhood> together with a success indicator, or having it be something like Result<ConfigNeighboorhood, (Option<ConfigNeighborhood>, String). But either way (optional best guess or mandatory best guess), having a ConfigNeighborhood on both sides of the Result feels a bit redundant.

Two thoughts: * In the new Realization structure, the ConfigNeighborhood member could be called "outcome" or "output" or something -- it's name needn't just reiterate its type. * In the possible "best guess" future world, we may still want to allow the engine sometimes to throw up its hands completely. So that would mean the two ways you suggest would become having the outcome be an `Option<ConfigNeighboorhood>` together with a success indicator, or having it be something like `Result<ConfigNeighboorhood, (Option<ConfigNeighborhood>, String)`. But either way (optional best guess or mandatory best guess), having a ConfigNeighborhood on both sides of the Result _feels_ a bit redundant.
Author
Member

In the new Realization structure, the ConfigNeighborhood member could be called "outcome" or "output" or something -- it's name needn't just reiterate its type.

How about keeping the name result? This both hints that it has a Result type and accurately describes that it's the main result of trying to realize (in contrast to the extra data in history). To me, it feels similar to outcome, but a little better because it provides the type hint.

> In the new Realization structure, the ConfigNeighborhood member could be called "outcome" or "output" or something -- it's name needn't just reiterate its type. How about keeping the name `result`? This both hints that it has a `Result` type and accurately describes that it's the main result of trying to realize (in contrast to the extra data in `history`). To me, it feels similar to `outcome`, but a little better because it provides the type hint.
Owner

Yup, result is fine by me.

Yup, result is fine by me.
Author
Member

Renaming done (commit f1865f8). I decided not to add the type alias Configuration for DMatrix<f64>, as discussed below

Renaming done (commit f1865f8). I decided not to add the type alias `Configuration` for `DMatrix<f64>`, as discussed [below](#issuecomment-3044)
Owner

These names seem fine, so closing, but note I did raise one question about the name of a member of ConfigNeighborhood.

These names seem fine, so closing, but note I did raise one question about the name of a member of ConfigNeighborhood.
glen marked this conversation as resolved
Owner

(in case it wasn't clear I've finished this round of review ;-)

(in case it wasn't clear I've finished this round of review ;-)
Author
Member

The file name app-proto/examples/common/mod.rs (well, the "mod.rs" part specifically) is inscrutable to me.

[…] Rust has apparently introduced a new module filename system which is more readable, so I'll switch to that!

Whoops: it looks like we can't use the new module filename system here, because when I put a file called common.rs in the examples directory, Rust thinks it's an example! Should I try to find a workaround, or just keep using the old module filename system?

The shared crate technique from this StackOverflow answer is one possible workaround, but I'd feel weird creating a whole crate for a few lines of common code.

> > > The file name `app-proto/examples/common/mod.rs` (well, the "mod.rs" part specifically) is inscrutable to me. > > […] Rust has apparently introduced a [new module filename system](https://doc.rust-lang.org/reference/items/modules.html#module-source-filenames) which is more readable, so I'll switch to that! Whoops: it looks like we can't use the new module filename system here, because when I put a file called `common.rs` in the `examples` directory, Rust thinks it's an example! Should I try to find a workaround, or just keep using the old module filename system? The shared crate technique from [this StackOverflow answer](https://stackoverflow.com/a/44545091) is one possible workaround, but I'd feel weird creating a whole crate for a few lines of common code.
Vectornaut added 1 commit 2025-07-16 05:21:58 +00:00
Give the run-examples script a shell extension
All checks were successful
/ test (pull_request) Successful in 3m43s
2137284358
Also make the script non-executable, so it has to be run with a command
like `sh run-examples.sh`. This makes the shebang line superfluous.
Author
Member

The file app-proto/run-examples has gotten more code-y in this PR, begging the question as to what command interpreter is intended to run it. Please rename it to make that clear from the file name, e.g. "run-examples.sh" or "run-examples.bash" or something. Thanks!

Commit 2137284 gives the script a .sh extension and makes it non-executable, so it has to be run with a command
like sh run-examples.sh. This commit also removes the shebang line, which I'd expect to be irrelevant for non-executable scripts.

> The file `app-proto/run-examples` has gotten more code-y in this PR, begging the question as to what command interpreter is intended to run it. Please rename it to make that clear from the file name, e.g. "run-examples.sh" or "run-examples.bash" or something. Thanks! Commit 2137284 gives the script a `.sh` extension and makes it non-executable, so it has to be run with a command like `sh run-examples.sh`. This commit also removes the shebang line, which I'd expect to be irrelevant for non-executable scripts.
Owner

@Vectornaut wrote in #92 (comment):

Whoops: it looks like we can't use the new module filename system here, because when I put a file called common.rs in the examples directory, Rust thinks it's an example! Should I try to find a workaround, or just keep using the old module filename system?

Umm I do not fully understand what is going on here, but yes, we need sane names (i.e. not "mod.rs" for a file that has logging functions). Why does the common code have to be in the examples directory at all? Or conversely, why does it need to be a "module", whatever exactly that is in Rust?

@Vectornaut wrote in https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/92#issuecomment-3026: > Whoops: it looks like we can't use the new module filename system here, because when I put a file called `common.rs` in the `examples` directory, Rust thinks it's an example! Should I try to find a workaround, or just keep using the old module filename system? Umm I do not fully understand what is going on here, but yes, we need sane names (i.e. not "mod.rs" for a file that has logging functions). Why does the common code have to be in the examples directory at all? Or conversely, why does it need to be a "module", whatever exactly that is in Rust?
Author
Member

yes, we need sane names (i.e. not "mod.rs" for a file that has logging functions).

People who stare at Rust source trees all day will read the filename as common/mod.rs, which translates as "the top level of the common module" in the old module filename system. Of course, knowing that doesn't make the name any more legible to anyone else.

Why does the common code have to be in the examples directory at all?

The Rust documentation suggests this way of sharing code among Cargo integration tests, and it also seems to be widely used for Cargo examples, which have the same directory structure. There are other ways to accomplish this, and some of them might allow more legible naming, but they have their own disadvantages, described at the end of this comment.

Or conversely, why does it need to be a "module", whatever exactly that is in Rust?

A review of modules

In the dyna3 src directory, the files

  • add_remove.rs
  • assembly.rs
  • diagnostics.rs
  • display.rs
  • engine.rs
  • outline.rs
  • specified.rs

each define a module. These modules are declared by the expressions

  • mod add_remove
  • mod specified

at the top of main.rs. As you've probably picked up through code review, modules define namespaces, control code visibility, and help factor out shared code.

Modules in integration tests and examples

When it comes to sharing code among Cargo examples, I see the following advantages to putting shared code in a submodule in the examples directory:

  • The shared code is near the examples in the source tree.
    • All of the alternate approaches described here put the shared code outside the examples directory. That could be annoying for someone who's looking at the examples and trying to find the shared code. It could also be confusing for someone who's looking at the shared code and wondering what it's for.
  • The shared code is outside the application source directory.
    • When the shared code is specific to the examples, and irrelevant to the application—as I think it is in our case—it seems weird to lump it in with the application code.
    • The "test-only module" technique described here uses extra flags to make sure the shared code is only compiled when we build the examples. I don't know if it's possible to work around that.
  • The shared code is factored out in a familiar way.
    • We might be able to share code with the include! macro, but the macro documentation explicitly discourages this usage; it recommends using modules instead.
  • For Rust developers, the shared code is easy to recognize, because it's laid out in a familiar way.
    • It's a way the Rust documentation suggests, as mentioned above.

If you think the potential for better naming outweighs these advantages, I'll look more into the alternate approaches I've been comparing to.

> yes, we need sane names (i.e. not "mod.rs" for a file that has logging functions). People who stare at Rust source trees all day will read the filename as `common/mod.rs`, which translates as "the top level of the `common` module" in the old module filename system. Of course, knowing that doesn't make the name any more legible to anyone else. > Why does the common code have to be in the examples directory at all? The Rust documentation suggests this way of sharing code among [Cargo integration tests](https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-tests), and it also seems to be widely used for [Cargo examples](https://stackoverflow.com/a/79131054), which have the same directory structure. There are other ways to accomplish this, and some of them might allow more legible naming, but they have their own disadvantages, described at the end of this comment. > Or conversely, why does it need to be a "module", whatever exactly that is in Rust? #### A review of modules In the dyna3 `src` directory, the files - `add_remove.rs` - `assembly.rs` - `diagnostics.rs` - `display.rs` - `engine.rs` - `outline.rs` - `specified.rs` each define a module. These modules are declared by the expressions - `mod add_remove` - ⋮ - `mod specified` at the top of `main.rs`. As you've probably picked up through code review, modules define namespaces, control code visibility, and help factor out shared code. #### Modules in integration tests and examples When it comes to sharing code among Cargo examples, I see the following advantages to putting shared code in a submodule in the `examples` directory: - The shared code is near the examples in the source tree. - All of the alternate approaches described [here](https://stackoverflow.com/a/44545091) put the shared code outside the examples directory. That could be annoying for someone who's looking at the examples and trying to find the shared code. It could also be confusing for someone who's looking at the shared code and wondering what it's for. - The shared code is outside the application source directory. - When the shared code is specific to the examples, and irrelevant to the application—as I think it is in our case—it seems weird to lump it in with the application code. - The "test-only module" technique described [here](https://stackoverflow.com/a/44545091) uses extra flags to make sure the shared code is only compiled when we build the examples. I don't know if it's possible to work around that. - The shared code is factored out in a familiar way. - We might be able to share code with the `include!` macro, but the macro documentation explicitly [discourages](https://doc.rust-lang.org/std/macro.include.html) this usage; it recommends using modules instead. - For Rust developers, the shared code is easy to recognize, because it's laid out in a familiar way. - It's a way the Rust documentation [suggests](https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-tests), as mentioned above. If you think the potential for better naming outweighs these advantages, I'll look more into the alternate approaches I've been comparing to.
Owner

At the last meeting, we agreed to put the common code for examples where it makes the most sense, and just do whatever's needed in the Rust syntax for it to locate the code where we've put it. In particular, the filename will be something like ...examples/common/print.rs. We also settled on names Configuration for a raw collection of coordinates for everything, ConfigurationNeighborhood for that plus a subspace of the infinitesimal motions at that position, and Realization for a Result containing that and an optimization history.

At the last meeting, we agreed to put the common code for examples where it makes the most sense, and just do whatever's needed in the Rust syntax for it to locate the code where we've put it. In particular, the filename will be something like `...examples/common/print.rs`. We also settled on names Configuration for a raw collection of coordinates for everything, ConfigurationNeighborhood for that plus a subspace of the infinitesimal motions at that position, and Realization for a Result containing that and an optimization history.
Vectornaut added 1 commit 2025-07-18 18:13:58 +00:00
Reorganize the shared example code
All checks were successful
/ test (pull_request) Successful in 3m36s
477d6a5064
The new layout deviates from what the Rust book suggests

  https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-tests

and uses the frowned-upon `#[path]` attribute,

  https://doc.rust-lang.org/style-guide/advice.html#modules

but we've decided that having a descriptive module filename instead of
`mod.rs` is worth the cost.
Author
Member

@glen wrote in #92 (comment):

At the last meeting, we agreed to put the common code for examples where it makes the most sense, and just do whatever's needed in the Rust syntax for it to locate the code where we've put it. In particular, the filename will be something like ...examples/common/print.rs.

This has now been implemented in commit 477d6a5, with a commit message explaining why we're using the frowned-upon #[path] annotation.

@glen wrote in https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/92#issuecomment-3038: > At the last meeting, we agreed to put the common code for examples where it makes the most sense, and just do whatever's needed in the Rust syntax for it to locate the code where we've put it. In particular, the filename will be something like `...examples/common/print.rs`. This has now been implemented in commit 477d6a5, with a commit message explaining why we're using the [frowned-upon](https://doc.rust-lang.org/style-guide/advice.html#modules) `#[path]` annotation.
Vectornaut added 1 commit 2025-07-18 19:25:41 +00:00
Improve naming in realization output structures
All checks were successful
/ test (pull_request) Successful in 3m32s
f1865f85a1
Author
Member

@glen wrote in #92 (comment):

We also settled on names Configuration for a raw collection of coordinates for everything, ConfigurationNeighborhood for that plus a subspace of the infinitesimal motions at that position, and Realization for a Result containing that and an optimization history.

Done in commit f1865f8. I decided not to add the type alias Configuration for DMatrix<f64>, because the places where it seemed suitable (like ConfigurationNeighborhood and SearchState) and the places where it didn't (like ConfigSubspace and PartialMatrix) were closely intertwined, so it seemed confusing to use the alias in some places but not others.

The pull request should now be ready for the next round of review!

@glen wrote in https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/92#issuecomment-3038: > We also settled on names Configuration for a raw collection of coordinates for everything, ConfigurationNeighborhood for that plus a subspace of the infinitesimal motions at that position, and Realization for a Result containing that and an optimization history. Done in commit f1865f8. I decided not to add the type alias `Configuration` for `DMatrix<f64>`, because the places where it seemed suitable (like `ConfigurationNeighborhood` and `SearchState`) and the places where it didn't (like `ConfigSubspace` and `PartialMatrix`) were closely intertwined, so it seemed confusing to use the alias in some places but not others. The pull request should now be ready for the next round of review!
Owner

Checked out the latest commit in this PR. Then from top-level-directory:

> sh app-proto/run-examples.sh
error: could not find `Cargo.toml` in `/home/glen/code/dyna3` or any parent directory

error: could not find `Cargo.toml` in `/home/glen/code/dyna3` or any parent directory

error: could not find `Cargo.toml` in `/home/glen/code/dyna3` or any parent directory

error: could not find `Cargo.toml` in `/home/glen/code/dyna3` or any parent directory

Is it possible to make run-examples.sh work independent of the current directory when it is executed?

Checked out the latest commit in this PR. Then from top-level-directory: ``` > sh app-proto/run-examples.sh error: could not find `Cargo.toml` in `/home/glen/code/dyna3` or any parent directory error: could not find `Cargo.toml` in `/home/glen/code/dyna3` or any parent directory error: could not find `Cargo.toml` in `/home/glen/code/dyna3` or any parent directory error: could not find `Cargo.toml` in `/home/glen/code/dyna3` or any parent directory ``` Is it possible to make run-examples.sh work independent of the current directory when it is executed?
glen reviewed 2025-07-20 18:55:06 +00:00
@ -711,0 +714,4 @@
self.descent_history.set(history);
match result {
Ok(ConfigNeighborhood { config, nbhd: tangent }) => {
Owner

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

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

OK, just these last two questions/comments are all I see left for this PR at this point. Thanks!

OK, just these last two questions/comments are all I see left for this PR at this point. Thanks!
Author
Member

@glen wrote in #92 (comment):

Is it possible to make run-examples.sh work independent of the current directory when it is executed?

Yes, it should be! I'm not quite finished working out how, but I should be able to do it by Monday.

@glen wrote in https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/92#issuecomment-3060: > Is it possible to make run-examples.sh work independent of the current directory when it is executed? Yes, it should be! I'm not quite finished working out how, but I should be able to do it by Monday.
Vectornaut added 2 commits 2025-07-21 02:29:51 +00:00
Author
Member

@glen wrote in #92 (comment):

Is it possible to make run-examples.sh work independent of the current directory when it is executed?

Commit f8e9624 accomplishes this, as long as you invoke the script by calling sh or another interpreter rather than calling souce. This approach makes the repeated cargo call longer, so in commit edeb080 I've factored out the repeated call.

@glen wrote in https://code.studioinfinity.org/StudioInfinity/dyna3/pulls/92#issuecomment-3060: > Is it possible to make run-examples.sh work independent of the current directory when it is executed? Commit f8e9624 accomplishes this, as long as you invoke the script by calling `sh` or another interpreter rather than calling `souce`. This approach makes the repeated `cargo` call longer, so in commit edeb080 I've factored out the repeated call.
Owner

OK, works for me and looks fine. I get the feeling that you're not troubled by calling the tangent subspace nbhd in the struct and tangent in the place it's used (I'd pick one or the other and use it in both places, but it's not worth fussing over), so I am merging this. Thanks!

OK, works for me and looks fine. I get the feeling that you're not troubled by calling the tangent subspace nbhd in the struct and tangent in the place it's used (I'd pick one or the other and use it in both places, but it's not worth fussing over), so I am merging this. Thanks!
glen merged commit 5864017e6f into main 2025-07-21 04:18:50 +00:00
glen referenced this pull request from a commit 2025-07-21 04:18:52 +00:00
Sign in to join this conversation.
No description provided.