o
    ei%                     @   sF   d Z ddlZddlZG dd deZedg dZG dd dZdS )	a  A dependency graph for finding evaluation order.

Example
-------
>>> # The basic use case is that you have a bunch of keys
>>> # and some of them depend on each other:
>>> database = []
>>> functions = {'read': {'func': lambda: (0,1,2),
...                       'needs': []},
...              'process': {'func': lambda X: [x**2 for x in X],
...                          'needs': ['read']},
...              'save': {'func': lambda x: database.append(x),
...                       'needs': ['process']},
...              'print': {'func': lambda x,y: print(x, "became", y),
...                        'needs': ['read', 'process']},
...              'auxiliary': {'func': lambda: (1,2,3),
...                            'needs': []}}
>>> # If this is user supplied info, so you can't just hardcode the order,
>>> # a dependency graph may be needed.
>>> dg = DependencyGraph()
>>> # In simple cases, you can just encode the dependencies directly:
>>> for key, conf in functions.items():
...     for needed in conf["needs"]:
...         dg.add_edge(key, needed)
>>> # Now we can evaluate:
>>> outputs = {}
>>> for node in dg.get_evaluation_order():
...     f = functions[node.key]['func']
...     args = [outputs[needed] for needed in functions[node.key]['needs']]
...     outputs[node.key] = f(*args)
(0, 1, 2) became [0, 1, 4]
>>> # This added nodes implicitly.
>>> # However, since 'auxiliary' didn't depend on anything,
>>> # it didn't get added!
>>> assert 'auxiliary' not in outputs
>>> # So to be careful, we should also manually add nodes for any thing that
>>> # is not an intermediate step.
>>> _ = dg.add_node('auxiliary')
>>> assert 'auxiliary' in (node.key for node in dg.get_evaluation_order())
>>> # Arbitrary data can be added to nodes:
>>> dg2 = DependencyGraph()
>>> for key, conf in functions.items():
...     _ = dg2.add_node(key, conf)
...     for needed in conf["needs"]:
...         dg2.add_edge(key, needed)
>>> # Now we get access to the data in evaluation:
>>> outputs2 = {}
>>> for key, _, conf in dg2.get_evaluation_order():
...     f = conf['func']
...     args = [outputs[needed] for needed in conf['needs']]
...     outputs[key] = f(*args)
(0, 1, 2) became [0, 1, 4]

Authors:
    * Aku Rouhe 2020
    Nc                   @   s   e Zd ZdZdS )CircularDependencyErrorz
    An error caused by running into circular dependencies while searching for
    an evaluation order in a DependencyGraph.
    N)__name__
__module____qualname____doc__ r   r   X/home/ubuntu/transcripts/venv/lib/python3.10/site-packages/speechbrain/utils/depgraph.pyr   >   s    r   DGNode)keyedgesdatac                   @   s`   e Zd ZdZdd Zedd ZdddZd	d
 Zdd Z	dd Z
dddZdd Zdd ZdS )DependencyGraphaW  General-purpose dependency graph.

    Essentially a directed acyclic graph.
    Usually used to find an evaluation order for e.g. variable substitution
    The relation that an edge between A and B represents is:
    "A depends on B, i.e. B should be evaluated before A"

    Nodes can be added explicitly or they can be created implicitly
    while adding edges.
    Nodes have keys, which should be some hashable value that identifies
    the elements the graph represents in your use case. E.G. they can just
    be the variable name you want to substitute.
    However, if needed, more generally you can attach any data to a node
    (e.g. a path in your tree), and if so desired, a unique key can be
    created for you. You'll only need to know that key while adding edges
    to/from it.
    Implicit keys and explicit keys can also be mixed.
    c                 C   s   g | _ i | _g | _d S N)digraphkey2ind_manually_added_keysselfr   r   r   __init___   s   
zDependencyGraph.__init__c                   C   s   t  S )z%Returns a unique hashable identifier.)uuiduuid4r   r   r   r   get_unique_keye   s   zDependencyGraph.get_unique_keyNc                 C   s   |du r	|   }n|| jv rtdj|d| j| || jv r8| j| }| j| }t|j|j	|| j|< |S t
| j| j|< | jt|g | |S )a  Adds a node explicitly.

        Arguments
        ---------
        key : hashable, optional
            If not given, a key is created for you.
        data : Any, optional
            Any additional data you wish to attach to this node.

        Returns
        -------
        hashable
            The key that was used (either yours or generated).

        Raises
        ------
        ValueError
            If node with the given key has already been added explicitly
            (with this method, not "add_edge").
        NzAdding duplicate node: {key})r
   )r   r   
ValueErrorformatappendr   r   r	   r
   r   len)r   r
   r   indnoder   r   r   add_nodej   s   




zDependencyGraph.add_nodec                 C   s:   |  |}|  |}| j| j}||vr|| dS dS )a  Adds an edge, and implicitly also creates nodes for keys which have
        not been seen before. This will not let you add data to your nodes.
        The relation encodes: "from_key depends on to_key"
        (to_key must be evaluated before from_key).

        Arguments
        ---------
        from_key : hashable
            The key which depends on.
        to_key : hashable
            The key which is depended on.
        N)_get_ind_and_add_if_newr   r   r   )r   from_keyto_keyfrom_indto_ind
edges_listr   r   r   add_edge   s   

zDependencyGraph.add_edgec                 C   s8   || j vrt| j| j |< | jt|g d  | j | S r   )r   r   r   r   r	   r   r
   r   r   r   r      s   

z'DependencyGraph._get_ind_and_add_if_newc                 C   s
   |    S )a  Checks if an evaluation order can be found.

        A dependency graph is evaluatable if there are no circular
        dependencies, i.e., the graph is acyclic.

        Returns
        -------
        bool
            Indicating if the graph is evaluatable.
        )_find_first_cycler   r   r   r   is_valid   s   
zDependencyGraph.is_validc                 #   sl    t    fdd|du rttj}n	fdd|D }|D ]}|g D ]}j| V  q*q#dS )a  Finds one valid evaluation order.

        There can be many different valid
        orders.
        NOTE: Generates output one DGNode at a time. May generate DGNodes
        before it finds a circular dependency. If you really need to know
        whether an order can be found, check is_valid() first. However,
        the algorithm for finding cycles is essentially the same as the one
        used for finding an evaluation order, so for very large graphs...
        Ah well, but maybe then you should be using some other solution
        anyway.

        Arguments
        ---------
        selected_keys : list, None
            List of keys. If not None, only the selected keys are guaranteed
            in the evaluation order (along with the keys they depend on).

        Yields
        ------
        DGNode
            The added DGNodes in a valid evaluation order.
            See the DGNode namedtuple above.

        Raises
        ------
        CircularDependencyError
            If a circular dependency is found.
        c                 3   s    || g }| |v rt djdfdd|D d|  v r"dS  t| g j|  jD ]}||dD ]}|V  q8q0| V  dS )zImplementation of toposort.z{cycle}z -> c                 3   s     | ]}t  j| jV  qd S r   )strr   r
   ).0ir   r   r   	<genexpr>   s    
zIDependencyGraph.get_evaluation_order.<locals>.toposort.<locals>.<genexpr>)cycleN)visited)r   r   joinunionsetr   r   )root_indr.   herer#   r   	seen_everr   toposortr   r   r6      s$   

z6DependencyGraph.get_evaluation_order.<locals>.toposortNc                    s   g | ]} j | qS r   r   )r*   r
   r   r   r   
<listcomp>   s    z8DependencyGraph.get_evaluation_order.<locals>.<listcomp>r1   ranger   r   )r   selected_keys
start_inds	start_indr   r   r4   r   get_evaluation_order   s   z$DependencyGraph.get_evaluation_orderc                    sL   t   fdd ttjD ]}|vr# |g }|r#|  S qg S )zCDepth-first search based algorithm for finding cycles in the graph.c                    sh   t | | || g }| |v r|S | v rg S t| gj|  jD ]} ||}|r1|  S q$g S )zImplementation of cycle_dfs.)printr0   r1   r   r   )r2   r.   r3   r#   r-   	cycle_dfsr5   r   r   r   rA      s   


z4DependencyGraph._find_first_cycle.<locals>.cycle_dfsr9   )r   r   r-   r   r@   r   r'      s   
z!DependencyGraph._find_first_cyclec                 C   s
   || j v S r   r7   r&   r   r   r   __contains__  s   
zDependencyGraph.__contains__)NNr   )r   r   r   r   r   staticmethodr   r   r%   r   r(   r>   r'   rB   r   r   r   r   r   K   s    

%
=r   )r   collectionsr   r   r   
namedtupler	   r   r   r   r   r   <module>   s    9	