def __call__(self, t):
        structure_id = t[0]

        if self.bio < 1:
            raise ValueError('bio assembly number must be >= 1, was:',
                             self.bio)

        # if the specified bio assembly does not exist, return an empty list
        if len(t[1].bio_assembly) < self.bio:
            return []

        structure = ColumnarStructure(t[1])

        # Get a pandas dataframe representation of the structure
        df = structure.to_pandas()
        if df is None:
            return []

        # Apply query filter
        if self.query is None:
            q = df
        else:
            q = df.query(self.query)

        if q is None or q.shape[0] == 0:
            return []

        # Apply target filter
        if self.target is None:
            t = df
        elif self.target == self.query:
            # if query and target are identical, reuse the query dataframe
            t = q
        else:
            t = df.query(self.target)

        if t is None or t.shape[0] == 0:
            return []

        # Group by chain ids
        q_chains = q.groupby('chain_id')
        t_chains = t.groupby('chain_id')

        rows = list()

        # Find interactions between pairs of chains in bio assembly
        transforms = self.get_transforms(structure)
        for q_transform in transforms:
            qindex = q_transform[0]  # transformation id
            qchain = q_transform[1]  # chain id

            if qchain in q_chains.groups.keys():
                qt = q_chains.get_group(qchain).reset_index(drop=True)
            else:
                continue

            # Stack coordinates into an nx3 array
            cq = np.column_stack(
                (qt['x'].values, qt['y'].values, qt['z'].values)).copy()
            # Create transformation matrix
            qmat = np.array(q_transform[2]).reshape((4, 4))

            # Apply bio assembly transformations
            #   apply rotation
            cqt = np.matmul(cq, qmat[0:3, 0:3])
            #   apply translation
            cqt += qmat[3, 0:3].transpose()

            for t_transform in transforms:
                tindex = t_transform[0]
                tchain = t_transform[1]

                # exclude intra interactions (same transformation and same chain id)
                if not self.intra and qindex == tindex and qchain == tchain:
                    continue

                if not self.inter and qindex != tindex and qchain != tchain:
                    continue

                if tchain in t_chains.groups.keys():
                    tt = t_chains.get_group(tchain).reset_index(drop=True)
                else:
                    continue

                # Stack coordinates into an nx3 array
                ct = np.column_stack(
                    (tt['x'].values, tt['y'].values, tt['z'].values)).copy()

                # Get a 4x4 transformation matrix
                tmat = np.array(t_transform[2]).reshape((4, 4))

                # Apply bio assembly transformations
                #   apply rotation
                ctt = np.matmul(ct, tmat[0:3, 0:3])
                #   apply translation
                ctt += tmat[3, 0:3].transpose()

                rows += _calc_interactions(structure_id, qt, tt, cqt, ctt,
                                           self.level, self.distance_cutoff,
                                           self.bio, qindex, tindex)

        return rows
    def __call__(self, t):
        structure_id = t[0]

        # Get a pandas dataframe representation of the structure
        structure = ColumnarStructure(t[1])

        df = structure.to_pandas()
        if df is None:
            return []

        # Apply query filter
        if self.query is None:
            q = df
        else:
            q = df.query(self.query)

        if q is None or q.shape[0] == 0:
            return []

        # Apply target filter
        if self.target is None:
            t = df
        elif self.target == self.query:
            # if query and target are identical, reuse the query dataframe
            t = q
        else:
            t = df.query(self.target)

        if t is None or t.shape[0] == 0:
            return []

        # group by chain ids
        q_chains = q.groupby('chain_id')
        t_chains = t.groupby('chain_id')

        rows = list()

        # Find interactions between pairs of chains
        for q_chain in q_chains.groups.keys():
            qt = q_chains.get_group(q_chain).reset_index(drop=True)

            for t_chain in t_chains.groups.keys():

                # exclude intra interactions (same chain id)
                if not self.intra and q_chain == t_chain:
                    continue

                if not self.inter and q_chain != t_chain:
                    continue

                tt = t_chains.get_group(t_chain).reset_index(drop=True)

                # Stack coordinates into an nx3 array
                cq = np.column_stack(
                    (qt['x'].values, qt['y'].values, qt['z'].values)).copy()
                ct = np.column_stack(
                    (tt['x'].values, tt['y'].values, tt['z'].values)).copy()

                rows += _calc_interactions(structure_id, qt, tt, cq, ct,
                                           self.level, self.distance_cutoff,
                                           None, -1, -1)

        return rows