def test_average_stream(RE, hw): # Create callback chain avg = AverageStream(10) c = CallbackCounter() d = DocCollector() avg.subscribe(c) avg.subscribe(d.insert) # Run a basic plan RE(stepscan(hw.det, hw.motor), {'all': avg}) assert c.value == 1 + 1 + 2 # events, descriptor, start and stop # See that we made sensible descriptor start_uid = d.start[0]['uid'] assert start_uid in d.descriptor desc_uid = d.descriptor[start_uid][0]['uid'] assert desc_uid in d.event evt = d.event[desc_uid][0] assert evt['seq_num'] == 1 assert all([ key in d.descriptor[start_uid][0]['data_keys'] for key in evt['data'].keys() ]) # See that we returned the correct average assert evt['data']['motor'] == -0.5 # mean of range(-5, 5) assert evt['data']['motor_setpoint'] == -0.5 # mean of range(-5, 5) assert start_uid in d.stop assert d.stop[start_uid]['num_events'] == {'primary': 1}
def test_multirun_smoke(RE, hw): dc = DocCollector() def interlaced_plan(dets, motor): to_read = (motor, *dets) run_ids = list("abc") for rid in run_ids: yield from drw(bps.open_run(md={rid: rid}), run=rid) for j in range(5): for i, rid in enumerate(run_ids): yield from bps.mov(motor, j + 0.1 * i) yield from drw(bps.trigger_and_read(to_read), run=rid) for rid in run_ids: yield from drw(bps.close_run(), run=rid) RE(interlaced_plan([hw.det], hw.motor), dc.insert) assert len(dc.start) == 3 for start in dc.start: desc, = dc.descriptor[start["uid"]] assert len(dc.event[desc["uid"]]) == 5 for stop in dc.stop.values(): for start in dc.start: assert start["time"] < stop["time"]
def test_seq_recommender(RE, hw): recommender = SequenceRecommender([[ 1, ], [ 2, ], [ 3, ]]) # noqa cb, queue = recommender_factory(recommender, ["motor"], ["det"]) dc = DocCollector() # pre-poison the queue to simulate a messy reccomender queue.put(None) queue.put({}) RE( adaptive_plan([hw.det], {hw.motor: 0}, to_recommender=cb, from_recommender=queue), dc.insert, ) assert len(dc.start) == 4 assert len(dc.event) == 4 for ev in dc.event.values(): assert len(ev) == 1 # check that our reccomender does not leave anything behind with pytest.raises(Empty): queue.get(block=False)
def test_mesh_pseudo(hw, RE): p3x3 = hw.pseudo3x3 sig = hw.sig d = DocCollector() RE.subscribe(d.insert) rs = RE(bp.grid_scan([sig], p3x3.pseudo1, 0, 3, 5, p3x3.pseudo2, 7, 10, 7)) if RE.call_returns_result: uid = rs.run_start_uids[0] else: uid = rs[0] df = pd.DataFrame( [_['data'] for _ in d.event[d.descriptor[uid][0]['uid']]]) for k in p3x3.describe(): assert k in df for k in sig.describe(): assert k in df assert all(df[sig.name] == 0) assert all(df[p3x3.pseudo3.name] == 0)
def test_plan_header(RE, hw): args = [] ## args.append((bp.grid_scan([hw.det], hw.motor, 1, 2, 3, hw.motor1, 4, 5, 6, hw.motor2, 7, 8, 9, snake_axes=True), {'motors': ('motor', 'motor1', 'motor2'), 'extents': ([1, 2], [4, 5], [7, 8]), 'shape': (3, 6, 9), 'snaking': (False, True, True), 'plan_pattern_module': 'bluesky.plan_patterns', 'plan_pattern': 'outer_product', 'plan_name': 'grid_scan'})) ## args.append((bp.inner_product_scan([hw.det], 9, hw.motor, 1, 2, hw.motor1, 4, 5, hw.motor2, 7, 8), {'motors': ('motor', 'motor1', 'motor2')})) for plan, target in args: c = DocCollector() RE(plan, c.insert) for s in c.start: _validate_start(s, target)
def test_multirun_smoke(RE, hw): """Test on interlaced runs (using wrapper on each command)""" dc = DocCollector() def interlaced_plan(dets, motor): to_read = (motor, *dets) run_names = ["run_one", "run_two", "run_three"] for rid in run_names: yield from srkw(bps.open_run(md={rid: rid}), run=rid) for j in range(5): for i, rid in enumerate(run_names): yield from bps.mov(motor, j + 0.1 * i) yield from srkw(bps.trigger_and_read(to_read), run=rid) for rid in run_names: yield from srkw(bps.close_run(), run=rid) RE(interlaced_plan([hw.det], hw.motor), dc.insert) assert len(dc.start) == 3 for start in dc.start: desc, = dc.descriptor[start["uid"]] assert len(dc.event[desc["uid"]]) == 5 for stop in dc.stop.values(): for start in dc.start: assert start["time"] < stop["time"]
def test_multirun_run_key_type(RE, hw): """Test calls to wrapper with run key set to different types""" dc = DocCollector() @bsp.run_decorator(md={}) def empty_plan(): yield from bps.mov(hw.motor, 5) # The wrapper is expected to raise an exception if called with run ID = None with pytest.raises(ValueError, match="run ID can not be None"): def plan1(): yield from srkw(empty_plan(), None) RE(plan1(), dc.insert) # Check with run ID of type reference def plan2(): yield from srkw(empty_plan(), object()) RE(plan2(), dc.insert) # Check with run ID of type 'int' def plan3(): yield from srkw(empty_plan(), 10) RE(plan3(), dc.insert) # Check if call with correct parameter type are successful def plan4(): yield from srkw(empty_plan(), "run_name") RE(plan4(), dc.insert) def plan5(): yield from srkw(empty_plan(), run="run_name") RE(plan5(), dc.insert)
def test_force_stop_exit_status(bail_func, status, RE): d = DocCollector() RE.subscribe(d.insert) @run_decorator() def bad_plan(): print('going in') yield Msg('pause') with pytest.raises(RunEngineInterrupted): RE(bad_plan()) rs = getattr(RE, bail_func)() if RE.call_returns_result: if bail_func == "resume": assert rs.plan_result is not RE.NO_PLAN_RETURN else: assert rs.plan_result is RE.NO_PLAN_RETURN uid = rs.run_start_uids[0] assert rs.exit_status == status else: uid = rs[0] assert len(d.start) == 1 assert d.start[0]['uid'] == uid assert len(d.stop) == 1 assert d.stop[uid]['exit_status'] == status
def test_rmesh_pseudo(hw, RE): p3x3 = hw.pseudo3x3 p3x3.set(1, -2, 100) init_pos = p3x3.position sig = hw.sig d = DocCollector() RE.subscribe(d.insert) rs = RE( bp.rel_grid_scan([sig], p3x3.pseudo1, 0, 3, 5, p3x3.pseudo2, 7, 10, 7)) if RE.call_returns_result: uid = rs.run_start_uids[0] else: uid = rs[0] df = pd.DataFrame( [_['data'] for _ in d.event[d.descriptor[uid][0]['uid']]]) for k in p3x3.describe(): assert k in df for k in sig.describe(): assert k in df assert all(df[sig.name] == 0) assert all(df[p3x3.pseudo3.name] == 100) assert len(df) == 35 assert min(df[p3x3.pseudo1.name]) == 1 assert init_pos == p3x3.position
def test_grid_scans(RE, hw, args, snake_axes, plan, is_relative): """ Basic test of functionality of `grid_scan` and `rel_grid_scan`: Tested: - positions of the simulated motors at each step of the scan - contents of the 'snaking' field of the start document """ # Convert motor names to actual motors in the argument list using fixture 'hw' args = [getattr(hw, _) if isinstance(_, str) else _ for _ in args] # Do the same in `snake_axes` if it contains the list of motors if isinstance(snake_axes, collections.abc.Iterable): snake_axes = [getattr(hw, _) for _ in snake_axes] # Place motors at random initial positions. Do it both for relative and # absolute scans. The absolute scans will ignore the inital positions # automatically. motors = [_[0] for _ in chunk_outer_product_args(args)] motors_pos = [2 * random.random() - 1 for _ in range(len(motors))] for _motor, _pos in zip(motors, motors_pos): RE(bps.mv(_motor, _pos)) c = DocCollector() RE(plan([hw.det], *args, snake_axes=snake_axes), c.insert) positions = _retrieve_motor_positions(c, [hw.motor, hw.motor1, hw.motor2]) # Retrieve snaking data from the start document snaking = c.start[0]["snaking"] # Generate the list of positions based on positions_expected, snaking_expected = \ _grid_scan_position_list(args=args, snake_axes=snake_axes) assert snaking == snaking_expected, \ "The contents of the 'snaking' field in the start document "\ "does not match the expected values" assert set(positions.keys()) == set(positions_expected.keys()), \ "Different set of motors in dictionaries of actual and expected positions" # The dictionary of the initial postiions motor_pos_shift = { _motor.name: _pos for (_motor, _pos) in zip(motors, motors_pos) } for name in positions_expected.keys(): # The positions should be shifted only if the plan is relative. # Absolute plans will ignore the initial motor positions shift = motor_pos_shift[name] if is_relative else 0 npt.assert_array_almost_equal( positions[name], np.array(positions_expected[name]) + shift, err_msg= f"Expected and actual positions for the motor '{name}' don't match" )
def test_bec_peak_stats_derivative_and_stats(RE, hw): bec = BestEffortCallback(calc_derivative_and_stats=True) RE.subscribe(bec) c = DocCollector() RE.subscribe(c.insert) res = RE(scan([hw.ab_det], hw.motor, 1, 5, 5)) if RE.call_returns_result: uid = res.run_start_uids[0] else: uid = res[0] desc_uid = c.descriptor[uid][0]["uid"] ps = bec._peak_stats[desc_uid]["det_a"] assert hasattr(ps, "derivative_stats") fields = ["min", "max", "com", "cen", "fwhm", "crossings"] der_fields = ["x", "y"] + fields for field in der_fields: assert hasattr(ps.derivative_stats, field), f"{field} is not an attribute of ps.der" assert isinstance(ps.__repr__(), str) # These imports are needed by the `eval` below: from numpy import array # noqa F401 from collections import OrderedDict # noqa F401 out = eval(str(ps)) assert isinstance(out, dict) for key in ("stats", "derivative_stats"): assert key in out for field in fields: stats_value = getattr(ps.stats, field) out_value = out["stats"][field] if stats_value is not None: assert np.allclose(stats_value, out_value) else: stats_value == out_value for field in der_fields: stats_value = getattr(ps.derivative_stats, field) out_value = out["derivative_stats"][field] if stats_value is not None: assert np.allclose(stats_value, out_value) else: stats_value == out_value
def test_ops_dimension_hints(RE, hw): det = hw.det motor = hw.motor motor1 = hw.motor1 c = DocCollector() RE.subscribe(c.insert) RE(bp.grid_scan([det], motor, -1, 1, 7, motor1, 0, 2, 3)) st = c.start[0] assert 'dimensions' in st['hints'] assert st['hints']['dimensions'] == [(m.hints['fields'], 'primary') for m in (motor, motor1)]
def test_force_stop_exit_status(bail_func, status, RE): d = DocCollector() RE.subscribe(d.insert) @run_decorator() def bad_plan(): yield Msg('pause') try: RE(bad_plan()) except: ... rs, = getattr(RE, bail_func)() assert len(d.start) == 1 assert d.start[0]['uid'] == rs assert len(d.stop) == 1 assert d.stop[rs]['exit_status'] == status
def test_plotting_hints(RE, hw, db): ''' This tests the run and checks that the correct hints are created. Hints are mainly created to help the BestEffortCallback in plotting the data. Use a callback to do the checking. ''' dc = DocCollector() RE.subscribe(dc.insert) # check that the inner product hints are passed correctly hint = { 'dimensions': [([hw.motor1.name, hw.motor2.name, hw.motor3.name], 'primary')] } RE( inner_product_scan([hw.det], 20, hw.motor1, -1, 1, hw.motor2, -1, 1, hw.motor3, -2, 0)) assert dc.start[-1]['hints'] == hint # check that the outer product (grid_scan) hints are passed correctly hint = { 'dimensions': [(['motor1'], 'primary'), (['motor2'], 'primary'), (['motor3'], 'primary')] } # grid_scan passes "rectilinear" gridding as well # make sure this is also passed output_hint = hint.copy() output_hint['gridding'] = 'rectilinear' RE( grid_scan([hw.det], hw.motor1, -1, 1, 2, hw.motor2, -1, 1, 2, True, hw.motor3, -2, 0, 2, True)) assert dc.start[-1]['hints'] == output_hint # check that if gridding is supplied, it's not overwritten by grid_scan # check that the outer product (grid_scan) hints are passed correctly hint = { 'dimensions': [(['motor1'], 'primary'), (['motor2'], 'primary'), (['motor3'], 'primary')], 'gridding': 'rectilinear' } RE( grid_scan([hw.det], hw.motor1, -1, 1, 2, hw.motor2, -1, 1, 2, True, hw.motor3, -2, 0, 2, True)) assert dc.start[-1]['hints'] == hint
def test_describe_config_optional(RE): class Simple: """A trivial flyer that omits the configuration methods""" name = "simple_flyer" parent = None def kickoff(self): return NullStatus() def describe_collect(self): return {"stream_name": {}} def complete(self): return NullStatus() def collect(self): for i in range(100): yield {"data": {}, "timestamps": {}, "time": i, "seq_num": i} def stop(self, *, success=False): pass d = DocCollector() flyer = Simple() RE( [ Msg("open_run"), Msg("kickoff", flyer, group="foo"), Msg("wait", group="foo"), Msg("complete", flyer, group="bar"), Msg("wait", group="bar"), Msg("collect", flyer), Msg("close_run"), ], d.insert, ) ((desc, ), ) = d.descriptor.values() assert desc["name"] == "stream_name" assert "simple_flyer" in desc["object_keys"] assert "simple_flyer" in desc["configuration"]
def test_mesh_pseudo(hw, RE): p3x3 = hw.pseudo3x3 sig = hw.sig d = DocCollector() RE.subscribe(d.insert) rs, = RE(bp.grid_scan([sig], p3x3.pseudo1, 0, 3, 5, p3x3.pseudo2, 7, 10, 7)) df = pd.DataFrame([_['data'] for _ in d.event[d.descriptor[rs][0]['uid']]]) for k in p3x3.describe(): assert k in df for k in sig.describe(): assert k in df assert all(df[sig.name] == 0) assert all(df[p3x3.pseudo3.name] == 0)
def test_force_stop_exit_status(bail_func, status, RE): d = DocCollector() RE.subscribe(d.insert) @run_decorator() def bad_plan(): print('going in') yield Msg('pause') with pytest.raises(RunEngineInterrupted): RE(bad_plan()) rs, = getattr(RE, bail_func)() assert len(d.start) == 1 assert d.start[0]['uid'] == rs assert len(d.stop) == 1 assert d.stop[rs]['exit_status'] == status
def test_multirun_smoke_fail(RE, hw): dc = DocCollector() def interlaced_plan(dets, motor): run_names = ["run_one", "run_two", "run_three"] for rid in run_names: yield from srkw(bps.open_run(md={rid: rid}), run=rid) raise Exception("womp womp") with pytest.raises(Exception): RE(interlaced_plan([hw.det], hw.motor), dc.insert) assert len(dc.start) == len(dc.stop) assert len(dc.start) == 3 for v in dc.stop.values(): assert v["exit_status"] == "fail" assert v["reason"] == "womp womp"
def test_exceptions_exit_status(RE): d = DocCollector() RE.subscribe(d.insert) class Snowflake(Exception): ... @run_decorator() def bad_plan(): yield Msg('null') raise Snowflake('boo') with pytest.raises(Snowflake) as sf: RE(bad_plan()) assert len(d.start) == 1 rs = d.start[0]['uid'] assert len(d.stop) == 1 assert d.stop[rs]['exit_status'] == 'fail' assert d.stop[rs]['reason'] == str(sf.value)
def test_ramp(RE): from ophyd.positioner import SoftPositioner from ophyd import StatusBase from ophyd.sim import SynGauss tt = SoftPositioner(name='mot') tt.set(0) dd = SynGauss('det', tt, 'mot', 0, 3) st = StatusBase() def kickoff(): yield Msg('null') for j, v in enumerate(np.linspace(-5, 5, 10)): RE.loop.call_later(.1 * j, lambda v=v: tt.set(v)) RE.loop.call_later(1.2, st._finished) return st def inner_plan(): yield from trigger_and_read([dd]) g = ramp_plan(kickoff(), tt, inner_plan, period=0.08) db = DocCollector() RE.subscribe(db.insert) rs = RE(g) if RE.call_returns_result: uid = rs.run_start_uids[0] else: uid = rs[0] assert db.start[0]['uid'] == uid assert len(db.descriptor[uid]) == 2 descs = {d['name']: d for d in db.descriptor[uid]} assert set(descs) == set(['primary', 'mot_monitor']) primary_events = db.event[descs['primary']['uid']] assert len(primary_events) > 11 monitor_events = db.event[descs['mot_monitor']['uid']] # the 10 from the updates, 1 from 'run at subscription time' assert len(monitor_events) == 11
def test_straight_through_stream(RE, hw): # Just a stream that sinks the events it receives ss = NegativeStream() # Create callback chain c = CallbackCounter() d = DocCollector() ss.subscribe(c) ss.subscribe(d.insert) # Run a basic plan RE(stepscan(hw.det, hw.motor), {'all': ss}) # Check that our metadata is there assert c.value == 10 + 1 + 2 # events, descriptor, start and stop assert d.start[0]['stream_level'] == 'boring' desc = d.descriptor[d.start[0]['uid']][0] events = d.event[desc['uid']] print(desc) print([evt['data'] for evt in events]) assert all([ evt['data'][key] <= 0 for evt in events for key in evt['data'].keys() ]) assert all([key in desc['data_keys'] for key in events[0]['data'].keys()])
def test_multirun_smoke_nested(RE, hw): """Test on nested runs (using decorator on each plan)""" dc = DocCollector() to_read = (hw.motor, hw.det) def some_plan(): """This plan is called on each level of nesting""" for j in range(5): yield from bps.mov(hw.motor, j) yield from bps.trigger_and_read(to_read) @bsp.set_run_key_decorator("run_one") @bsp.run_decorator(md={}) def plan_inner(): yield from some_plan() @bsp.set_run_key_decorator("run_two") @bsp.run_decorator(md={}) def plan_middle(): yield from some_plan() yield from plan_inner() @bsp.set_run_key_decorator(run="run_three") # Try kwarg @bsp.run_decorator(md={}) def plan_outer(): yield from some_plan() yield from plan_middle() RE(plan_outer(), dc.insert) assert len(dc.start) == 3 for start in dc.start: desc, = dc.descriptor[start["uid"]] assert len(dc.event[desc["uid"]]) == 5 for stop in dc.stop.values(): for start in dc.start: assert start["time"] < stop["time"]
def test_rmesh_pseudo(hw, RE): p3x3 = hw.pseudo3x3 p3x3.set(1, -2, 100) init_pos = p3x3.position sig = hw.sig d = DocCollector() RE.subscribe(d.insert) rs, = RE( bp.rel_grid_scan([sig], p3x3.pseudo1, 0, 3, 5, p3x3.pseudo2, 7, 10, 7, False)) df = pd.DataFrame([_['data'] for _ in d.event[d.descriptor[rs][0]['uid']]]) for k in p3x3.describe(): assert k in df for k in sig.describe(): assert k in df assert all(df[sig.name] == 0) assert all(df[p3x3.pseudo3.name] == 100) assert len(df) == 35 assert min(df[p3x3.pseudo1.name]) == 1 assert init_pos == p3x3.position
def test_seq_recommender(RE, hw): recommender = SequenceRecommender([[ 1, ], [ 2, ], [ 3, ]]) # noqa cb, queue = recommender_factory(recommender, ["motor"], ["det"]) dc = DocCollector() RE( adaptive_plan([hw.det], {hw.motor: 0}, to_recommender=cb, from_recommender=queue), dc.insert, ) assert len(dc.start) == 1 assert len(dc.event) == 1 (events, ) = dc.event.values() assert len(events) == 4
def test_plotting_hints(RE, hw, db): """ This tests the run and checks that the correct hints are created. Hints are mainly created to help the BestEffortCallback in plotting the data. Use a callback to do the checking. """ dc = DocCollector() RE.subscribe(dc.insert) # check that the inner product hints are passed correctly hint = { "dimensions": [([hw.motor1.name, hw.motor2.name, hw.motor3.name], "primary")] } RE( inner_product_scan([hw.det], 20, hw.motor1, -1, 1, hw.motor2, -1, 1, hw.motor3, -2, 0)) assert dc.start[-1]["hints"] == hint # check that the outer product (grid_scan) hints are passed correctly hint = { "dimensions": [ (["motor1"], "primary"), (["motor2"], "primary"), (["motor3"], "primary"), ] } # grid_scan passes "rectilinear" gridding as well # make sure this is also passed output_hint = hint.copy() output_hint["gridding"] = "rectilinear" RE( grid_scan( [hw.det], hw.motor1, -1, 1, 2, hw.motor2, -1, 1, 2, True, hw.motor3, -2, 0, 2, True, )) assert dc.start[-1]["hints"] == output_hint # check that if gridding is supplied, it's not overwritten by grid_scan # check that the outer product (grid_scan) hints are passed correctly hint = { "dimensions": [ (["motor1"], "primary"), (["motor2"], "primary"), (["motor3"], "primary"), ], "gridding": "rectilinear", } RE( grid_scan( [hw.det], hw.motor1, -1, 1, 2, hw.motor2, -1, 1, 2, True, hw.motor3, -2, 0, 2, True, )) assert dc.start[-1]["hints"] == hint
def test_plan_header(fresh_RE, plan, target): RE = fresh_RE c = DocCollector() RE(plan, c.insert) for s in c.start: _validate_start(s, target)