bluesky / bluesky-enhancement-proposals

0 stars 4 forks source link

Get a result out of a plan #6

Open danielballan opened 6 years ago

danielballan commented 6 years ago

876 prompted discussion about establishing a pattern for letting plans return some result for interactive use. This has come up only incidentally on the NSLS-II floor so far. We have employed various works arounds such as:

This might be the way to go:

import bluesky.plans as bp
import inspect

class Foo:
    """
    Perform a scan and collect the readings (in leui of some interesting analysis.)
    """
    def __init__(self):a
        self.result = None

    def __call__(self, *args, **kwargs):
        result = []
        ret_val = None
        for msg in bp.scan(*args, **kwargs):
            if msg.command == 'read':
                reading = yield msg
                result.append(reading)
            else:
                ret_val = yield msg
        self.result = result
        return ret_val
    __call__.__signature__ = inspect.signature(bp.scan)

from bluesky import RunEngine
from ophyd.sim import motor, det  # apologies -- this uses work currently in progress in ophyd
RE = RunEngine({})

foo = Foo()

RE(foo([det], motor, 1, 5 ,5))
foo.result  # has a bunch of collected data -- but this could an analysis result
prjemian commented 6 years ago

Perhaps even a method of a class? Such as a stage that has default settings when tuning?

class SomeStage(Device):
    # ...
    # motor, detector, min_step are set by other methods
    def tune(start, stop):
        yield from tune_centroid([self.detector], self.detector.name, self.motor, start, stop, self.min_step)
        self.centroid = motor.read()[motor.name]["value"] # replace with Tom's suggested revision
danielballan commented 6 years ago

Device methods that return plans are an interesting direction to explore. Currently there are zero examples of this.

tacaswell commented 6 years ago

I am :+1: on devices knowing how to create plans using them selves.

Some quibbles about how much state the device should store vs how much should be passed in and what the scope of what the SomeStage device owns should be.

tacaswell commented 6 years ago

I have been thinking about this and have started to think we should stash the uids on an RE property and let the plan return what ever it wants.

It is a pretty big API change, but I have not seen the return from RE() being used except in a few cases where it would be better handled by a callback on the RE or by capturing the uids returned by the start message.

danielballan commented 6 years ago

Interesting. For what it's worth, I do make use of

uid = RE(...)
db[uid]

widely in tutorials and examples, but I agree that anecdotally that uptake by users does not seem to be high. Using db[-1] some future db[RE.uids] would be fine too.

It does seem like

result = RE(...)

and

def overnight():
    result1 = yield from plan1(...)
    print(result1)
    result2 = yield from plan2(...)
    print(result2)

RE(overnight())

would be popular and nice to have.

mrakitin commented 6 years ago

What about the following scenario?

def overnight():
    result1 = yield from plan1(...)
    result2 = yield from plan2(...)
    return result1, result2

result1, result2 = RE(overnight())
# do some stuff with result1 and result2 interactively
danielballan commented 6 years ago

Yep, I agree that is a better version of overnight.

I think the take-away message is: by allowing plans to return anything they want, as opposed to standardizing on uid(s), as we currently do, plans can straightforwardly return results (1) for use by other sub-plans (2) through RE.__call__ back to the user.

tacaswell commented 6 years ago

Do we have any rules on what plans return right now? I think we can do @danielballan 's version of overnight right now. The RE just ignores the return value and returns the uids from __call__ instead.

danielballan commented 6 years ago

I would be interested in feedback on this latest proposal from @prjemian @teddyrendahl @ZLLentz. To summarize:

Current situation

Proposal

Advantages

ZLLentz commented 6 years ago

I'm 100% on-board with having the RE return the plan result.

To compensate, perhaps LiveTable should be made to print the uids along with the tables? I imagine there are use cases for grabbing these uids interactively.

tacaswell commented 6 years ago

A edge we will have to deal with is that currently __call__ returns the uids collected so far, so if you pause it, it will return something (maybe an empty list). If we start returning the return value of the plan then we can not do this. I think this is ok and will mostly break people who are using RE inside of for-loops so I don't feel too bad.

danielballan commented 6 years ago

And you get back the same information again anyway when you call RE.stop() (or similar).

tacaswell commented 5 years ago

Actually, re-considering my comment from May, when you interrupt the RE we now raise so we can avoid having to return anything. The issue @danielballan points out is still a problem, when you call stop() the plan may not gracefully exit, hence has no return value we can recover (but we should check to see if it did catch the exception and return instead. Probably just return None otherwise?