def get_multiple_interactions_as_row(self, maxInteractions):
        '''Returns interactions and geometric information in a single row

        Returns
        -------
        int
           row of itneractions and geometric information

        '''
        while self.get_num_interactions() < maxInteractions:
            self.neighbors.append(InteractionCenter())

        self.length = InteractionCenter.get_length()

        self.calc_coordination_geometry(maxInteractions)

        data = [self.structureId, self._get_number_of_polymer_chains(),
                self.q3, self.q4, self.q5, self.q6]

        # Copy data for query atom
        data += self.center.get_as_object()

        # Copy data for interaction atoms
        for i, neighbor in enumerate(self.neighbors):
            data += neighbor.get_as_object()
            data.append(self.distances[i])

        data += self.angles

        return Row(data)
    def get_schema(self, maxInteractions):
        '''Returns the schema for a row of atom interaction inforamtion. The schema
        is used to create a Dataset<Row> from the row information.

        Returns
        -------
            return schema for dataset
        '''

        sf = []
        sf.append(StructField("pdbId", StringType(), False))
        sf.append(StructField("polyChains", IntegerType(), False))
        sf.append(StructField("q3", FloatType(), True))
        sf.append(StructField("q4", FloatType(), True))
        sf.append(StructField("q5", FloatType(), True))
        sf.append(StructField("q6", FloatType(), True))

        # Copy schema for query atom
        sf += InteractionCenter.get_struct_fields(0)

        # Copy schema info for interacting atoms and their distances
        for i in range(maxInteractions):
            sf += InteractionCenter.get_struct_fields(i + 1)
            sf.append(StructField(f"distance{i+1}", FloatType(), True))

        # Add schema for angles
        for i in range(maxInteractions - 1):
            for j in range(i + 1, maxInteractions):
                sf.append(StructField(f"angle{i+1}-{j+1}", FloatType(), True))

        return StructType(sf)
    def get_pair_interaction_schema(self):
        '''Returns the schema for a row of pairwise atom interactions.
        The schema is used to create a Dataset<Row> from the row information

        Return
        ------
            return schema for dataset
        '''

        sf = []
        sf.append(StructField("pdbId", StringType(), False))

        # copy schema info for query atom
        sf += InteractionCenter.get_struct_fields(0)

        # copy schema infor for interacting atoms and their distnce
        sf += InteractionCenter.get_struct_fields(1)
        sf.append(StructField("distance1", FloatType(), True))

        return StructType(sf)
    def get_pair_interactions_as_rows(self):
        '''Return rows of pairwise interactions with the central atom

        Returns
        -------
            rows of pairwise interactions with the central atom
        '''

        rows = []
        length = InteractionCenter.get_length()
        self.calc_coordination_geometry()

        for i, neighbor in enumerate(self.neighbors):
            index = 0

        return rows
    def _get_interactions(self, arrays, queryAtomIndex, box):
        '''Get the interacting neighbors of an atom in a structure

        Attributes
        ----------
            arrays (columnarStructure): structure in columnarStructure format
            queryAtomIndex (int): the index of the querying atom
            box (distanceBox): the distance box of the query atom

        Returns
        -------
            an AtomInteraction class with interacting neighbors
        '''
        interaction = AtomInteraction()

        # get the x,y,z coordinates of the structure
        x = arrays.get_x_coords()
        y = arrays.get_y_coords()
        z = arrays.get_z_coords()

        # get the query atom coordinates
        qx = x[queryAtomIndex]
        qy = y[queryAtomIndex]
        qz = z[queryAtomIndex]

        # get required information of the columnarStructure
        atomToGroupIndices = arrays.get_atom_to_group_indices()
        occupancies = arrays.get_occupancies()
        normalizedbFactors = arrays.get_normalized_b_factors()
        groupNames = arrays.get_group_names()

        # record query atom info
        queryCenter = InteractionCenter(arrays, queryAtomIndex)
        interaction.set_center(queryCenter)

        # Retrieve atom indices of atoms that lay within grid cubes that are
        # within cutoff distance of the query atom
        cutoffDistanceSq = self.filter.get_distance_cutoff() ** 2

        # Retrieve atom indices of atoms that lay within grid cubes
        # that are within cutoff distance of the query atom
        neighborIndices = box.get_neighbors(np.array([qx, qy, qz]))
        # TEST: flattern neighborIndices
        neighborIndices = [
            n for neighbors in neighborIndices for n in neighbors]

        # determine and record interactions with neighbor atoms
        for neighborIndex in neighborIndices:

            # exclude self interactions with a group
            if atomToGroupIndices[neighborIndex] == atomToGroupIndices[queryAtomIndex]:
                continue

            # check if interaction is within distance cutoff
            dx = qx - x[neighborIndex]
            dy = qy - y[neighborIndex]
            dz = qz - z[neighborIndex]
            distSq = dx * dx + dy * dy + dz * dz

            if distSq <= cutoffDistanceSq:
                # Exclude interactions with undesired groups and
                # atoms with partial occupancy (< 1.0)
                if self.filter.is_prohibited_target_group(groupNames[neighborIndex]) \
                        or self.filter.get_normalized_b_factor_cutoff() < normalizedbFactors[neighborIndex] \
                        or occupancies[neighborIndex] < float(1.0):

                    # return an empty atom interaction
                    return AtomInteraction()

                # add interacting atom info
                neighbor = InteractionCenter(arrays, neighborIndex)
                interaction.add_neighbor(neighbor)

                # terminate early if the number of interactions exceeds limit
                if interaction.get_num_interactions() > self.filter.get_max_interactions():
                    return interaction

        return interaction