Calculates the time it takes to do a move.

    Calculates how long it will take to complete a move of the motor. It
    is assumed that the motor will decerate to a stop for the end of the
    move as opposed to keep moving at velocity.

    Everything is in motor units which are encoder counts for distance,
    pitches/s for velocity, and pitches/s^2 for acceleration.

    Parameters
    ----------
    move : dict
        Contains the move parameters in its fields: acceleration ('A'),
        deceleration ('AD' with 0 meaning the value of the acceleration
        is used), velocity ('V'), and the distance/position ('D').
    eres : int
        Encoder resolution.

    Returns
    -------
    time : float
        Time the move will take in seconds.

    See Also
    --------
    compile_sequence
    get_sequence_time

def move_time(move, eres):
    """ Calculates the time it takes to do a move.

    Calculates how long it will take to complete a move of the motor. It
    is assumed that the motor will decerate to a stop for the end of the
    move as opposed to keep moving at velocity.

    Everything is in motor units which are encoder counts for distance,
    pitches/s for velocity, and pitches/s^2 for acceleration.

    Parameters
    ----------
    move : dict
        Contains the move parameters in its fields: acceleration ('A'),
        deceleration ('AD' with 0 meaning the value of the acceleration
        is used), velocity ('V'), and the distance/position ('D').
    eres : int
        Encoder resolution.

    Returns
    -------
    time : float
        Time the move will take in seconds.

    See Also
    --------
    compile_sequence
    get_sequence_time

    """
    # Grab the move parameters. If the deceleration is given as zero,
    # that means it has the same value as the acceleration. Distance is
    # converted to the same units as the others by dividing by the
    # encoder resolution. The absolute value of everything is taken for
    # simplicity.
    A = abs(move['A'])
    AD = abs(move['AD'])
    if AD == 0.0:
        AD = A
    V = abs(move['V'])
    D = abs(move['D'])/eres

    # Calculate the times it would take to accelerate from stop to V and
    # decelerate to stop at rates A and AD respectively.
    accel_times = [V/A, V/AD]

    # Calculate the distances that would be moved in those times.
    dists = [0.5*A*(accel_times[0]**2), 0.5*AD*(accel_times[1]**2)]

    # If the sum of those dists is greater than D, then the velocity V
    # is never reached. The way the time is calculated depends on which
    # case it is.
    if sum(dists) <= D:
        # The time is just the sum of the acceleration times plus the
        # remaining distance divided by V.
        return (sum(accel_times) + (D-sum(dists))/V)
    else:
        # We need to find the time it takes for the acceleration path
        # and deceleration path to meet and have the same speeds.
        #
        # (1) t = t_1 + t_2
        # (2) A*t_1 = AD*t_2
        # (3) D = 0.5*A*(t_1**2) + 0.5*AD*(t_2**2)
        #
        # Re-writing t_2 in terms of t_1 using (2)
        # (4) t_2 = (A / AD) * t_1
        #
        # Putting that into (1) and (3)
        # (4) t = (1 + (A / AD)) * t_1
        # (5) D = 0.5*A*(1 + (A / AD)) * (t_1**2)
        #
        # Solving (5) for t_1,
        # (6) t_1 = sqrt( 2*D / (A * (1 + (A / AD))))
        #
        # Putting that into (4),
        # t = sqrt(2*D*(1 + (A / AD)) / A)
        return math.sqrt(2*D * (1 + (A / AD)) / A)