Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Means of accessing child fragment results within a kernel #415

Closed
hartytp opened this issue Nov 4, 2024 · 1 comment · Fixed by #421
Closed

[RFC] Means of accessing child fragment results within a kernel #415

hartytp opened this issue Nov 4, 2024 · 1 comment · Fixed by #421

Comments

@hartytp
Copy link
Contributor

hartytp commented Nov 4, 2024

A use-case I've come across a number of times now is wanting to access the results of a fragment from within a kernel.

This often comes up in the context of AggregateExpFragment where one aggregates multiple ExpFragments into a parent ExpFragment (with the notion of "a fragment is essentially a function" this is basically saying that when one has functions which call other functions, it's often useful to be able to act on their return values). One example of this is "I have a sideband thermometry AggregateExpFragment which calls a red and blue sideband and now I want to calculate nbar based on the measured populations. Another example might be I want to run a servo with a wide capture range and then use a result to seed a servo with an narrow capture range (forward a result channel to a parameter).

Since result channel sinks should never be accessed by kernel code, there is currently no sanctioned way of achieving this within ndscan. This thread is a request for comments to discuss a proposed implementation. I think a good implementation of this should:

  1. Be ergonomic and user-friendly
  2. Work naturally with the current limitations of ARTIQ python
  3. Not rely on synchronous RPCs - getting results should be supported without having to leave the kernel

@dnadlinger LMK if you would be happy merging a PR along the lines of the below (and feel free to suggest names / API) and I'd be happy to write one.

Proposal 1: Introduce an API for absorbing result channels

Absorbing result channels - taking the result channel out of the scan schema seen by the ScanRunner and then re-exporting - is already used by subscans. We've discussed having a convenient API for this. I think it would look like the following:

1 - We introduce a new AbsorbedResultSink.

class AbsorbedResultSink(ResultSink):
    """ Sink used when result channels are "absorbed" by another fragment.

    This sink acts much like a :class:`LastValueSink` with the addition of forwarding all
    pushed values to the specified sink.
    """
    def __init__(self, channel: ResultChannel):
        self.channel = channel
        self.value = None

    def push(self, value: Any) -> None:
        self.channel.push(value)
        self.value = value

    def get_last(self) -> Any:
        """Return the last-pushed value, or ``None`` if none yet."""
        return self.value

2 - We introduce a new method Fragment.absorb_result(self, channel: ResultChannel) -> AbsorbedResultSink

This boils down to essentially:

def Fragment.absorb_result(self, name: str, channel: ResultChannel) -> AbsorbedResultSink:
    self.setattr_result_like(name, channel)
    sink = channel.sink = AbsorbedResultSink(getattr(self, name))
    return sink

The returned object can then be accessed from within kernels to get the most recent value through the get_last method.

The main issue I see with a proposal along these lines is that the sink mechanism relies on synchronous RPCs. This is a shame but is essentially forced on us by limitations of ARTIQ python - the RPCs are essentially a means of allowing result channels of the same type to have different types of sinks within a single kernel. We may want to implement this (or something like it) anyway to make things like the subscan implementation a little cleaner.

Proposal 2: Introduce a new NumericChannel.get_last()

The only way I see of implementing this while avoiding sync RPCs would be to add a new NumericChannel.get_last() method.

Comments:

  • feel free to suggest a better name!
  • due to the lack of dynamic memory allocation and incomplete lifetime tracking in ARTIQ python we can't really do this for anything other than a numeric (int/float) channel. However, I suspect that will catch the vast majority of the use cases and for everything else falling back to something along the lines of the above.

It's a bit disappointing to need to add this on top of the existing sink interface, but arguably less so than having to go via the host to pass results between different fragments executing inside the same kernel!

@hartytp
Copy link
Contributor Author

hartytp commented Nov 28, 2024

@dnadlinger : any objections to me adding a NumericChannel.get_lastI() method as a solution to this? If you're happy with that approach, I'll submit a PR in the next few days

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant