def test_outer_product(hw, args):
    """Basic test of the functionality"""

    # Convert motor names to actual motors in the argument list using fixture 'hw'
    args = [getattr(hw, _) if isinstance(_, str) else _ for _ in args]

    full_cycler = outer_product(args=args)
    event_list = list(full_cycler)

    # The list of motors
    chunk_args = list(chunk_outer_product_args(args))
    motors = [_[0] for _ in chunk_args]
    motor_names = [_.name for _ in motors]

    positions = {k: [] for k in motor_names}
    for event in event_list:
        for m, mn in zip(motors, motor_names):
            positions[mn].append(event[m])

    positions_expected = _gen_outer_product(args)

    assert set(positions.keys()) == set(positions_expected.keys()), \
        "Different set of motors in dictionaries of actual and expected positions"

    for name in positions_expected.keys():
        npt.assert_array_almost_equal(
            positions[name],
            positions_expected[name],
            err_msg=
            f"Expected and actual positions for the motor '{name}' don't match"
        )
Beispiel #2
0
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_chunk_outer_product_args_2(hw, args, chunked_args):
    """Check if the pattern (Pattern 2) works"""

    # Convert motor names to actual motors in the argument list using fixture 'hw'
    args = tuple([getattr(hw, _) if isinstance(_, str) else _ for _ in args])
    for n, a in enumerate(chunked_args):
        chunked_args[n] = tuple(
            [getattr(hw, _) if isinstance(_, str) else _ for _ in a])
    """Check if deprecated pattern (Pattern 2) is properly supported"""
    assert list(chunk_outer_product_args(args)) == chunked_args, \
        "Argument list was split into chunks incorrectly"
def test_chunk_outer_product_args_4(hw, args, chunked_args, pattern):
    """Test if `chunk_outer_product_args` works with externally supplied pattern"""

    # Convert motor names to actual motors in the argument list using fixture 'hw'
    args = tuple([getattr(hw, _) if isinstance(_, str) else _ for _ in args])
    for n, a in enumerate(chunked_args):
        chunked_args[n] = tuple(
            [getattr(hw, _) if isinstance(_, str) else _ for _ in a])

    assert list(chunk_outer_product_args(args, pattern)) == chunked_args, \
        "Argument list was split into chunks incorrectly"
def _gen_outer_product(args):
    """
    Generate expected output for the `outer_product` function.
    The output is generated using completely different method not based
    on Cycler, so it is better suited for testing.

    Parameters
    ----------
    args: list
        same as input of the `outer_product` function

    Returns
    -------
    Dictionary:
        {'motor_name_1': list of positions,
         'motor_name_2': list of positions, ...}
    """

    chunk_args = list(chunk_outer_product_args(args))

    # The number of steps is the multiple of the number of steps along all axes
    n_points = 1
    for chunk in chunk_args:
        n_points *= chunk[3]

    positions = {}

    n_passes = 1
    for chunk in chunk_args:
        motor, start, stop, num, snake = chunk

        # The number of measurement during which the motor has to stay at the same position
        n_steps = int(round(n_points / num / n_passes))

        pts = []
        for n in range(n_passes):
            if (n % 2
                ) and snake:  # Odd pass goes backward if the motor is snaked
                v_start, v_stop = stop, start
            else:
                v_start, v_stop = start, stop
            p = list(np.linspace(v_start, v_stop, num))
            p = list(
                itertools.chain.from_iterable(
                    itertools.repeat(_, n_steps) for _ in p))
            pts += p

        positions[motor.name] = pts

        n_passes *= num

    return positions
def test_chunk_outer_product_args_3(hw, args, chunked_args):
    """Check the ambiguous case: function is called with 24 arguments,
    the pattern can't be resolved just by counting arguments, so the
    presence of boolean values is checked"""

    # Convert motor names to actual motors in the argument list using fixture 'hw'
    args = tuple([getattr(hw, _) if isinstance(_, str) else _ for _ in args])
    for n, a in enumerate(chunked_args):
        chunked_args[n] = tuple(
            [getattr(hw, _) if isinstance(_, str) else _ for _ in a])

    assert list(chunk_outer_product_args(args)) == chunked_args, \
        "Argument list was split into chunks incorrectly"
def test_chunk_outer_product_args_failing(hw):
    """Failing cases for `chunk_outer_product_args` function """

    # Wrong number of arguments
    args = (hw.motor, 1, 7, 24, True)
    with pytest.raises(ValueError, match="Wrong number of elements in 'args'"):
        list(chunk_outer_product_args(args))

    args = (hw.motor, 1, 7, 24, hw.motor1, 1, 5, 10, False, hw.motor2, 2, 10,
            20)
    with pytest.raises(ValueError, match="Wrong number of elements in 'args'"):
        list(chunk_outer_product_args(args))

    args = (
        hw.motor,
        1,
        7,
        24,
        hw.motor1,
        1,
        5,
        10,
        "abc",
        2,
        10,
        20,
    )
    with pytest.raises(
            ValueError,
            match="Incorrect order of elements in the argument list"):
        list(chunk_outer_product_args(args))

    args = (hw.motor, 1, 7, 24, hw.motor1, hw.motor3, 5, 10, hw.motor2, 2, 10,
            20)
    with pytest.raises(
            ValueError,
            match="Incorrect order of elements in the argument list"):
        list(chunk_outer_product_args(args))
Beispiel #8
0
def _grid_scan_position_list(args, snake_axes):
    """
    Generates the lists of positions for each motor during the 'grid_scan'.

    Parameters
    ----------
    args: list
        list of arguments, same as the parameter `args` of the `grid_scan`
    snake_axes: None, True, False or iterable
        same meaning as `snake_axes` parameter of the `grid_scan`

    Returns
    -------
    Tuple of the dictionary:
        {'motor_name_1': list of positions,
         'motor_name_2': list of positions, ...}
    and the tuple that lists `snaking` status of each motor (matches the contents
    of the 'snaking' field of the start document.
    """
    # If 'snake_axis' is specified, it always overwrides any snaking values specified in 'args'
    chunk_args = list(chunk_outer_product_args(args))
    if isinstance(snake_axes, collections.abc.Iterable):
        for n, chunk in enumerate(chunk_args):
            motor, start, stop, num, snake = chunk
            if motor in snake_axes:
                chunk_args[n] = tuple([motor, start, stop, num, True])
            else:
                chunk_args[n] = tuple([motor, start, stop, num, False])
    elif snake_axes is True:
        chunk_args = [(motor, start, stop, num, True) if n > 0 else
                      (motor, start, stop, num, False)
                      for n, (motor, start, stop, num,
                              _) in enumerate(chunk_args)]
    elif snake_axes is False:
        chunk_args = [(motor, start, stop, num, False)
                      for (motor, start, stop, num, _) in chunk_args]
    elif snake_axes is None:
        pass
    else:
        raise ValueError(
            f"The value of 'snake_axes' is not iterable, boolean or None: '{snake_axes}'"
        )

    # Expected contents of the 'snaking' field in the start document
    snaking = tuple([_[4] for _ in chunk_args])

    # Now convert the chunked argument list to regular argument list before calling the cycler
    args_modified = []
    for n, chunk in enumerate(chunk_args):
        if n > 0:
            args_modified.extend(chunk)
        else:
            args_modified.extend(chunk[:-1])

    # Note, that outer_product is used to generate the list of coordinate points
    #   while the plan is executed, but it is tested elsewhere, so it can be trusted
    full_cycler = outer_product(args=args_modified)
    event_list = list(full_cycler)

    # The list of motors
    motors = [_[0] for _ in chunk_args]
    motor_names = [_.name for _ in motors]

    positions = {k: [] for k in motor_names}
    for event in event_list:
        for m, mn in zip(motors, motor_names):
            positions[mn].append(event[m])

    return positions, snaking