Allow excluding capabilities from filling downstream dependencies. - #611
Allow excluding capabilities from filling downstream dependencies.#611ChrisJChang wants to merge 6 commits into
Conversation
Adds a new ObsLikes field, exclude_from_dependency_resolution, so a second function providing the same capability can be computed and printed to the output file (e.g. to assess systematic uncertainty against a "standard" calculation) without ever being wired in as the input dependency of any other module function. - Observable gains exclude_from_dependency_resolution (default false), parsed from the new YAML key in ObsLikes entries. - functor gains a matching flag/setter/getter, mirroring the existing critical flag. - DependencyResolver sets the flag on the resolved vertex whenever an ObsLikes entry requests it, and excludes any so-flagged functor from candidacy when resolving ordinary (non-ObsLike) DEPENDENCY edges. With only the "standard" candidate left after this filter, downstream consumers resolve unambiguously with no extra Rules: needed in the common two-function case.
This allows demonstrating that we can exclude capabilities from the dependency graph.
So with the changes in this PR, your yaml code would be something like: This would run both relic density calculations, but only use the DarkSUSY one to fill downstream dependencies. Both of these would be written to file, and so you could manually compute the difference after the scan has run. It would not be doing this during a scan. We could look at adding functionality to do this online during a scan, but this would not be using our standard dependency resolution mechanics, because it would need to have two dependencies on the same capability. |
|
Right, but that's my point -- by 'doing it manually' #56 means declaring the module function to have two dependencies and manually writing code into the module function to take the difference between the two dependency values computed in the scan. This would still just be a waypoint to a more sophisticated system that allows multiple functions to be run to fulfill ~any dependency, and the range between them automatically taken as a systematic error on the dependent quantity. Possibly with the central value taken as the mean or something like that too. Allowing a scan to compute two things with the same capability is another, even earlier and simpler step on this path. I agree it's worthwhile doing, but I think a) it doesn't close #56, and b) steps 2 and 3 are the ones that would add the most value. |
…ability Adds DEPENDENCY_ON_FUNCTION (and DEPENDENCY_ON_FUNCTION_IN_MODULE), a variant of DEPENDENCY that pins resolution directly to a named function in the rollcall header, tagged separately from the target capability. Because the tag (not the capability) keys the dependency storage and the compile-time Dep accessor, a function can now declare more than one dependency on the same capability -- e.g. to compare two calculators of the same quantity -- with the target of each pin fully determined in C++, so no Rules: entry is needed or consulted to disambiguate them. Threads the new tagged/pinned dependency slots through the functor storage layer (DependencySlot, setPinnedDependency/resolvePinnedDependency, kept additive alongside the existing single-dependency-per-capability path) and the dependency resolver (QueueEntry tag/pin fields, a dedicated resolution branch that matches the pin directly and skips the Rules/model-specificity/ exclude_from_dependency_resolution machinery meant for open dependencies). Demonstrates the feature with a new TestDependencyVariation capability in ExampleBit_A that depends on both testdependency_1 and testdependency_2 to compute their difference, exercised via spartan.yaml. Also removes a stray DEPENDENCY(DELETEME, double) left on testdependency_2, which had no provider and would have blocked this (and any other) dependency on testdependency_2 from resolving. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JZvTXwU7Nf2cTpAjPevE2c
…exts too
DEPENDENCY (like all rollcall macros) is redirected differently depending on
who is including the rollcall header: module_macros_incore.hpp/_defs.hpp for
the Core (which does the actual registration and dependency resolution) vs.
module_macros_inmodule.hpp/_defs.hpp for the module's own translation units
(which just need an extern dep_bucket declaration to link against). Models
have their own analogous pair of redirections in model_macros.hpp.
DEPENDENCY_ON_FUNCTION was previously only added to the in-core chain, so
compiling ExampleBit_A.cpp itself (an in-module context) left the macro
undefined, producing "expected constructor, destructor, or type conversion
before '(' token" at the call site and "'calc1'/'calc2' is not a member of
...Dep" where the pipe was used.
Adds the missing MODULE_DEPENDENCY_ON_FUNCTION macro (mirroring
MODULE_DEPENDENCY, filing the extern dep_bucket under TAG instead of DEP)
and wires DEPENDENCY_ON_FUNCTION/DEPENDENCY_ON_FUNCTION_IN_MODULE through
all four existing DEPENDENCY redirection sites: module_macros_inmodule.hpp,
and both the core-side and module-side branches of model_macros.hpp (which
reuse the already-defined CORE_DEPENDENCY_ON_FUNCTION/
MODULE_DEPENDENCY_ON_FUNCTION macros).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZvTXwU7Nf2cTpAjPevE2c
I added in the ability to have multiple dependencies from the same capability. These are declared with a new macro DEPENDENCY_ON_FUNCTION: Here TestDependency is the capability it depends on, testdependency_1/_2 are the two functions that are both for TestDependency. calc1/calc2 are tags that you name (only needed in the DEPDENDENCY_ON_FUNCTION macro), so that you can refer to them in the function, e.g.: Then you can do whatever you want this this in your new capability. Does this fulfill what you were thinking? |
|
It definitely goes in the right direction. The only concern I have with it is that it requires declaring in advance in the header what function is used to fulfill the two dependencies. This should really be done by the dep resolver according to applicable run-time rules. I think the function-local tag/alias for the dependency is a good design though. Could we perhaps just add alias as an optional arg to DEPENDENCY, so that it overrides how you refer to the dep in the pipes, as in your solution - but then have the dep resolver resolve each of the dependencies, using the tag/alias as an additional part of the conditional? Like this: with matching rules in the YAML something like and referred to in the module function as in your example: We'd maybe also want some dep resolver option |
This PR adds the ability to use multiple functions for the same capability, where only one is expected to fill the downstream dependencies. One use of this is that you could use different calculators for a physics observable, and save them both for the purpose of understanding systematic uncertainty.
This is done via yaml rules:
It will still add any functions needed for this to the dependency graph.
If you specify one excluded capability, but not which will capability will actually fill the dependency, it will pick the other if there is only one other possible, and raise the regular dependency resolution error if there are more than one options.
I have added an example to spartan.yaml doing this.
This closes issue #56
This is a very simple PR, so should not take too much effort to review.