Skip to content

Allow excluding capabilities from filling downstream dependencies. - #611

Open
ChrisJChang wants to merge 6 commits into
GambitBSM:masterfrom
ChrisJChang:claude/gambit-multiple-dependencies-os1myh
Open

Allow excluding capabilities from filling downstream dependencies.#611
ChrisJChang wants to merge 6 commits into
GambitBSM:masterfrom
ChrisJChang:claude/gambit-multiple-dependencies-os1myh

Conversation

@ChrisJChang

Copy link
Copy Markdown
Collaborator

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:

- capability: TestDependency
  function: testdependency_2
  purpose: Observable
  exclude_from_dependency_resolution: true

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.

claude and others added 3 commits August 7, 2026 10:20
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.
@ChrisJChang ChrisJChang self-assigned this Aug 7, 2026
@ChrisJChang ChrisJChang added the Core Core group task label Aug 7, 2026

@patscott patscott left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good, but I don't think it actually addresses #56. How would one use this option to allow a module function to declare the same dependency twice, in order to get it from two sources and compute the difference?

@ChrisJChang

Copy link
Copy Markdown
Collaborator Author

How would one use this option to allow a module function to declare the same dependency twice, in order to get it from two sources and compute the difference?

So with the changes in this PR, your yaml code would be something like:

- capability: RD_oh2
  function: RD_oh2_DS
  purpose: Observable

- capability: RD_oh2
  function: RD_oh2_micromegas
  purpose: Observable
  exclude_from_dependency_resolution: true

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.

@patscott

Copy link
Copy Markdown
Member

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.

claude and others added 3 commits August 11, 2026 12:42
…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
@ChrisJChang

Copy link
Copy Markdown
Collaborator Author

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.

I added in the ability to have multiple dependencies from the same capability. These are declared with a new macro DEPENDENCY_ON_FUNCTION:

DEPENDENCY_ON_FUNCTION(calc1, TestDependency, double, testdependency_1)
DEPENDENCY_ON_FUNCTION(calc2, TestDependency, double, testdependency_2)

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

double blah1 = *Dep::calc1;
double blah2 = *Dep::calc2;

Then you can do whatever you want this this in your new capability.

Does this fulfill what you were thinking?

@patscott

Copy link
Copy Markdown
Member

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:

DEPENDENCY(TestDependency, double, calc1)
DEPENDENCY(TestDependency, double, calc2)

with matching rules in the YAML something like

- if: 
    tag: calc1
  then:
    function: testdependency1
- if: 
    tag: calc2
  then:
    function: testdependency2

and referred to in the module function as in your example:

double blah1 = *Dep::calc1;
double blah2 = *Dep::calc2;

We'd maybe also want some dep resolver option prefer_different_functions_for_duplicate_dependencies that defaulted to true.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Core Core group task

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow systematics to be computed as difference of two functions with same capability

3 participants