def _take_constrained_step_up_to_boundary(
    x_candidate, direction, active_bound, index_bound_active
):
    """Take largest constrained step possible until trust-region boundary is hit.

    Args:
        x_candidate (np.ndarray): Current candidate vector of shape (n,).
        direction (np.ndarray): Direction vector of shape (n,).
        active_bound (float): The active (lower or upper) bound.
        index_bound_active (int): Index where an active lower or upper bound
            has been found.

    Returns:
        (tuple):
        - x_candidate (np.ndarray): New candidate vector of shape (n,).
        - direction (np.ndarray): New direction vector of shape (n,), where the
            search direction of the active_bound has been set to zero.
    """
    step_size_constr = (active_bound - x_candidate[index_bound_active]) / direction[
        index_bound_active
    ]

    x_candidate = x_candidate + step_size_constr * direction
    x_candidate[index_bound_active] = active_bound

    # Do not search in this direction anymore
    direction[index_bound_active] = 0.0

    return x_candidate, direction