o
    i%                     @  s   d Z ddlmZ ddlmZ ddlmZ ddlmZ ddl	m
Z
 ddlmZ ddlmZ ed	ed
edZeG dd de
e ZeG dd de
e ZdS )aG  Parent fork identification and deadlock avoidance in parallel graph execution.

This module provides functionality to identify "parent forks" in a graph, which are dominating
fork nodes that control access to join nodes. A parent fork is a fork node that:

1. Dominates a join node (all paths to the join must pass through the fork)
2. Does not participate in cycles that bypass it to reach the join

Identifying parent forks is crucial for deadlock avoidance in parallel execution. When a join
node waits for all its incoming branches, knowing the parent fork helps determine when it's
safe to proceed without risking deadlock.

In most typical graphs, such dominating forks exist naturally. However, when there are multiple
subsequent forks, the choice of parent fork can be ambiguous and may need to be specified by
the graph designer.
    )annotations)Hashable)	dataclass)cached_property)Generic)TypeVar)GraphBuildingErrorTT)boundinfer_variancedefaultc                   @  s$   e Zd ZU dZded< 	 ded< dS )
ParentForka3  Represents a parent fork node and its relationship to a join node.

    A parent fork is a dominating fork that controls the execution flow to a join node.
    It tracks which nodes lie between the fork and the join, which is essential for
    determining when it's safe to proceed past the join point.
    r	   fork_idset[T]intermediate_nodesN)__name__
__module____qualname____doc____annotations__ r   r   f/var/www/html/karishye-ai-python/venv/lib/python3.10/site-packages/pydantic_graph/beta/parent_forks.pyr       s   
 r   c                   @  s|   e Zd ZU dZded< 	 ded< 	 ded< 	 ded< 	 dd	d
d!ddZed"ddZed#ddZd$ddZ	d%dd Z
dS )&ParentForkFinderzAnalyzes graph structure to identify parent forks for join nodes.

    This class implements algorithms to find dominating forks in a directed graph,
    which is essential for coordinating parallel execution and avoiding deadlocks.
    r   nodes	start_idsfork_idsdict[T, list[T]]edgesNF)parent_fork_idprefer_closestjoin_idr	   r   T | Noner   boolreturnParentFork[T] | Nonec                C  s   |dur |  ||}|du rtd|d|dtt ||S t }|}d}	 | |}|du r4	 |S ||vsEJ d| d| d| || || jvrPq'|  ||}|durftt ||}|re|S n|durl|S q()a5  Find the parent fork for a given join node.

        Searches for the _most_ ancestral dominating fork that can serve as a parent fork
        for the specified join node. A valid parent fork must dominate the join without
        allowing cycles that bypass it.

        Args:
            join_id: The identifier of the join node to analyze.
            parent_fork_id: Optional manually selected node ID to attempt to use as the parent fork node.
            prefer_closest: If no explicit fork is specified, this argument is used to determine
                whether to find the closest or farthest (i.e., most ancestral) dominating fork.

        Returns:
            A ParentFork object containing the fork ID and intermediate nodes if a valid
            parent fork exists, or None if no valid parent fork can be found (which would
            indicate potential deadlock risk).

        Note:
            If every dominating fork of the join lets it participate in a cycle that avoids
            the fork, None is returned since no valid "parent fork" exists.
        Nz.There is a cycle in the graph passing through z that does not include zJ. Parent forks of a join must be a part of any cycles involving that join.Tz"Cycle detected in dominator tree: u    → )_get_upstream_nodes_if_parentr   r   r	   set_immediate_dominatoraddr   )selfr    r   r   upstream_nodesvisitedcurparent_forkr   r   r   find_parent_forkH   s8   
"

z!ParentForkFinder.find_parent_forkc                 C  sB   dd | j D }| j D ]}| j|g D ]	}|| | qq|S )zCompute and cache the predecessor mapping for all nodes.

        Returns:
            A dictionary mapping each node to a list of its immediate predecessors.
        c                 S  s   i | ]}|g qS r   r   .0nr   r   r   
<dictcomp>   s    z2ParentForkFinder._predecessors.<locals>.<dictcomp>)r   r   getappend)r)   predecessors	source_iddestination_idr   r   r   _predecessors   s   
zParentForkFinder._predecessorsdict[T, set[T]]c                   s   t | j| j}fddD  |D ]}|h |< qd}|rXd}| D ]0}| j| }|s/q%|r?t t j fdd|D  nt t  }|h|B }| | krU| |< d}q%|s S )aJ  Compute the dominator sets for all nodes using iterative dataflow analysis.

        A node D dominates node N if every path from a start node to N must pass through D.
        This is computed using a fixed-point iteration algorithm.

        Returns:
            A dictionary mapping each node to its set of dominators.
        c                   s   i | ]}|t  qS r   )r&   r/   )node_idsr   r   r2      s    z0ParentForkFinder._dominators.<locals>.<dictcomp>TFc                 3  s    | ]} | V  qd S Nr   )r0   p)domr   r   	<genexpr>   s    z/ParentForkFinder._dominators.<locals>.<genexpr>)r&   r   r   r8   r	   intersection)r)   r   schangedr1   predsr?   new_domr   )r=   r:   r   _dominators   s(   


*
zParentForkFinder._dominatorsnode_idc                   sB   | j | |h }|D ] t fdd|D r   S qdS )aS  Find the immediate dominator of a node.

        The immediate dominator is the closest dominator to a node (other than itself)
        in the dominator tree.

        Args:
            node_id: The node to find the immediate dominator for.

        Returns:
            The immediate dominator's ID if one exists, None otherwise.
        c                 3  s$    | ]} |kp | vV  qd S r;   r   )r0   dcr=   r   r   r>      s   " z8ParentForkFinder._immediate_dominator.<locals>.<genexpr>N)rD   all)r)   rE   
candidatesr   rG   r   r'      s   z%ParentForkFinder._immediate_dominatorr   set[T] | Nonec                 C  sd   t  }|g}|r0| }| j| D ]}||krq||kr dS ||vr-|| || q|s|S )a  Check if a fork is a valid parent and return upstream nodes.

        Tests whether the given fork can serve as a parent fork for the join by checking
        for cycles that bypass the fork. If valid, returns all nodes that can reach the
        join without going through the fork.

        Args:
            join_id: The join node being analyzed.
            fork_id: The potential parent fork to test.

        Returns:
            The set of node IDs upstream of the join (excluding the fork) if the fork is
            a valid parent, or None if a cycle exists that bypasses the fork (making it
            invalid as a parent fork).

        Note:
            If, in the graph with fork_id removed, a path exists that starts and ends at
            the join (i.e., join is on a cycle avoiding the fork), we return None because
            the fork would not be a valid "parent fork".
        N)r&   popr8   r(   r4   )r)   r    r   upstreamstackvr<   r   r   r   r%      s   


z.ParentForkFinder._get_upstream_nodes_if_parent)r    r	   r   r!   r   r"   r#   r$   )r#   r   )r#   r9   )rE   r	   r#   r!   )r    r	   r   r	   r#   rK   )r   r   r   r   r   r.   r   r8   rD   r'   r%   r   r   r   r   r   4   s$   
 @
r   N)r   
__future__r   collections.abcr   dataclassesr   	functoolsr   typingr   typing_extensionsr   pydantic_graph.exceptionsr   strr	   r   r   r   r   r   r   <module>   s    