Return window diameter and window's centre.

    Parameters
    ----------
    widnow: list

    elements: numpy.array

    coordinates: numpy.array

    elements_vdw: numpy.array

    step: float

def window_analysis(window,
                    elements,
                    coordinates,
                    elements_vdw,
                    increment2=0.1,
                    z_bounds=[None, None],
                    lb_z=True,
                    z_second_mini=False,
                    **kwargs):
    """
    Return window diameter and window's centre.

    Parameters
    ----------
    widnow: list

    elements: numpy.array

    coordinates: numpy.array

    elements_vdw: numpy.array

    step: float

    """
    # Copy the coordinates as we will manipulate them.
    coordinates = deepcopy(coordinates)
    # Find the vector with the largest window sampling diameter from the pool.
    vector_ = window[window.argmax(axis=0)[1]][5:8]
    vector_analysed = vector_analysis(
        vector_, coordinates, elements_vdw, increment=increment2)
    # A safety check, if the refined analysis give None we end the function.
    if vector_analysed is not None:
        pass
    else:
        return None
    vector = vector_analysed[5:8]
    # Unit vectors.
    vec_a = [1, 0, 0]
    vec_b = [0, 1, 0]
    vec_c = [0, 0, 1]
    # Angles needed for rotation (in radians) to rotate and translate the
    # molecule for the vector to become the Z-axis.
    angle_1 = angle_between_vectors(np.array([vector[0], vector[1], 0]), vec_a)
    angle_2 = angle_between_vectors(vector, vec_c)
    # Depending in which cartesian coordinate system area the vector is
    # We need a rotation into a different direction and by different value.
    if vector[0] >= 0 and vector[1] >= 0 and vector[2] >= 0:
        angle_1 = -angle_1
        angle_2 = -angle_2
    if vector[0] < 0 and vector[1] >= 0 and vector[2] >= 0:
        angle_1 = np.pi * 2 + angle_1
        angle_2 = angle_2
    if vector[0] >= 0 and vector[1] < 0 and vector[2] >= 0:
        angle_1 = angle_1
        angle_2 = -angle_2
    if vector[0] < 0 and vector[1] < 0 and vector[2] >= 0:
        angle_1 = np.pi * 2 - angle_1
    if vector[0] >= 0 and vector[1] >= 0 and vector[2] < 0:
        angle_1 = -angle_1
        angle_2 = np.pi + angle_2
    if vector[0] < 0 and vector[1] >= 0 and vector[2] < 0:
        angle_2 = np.pi - angle_2
    if vector[0] >= 0 and vector[1] < 0 and vector[2] < 0:
        angle_2 = angle_2 + np.pi
    if vector[0] < 0 and vector[1] < 0 and vector[2] < 0:
        angle_1 = -angle_1
        angle_2 = np.pi - angle_2
    # Rotation matrix for rotation around Z-axis with angle_1.
    rotation_around_z = np.array([[np.cos(angle_1), -np.sin(angle_1), 0],
                                  [np.sin(angle_1), np.cos(angle_1), 0],
                                  [0, 0, 1]])
    # Rotate the whole molecule around with rotation_around_z.
    coordinates = np.array([np.dot(rotation_around_z, i) for i in coordinates])
    # Rotation matrix for rotation around Y-axis with angle_2
    rotation_around_y = np.array([[np.cos(angle_2), 0, np.sin(angle_2)],
                                  [0, 1, 0],
                                  [-np.sin(angle_2), 0, np.cos(angle_2)]])
    # Rotate the whole molecule around with rotation_around_y.
    coordinates = np.array([np.dot(rotation_around_y, i) for i in coordinates])
    # Third step is translation. We are now at [0, 0, -z].
    # We shift the molecule so that center of the window is at the origin.
    # The `z` is from original vector analysis. It is the point on the vector
    # where the largest sampling sphere was (vector_analysed[0]).
    new_z = vector_analysed[0]
    # Translate the whole molecule to shift window's center to origin.
    coordinates = coordinates - np.array([[0, 0, new_z]] *
                                         coordinates.shape[0])
    # !!!Here the window center (xy and z) optimisation take place!!!
    window_com = np.array([0, 0, 0], dtype=float)
    # The lb_z parameter is 'lower bound equal to z' which means,
    # that we set the lower bound for the z optimisation to be equal
    # to the -new_z as in some cages it's the COM - pore that is the
    # limiting diameter. But, no lower than new_z because we don't want to
    # move into the other direction.
    if lb_z:
        z_bounds[0] = -new_z
    window_diameter, _ = pore_diameter(elements, coordinates, com=window_com)
    # SciPy minimisation on z coordinate.
    z_args = (window_com[0], window_com[1], elements, coordinates)
    z_optimisation = minimize(
        optimise_z, x0=window_com[2], args=z_args, bounds=[z_bounds])
    # Substitute the z coordinate for a minimised one.
    window_com[2] = z_optimisation.x[0]
    # SciPy brute optimisation on x and y coordinates in window plane.
    xy_args = (window_com[2], elements, coordinates)
    xy_bounds = ((-window_diameter / 2, window_diameter / 2),
                 (-window_diameter / 2, window_diameter / 2))
    xy_optimisation = brute(
        optimise_xy, xy_bounds, args=xy_args, full_output=True, finish=fmin)
    # Substitute the x and y coordinates for the optimised ones.
    window_com[0] = xy_optimisation[0][0]
    window_com[1] = xy_optimisation[0][1]
    # Additional SciPy minimisation on z coordinate. Added on 18 May 2017.
    # We can argue which aproach is best. Whether z opt and then xy opt
    # or like now z opt -> xy opt -> additional z opt etc. I have also tested
    # a loop of optimisations until some convergence and optimisation of
    # xyz coordinates at the same time by optimising these two optimisations.
    # In the end. I think this approach is best for cages.
    # Update 20 October 2017: I made this optional and turned off by default
    # In many cases that worsen the quality of the results and should be used
    # with caution.
    if z_second_mini is not False:
        z_args = (window_com[0], window_com[1], elements, coordinates)
        # The z_bounds should be passed in kwargs.
        z_optimisation = minimize(
            optimise_z, x0=window_com[2], args=z_args, bounds=[z_bounds])
        # Substitute the z coordinate for a minimised one.
        window_com[2] = z_optimisation.x[0]
    # Calculate the new window diameter.
    window_diameter, _ = pore_diameter(elements, coordinates, com=window_com)
    # To get the window true centre of mass we need to revere the rotation and
    # translation operations on the window com.
    # Reverse the translation by substracting the new_z.
    window_com[2] = window_com[2] + new_z
    angle_2_1 = -angle_2
    reverse_around_y = np.array([[np.cos(angle_2_1), 0, np.sin(angle_2_1)],
                                 [0, 1, 0],
                                 [-np.sin(angle_2_1), 0, np.cos(angle_2_1)]])
    # Reversing the second rotation around Y-axis.
    window_com = np.dot(reverse_around_y, window_com)
    angle_1_1 = -angle_1
    reverse_around_z = np.array([[np.cos(angle_1_1), -np.sin(angle_1_1), 0],
                                 [np.sin(angle_1_1), np.cos(angle_1_1), 0],
                                 [0, 0, 1]])
    # Reversing the first rotation around Z-axis.
    window_com = np.dot(reverse_around_z, window_com)
    return (window_diameter, window_com)