Simplify the realization triggering system #105
No reviewers
Labels
No labels
bug
design
duplicate
enhancement
maintenance
prospective
question
regression
stub
todo
ui
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: StudioInfinity/dyna3#105
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "Vectornaut/dyna3:reactive-realization-cleanup"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Overview
The incoming branch simplifies the system that reactively triggers realizations, at the cost of removing the preconditioning step described in issue #101 and doing unnecessary realizations after certain kinds of updates.
The new system should trigger a realization after any update that could affect the assembly's deformation space. For simplicity, any update to the regulator list triggers an update, even if it doesn't affect the set of constraints. In particular, adding a regulator triggers an unnecessary realization.
Future directions
Preconditioning
Issue #101 will remind us to replace the preconditioning functionality that this pull request removes.
Unnecessary realizations
The main cost of an unnecessary realization is probably recomputing the tangent space of the solution variety. If we keep the realization triggering system from this pull request, we should keep an eye on the cost of this computation and decide whether it's possible to speed it up, background it, or do it only as needed. We currently use or expect to use tangent space information for:
3eddef784b
toc73008d702
Simplify the realization triggering systemto WIP: Simplify the realization triggering systemWIP: Simplify the realization triggering systemto Simplify the realization triggering system@ -206,2 +173,2 @@
format!("Eigenvalues used to find kernel:{}", eig.eigenvalues)
));
#[cfg(not(feature = "dev"))]
console_log!("Eigenvalues used to find kernel:{}", eig.eigenvalues);
What does this #-directive do? Not compile the next line if this is not a "dev" compile? Why do we not want to log the eigenvalues on a dev build? More likely I am misunderstanding the meaning here...
Correct, if you replace "not compile" with "only compile" (I think this was a typo, based on your next sentence) and "line" with the various language constructs that attributes can be applied to.
Short answer
The eigenvalue logging is part of the function
ConfigSubspace::symmetric_kernel
, which currently runs at the end of everyrealize_gram
call. This messes up the formatting of the Cargo examples, which are built with thedev
feature.If we want to add eigenvalue logging to the example output, we'd have to reorganize the Cargo examples a little bit, which seems a bit outside the scope of this pull request.
Now that we have a diagnostics panel, it seems more useful to plot the eigenvalues there than to log them to the console, so this logging code's days should be numbered. As we discussed during our last meeting, I'm planning to experiment with a new implementation of
ConfigSubspace::symmetric_kernel
this week. If we adopt a new implementation, it will be a good opportunity to replace the console logging code with a diagnostics panel plot.More context
On the main branch, the eigenvalue logging uses the
console::log_1
function from theweb-sys
crate, which can only run in a browser. It's therefore confined to WebAssembly builds using the following attribute:Since the examples typically aren't built for a WebAssembly target, they don't include the eigenvalue logging code.
This pull request seemed like a good opportunity to switch to Sycamore's more flexible
console_log
macro, which works both inside and outside the browser. I've been gradually replacingconsole::log_/* number */
calls withconsole_log
calls throughout the codebase. The eigenvalues now can be logged in the Cargo examples, but I decided to keep the eigenvalue logging out of the examples to preserve existing behavior.It feels to me as though we are using compile time directives to make a runtime choice about logging, because by coincidence we don't want logging in the examples which happen to be our only non-dev builds at the moment. That mechanism doesn't seem fitted to the purpose. Could we select logging or not in another way? Or just cut this particular logging altogether if we don't really need it anyway?
I'm fine with dropping the eigenvalue logging now and replacing it with a diagnostics panel plot later—either in a stand-alone pull request or as part of a pull request that revises
ConfigSubspace::symmetric_kernel
.I've dropped the eigenvalue logging code (in commit
eafb133
) and filed an issue (#106) to remind us to replace it.@ -428,0 +403,4 @@
);
return Realization { result, history }
}
(a) Why do we now need an empty-assembly special case, when we didn't before? Or is it just for efficiency?
(b) Could this be expanded relatively easily to the more common no-set-regulators case?
The realization triggering system on the main branch never tries to realize an empty assembly, so we've never had to worry about the empty-assembly case.
As I mentioned in the pull request description, the realization triggering system on the incoming branch allows some unnecessary realizations in exchange for simplicity. One of those unnecessary realizations happens when we construct an empty assembly using the constructor
Assembly::new
.As it turns out, the realization function on the main branch panics when you try to realize an empty assembly. This is because an empty assembly has an empty (zero by zero) Hessian, and the
DMatrix::from_columns
function, which we use to build the Hessian, can't handle an empty list of columns.Sure. We could give
PartialMatrix
anis_empty
method and then expand the early return condition to:Any reason not to go ahead and do that in this PR? Seems like more bang for not many more bucks -- as long as we are checking for one kind of trivial case, why not exclude many more trivial cases? Seems to me like there's no downside...
On second thought, we'd actually have to use a separate conditional for the unconstrained case, because the tangent space would be different. When there are no elements, we return the zero subspace as the tangent space; when there are elements but no constraints, we want to return the whole configuration vector space as the tangent space.
I'd recommend leaving
realize_gram
mostly as it is. When there are no constraints,realize_gram
still exits before doing a Cholesky decomposition, which is the most costly part of the optimization step: the loop breaks halfway through the first iteration, in the block with the comment "stop if the loss is tolerably low." After the break, we useConfigSubspace::symmetric_kernel
to find the tangent space. This costs more computationally than hard-coding the tangent space for the unconstrained case, but I think it makes maintenance easier.Note that to get the spectrum for the diagnostics panel,
realize_gram
finds the eigenvalues of the Hessian on every step. I'm not factoring this into the cost considerations above, because I'd only want to do this in development builds.@Vectornaut wrote in #105 (comment):
How do those differ? When there are no elements, the whole configuration vector space is the zero space...
Good point: in principle, a
ConfigSubspace
variant representing a full subspace could be used for both empty assemblies and unconstrained assemblies. I've prototyped a full subspace representation and saved the code locally in case we want it later. In the process, however, I realized that we'll never actually encounter an unconstrained assembly, because every element comes with a normalization constraint. I'm now strongly against adding special handling for unconstrained assemblies, because during normal operation, this code will never run.OK, yes, I had forgotten that our coordinates are themselves underdetermined and that we use essentially the same mechanism to keep the coordinates for a given item in the form we want as to place an actual Euclidean-geometric constraint. So I agree, there's no point in any code for unconstrained assemblies other than the totally empty assembly. So yes go ahead and leave the special case for empty assemblies; please just add a comment as to why it must be handled specially. Thanks!
Done in commit
779c026
!Mostly looks just fine, a couple of questions and then we should be good to go.
symmetric_kernel