def tctc(data, tau, epsilon, sigma, kappa=0, largedataset=False, rule='flock', noise=None, raw_signal='amplitude', output='array', tempdir=None, njobs=1, largestonly=False):
    r"""
    Runs TCTC community detection

    Parameters
    ----------
    data : array
        Multiariate series with dimensions: "time, node" that belong to a network.
    tau : int
        tau specifies the minimum number of time-points of each temporal community must last.
    epsilon : float
        epsilon specifies the distance points in a community can be away from each other.
    sigma : int
        sigma specifies the minimum number of nodes that must be in a community.
    kappa : int
        kappa specifies the number of consecutive time-points that can break the distance or size rules.
    largedataset : bool
        If true, runs with HDF5 (beta)
    rule : str
        Can be 'convoy' or 'flock'.
            - flock entials all nodes are max epsilon apart in a communiy.
            - convoy entails that there is at least one node that is epsilon apart.
    noise : array (defauly None)
        Timeseries of dimensions "time, N" where N is the number of noise time series added. Any community that contains this time series is excluded.
    raw_signal : str
        Can be amplitude or phase
    output : str
        Can be array or df or None
    tempdir : str
        Specify where the temporary directory is if largedataset is True
    njobs : int
        number of jobs (not implemented yet)
    largestonly : bool (default False)
        If True only considers largest communities in rule application (should generally be false)

    Returns
    -----------
        tctc : array, df
    """
    # Get distance matrix
    if largedataset:
        raise NotImplementedError(
            'HDF5 implementation for large datasets is not available yet')
    else:
        N_data = data.shape[1]
        if noise is not None:
            if len(noise.shape) == 1:
                noise = np.array(noise, ndmin=2).transpose()
            N_data = data.shape[1]
            data = np.hstack([data, noise])

        N = data.shape[1]
        #T = data.shape[0]

        if raw_signal == 'amplitude':
            d = np.array([np.abs(data[:, n]-data[:, m])
                          for n in range(data.shape[-1]) for m in range(data.shape[-1])])
            d = np.reshape(d, [data.shape[-1], data.shape[-1], data.shape[0]])

        elif raw_signal == 'phase':
            analytic_signal = hilbert(data.transpose())
            instantaneous_phase = np.angle(analytic_signal)
            d = np.zeros([data.shape[1], data.shape[1], data.shape[0]])
            for n in range(data.shape[1]):
                for m in range(data.shape[1]):
                    d[n, m, :] = np.remainder(
                        np.abs(instantaneous_phase[n, :] - instantaneous_phase[m, :]), np.pi)

        # Shape of datin (with any addiitonal 0s or noise added to nodes)
        dat_shape = [int(d.shape[-1]), int(d.shape[0])]
        # Make trajectory matrix 1 where distance critera is kept
        tctc_mat = np.zeros([dat_shape[1], dat_shape[1], dat_shape[0]])
        tctc_mat[:, :, :][d <= epsilon] = 1

        t1 = 1
        t2 = 2
        # The next two rules have to be run iteratively until it converges. i.e. when applying the sigma and tau parameters, if nothing more is pruned, then this is complete
        # There may be a case where running it in this order could through some value that is unwanted due to the skipping mechanic.
        # Doing it in the other order does create possible bad values.
        while t1 != t2:

            t1 = tctc_mat.sum()
            cliques = []
            if tctc_mat.sum() > 0:
                # Run the trajectory clustering rule
                if rule == 'flock':

                    cliques = [list(filter(lambda x: (len(x) >= sigma) and (len(set(x).intersection(np.arange(N_data, N+1))) == 0), nx.find_cliques(
                        nx.Graph(tctc_mat[:, :, t])))) for t in range(tctc_mat.shape[-1])]
                    #cliques = []
                    # with ProcessPoolExecutor(max_workers=njobs) as executor:
                    #    job = {executor.submit(_cluster_flocks,tctc_mat[:,:,t],sigma) for t in range(tctc_mat.shape[-1])}
                    #    for j in as_completed(job):
                    #        cliques.append(j.result()[0])

                elif rule == 'convoy':
                    cliques = [list(map(list, filter(lambda x: (len(x) >= sigma) and (len(set(x).intersection(np.arange(N_data, N+1))) == 0), nx.connected_components(
                        nx.Graph(tctc_mat[:, :, t]))))) for t in range(tctc_mat.shape[-1])]

                # Reset the trajectory matrix (since info is now in "cliques").
                # Add the infomation from clique into tctc_mat (i.e sigma is now implemented)
                tctc_mat = np.zeros([dat_shape[1], dat_shape[1], dat_shape[0]])
                # Due to advanced index copy, I've done this with too many forloops
                for t in range(dat_shape[0]):
                    for c in cliques[t]:
                        # Make one of index communitytors a list.
                        cv = [[i] for i in c]
                        tctc_mat[cv, c, t] = 1

            if tctc_mat.sum() > 0:
                # Now impose tau criteria. This is done by flattening and (since tau has been added to the final dimension)
                # Add some padding as this is going to be needed when flattening (ie different lines must have at least tau+kappa spacing between them)
                tctc_mat = np.dstack([np.zeros([dat_shape[1], dat_shape[1], 1]), tctc_mat, np.zeros(
                    [dat_shape[1], dat_shape[1], tau+kappa])])
                # Make to singular communitytor
                tctc_mat_community = np.array(tctc_mat.flatten())
                # Add an extra 0
                tctc_mat_dif = np.append(tctc_mat_community, 0)
                # Use diff. Where there is a 1 trajectory starts, where -1 trajectory ends
                tctc_mat_dif = np.diff(tctc_mat_dif)
                start_ones = np.where(tctc_mat_dif == 1)[0]
                end_ones = np.where(tctc_mat_dif == -1)[0]
                skip_ind = np.where(start_ones[1:]-end_ones[:-1] <= kappa)[0]
                start_ones = np.delete(start_ones, skip_ind+1)
                end_ones = np.delete(end_ones, skip_ind)

                traj_len = end_ones - start_ones
                # whereever traj_len is not long enough, loop through ind+t and make these 0
                ind = start_ones[traj_len >= tau] + 1
                l2 = traj_len[traj_len >= tau]
                # for t in range(tau-1): # this didn't work (but was quicker) because of tau bug
                #    tctc_mat[ind+t] = 0
                # Looping over each valid trajectory instance is slower but the safest was to impose tau restrain and reinserting it.
                tctc_mat = np.zeros(tctc_mat_community.shape)
                for i in range(len(ind)):
                    tctc_mat[ind[i]:ind[i]+l2[i]] = 1
                tctc_mat = tctc_mat.reshape(
                    dat_shape[1], dat_shape[1], dat_shape[0]+kappa+tau+1)
                # remove padding
                tctc_mat = tctc_mat[:, :, 1:dat_shape[0]+1]

            t2 = tctc_mat.sum()

        # remove noise
        tctc_mat = tctc_mat[:N_data, :N_data]
        if output == 'array':
            return tctc_mat

        elif output == 'df':
            if np.sum(tctc_mat) != 0:
                df = partition_inference(
                    tctc_mat, cliques, tau, sigma, kappa)
                return df
            else:
                return []
def tctc ( data , tau , epsilon , sigma , kappa = 0 , largedataset = False , rule = 'flock' , noise = None , raw_signal = 'amplitude' , output = 'array' , tempdir = None , njobs = 1 , largestonly = False ) : if largedataset : raise NotImplementedError ( 'HDF5 implementation for large datasets is not available yet' ) else : N_data = data . shape [ 1 ] if noise is not None : if len ( noise . shape ) == 1 : noise = np . array ( noise , ndmin = 2 ) . transpose ( ) N_data = data . shape [ 1 ] data = np . hstack ( [ data , noise ] ) N = data . shape [ 1 ] if raw_signal == 'amplitude' : d = np . array ( [ np . abs ( data [ : , n ] - data [ : , m ] ) for n in range ( data . shape [ - 1 ] ) for m in range ( data . shape [ - 1 ] ) ] ) d = np . reshape ( d , [ data . shape [ - 1 ] , data . shape [ - 1 ] , data . shape [ 0 ] ] ) elif raw_signal == 'phase' : analytic_signal = hilbert ( data . transpose ( ) ) instantaneous_phase = np . angle ( analytic_signal ) d = np . zeros ( [ data . shape [ 1 ] , data . shape [ 1 ] , data . shape [ 0 ] ] ) for n in range ( data . shape [ 1 ] ) : for m in range ( data . shape [ 1 ] ) : d [ n , m , : ] = np . remainder ( np . abs ( instantaneous_phase [ n , : ] - instantaneous_phase [ m , : ] ) , np . pi ) dat_shape = [ int ( d . shape [ - 1 ] ) , int ( d . shape [ 0 ] ) ] tctc_mat = np . zeros ( [ dat_shape [ 1 ] , dat_shape [ 1 ] , dat_shape [ 0 ] ] ) tctc_mat [ : , : , : ] [ d <= epsilon ] = 1 t1 = 1 t2 = 2 while t1 != t2 : t1 = tctc_mat . sum ( ) cliques = [ ] if tctc_mat . sum ( ) > 0 : if rule == 'flock' : cliques = [ list ( filter ( lambda x : ( len ( x ) >= sigma ) and ( len ( set ( x ) . intersection ( np . arange ( N_data , N + 1 ) ) ) == 0 ) , nx . find_cliques ( nx . Graph ( tctc_mat [ : , : , t ] ) ) ) ) for t in range ( tctc_mat . shape [ - 1 ] ) ] elif rule == 'convoy' : cliques = [ list ( map ( list , filter ( lambda x : ( len ( x ) >= sigma ) and ( len ( set ( x ) . intersection ( np . arange ( N_data , N + 1 ) ) ) == 0 ) , nx . connected_components ( nx . Graph ( tctc_mat [ : , : , t ] ) ) ) ) ) for t in range ( tctc_mat . shape [ - 1 ] ) ] tctc_mat = np . zeros ( [ dat_shape [ 1 ] , dat_shape [ 1 ] , dat_shape [ 0 ] ] ) for t in range ( dat_shape [ 0 ] ) : for c in cliques [ t ] : cv = [ [ i ] for i in c ] tctc_mat [ cv , c , t ] = 1 if tctc_mat . sum ( ) > 0 : tctc_mat = np . dstack ( [ np . zeros ( [ dat_shape [ 1 ] , dat_shape [ 1 ] , 1 ] ) , tctc_mat , np . zeros ( [ dat_shape [ 1 ] , dat_shape [ 1 ] , tau + kappa ] ) ] ) tctc_mat_community = np . array ( tctc_mat . flatten ( ) ) tctc_mat_dif = np . append ( tctc_mat_community , 0 ) tctc_mat_dif = np . diff ( tctc_mat_dif ) start_ones = np . where ( tctc_mat_dif == 1 ) [ 0 ] end_ones = np . where ( tctc_mat_dif == - 1 ) [ 0 ] skip_ind = np . where ( start_ones [ 1 : ] - end_ones [ : - 1 ] <= kappa ) [ 0 ] start_ones = np . delete ( start_ones , skip_ind + 1 ) end_ones = np . delete ( end_ones , skip_ind ) traj_len = end_ones - start_ones ind = start_ones [ traj_len >= tau ] + 1 l2 = traj_len [ traj_len >= tau ] tctc_mat = np . zeros ( tctc_mat_community . shape ) for i in range ( len ( ind ) ) : tctc_mat [ ind [ i ] : ind [ i ] + l2 [ i ] ] = 1 tctc_mat = tctc_mat . reshape ( dat_shape [ 1 ] , dat_shape [ 1 ] , dat_shape [ 0 ] + kappa + tau + 1 ) tctc_mat = tctc_mat [ : , : , 1 : dat_shape [ 0 ] + 1 ] t2 = tctc_mat . sum ( ) tctc_mat = tctc_mat [ : N_data , : N_data ] if output == 'array' : return tctc_mat elif output == 'df' : if np . sum ( tctc_mat ) != 0 : df = partition_inference ( tctc_mat , cliques , tau , sigma , kappa ) return df else : return [ ]
r"""
    Runs TCTC community detection

    Parameters
    ----------
    data : array
        Multiariate series with dimensions: "time, node" that belong to a network.
    tau : int
        tau specifies the minimum number of time-points of each temporal community must last.
    epsilon : float
        epsilon specifies the distance points in a community can be away from each other.
    sigma : int
        sigma specifies the minimum number of nodes that must be in a community.
    kappa : int
        kappa specifies the number of consecutive time-points that can break the distance or size rules.
    largedataset : bool
        If true, runs with HDF5 (beta)
    rule : str
        Can be 'convoy' or 'flock'.
            - flock entials all nodes are max epsilon apart in a communiy.
            - convoy entails that there is at least one node that is epsilon apart.
    noise : array (defauly None)
        Timeseries of dimensions "time, N" where N is the number of noise time series added. Any community that contains this time series is excluded.
    raw_signal : str
        Can be amplitude or phase
    output : str
        Can be array or df or None
    tempdir : str
        Specify where the temporary directory is if largedataset is True
    njobs : int
        number of jobs (not implemented yet)
    largestonly : bool (default False)
        If True only considers largest communities in rule application (should generally be false)

    Returns
    -----------
        tctc : array, df