def __init__(self):
        super(Array, self).__init__()

        # Define hydrophones
        self.hydrophones = Phys_Obj()

        # Define Pinger_Contours
        self.pinger_contours = []

        # Define unknown variables
        self.last_known_pinger_location = None
        self.last_known_pinger_direction = None
class Array(Phys_Obj):
    # Always mind the coordinate axes!
    #
    #           z    y          
    #           ^   7           
    #           |  /            
    #           | /              
    #           |/               
    #           - - - - - - > x
    #
    # ...z represents the vertical axis (gravity acts in
    # this direction. y represents the forward direction of
    # the array. As shown, the frame (a.k.a. coordinate axes)
    # are fixed to the array such the if the array rotates, then
    # the frame will rotate the same amount.

    def __init__(self):
        super(Array, self).__init__()

        # Define hydrophones
        self.hydrophones = Phys_Obj()

        # Define Pinger_Contours
        self.pinger_contours = []

        # Define unknown variables
        self.last_known_pinger_location = None
        self.last_known_pinger_direction = None

    def define(self, locations):
        """Specify the hydrophone configuration by passing an Nx3 numpy
        array, where each row is an XYZ coordinate representing a coordinate
        location of a hydrophone with respect to the array origin.
        """
        self.hydrophones.set_XYZ(XYZ=locations)

        # Generate immediate parameters
        self.n_elements = locations.shape[0]

        # Make a list of all relative hydrophone locations
        self.element_pos = locations

        # Enumerate all pair combinations
        self.pairs = [
            ID for ID in itertools.combinations(range(self.n_elements), 2)
        ]

        # compute pair properties
        self.d = []
        self.COM = []
        self.d_vect = []
        self.norm_uvect = []
        self.pair_y_rot = []
        for (ID0, ID1) in self.pairs:
            # General basic paremeters
            l0 = self.element_pos[ID0]
            l1 = self.element_pos[ID1]

            # compute vectorized distance between pairs
            d_vect = l1 - l0
            self.d_vect.append(d_vect)

            # absolute distance between each pair
            self.d.append(np.linalg.norm(l0 - l1))

            # determine center of mass for each pair
            self.COM.append(l0 + (l1 - l0) / 2)

            # Get nNormal for each pair (assuming each pair rests on
            # the z = 0 plane).
            norm_vect = np.array([[-d_vect[1], d_vect[0], 0]])
            norm_uvect = norm_vect / np.linalg.norm(norm_vect)
            self.norm_uvect.append(norm_uvect)

            # Get y_based rotation
            abs_y_rot = np.arccos(np.dot(norm_vect[0], K_HAT[0]) / np.linalg.norm(norm_vect)) # [0] is ugly syntax to yield a 1D vector
            if (norm_uvect[DIM1, 0] < 0):
                y_rot = -abs_y_rot
            else:
                y_rot = abs_y_rot

            self.pair_y_rot.append(y_rot)

    def compute_D1minusD2(self, Dx):
        self.ddoa = []
        for (ID_a, ID_b) in self.pairs:
            ddoa = Dx[ID_a] - Dx[ID_b]
            (self.ddoa).append(ddoa)

    def compute_ab_coefficients(self):
        n_pairs = len(self.pairs)

        i = 0
        self.ab = []
        for i in range(n_pairs):
            ab = dd_to_hyperboloid_coe(self.ddoa[i], self.d[i])
            (self.ab).append(ab)

    def bulk_compute_ab_from_distances(self, Dx):
        self.compute_D1minusD2(Dx)
        self.compute_ab_coefficients()

    def get_direction(self, doa):
        """
        Takes a list of distances of arrival (doa) values corresponding to each
        hydrophone element and uses
        such information to generate a pinger location
        """
        self.bulk_compute_ab_from_distances(doa)
        # ^^ Updates self.ddoa and self.ab

        self.last_known_pinger_direction = 0

    def re_build(self, locations):
        self.__init__(locations)

    def refresh_data(self):
        pass

    def print_drawing(self):
        pass