package Graph;

use strict;
use warnings;
BEGIN { warnings->unimport('recursion') if $ENV{GRAPH_ALLOW_RECURSION} }

sub __carp_confess { require Carp; Carp::confess(@_) }
BEGIN {
    if (0) { # SET THIS TO ZERO FOR TESTING AND RELEASES!
	$SIG{__DIE__ } = \&__carp_confess;
	$SIG{__WARN__} = \&__carp_confess;
    }
}

use Graph::AdjacencyMap qw(:flags :fields);

our $VERSION = '0.9735';

require 5.006; # Weak references are absolutely required.

my @GRAPH_PROPS_COPIED = qw(
    undirected refvertexed countvertexed multivertexed __stringified
    hyperedged countedged multiedged
);
my $_empty_array = [];
sub _empty_array () { $_empty_array }

my $can_deep_copy_Storable;
sub _can_deep_copy_Storable () {
    return $can_deep_copy_Storable if defined $can_deep_copy_Storable;
    return $can_deep_copy_Storable = 0 if $] < 5.010; # no :load tag Safe 5.8
    eval {
        require Storable;
        require B::Deparse;
        Storable->VERSION(2.05);
        B::Deparse->VERSION(0.61);
    };
    $can_deep_copy_Storable = !$@;
}

sub _F () { 0 } # Flags.
sub _G () { 1 } # Generation.
sub _V () { 2 } # Vertices.
sub _E () { 3 } # Edges.
sub _A () { 4 } # Attributes.
sub _U () { 5 } # Union-Find.

my $Inf;

BEGIN {
  if ($] >= 5.022) {
    $Inf = eval '+"Inf"'; # uncoverable statement
  } else {
    local $SIG{FPE}; # uncoverable statement
    eval { $Inf = exp(999) } || # uncoverable statement
	eval { $Inf = 9**9**9 } || # uncoverable statement
	    eval { $Inf = 1e+999 } || # uncoverable statement
		{ $Inf = 1e+99 }; # uncoverable statement
                # Close enough for most practical purposes.
  }
}

sub Infinity () { $Inf }

# Graphs are blessed array references.
# - The first element contains the flags.
# - The second element is the vertices.
# - The third element is the edges.
# - The fourth element is the attributes of the whole graph.
# The defined flags for Graph are:
# - unionfind
# The vertices are contained in a "simplemap"
# (if no attributes) or in a "map".
# The edges are always in a "map".
# The defined flags for maps are:
# - _COUNT for countedness: more than one instance
#   expects one for vertices and two for edges
# - _UNORD for unordered coordinates (a set): if _UNORD is not set
#   the coordinates are assumed to be meaningfully ordered
# Vertices and edges assume none of these flags.

use Graph::Attribute array => _A, map => 'graph';

sub stringify {
    my ($u, $h) = (&is_undirected, &is_hyperedged);
    my $e = $u ? '=' : '-';
    my @edges = map join($e,
	$u ? sort { "$a" cmp "$b" } @$_ :
	$h ? map '['.join(",", sort { "$a" cmp "$b" } @$_).']', @$_ :
	@$_), &_edges05;
    my @s = sort @edges;
    push @s, sort { "$a" cmp "$b" } &isolated_vertices;
    join(",", @s);
}

sub eq {
    "$_[0]" eq "$_[1]"
}

sub boolify {
  1;  # Important for empty graphs: they stringify to "", which is false.
}

sub ne {
    "$_[0]" ne "$_[1]"
}

use overload
    '""' => \&stringify,
    'bool' => \&boolify,
    'eq' => \&eq,
    'ne' => \&ne;

sub _opt {
    my ($opt, $flags, %flags) = @_;
    while (my ($flag, $FLAG) = each %flags) {
	$$flags |= $FLAG if delete $opt->{$flag};
	$$flags &= ~$FLAG if delete $opt->{"non$flag"};
    }
}

sub _opt_get {
    my ($opt, $key, $var) = @_;
    return if !exists $opt->{$key};
    $$var = delete $opt->{$key};
}

sub _opt_unknown {
    my ($opt) = @_;
    return unless my @opt = keys %$opt;
    __carp_confess sprintf
        "@{[(caller(1))[3]]}: Unknown option%s: @{[map qq['$_'], sort @opt]}",
        @opt > 1 ? 's' : '';
}

sub _opt_from_existing {
    my ($g) = @_;
    my %existing;
    $existing{$_}++ for grep $g->$_, @GRAPH_PROPS_COPIED;
    $existing{unionfind}++ if $g->has_union_find;
    %existing;
}

sub _opt_to_vflags {
    my ($vflags, $opt) = (0, @_);
    _opt($opt, \$vflags,
	 countvertexed	=> _COUNT,
	 multivertexed	=> _MULTI,
	 refvertexed	=> _REF,
	 refvertexed_stringified => _REFSTR ,
	 __stringified => _STR,
	);
    $vflags;
}

sub _opt_to_eflags {
    my ($eflags, $opt) = (0, @_);
    $opt->{undirected} = !delete $opt->{directed} if exists $opt->{directed};
    _opt($opt, \$eflags,
	 countedged	=> _COUNT,
	 multiedged	=> _MULTI,
	 undirected	=> _UNORD,
	);
    ($eflags, delete $opt->{hyperedged});
}

sub new {
    my ($class, @args) = @_;
    my $gflags = 0;
    my %opt = _get_options( \@args );

    %opt = (_opt_from_existing($class), %opt) # allow overrides
	if ref $class && $class->isa('Graph');

    my $vflags = _opt_to_vflags(\%opt);
    my ($eflags, $is_hyper) = _opt_to_eflags(\%opt);

    _opt(\%opt, \$gflags,
	 unionfind     => _UNIONFIND,
	);

    my @V;
    if ($opt{vertices}) {
	__carp_confess "Graph: vertices should be an array ref"
	    if ref $opt{vertices} ne 'ARRAY';
	@V = @{ delete $opt{vertices} };
    }

    my @E;
    if ($opt{edges}) {
        __carp_confess "Graph: edges should be an array ref of array refs"
	    if ref $opt{edges} ne 'ARRAY';
	@E = @{ delete $opt{edges} };
	__carp_confess "Graph: edges should be array refs"
	    if grep ref $_ ne 'ARRAY', @E;
    }

    _opt_unknown(\%opt);

    __carp_confess "Graph: both countvertexed and multivertexed"
	if ($vflags & _COUNT) && ($vflags & _MULTI);

    __carp_confess "Graph: both countedged and multiedged"
	if ($eflags & _COUNT) && ($eflags & _MULTI);

    my $g = bless [ ], ref $class || $class;
    $g->[ _F ] = $gflags;
    $g->[ _G ] = 0;
    $g->[ _V ] = _make_v($vflags);
    $g->[ _E ] = _make_e($is_hyper, $eflags);
    $g->[ _U ] = do { require Graph::UnionFind; Graph::UnionFind->new }
	if $gflags & _UNIONFIND;
    $g->add_vertices(@V) if @V;
    $g->add_edges(@E) if @E;

    return $g;
}

sub _make_v {
    my ($vflags) = @_;
    $vflags ? _am_heavy($vflags, 1) : _am_light($vflags, 1);
}

sub _make_e {
    my ($is_hyper, $eflags) = @_;
    ($is_hyper or $eflags & ~_UNORD) ?
	_am_heavy($eflags, $is_hyper ? 0 : 2) :
	    _am_light($eflags, 2);
}

sub _am_light {
    require Graph::AdjacencyMap::Light;
    Graph::AdjacencyMap::Light->_new(@_);
}

sub _am_heavy {
    Graph::AdjacencyMap->_new(@_);
}

sub countvertexed { $_[0]->[ _V ]->_is_COUNT }
sub multivertexed { $_[0]->[ _V ]->_is_MULTI }
sub refvertexed   { $_[0]->[ _V ]->_is_REF   }
sub refvertexed_stringified { $_[0]->[ _V ]->_is_REFSTR }
sub __stringified { $_[0]->[ _V ]->_is_STR   }

sub countedged    { $_[0]->[ _E ]->_is_COUNT }
sub multiedged    { $_[0]->[ _E ]->_is_MULTI }
sub hyperedged    { !$_[0]->[ _E ]->[ _arity ] }
sub undirected    { $_[0]->[ _E ]->_is_UNORD }

sub directed { ! $_[0]->[ _E ]->_is_UNORD }

*is_directed      = \&directed;
*is_undirected    = \&undirected;

*is_countvertexed = \&countvertexed;
*is_multivertexed = \&multivertexed;
*is_refvertexed   = \&refvertexed;
*is_refvertexed_stringified = \&refvertexed_stringified;

*is_countedged    = \&countedged;
*is_multiedged    = \&multiedged;
*is_hyperedged    = \&hyperedged;

sub has_union_find { $_[0]->[ _U ] }

sub add_vertex {
    __carp_confess "Graph::add_vertex: use add_vertices for more than one vertex" if @_ != 2;
    __carp_confess "Graph::add_vertex: undef vertex" if grep !defined, @_;
    goto &add_vertices;
}

sub has_vertex {
    my $g = $_[0];
    my $V = $g->[ _V ];
    return defined $V->has_path($_[1]) if ($V->[ _f ] & _REF);
    exists $V->[ _pi ]->{ $_[1] };
}

sub _vertices05 {
    my $g = $_[0];
    $g->[ _V ]->paths;
}

sub vertices {
    my $g = $_[0];
    my @v = &_vertices05;
    return @v if !(&is_multivertexed || &is_countvertexed);
    return map +(($_) x $g->get_vertex_count($_)), @v if wantarray;
    my $V = 0;
    $V += $g->get_vertex_count($_) for @v;
    return $V;
}

*unique_vertices = \&_vertices05;

sub has_vertices {
    my $g = shift;
    scalar $g->[ _V ]->has_any_paths;
}

sub add_edge {
    &expect_hyperedged, &expect_undirected if @_ != 3;
    $_[0]->add_edges([ @_[1..$#_] ]);
}

sub _vertex_ids_ensure {
    push @_, 1;
    goto &_vertex_ids_maybe_ensure;
}

sub _vertex_ids_ensure_multi {
    my $id = pop;
    my @i = &_vertex_ids_ensure;
    push @_, $id;
    @i ? (@i, $id) : ();
}

sub _vertex_ids {
    push @_, 0;
    goto &_vertex_ids_maybe_ensure;
}

sub _vertex_ids_multi {
    my $id = pop;
    my @i = &_vertex_ids;
    push @_, $id;
    @i ? (@i, $id) : ();
}

sub _vertex_ids_maybe_ensure {
    my $ensure = pop;
    my ($g, @args) = @_;
    __carp_confess "Graph: given undefined vertex" if grep !defined, @args;
    my $V = $g->[ _V ];
    my $deep = &is_hyperedged && &is_directed;
    return $V->get_ids_by_paths(\@args, $ensure, $deep) if ($V->[ _f ] & _REF) or $deep;
    my $pi = $V->[ _pi ];
    my @non_exist = grep !exists $pi->{ $_ }, @args;
    return if !$ensure and @non_exist;
    $V->get_ids_by_paths(\@non_exist, 1) if @non_exist;
    @$pi{ @args };
}

sub has_edge {
    my $g = $_[0];
    my $E = $g->[ _E ];
    my ($Ef, $Ea) = @$E[ _f, _arity ];
    return 0 if $Ea and @_ != $Ea + 1;
    my $directed = &is_directed;
    my $deep = &is_hyperedged && $directed;
    return 0 if (my @i = &_vertex_ids) != @_ - 1;
    return defined $E->has_path($directed ? \@i : [ map [ sort @$_ ], @i ]) if $deep;
    @i = sort @i if !$directed;
    exists $E->[ _pi ]{ "@i" };
}

sub any_edge {
    my ($g, @args) = @_;
    my $E = $g->[ _E ];
    my $V = $g->[ _V ];
    return 0 if (my @i = $V->get_ids_by_paths(\@args)) != @args;
    $E->has_successor(@i);
}

sub _edges05 {
    my $g = $_[0];
    my @e = $g->[ _E ]->paths;
    return @e if !wantarray;
    $g->[ _V ]->get_paths_by_ids(\@e, &is_hyperedged && &is_directed);
}

*unique_edges = \&_edges05;

sub edges {
    my $g = $_[0];
    my @e = &_edges05;
    return @e if !(&is_multiedged || &is_countedged);
    return map +(($_) x $g->get_edge_count(@$_)), @e if wantarray;
    my $E = 0;
    $E += $g->get_edge_count(@$_) for @e;
    return $E;
}

sub has_edges {
    scalar $_[0]->[ _E ]->has_any_paths;
}

###
# by_id
#

sub add_vertex_by_id {
    &expect_multivertexed;
    my ($g, $v, $id) = @_;
    my $V = $g->[ _V ];
    return $g if $V->has_path_by_multi_id( my @args = ($v, $id) );
    my ($i) = $V->set_path_by_multi_id( @args );
    $g->[ _U ]->add($i) if &has_union_find;
    $g->[ _G ]++;
    return $g;
}

sub add_vertex_get_id {
    &expect_multivertexed;
    my ($g, $v) = @_;
    my ($i, $multi_id) = $g->[ _V ]->set_path_by_multi_id( $v, _GEN_ID );
    $g->[ _U ]->add($i) if &has_union_find;
    $g->[ _G ]++;
    return $multi_id;
}

sub has_vertex_by_id {
    &expect_multivertexed;
    my ($g, $v, $id) = @_;
    $g->[ _V ]->has_path_by_multi_id( $v, $id );
}

sub delete_vertex_by_id {
    &expect_multivertexed;
    &expect_non_unionfind;
    my ($g, $v, $id) = @_;
    return $g unless &has_vertex_by_id;
    if ($g->[ _V ]->get_multi_ids( $v ) == 1) {
      # only incarnation, zap edges
      my @i = &_vertex_ids_multi;
      pop @i; # the id
      my $E = $g->[ _E ];
      my @edges = $E->paths_from(@i);
      push @edges, $E->paths_to(@i) if !&is_undirected;
      $E->del_path( $_ ) for @edges;
    }
    $g->[ _V ]->del_path_by_multi_id( $v, $id );
    $g->[ _G ]++;
    return $g;
}

sub get_multivertex_ids {
    &expect_multivertexed;
    my $g = shift;
    $g->[ _V ]->get_multi_ids( @_ );
}

sub add_edge_by_id {
    &expect_multiedged;
    my $g = $_[0];
    my @i = &_vertex_ids_ensure_multi;
    my $id = pop @i;
    @i = sort @i if &is_undirected;
    $g->[ _E ]->set_path_by_multi_id( \@i, $id );
    $g->[ _G ]++;
    $g->[ _U ]->union(\@i) if &has_union_find;
    return $g;
}

sub add_edge_get_id {
    &expect_multiedged;
    my $g = $_[0];
    my @i = &_vertex_ids_ensure;
    @i = sort @i if &is_undirected;
    my (undef, $id) = $g->[ _E ]->set_path_by_multi_id( \@i, _GEN_ID );
    $g->[ _G ]++;
    $g->[ _U ]->union(\@i) if &has_union_find;
    return $id;
}

sub has_edge_by_id {
    &expect_multiedged;
    my $g = $_[0];
    my @i = &_vertex_ids_multi;
    return 0 if @i < @_ - 2;
    my $id = pop @i;
    @i = sort @i if &is_undirected;
    $g->[ _E ]->has_path_by_multi_id( \@i, $id );
}

sub delete_edge_by_id {
    &expect_multiedged;
    &expect_non_unionfind;
    my $g = $_[0];
    my $E = $g->[ _E ];
    my @i = &_vertex_ids_multi;
    return if @i < @_ - 2;
    my $id = pop @i;
    @i = sort @i if &is_undirected;
    return unless $E->has_path_by_multi_id( my @args = (\@i, $id) );
    $E->del_path_by_multi_id( @args );
    $g->[ _G ]++;
    return $g;
}

sub get_multiedge_ids {
    &expect_multiedged;
    return unless @_-1 == (my @i = &_vertex_ids);
    $_[0]->[ _E ]->get_multi_ids( \@i );
}

###
# Neighbourhood.
#

sub _edges_at {
    goto &_edges_from if &is_undirected;
    require Set::Object;
    Set::Object->new(&_edges_from, &_edges_to)->${ wantarray ? \'members' : \'size' };
}

sub _edges_from {
    my ($g, @args) = @_;
    my ($V, $E) = @$g[ _V, _E ];
    return if (my @i = $V->get_ids_by_paths(\@args, &is_hyperedged && &is_directed)) != @args;
    $E->paths_from(@i);
}

sub _edges_to {
    goto &_edges_from if &is_undirected;
    my ($g, @args) = @_;
    my ($V, $E) = @$g[ _V, _E ];
    return if (my @i = $V->get_ids_by_paths(\@args, &is_hyperedged && &is_directed)) != @args;
    $E->paths_to(@i);
}

sub edges_at {
    goto &_edges_at if !wantarray;
    $_[0]->[ _V ]->get_paths_by_ids([ &_edges_at ], &is_hyperedged && &is_directed);
}

sub edges_from {
    goto &_edges_from if !wantarray;
    $_[0]->[ _V ]->get_paths_by_ids([ &_edges_from ], &is_hyperedged && &is_directed);
}

sub edges_to {
    goto &edges_from if &is_undirected;
    goto &_edges_to if !wantarray;
    $_[0]->[ _V ]->get_paths_by_ids([ &_edges_to ], &is_hyperedged && &is_directed);
}

sub successors {
    my ($g, @args) = @_;
    my ($V, $E) = @$g[ _V, _E ];
    return if (my @i = $V->get_ids_by_paths(\@args)) != @args;
    my @v = $E->successors(@i);
    return @v if !wantarray;
    map @$_, $V->get_paths_by_ids([ \@v ]);
}

sub predecessors {
    goto &successors if &is_undirected;
    my ($g, @args) = @_;
    my ($V, $E) = @$g[ _V, _E ];
    return if (my @i = $V->get_ids_by_paths(\@args)) != @args;
    my @v = $E->predecessors(@i);
    return @v if !wantarray;
    map @$_, $V->get_paths_by_ids([ \@v ]);
}

sub _cessors_by_radius {
    my ($radius, $method, $self_only_if_loop) = splice @_, -3, 3;
    my ($g, @v) = @_;
    require Set::Object;
    my ($init, $next) = map Set::Object->new(@v), 1..2;
    my $self = $self_only_if_loop ? Set::Object->new(grep $g->has_edge($_, $_), @v) : undef;
    my ($got, $found) = map Set::Object->new, 1..2;
    while (!defined $radius or $radius-- > 0) {
	$found->insert($g->$method($next->members));
	$next = $found->difference($got);
	last if $next->is_null;  # Leave if no new found.
	$got->insert($next->members);
	$found->clear;
    }
    $got->remove($init->difference($self)->members) if $self_only_if_loop;
    $got->${ wantarray ? \'members' : \'size' };
}

sub all_successors {
    &expect_directed;
    push @_, undef, 'successors', 0;
    goto &_cessors_by_radius;
}

sub successors_by_radius {
    &expect_directed;
    push @_, 'successors', 0;
    goto &_cessors_by_radius;
}

sub all_predecessors {
    &expect_directed;
    push @_, undef, 'predecessors', 0;
    goto &_cessors_by_radius;
}

sub predecessors_by_radius {
    &expect_directed;
    push @_, 'predecessors', 0;
    goto &_cessors_by_radius;
}

sub neighbours_by_radius {
    push @_, 'neighbours', 1;
    goto &_cessors_by_radius;
}
*neighbors_by_radius = \&neighbours_by_radius;

sub neighbours {
    require Set::Object;
    my $s = Set::Object->new(&successors);
    $s->insert(&predecessors) if &is_directed;
    $s->${ wantarray ? \'members' : \'size' };
}
*neighbors = \&neighbours;

sub all_neighbours {
    push @_, undef, 'neighbours', 1;
    goto &_cessors_by_radius;
}
*all_neighbors = \&all_neighbours;

sub all_reachable {
    &directed ? goto &all_successors : goto &all_neighbors;
}

sub reachable_by_radius {
    &directed ? goto &successors_by_radius : goto &neighbors_by_radius;
}

sub delete_edge {
    &expect_non_unionfind;
    my $g = $_[0];
    return $g if (my @i = &_vertex_ids) != @_ - 1;
    @i = sort @i if &is_undirected;
    return $g unless @i and $g->[ _E ]->del_path( \@i );
    $g->[ _G ]++;
    return $g;
}

sub delete_vertex {
    &expect_non_unionfind;
    my $g = $_[0];
    return $g if @_ != 2;
    my $V = $g->[ _V ];
    return $g unless defined $V->has_path($_[1]);
    # TODO: _edges_at is excruciatingly slow (rt.cpan.org 92427)
    my $E = $g->[ _E ];
    $E->del_path( $_ ) for &_edges_at;
    $V->del_path($_[1]);
    $g->[ _G ]++;
    return $g;
}

sub get_vertex_count {
    my $g = shift;
    $g->[ _V ]->_get_path_count( @_ );
}

sub get_edge_count {
    my $g = $_[0];
    return 0 if (my @i = &_vertex_ids) != @_ - 1;
    @i = sort @i if &is_undirected;
    $g->[ _E ]->_get_path_count( \@i );
}

sub delete_vertices {
    &expect_non_unionfind;
    my $g = shift;
    while (@_) {
	my $v = shift @_;
	$g->delete_vertex($v);
    }
    return $g;
}

sub delete_edges {
    &expect_non_unionfind;
    my $g = shift;
    while (@_) {
	my ($u, $v) = splice @_, 0, 2;
	$g->delete_edge($u, $v);
    }
    return $g;
}

###
# Degrees.
#

sub in_degree {
    my $g = $_[0];
    return undef unless @_ > 1 && &has_vertex;
    my $in = 0;
    $in += $g->get_edge_count( @$_ ) for &edges_to;
    $in++ if &is_undirected and &is_self_loop_vertex;
    return $in;
}

sub out_degree {
    my $g = $_[0];
    return undef unless @_ > 1 && &has_vertex;
    my $out = 0;
    $out += $g->get_edge_count( @$_ ) for &edges_from;
    $out++ if &is_undirected and &is_self_loop_vertex;
    return $out;
}

sub _total_degree {
    return undef unless @_ > 1 && &has_vertex;
    &is_undirected ? &in_degree : &in_degree - &out_degree;
}

sub degree {
    goto &_total_degree if @_ > 1;
    return 0 if &is_directed;
    my $g = $_[0];
    my $total = 0;
    $total += $g->_total_degree( $_ ) for &_vertices05;
    return $total;
}

*vertex_degree = \&degree;

sub is_sink_vertex {
    return 0 unless @_ > 1;
    &successors == 0 && &predecessors > 0;
}

sub is_source_vertex {
    return 0 unless @_ > 1;
    &predecessors == 0 && &successors > 0;
}

sub is_successorless_vertex {
    return 0 unless @_ > 1;
    &successors == 0;
}

sub is_predecessorless_vertex {
    return 0 unless @_ > 1;
    &predecessors == 0;
}

sub is_successorful_vertex {
    return 0 unless @_ > 1;
    &successors > 0;
}

sub is_predecessorful_vertex {
    return 0 unless @_ > 1;
    &predecessors > 0;
}

sub is_isolated_vertex {
    return 0 unless @_ > 1;
    &predecessors == 0 && &successors == 0;
}

sub is_interior_vertex {
    return 0 unless @_ > 1;
    my $s = &successors;
    $s-- if my $isl = &is_self_loop_vertex;
    return 0 if $s == 0;
    return $s > 0 if &is_undirected;
    my $p = &predecessors;
    $p-- if $isl;
    $p > 0;
}

sub is_exterior_vertex {
    return 0 unless @_ > 1;
    &predecessors == 0 || &successors == 0;
}

sub is_self_loop_vertex {
    return 0 unless @_ > 1;
    return 1 if grep $_ eq $_[1], &successors; # @todo: multiedges
    return 0;
}

for my $p (qw(
    is_sink_vertex
    is_source_vertex
    is_successorless_vertex
    is_predecessorless_vertex
    is_successorful_vertex
    is_predecessorful_vertex
    is_isolated_vertex
    is_interior_vertex
    is_exterior_vertex
    is_self_loop_vertex
)) {
    no strict 'refs';
    (my $m = $p) =~ s/^is_(.*)ex$/${1}ices/;
    *$m = sub { my $g = $_[0]; grep $g->$p($_), &_vertices05 };
}

###
# Paths and cycles.
#

sub add_path {
    my $g = shift;
    my $u = shift;
    my @edges;
    while (@_) {
	my $v = shift;
	push @edges, [ $u, $v ];
	$u = $v;
    }
    $g->add_edges(@edges);
    return $g;
}

sub delete_path {
    &expect_non_unionfind;
    my $g = shift;
    my $u = shift;
    while (@_) {
	my $v = shift;
	$g->delete_edge($u, $v);
	$u = $v;
    }
    return $g;
}

sub has_path {
    my $g = shift;
    my $u = shift;
    while (@_) {
	my $v = shift;
	return 0 unless $g->has_edge($u, $v);
	$u = $v;
    }
    return $g;
}

sub add_cycle {
    push @_, $_[1];
    goto &add_path;
}

sub delete_cycle {
    &expect_non_unionfind;
    push @_, $_[1];
    goto &delete_path;
}

sub has_cycle {
    return 0 if @_ == 1;
    push @_, $_[1];
    goto &has_path;
}

*has_this_cycle = \&has_cycle;

sub has_a_cycle {
    my $g = shift;
    require Graph::Traversal::DFS;
    my $t = Graph::Traversal::DFS->new($g, has_a_cycle => 1, @_);
    $t->dfs;
    return $t->get_state('has_a_cycle');
}

sub find_a_cycle {
    require Graph::Traversal::DFS;
    my @r = ( back_edge => \&Graph::Traversal::find_a_cycle);
    push @r,
      down_edge => \&Graph::Traversal::find_a_cycle
	if &is_undirected;
    my $g = shift;
    my $t = Graph::Traversal::DFS->new($g, @r, @_);
    $t->dfs;
    $t->has_state('a_cycle') ? @{ $t->get_state('a_cycle') } : ();
}

###
# Attributes.

my @generic_methods = (
    [ 'set_attribute', 'my (\$attr, \$value) = splice \@_, -2; &$add unless &$has;',
      '\$_[0]->[ $offset ]->_set_path_attr( \@args, \$attr, \$value );' ],
    [ 'set_attributes', 'my \$attr = pop; &$add unless &$has;',
      '\$_[0]->[ $offset ]->_set_path_attrs( \@args, \$attr );', ],
    [ 'has_attributes', 'return 0 unless &$has;',
      '\$_[0]->[ $offset ]->_has_path_attrs( \@args );', ],
    [ 'has_attribute', 'my \$attr = pop; return 0 unless &$has;',
      '\$_[0]->[ $offset ]->_has_path_attr( \@args, \$attr );', ],
    [ 'get_attributes', 'return undef unless &$has;',
      'scalar \$_[0]->[ $offset ]->_get_path_attrs( \@args );', ],
    [ 'get_attribute', 'my \$attr = pop; return undef unless &$has;',
      'scalar \$_[0]->[ $offset ]->_get_path_attr( \@args, \$attr );', ],
    [ 'get_attribute_names', 'return unless &$has;',
      '\$_[0]->[ $offset ]->_get_path_attr_names( \@args );', ],
    [ 'get_attribute_values', 'return unless &$has;',
      '\$_[0]->[ $offset ]->_get_path_attr_values( \@args );', ],
    [ 'delete_attributes', 'return undef unless &$has;',
      '\$_[0]->[ $offset ]->_del_path_attrs( \@args );', ],
    [ 'delete_attribute', 'my \$attr = pop; return undef unless &$has;',
      '\$_[0]->[ $offset ]->_del_path_attr( \@args, \$attr );', ],
);
my %entity2offset = (vertex => '_V', edge => '_E');
my %entity2args = (edge => '&_vertex_ids');
my $template_mid = 'my \@args = @{[ $args || "\@_[1..\$#_]" ]};$munge';
for my $entity (qw(vertex edge)) {
    no strict 'refs';
    my $has_base = 'has_' . $entity;
    my $add_base = 'add_' . $entity;
    my $offset = $entity2offset{$entity};
    for my $t (@generic_methods) {
	my ($raw, $t1, $t2) = @$t;
	my ($first, $rest) = ($raw =~ /^(\w+?)_(.+)/);
	my $is_vertex = $entity eq 'vertex';
	my $m = join '_', $first, $entity, $rest;
	my ($args, $munge, $has, $add) = ($entity2args{$entity}, $is_vertex ? '' : "\n\@args = &is_undirected ? [sort \@args] : [\@args];", $has_base, $add_base);
	my $func_text = "qq{sub $m {\n&expect_non_multi$entity;\n$t1\n$template_mid\n$t2\n}}\n"; #warn "$m:\n$func_text\n";
	my $tv2 = eval $func_text; #warn "$m v2:\n$tv2\n";
	eval $tv2; die if $@;
	$m .= '_by_id';
	($args, $munge, $has, $add) = ($entity2args{$entity} && "$entity2args{$entity}_multi", $is_vertex ? '' : "\n\@args = (&is_undirected ? [sort \@args[0..\$#args-1]] : [\@args[0..\$#args-1]], \$args[-1]);", $has_base.'_by_id', $add_base.'_by_id');
	$func_text = "qq{sub $m {\n&expect_multi$entity;\n$t1\n$template_mid\n$t2\n}}\n"; #warn "$m:\n$func_text\n";
	$tv2 = eval $func_text; #warn "$m v2:\n$tv2\n";
	eval $tv2; die if $@;
    }
}

sub get_edge_attribute_all {
  my ($g, $u, $v, $name) = @_;
  die "no attribute name given" if !defined $name;
  grep defined(),
    &is_multiedged ? (map $g->get_edge_attribute_by_id($u, $v, $_, $name),
      $g->get_multiedge_ids($u, $v))
      : $g->get_edge_attribute($u, $v, $name);
}

sub add_vertices {
    my ($g, @v) = @_;
    if (&is_multivertexed) {
	$g->add_vertex_by_id($_, _GEN_ID) for @v;
	return $g;
    }
    my @i = $g->[ _V ]->set_paths(@v);
    $g->[ _G ]++;
    return $g if !&has_union_find;
    $g->[ _U ]->add(@i);
    $g;
}

sub add_edges {
    my ($g, @args) = @_;
    my @edges;
    while (defined(my $u = shift @args)) {
	push @edges, ref $u eq 'ARRAY' ? $u : @args ? [ $u, shift @args ]
	    : __carp_confess "Graph::add_edges: missing end vertex";
    }
    if (&is_multiedged) {
	$g->add_edge_by_id(@$_, _GEN_ID) for @edges;
	return $g;
    }
    my $uf = &has_union_find;
    my $deep = &is_hyperedged && &is_directed;
    my @paths = $g->[ _V ]->get_ids_by_paths(\@edges, 1, 1 + ($deep ? 1 : 0));
    @paths = map [ sort @$_ ], @paths if &is_undirected;
    $g->[ _E ]->set_paths( @paths );
    $uf->union(@paths) if $uf;
    $g->[ _G ]++;
    return $g;
}

sub add_edges_by_id {
    &expect_multiedged;
    my ($g, $id) = (shift, pop);
    my @edges;
    while (defined(my $u = shift @_)) {
	push @edges, ref $u eq 'ARRAY' ? $u : @_ ? [ $u, shift @_ ]
	    : __carp_confess "Graph::add_edges: missing end vertex";
    }
    $g->add_edge_by_id(@$_, $id) for @edges;
    return $g;
}

sub rename_vertex {
    my $g = shift;
    $g->[ _V ]->rename_path(@_);
    return $g;
}

sub rename_vertices {
    my ($g, $code) = @_;
    my %seen;
    $g->rename_vertex($_, $code->($_))
	for grep !$seen{$_}++, $g->[ _V ]->paths;
    return $g;
}

sub filter_vertices {
  my ($g, $code) = @_;
  my @v = &_vertices05;
  if (&is_multivertexed) {
    for my $v (@v) {
      $g->delete_vertex_by_id($v, $_) for
        grep !$code->($g, $v, $_), $g->get_multivertex_ids($v);
    }
  } else {
    $g->delete_vertices(grep !$code->($g, $_), @v);
  }
  $g;
}

sub filter_edges {
  my ($g, $code) = @_;
  my @e = &_edges05;
  if (&is_multiedged) {
    for my $e (@e) {
      $g->delete_edge_by_id(@$e, $_) for
        grep !$code->($g, @$e, $_), $g->get_multiedge_ids(@$e);
    }
  } else {
    $g->delete_edges(map @$_, grep !$code->($g, @$_), @e);
  }
  $g;
}

sub as_hashes {
    my ($g) = @_;
    my (%v, %e, @e);
    my ($is_hyper, $is_directed) = (&is_hyperedged, &is_directed);
    if (&is_multivertexed) {
        for my $v ($g->unique_vertices) {
            $v{$v} = {
                map +($_ => $g->get_vertex_attributes_by_id($v, $_) || {}),
                    $g->get_multivertex_ids($v)
            };
        }
    } else {
        %v = map +($_ => $g->get_vertex_attributes($_) || {}), $g->unique_vertices;
    }
    my $multi_e = &is_multiedged;
    for my $e ($g->edges) {
	my $edge_attr = {
	    $multi_e
		? map +($_ => $g->get_edge_attributes_by_id(@$e, $_) || {}),
		    $g->get_multiedge_ids(@$e)
		: %{ $g->get_edge_attributes(@$e)||{} }
	};
	if ($is_hyper) {
	    my %h = (attributes => $edge_attr);
	    if ($is_directed) {
		@h{qw(predecessors successors)} = @$e;
	    } else {
		$h{vertices} = $e;
	    }
	    push @e, \%h;
	} else {
	    $e{ $e->[0] }{ $e->[1] } = $edge_attr;
	    $e{ $e->[1] }{ $e->[0] } = $edge_attr if !$is_directed;
	}
    }
    ( \%v, $is_hyper ? \@e : \%e );
}

sub ingest {
    my ($g, $g2) = @_;
    _copy_vertices($g2, $g, 1);
    _copy_edges($g2, $g, 1);
    $g;
}

###
# More constructors.
#

sub copy {
    my ($g, @args) = @_;
    my $c = $g->new(@args);
    _copy_vertices($g, $c);
    _copy_edges($g, $c);
    return $c;
}

*copy_graph = \&copy;

sub _deep_copy_best {
    _can_deep_copy_Storable()
        ? _deep_copy_Storable(@_) : _deep_copy_DataDumper(@_);
}

sub _deep_copy_Storable {
    my $g = shift;
    require Safe;   # For deep_copy().
    my $safe = Safe->new;
    $safe->permit(qw/:load/);
    local $Storable::Deparse = 1;
    local $Storable::Eval = sub { $safe->reval($_[0]) };
    return Storable::thaw(Storable::freeze($g));
}

sub _deep_copy_DataDumper {
    my $g = shift;
    require Data::Dumper;
    my $d = Data::Dumper->new([$g]);
    use vars qw($VAR1);
    $d->Purity(1)->Terse(1)->Deepcopy(1);
    $d->Deparse(1) if $] >= 5.008;
    eval $d->Dump;
}

sub deep_copy {
    local $. = $.;
    my $g2 = _deep_copy_best(@_);
    $g2->[ _V ]->reindex if grep ref, &_vertices05;
    $g2;
}

*deep_copy_graph = \&deep_copy;

sub transpose_edge {
    my $g = $_[0];
    return $g if !&is_directed;
    return undef unless &has_edge;
    my $c = &get_edge_count;
    my $a = &get_edge_attributes;
    my @e = reverse @_[1..$#_];
    &delete_edge unless $g->has_edge( @e );
    $g->add_edges(map \@e, 1..$c);
    $g->set_edge_attributes(@e, $a) if $a;
    return $g;
}

sub transpose_graph {
    my $t = &copy;
    return $t if !&directed;
    $t->transpose_edge(@$_) for &_edges05;
    return $t;
}

*transpose = \&transpose_graph;

sub complete_graph {
    my $directed = &is_directed;
    my $c = &new;
    my @v = &_vertices05;
    my @edges;
    for (my $i = $#v; $i >= 0; $i-- ) {
	push @edges, map +([$v[$i], $v[$_]], $directed ? [$v[$_], $v[$i]] : ()),
	    0..$i - 1;
    }
    $c->add_edges(@edges);
    return $c;
}

sub max_cliques {
    my ($g) = @_;
    &expect_undirected;
    $g->bron_kerbosch_pivot([], [$g->vertices], [], \ my @cliques);
    return wantarray ? @cliques : \@cliques
}

sub bron_kerbosch_pivot {
    my ($g, $r, $p, $x, $max_cliques) = @_;
    if (! @$p && ! @$x && @$r) {
        push @$max_cliques, [@$r];
        return;
    }
    my $pivot = (@$p, @$x)[0];
    for my $v (my @p = @$p) {
        next if $g->has_edge($pivot, $v);
        $g->bron_kerbosch_pivot(
            [@$r, $v],
            [grep { my $w = $_; grep $_ eq $w, @$p } $g->neighbours($v)],
            [grep { my $w = $_; grep $_ eq $w, @$x } $g->neighbours($v)],
            $max_cliques);
        @$p = grep $_ ne $v, @$p;
        push @$x, $v;
    }
}

*complement = \&complement_graph;

sub complement_graph {
    my $c = &complete_graph;
    $c->delete_edge(@$_) for &edges;
    return $c;
}

*complete = \&complete_graph;

sub subgraph {
  my ($g, $src, $dst) = @_;
  __carp_confess "Graph::subgraph: need src and dst array references"
    unless ref $src eq 'ARRAY' && (!defined($dst) or ref $dst eq 'ARRAY');
  require Set::Object;
  my $s = $g->new;
  my @u = grep $g->has_vertex($_), @$src;
  my $v = Set::Object->new($dst ? grep $g->has_vertex($_), @$dst : @u);
  $s->add_vertices(@u, $dst ? $v->members : ());
  my $directed = &is_directed;
  if ($directed) {
    $s->add_edges(grep $v->contains($_->[1]), $g->edges_from(@u));
  } else {
    my $valid = $dst ? $v + Set::Object->new(@u) : $v;
    $s->add_edges(
      grep +($v->contains($_->[0]) || $v->contains($_->[1])) &&
        ($valid->contains($_->[0]) && $valid->contains($_->[1])),
        $g->edges_from(@u)
    );
  }
  return $s;
}

###
# Transitivity.
#

sub is_transitive {
    my $g = shift;
    require Graph::TransitiveClosure;
    Graph::TransitiveClosure::is_transitive($g);
}

###
# Weighted vertices.
#

my $defattr = 'weight';

sub _defattr {
    return $defattr;
}

sub add_weighted_vertex {
    &expect_non_multivertexed;
    push @_, $defattr, pop;
    goto &set_vertex_attribute;
}

sub add_weighted_vertices {
    &expect_non_multivertexed;
    my $g = shift;
    while (@_) {
	my ($v, $w) = splice @_, 0, 2;
	$g->set_vertex_attribute($v, $defattr, $w);
    }
}

sub get_vertex_weight {
    &expect_non_multivertexed;
    push @_, $defattr;
    goto &get_vertex_attribute;
}

sub has_vertex_weight {
    &expect_non_multivertexed;
    push @_, $defattr;
    goto &has_vertex_attribute;
}

sub set_vertex_weight {
    &expect_non_multivertexed;
    push @_, $defattr, pop;
    goto &set_vertex_attribute;
}

sub delete_vertex_weight {
    &expect_non_multivertexed;
    push @_, $defattr;
    goto &delete_vertex_attribute;
}

sub add_weighted_vertex_by_id {
    &expect_multivertexed;
    push @_, $defattr, pop;
    goto &set_vertex_attribute_by_id;
}

sub add_weighted_vertices_by_id {
    &expect_multivertexed;
    my $g = shift;
    my $id = pop;
    while (@_) {
	my ($v, $w) = splice @_, 0, 2;
	$g->add_vertex_by_id($v, $id);
	$g->set_vertex_attribute_by_id($v, $id, $defattr, $w);
    }
}

sub get_vertex_weight_by_id {
    &expect_multivertexed;
    push @_, $defattr;
    goto &get_vertex_attribute_by_id;
}

sub has_vertex_weight_by_id {
    &expect_multivertexed;
    push @_, $defattr;
    goto &has_vertex_attribute_by_id;
}

sub set_vertex_weight_by_id {
    &expect_multivertexed;
    push @_, $defattr, pop;
    goto &set_vertex_attribute_by_id;
}

sub delete_vertex_weight_by_id {
    &expect_multivertexed;
    push @_, $defattr;
    goto &delete_vertex_attribute_by_id;
}

###
# Weighted edges.
#

sub add_weighted_edge {
    &expect_non_multiedged;
    push @_, $defattr, pop;
    goto &set_edge_attribute;
}

sub add_weighted_edges {
    &expect_non_multiedged;
    my $g = shift;
    while (@_) {
	my ($u, $v, $w) = splice @_, 0, 3;
	$g->set_edge_attribute($u, $v, $defattr, $w);
    }
}

sub add_weighted_edges_by_id {
    &expect_multiedged;
    my $g = shift;
    my $id = pop;
    while (@_) {
	my ($u, $v, $w) = splice @_, 0, 3;
	$g->set_edge_attribute_by_id($u, $v, $id, $defattr, $w);
    }
}

sub add_weighted_path {
    &expect_non_multiedged;
    my $g = shift;
    my $u = shift;
    while (@_) {
	my ($w, $v) = splice @_, 0, 2;
	$g->set_edge_attribute($u, $v, $defattr, $w);
	$u = $v;
    }
}

sub get_edge_weight {
    &expect_non_multiedged;
    push @_, $defattr;
    goto &get_edge_attribute;
}

sub has_edge_weight {
    &expect_non_multiedged;
    push @_, $defattr;
    goto &has_edge_attribute;
}

sub set_edge_weight {
    &expect_non_multiedged;
    push @_, $defattr, pop;
    goto &set_edge_attribute;
}

sub delete_edge_weight {
    &expect_non_multiedged;
    push @_, $defattr;
    goto &delete_edge_attribute;
}

sub add_weighted_edge_by_id {
    &expect_multiedged;
    push @_, $defattr, pop;
    goto &set_edge_attribute_by_id;
}

sub add_path_by_id {
    &expect_multiedged;
    my ($g, $u, $id) = (shift, shift, pop);
    my @edges;
    while (@_) {
	my $v = shift;
	push @edges, [ $u, $v ];
	$u = $v;
    }
    $g->add_edges_by_id(@edges, $id);
    return $g;
}

sub add_weighted_path_by_id {
    &expect_multiedged;
    my ($g, $u, $id) = (shift, shift, pop);
    while (@_) {
	my ($w, $v) = splice @_, 0, 2;
	$g->set_edge_attribute_by_id($u, $v, $id, $defattr, $w);
	$u = $v;
    }
}

sub get_edge_weight_by_id {
    &expect_multiedged;
    push @_, $defattr;
    goto &get_edge_attribute_by_id;
}

sub has_edge_weight_by_id {
    &expect_multiedged;
    push @_, $defattr;
    goto &has_edge_attribute_by_id;
}

sub set_edge_weight_by_id {
    &expect_multiedged;
    push @_, $defattr, pop;
    goto &set_edge_attribute_by_id;
}

sub delete_edge_weight_by_id {
    &expect_multiedged;
    push @_, $defattr;
    goto &delete_edge_attribute_by_id;
}

###
# Error helpers.
#

my %expected;
@expected{qw(directed undirected acyclic)} = qw(undirected directed cyclic);

sub _expected {
    my $exp = shift;
    my $got = @_ ? shift : $expected{$exp};
    $got = defined $got ? ", got $got" : "";
    if (my @caller2 = caller(2)) {
	die "$caller2[3]: expected $exp graph$got, at $caller2[1] line $caller2[2].\n";
    } else {
	my @caller1 = caller(1); # uncoverable statement
	die "$caller1[3]: expected $exp graph$got, at $caller1[1] line $caller1[2].\n"; # uncoverable statement
    }
}

sub expect_no_args {
    my $g = shift;
    return unless @_;
    my @caller1 = caller(1); # uncoverable statement
    die "$caller1[3]: expected no arguments, got " . scalar @_ . ", at $caller1[1] line $caller1[2]\n"; # uncoverable statement
}

sub expect_undirected {
    _expected('undirected') unless &is_undirected;
}

sub expect_directed {
    _expected('directed') unless &is_directed;
}

sub expect_acyclic {
    _expected('acyclic') unless &is_acyclic;
}

sub expect_dag {
    my @got;
    push @got, 'undirected' unless &is_directed;
    push @got, 'cyclic'     unless &is_acyclic;
    _expected('directed acyclic', "@got") if @got;
}

sub expect_hyperedged {
    _expected('hyperedged') unless &is_hyperedged;
}

sub expect_multivertexed {
    _expected('multivertexed') unless &is_multivertexed;
}
*expect_multivertex = \&expect_multivertexed;

sub expect_non_multivertexed {
    _expected('non-multivertexed') if &is_multivertexed;
}
*expect_non_multivertex = \&expect_non_multivertexed;

sub expect_non_multiedged {
    _expected('non-multiedged') if &is_multiedged;
}
*expect_non_multiedge = \&expect_non_multiedged;

sub expect_multiedged {
    _expected('multiedged') unless &is_multiedged;
}
*expect_multiedge = \&expect_multiedged;

sub expect_non_unionfind {
    _expected('non-unionfind') if &has_union_find;
}

sub _get_options {
    my @caller = caller(1);
    unless (@_ == 1 && ref $_[0] eq 'ARRAY') {
	die "$caller[3]: internal error: should be called with only one array ref argument, at $caller[1] line $caller[2].\n";
    }
    my @opt = @{ $_[0] };
    unless (@opt  % 2 == 0) {
	die "$caller[3]: expected an options hash, got a non-even number of arguments, at $caller[1] line $caller[2].\n"; # uncoverable statement
    }
    return @opt;
}

###
# Random constructors and accessors.
#

sub __fisher_yates_shuffle (@) {
    # From perlfaq4, but modified to be non-modifying.
    my @a = @_;
    my $i = @a;
    while ($i--) {
	my $j = int rand ($i+1);
	@a[$i,$j] = @a[$j,$i];
    }
    return @a;
}

BEGIN {
    sub _shuffle(@);
    # Workaround for the Perl bug [perl #32383] where -d:Dprof and
    # List::Util::shuffle do not like each other: if any debugging
    # (-d) flags are on, fall back to our own Fisher-Yates shuffle.
    # The bug was fixed by perl changes #26054 and #26062, which
    # went to Perl 5.9.3.  If someone tests this with a pre-5.9.3
    # bleadperl that calls itself 5.9.3 but doesn't yet have the
    # patches, oh, well.
    *_shuffle = $^P && $] < 5.009003 ?
	\&__fisher_yates_shuffle : do { require List::Util; \&List::Util::shuffle };
}

sub random_graph {
    my $class = (@_ % 2) == 0 ? 'Graph' : shift;
    my %opt = _get_options( \@_ );
    __carp_confess "Graph::random_graph: argument 'vertices' missing or undef"
	unless defined $opt{vertices};
    __carp_confess "Graph::random_graph: both arguments 'edges' and 'edges_fill' specified"
	if exists $opt{edges} && exists $opt{edges_fill};
    srand delete $opt{random_seed} if exists $opt{random_seed};
    my $random_edge = delete $opt{random_edge};
    my @V;
    if (my $ref = ref $opt{vertices}) {
	__carp_confess "Graph::random_graph: argument 'vertices' illegal"
	    if $ref ne 'ARRAY';
	@V = @{ $opt{vertices} };
    } else {
	@V = 0..($opt{vertices} - 1);
    }
    delete $opt{vertices};
    my $V = @V;
    my $C = $V * ($V - 1) / 2;
    my $E = exists $opt{edges_fill} ? $opt{edges_fill} * $C : $opt{edges};
    delete @opt{qw(edges edges_fill)};
    my $g = $class->new(%opt);
    $g->add_vertices(@V);
    return $g if $V < 2;
    $C *= 2 if $g->directed;
    $E = $C / 2 unless defined $E;
    $E = int($E + 0.5);
    my $p = $E / $C;
    $random_edge = sub { $p } unless defined $random_edge;
    __carp_confess "Graph::random_graph: needs to be countedged or multiedged ($E > $C)"
	if $p > 1.0 && !($g->countedged || $g->multiedged);
    # Shuffle the vertex lists so that the pairs at
    # the beginning of the lists are not more likely.
    my @V1 = _shuffle @V;
    my @V2 = _shuffle @V;
 LOOP:
    while ($E) {
	for my $v1 (@V1) {
	    for my $v2 (@V2) {
		next if $v1 eq $v2; # TODO: allow self-loops?
		my $q = $random_edge->($g, $v1, $v2, $p);
		if ($q && ($q == 1 || rand() <= $q) &&
		    !$g->has_edge($v1, $v2)) {
		    $g->add_edge($v1, $v2);
		    $E--;
		    last LOOP unless $E;
		}
	    }
	}
    }
    $g;
}

sub random_vertex {
    my @V = &_vertices05;
    @V[rand @V];
}

sub random_edge {
    my @E = &_edges05;
    @E[rand @E];
}

sub random_successor {
    my @S = &successors;
    @S[rand @S];
}

sub random_predecessor {
    my @P = &predecessors;
    @P[rand @P];
}

###
# Algorithms.
#

my $MST_comparator = sub { ($_[0] || 0) <=> ($_[1] || 0) };

sub _MST_attr {
    my $attr = shift;
    my $attribute =
	exists $attr->{attribute}  ?
	    $attr->{attribute}  : $defattr;
    my $comparator =
	exists $attr->{comparator} ?
	    $attr->{comparator} : $MST_comparator;
    return ($attribute, $comparator);
}

sub _MST_edges {
    my ($g, $attr) = @_;
    my ($attribute, $comparator) = _MST_attr($attr);
    map $_->[1],
        sort { $comparator->($a->[0], $b->[0], $a->[1], $b->[1]) }
             map [ $g->get_edge_attribute(@$_, $attribute), $_ ],
                 &_edges05;
}

sub MST_Kruskal {
    &expect_undirected;
    my ($g, %attr) = @_;
    require Graph::UnionFind;

    my $MST = Graph->new(directed => 0);

    my $UF  = Graph::UnionFind->new;
    $UF->add(&_vertices05);

    my @edges;
    for my $e ($g->_MST_edges(\%attr)) {
	my ($u, $v) = @$e; # TODO: hyperedges
	next if $UF->same( @$e );
	$UF->union([$u, $v]);
	push @edges, [ $u, $v ];
    }
    $MST->add_edges(@edges);

    return $MST;
}

sub _MST_add {
    my ($g, $h, $HF, $r, $attr, $unseen) = @_;
    $HF->add( Graph::MSTHeapElem->new( $r, $_, $g->get_edge_attribute( $r, $_, $attr ) ) )
	for grep exists $unseen->{ $_ }, $g->successors( $r );
}

sub _next_alphabetic { shift; (sort               keys %{ $_[0] })[0] }
sub _next_numeric    { shift; (sort { $a <=> $b } keys %{ $_[0] })[0] }
sub _next_random     { shift; (values %{ $_[0] })[ rand keys %{ $_[0] } ] }

sub _root_opt {
    my ($g, @args) = @_;
    my %opt = @args == 1 ? ( first_root => $args[0] ) : _get_options( \@args );
    my %unseen;
    my @unseen = $g->_vertices05;
    @unseen{ @unseen } = @unseen;
    @unseen = _shuffle @unseen;
    my $r;
    if (exists $opt{ start }) {
	$opt{ first_root } = delete $opt{ start };
	$opt{ next_root  } = undef;
    }
    if (exists $opt{ first_root }) {
	if (ref $opt{ first_root } eq 'CODE') {
	    $r = $opt{ first_root }->( $g, \%unseen );
	} else {
	    $r = $opt{ first_root };
	}
    } else {
	$r = shift @unseen;
    }
    my $next =
	exists $opt{ next_root } ?
	    $opt{ next_root } :
              $opt{ next_alphabetic } ?
                \&_next_alphabetic :
                  $opt{ next_numeric } ?
                    \&_next_numeric :
                      \&_next_random;
    my $code = ref $next eq 'CODE';
    my $attr = exists $opt{ attribute } ? $opt{ attribute } : $defattr;
    return ( \%opt, \%unseen, \@unseen, $r, $next, $code, $attr );
}

sub _heap_walk {
    my ($g, $h, $add, $etc, $opt, $unseenh, $unseena, $r, $next, $code, $attr) = @_;
    require Heap::Fibonacci;
    my $HF = Heap::Fibonacci->new;
    while (defined $r) {
        # print "r = $r\n";
	$add->($g, $h, $HF, $r, $attr, $unseenh, $etc);
	delete $unseenh->{ $r };
	while (defined $HF->top) {
	    my $t = $HF->extract_top;
	    # use Data::Dumper; print "t = ", Dumper($t);
	    if (defined $t) {
		my ($u, $v, $w) = $t->val;
		# print "extracted top: $u $v $w\n";
		if (exists $unseenh->{ $v }) {
		    $h->set_edge_attribute($u, $v, $attr, $w);
		    delete $unseenh->{ $v };
		    $add->($g, $h, $HF, $v, $attr, $unseenh, $etc);
		}
	    }
	}
	return $h unless defined $next;
	$r = $code ? $next->( $g, $unseenh ) : shift @$unseena;
        last unless defined $r;
    }
    return $h;
}

sub MST_Prim {
    &expect_undirected;
    require Graph::MSTHeapElem;
    $_[0]->_heap_walk(Graph->new(directed => 0), \&_MST_add, undef, &_root_opt);
}

*MST_Dijkstra = \&MST_Prim;

*minimum_spanning_tree = \&MST_Prim;

###
# Cycle detection.
#

*is_cyclic = \&has_a_cycle;

sub is_acyclic {
    !&is_cyclic;
}

sub is_dag {
    &is_directed && &is_acyclic ? 1 : 0;
}

*is_directed_acyclic_graph = \&is_dag;

###
# Simple DFS uses.
#

sub topological_sort {
    my $g = shift;
    my %opt = _get_options( \@_ );
    my $eic = delete $opt{ empty_if_cyclic };
    my $hac;
    if ($eic) {
	$hac = $g->has_a_cycle;
    } else {
	$g->expect_dag;
    }
    require Graph::Traversal::DFS;
    my $t = Graph::Traversal::DFS->new($g, %opt);
    my @s = $t->dfs;
    $hac ? () : reverse @s;
}

*toposort = \&topological_sort;

sub _copy_vertices {
  my ($g, $gc, $attr_too) = @_;
  if (&is_multivertexed) {
    for my $v (&_vertices05) {
      if ($attr_too) {
        $gc->set_vertex_attributes_by_id($v, $_, $g->get_vertex_attributes_by_id($v, $_))
          for $g->get_multivertex_ids($v);
      } else {
        $gc->add_vertex_by_id($v, $_) for $g->get_multivertex_ids($v);
      }
    }
  } else {
    if ($attr_too) {
      $gc->set_vertex_attributes($_, $g->get_vertex_attributes($_)) for &_vertices05;
    } else {
      $gc->add_vertices(&_vertices05);
    }
  }
}

sub _copy_edges {
  my ($g, $gc, $attr_too, $mirror) = @_;
  my @edges = &_edges05;
  if (&is_multiedged) {
    for my $e (@edges) {
      for my $id ($g->get_multiedge_ids(@$e)) {
        if ($attr_too) {
          $gc->set_edge_attributes_by_id(@$e, $id, $g->get_edge_attributes_by_id(@$e, $id));
          $gc->set_edge_attributes_by_id(reverse(@$e), $id, $g->get_edge_attributes_by_id(@$e, $id)) if $mirror;
        } else {
          $gc->add_edge_by_id(@$e, $id);
          $gc->add_edge_by_id(reverse(@$e), $id) if $mirror;
        }
      }
    }
  } else {
    if ($attr_too) {
      $gc->set_edge_attributes(@$_, $g->get_edge_attributes(@$_))
        for @edges;
      if ($mirror) {
        $gc->set_edge_attributes(reverse(@$_), $g->get_edge_attributes(@$_))
          for @edges;
      }
    } else {
      $gc->add_edges(@edges, !$mirror ? () : map [reverse @$_], @edges);
    }
  }
}

sub undirected_copy {
  &expect_directed;
  my $gc = $_[0]->new(undirected=>1);
  _copy_vertices($_[0], $gc);
  _copy_edges($_[0], $gc);
  $gc;
}

*undirected_copy_graph = \&undirected_copy;

sub undirected_copy_attributes {
  &expect_directed;
  my $gc = $_[0]->new(undirected=>1);
  $gc->set_graph_attributes($_[0]->get_graph_attributes);
  _copy_vertices($_[0], $gc, 1);
  _copy_edges($_[0], $gc, 1);
  $gc;
}

sub directed_copy {
  &expect_undirected;
  my $gc = $_[0]->new(undirected=>0);
  _copy_vertices($_[0], $gc);
  _copy_edges($_[0], $gc, 0, 1);
  $gc;
}

*directed_copy_graph = \&directed_copy;

sub directed_copy_attributes {
  &expect_undirected;
  my $gc = $_[0]->new(directed=>1);
  $gc->set_graph_attributes($_[0]->get_graph_attributes);
  _copy_vertices($_[0], $gc, 1);
  _copy_edges($_[0], $gc, 1, 1);
  $gc;
}

sub is_bipartite {
    &expect_undirected;
    my ($g) = @_;
    my $is_bipartite = 1;
    my %colors;
    my $operations = {
        tree_edge => sub {
            my( $seen, $unseen ) = @_;
            ( $seen, $unseen ) = sort { exists $colors{$b} <=> exists $colors{$a} } ( $seen, $unseen );
            $colors{$seen} ||= -1;
            $colors{$unseen} = -$colors{$seen};
        },
        non_tree_edge => sub {
            $is_bipartite = '' if $colors{$_[0]} == $colors{$_[1]};
        },
    };
    require Graph::Traversal::DFS;
    Graph::Traversal::DFS->new( $g, %$operations )->dfs;
    return $is_bipartite;
}

sub is_planar {
    &expect_undirected;
    my ($g) = @_;
    my @paths_at = map [], 1..$g->vertices;
    my $path_graph = Graph->new(undirected => 1);
    my ($n, $d, %order) = (0, 0);
    my $operations = {
        pre => sub {
            $order{$_[0]} = $n;
            $n++;
        },
        non_tree_edge => sub {
            my( $i, $j ) = sort map { $order{$_} } @_[0..1];
            for (@{$paths_at[$i]}) { # for all crossed paths
                $path_graph->add_edge( $_, $d );
            }
            for ($i+1..$j-1) {
                push @{$paths_at[$_]}, $d;
            }
            $d++;
        },
    };
    require Graph::Traversal::DFS;
    Graph::Traversal::DFS->new( $g, %$operations )->dfs;
    return $path_graph->is_bipartite;
}

###
# Cache or not.
#

my %_cache_type =
    (
     'connectivity'        => ['_ccc'],
     'strong_connectivity' => ['_scc'],
     'weak_connectivity_undirected_graph' => ['_wcug'],
     'biconnectivity'      => ['_bcc'],
     'SPT_Dijkstra'        => ['_spt_di', 'SPT_Dijkstra_root'],
     'SPT_Bellman_Ford'    => ['_spt_bf', 'SPT_Bellman_Ford_root'],
     'transitive_closure_matrix' => ['_tcm'],
    );

for my $t (keys %_cache_type) {
    no strict 'refs';
    my @attr = @{ $_cache_type{$t} };
    *{$t."_clear_cache"} = sub { $_[0]->delete_graph_attribute($_) for @attr };
}

sub _check_cache {
    my ($g, $type, $extra_vals, $code, @args) = @_;
    my $c = $_cache_type{$type};
    __carp_confess "Graph: unknown cache type '$type'" if !defined $c;
    my ($main_key, @extra_keys) = @$c;
    __carp_confess "Graph: wrong number of extra values (@extra_keys) vs (@$extra_vals)" if @extra_keys != @$extra_vals;
    my $a = $g->get_graph_attribute($main_key);
    __carp_confess "$c attribute set to unexpected value $a"
	if defined $a and ref $a ne 'ARRAY';
    unless (defined $a && $a->[ 0 ] == $g->[ _G ]) {
	$g->set_graph_attribute($main_key, $a = [ $g->[ _G ], $code->( $g, @args ) ]);
    }
    my $i = -1;
    my $extra_invalid = grep {
	my $v = $a->[ 1 ]->get_graph_attribute($_);
	$i++; # here so still incremented even if short-cut
	!defined $v or $v ne $extra_vals->[$i];
    } @extra_keys;
    if ($extra_invalid) {
	$g->set_graph_attribute($main_key, $a = [ $g->[ _G ], $code->( $g, @args ) ]);
    }
    return $a->[ 1 ];
}

###
# Connected components.
#

sub _connected_components_compute {
    my $g = $_[0];
    my %v2c;
    my @c;
    return [ [], {} ] unless my @v = $g->unique_vertices;
    if (my $UF = &has_union_find) {
	my $V  = $g->[ _V ];
	my @ids = $V->get_ids_by_paths(\@v, 0);
	my ($counter, %cc2counter) = 0;
	my @cc = $UF->find(@ids);
	for (my $i = 0; $i <= $#v; $i++) {
	    my $cc = $cc[$i];
	    __carp_confess "connected_component union-find did not have vertex '$v[$i]', please report"
		if !defined $cc;
	    $cc2counter{$cc} = $counter++ if !exists $cc2counter{$cc};
	    my $ci = $cc2counter{$cc};
	    $v2c{ $v[$i] } = $ci;
	    push @{ $c[$ci] }, $v[$i];
	}
    } else {
	require Graph::Traversal::DFS;
	my %r; @r{ @v } = @v;
	@c = [];
	my $t = Graph::Traversal::DFS->new(
	    $g,
	    first_root => sub { (each %r)[1] },
	    next_root  => sub { push @c, [] if keys %r; (each %r)[1]; },
	    pre => sub {
		my ($v, $t) = @_;
		$v2c{ $v } = $#c;
		push @{ $c[-1] }, $v;
		delete $r{ $v };
	    },
	    @_[1..$#_]
	);
	$t->dfs;
    }
    return [ \@c, \%v2c ];
}

sub _connected_components {
    my $ccc = _check_cache($_[0], 'connectivity', [],
			   \&_connected_components_compute);
    return @{ $ccc };
}

sub connected_component_by_vertex {
    &expect_undirected;
    (&_connected_components)[1]->{ $_[1] };
}

sub connected_component_by_index {
    &expect_undirected;
    my $value = (&_connected_components)[0]->[$_[1]];
    $value ? @{ $value || _empty_array } : ();
}

sub connected_components {
    &expect_undirected;
    @{ (&_connected_components)[0] };
}

sub same_connected_components {
    &expect_undirected;
    my ($g, @args) = @_;
    my @components;
    if (my $UF = &has_union_find) {
	my @ids = &_vertex_ids;
	return 0 if @ids != @args;
	@components = $UF->find(@ids);
    } else {
	@components = @{ (&_connected_components)[1] }{ @args };
    }
    return 0 if grep !defined, @components;
    require List::Util;
    List::Util::uniq( @components ) == 1;
}

sub _super_component { join("+", sort @_) }

sub connected_graph {
    &expect_undirected;
    my ($g, %opt) = @_;
    my $cg = Graph->new(undirected => 1);
    if ($g->has_union_find && $g->vertices == 1) {
	# TODO: super_component?
	$cg->add_vertices($g->vertices);
    } else {
	my $sc_cb = $opt{super_component} || \&_super_component;
	$cg->set_vertex_attribute(scalar $sc_cb->(@$_), 'subvertices', $_)
	    for $g->connected_components;
    }
    return $cg;
}

sub is_connected {
    &expect_undirected;
    return @{ (&_connected_components)[0] } == 1;
}

sub is_weakly_connected {
    &expect_directed;
    splice @_, 0, 1, &undirected_copy;
    goto &is_connected;
}

*weakly_connected = \&is_weakly_connected;

# because recreating undirected copy every time has different hash ordering
# so weakly_connected_component_by_index etc would be unstable
sub _weakly_connected_undir_graph {
    _check_cache($_[0], 'weak_connectivity_undirected_graph', [],
			   \&undirected_copy);
}

sub weakly_connected_components {
    &expect_directed;
    splice @_, 0, 1, &_weakly_connected_undir_graph;
    goto &connected_components;
}

sub weakly_connected_component_by_vertex {
    &expect_directed;
    splice @_, 0, 1, &_weakly_connected_undir_graph;
    goto &connected_component_by_vertex;
}

sub weakly_connected_component_by_index {
    &expect_directed;
    splice @_, 0, 1, &_weakly_connected_undir_graph;
    goto &connected_component_by_index;
}

sub same_weakly_connected_components {
    &expect_directed;
    splice @_, 0, 1, &_weakly_connected_undir_graph;
    goto &same_connected_components;
}

sub weakly_connected_graph {
    &expect_directed;
    splice @_, 0, 1, &_weakly_connected_undir_graph;
    goto &connected_graph;
}

sub _strongly_connected_components_compute {
    my $g = $_[0];
    require Graph::Traversal::DFS;
    require List::Util;
    my $t = Graph::Traversal::DFS->new($g);
    my @d = reverse $t->dfs;
    my @c;
    my %v2c;
    my $u = Graph::Traversal::DFS->new(
	$g->transpose_graph,
	next_root => sub {
	    my ($t, $u) = @_;
	    return if !defined(my $root = List::Util::first(
		sub { exists $u->{$_} }, @d
	    ));
	    push @c, [];
	    return $root;
	},
	pre => sub {
	    my ($v, $t) = @_;
	    push @{ $c[-1] }, $v;
	    $v2c{$v} = $#c;
	},
	next_alphabetic => 1,
    );
    $u->dfs;
    return [ \@c, \%v2c ];
}

sub _strongly_connected_components_v2c {
    &_strongly_connected_components->[1];
}

sub _strongly_connected_components_arrays {
    @{ &_strongly_connected_components->[0] };
}

sub _strongly_connected_components {
    _check_cache($_[0], 'strong_connectivity', [],
			   \&_strongly_connected_components_compute);
}

sub strongly_connected_components {
    &expect_directed;
    goto &_strongly_connected_components_arrays;
}

sub strongly_connected_component_by_vertex {
    &expect_directed;
    &_strongly_connected_components_v2c->{$_[1]};
}

sub strongly_connected_component_by_index {
    &expect_directed;
    my $i = $_[1];
    return if !defined(my $c = &_strongly_connected_components->[0][ $i ]);
    @$c;
}

sub same_strongly_connected_components {
    &expect_directed;
    my ($g, @args) = @_;
    require Set::Object;
    Set::Object->new(@{ &_strongly_connected_components_v2c }{@args})->size <= 1;
}

sub is_strongly_connected {
    &strongly_connected_components == 1;
}

*strongly_connected = \&is_strongly_connected;

sub strongly_connected_graph {
    &expect_directed;
    my ($g, %attr) = @_;
    my $sc_cb = \&_super_component;
    _opt_get(\%attr, super_component => \$sc_cb);
    _opt_unknown(\%attr);
    my ($c, $v2c) = @{ &_strongly_connected_components };
    my $s = Graph->new;
    my @s = map $sc_cb->(@$_), @$c;
    $s->set_vertex_attribute($s[$_], 'subvertices', $c->[$_]) for 0..$#$c;
    require List::Util;
    $s->add_edges(map [@s[ @$v2c{ @$_ } ]], grep List::Util::uniq( @$v2c{ @$_ } ) > 1, &_edges05);
    return $s;
}

###
# Biconnectivity.
#

sub _biconnectivity_out {
  my ($state, $u, $v) = @_;
  my @BC;
  while (@{$state->{stack}}) {
    push @BC, my $e = pop @{$state->{stack}};
    last if $e->[0] eq $u && $e->[1] eq $v;
  }
  push @{$state->{BC}}, \@BC if @BC;
}

sub _biconnectivity_dfs {
  my ($E, $u, $state) = @_;
  $state->{low}{$u} = $state->{num}{$u} = $state->{dfs}++;
  for my $v ($E->successors($u)) {
    if (!exists $state->{num}{$v}) {
      push @{$state->{stack}}, [$u, $v];
      $state->{pred}{$v} = $u;
      _biconnectivity_dfs($E, $v, $state);
      $state->{low}{$u} = List::Util::min(@{ $state->{low} }{$u, $v});
      _biconnectivity_out($state, $u, $v)
	if $state->{low}{$v} >= $state->{num}{$u};
    } elsif (defined $state->{pred}{$u} &&
	     $state->{pred}{$u} ne $v &&
	     $state->{num}{$v} < $state->{num}{$u}) {
      push @{$state->{stack}}, [$u, $v];
      $state->{low}{$u} = List::Util::min($state->{low}{$u}, $state->{num}{$v});
    }
  }
}

sub _biconnectivity_compute {
    require List::Util;
    my ($g) = @_;
    my ($V, $E) = @$g[ _V, _E ];
    my %state = (BC=>[], dfs=>0);
    my @u = $V->ids;
    for my $u (@u) {
	next if exists $state{num}->{$u};
	_biconnectivity_dfs($E, $u, \%state);
	push @{$state{BC}}, delete $state{stack} if @{ $state{stack} || _empty_array };
    }

    # Mark the components each vertex belongs to.
    my ($bci, %v2bc, %bc2v) = 0;
    for my $bc (@{$state{BC}}) {
      $v2bc{$_}{$bci} = undef for map @$_, @$bc;
      $bci++;
    }

    # Any isolated vertices get each their own component.
    $v2bc{$_}{$bci++} = undef for grep !exists $v2bc{$_}, @u;

    # build vector now we know how big to make it
    my ($Z, %v2bc_vec, @ap) = "\0" x (($bci + 7) / 8);
    @v2bc_vec{@u} = ($Z) x @u;
    for my $v (@u) {
	my @components = keys %{ $v2bc{$v} };
	vec($v2bc_vec{$v}, $_, 1) = 1 for @components;
	$bc2v{$_}{$v}{$_} = undef for @components;
	# Articulation points / cut vertices are the vertices
	# which belong to more than one component.
	push @ap, $v if @components > 1;
    }

    # Bridges / cut edges are the components of two vertices.
    my @br = grep @$_ == 2, map [keys %$_], values %bc2v;

    # Create the subgraph components.
    my @sg = map [ List::Util::uniq( map @$_, @$_ ) ], @{$state{BC}};
    my ($apdeep, $sgv, $brv) = $V->get_paths_by_ids([[\@ap], \@sg, \@br], 1);
    return [ @$apdeep, $sgv, $brv, \%v2bc, \%v2bc_vec, $Z ];
}

sub biconnectivity {
    &expect_undirected;
    @{ _check_cache($_[0], 'biconnectivity', [],
			   \&_biconnectivity_compute, @_[1..$#_]) || _empty_array };
}

sub is_biconnected {
    &edges >= 2 ? @{ (&biconnectivity)[0] } == 0 : undef ;
}

sub is_edge_connected {
    &edges >= 2 ? @{ (&biconnectivity)[2] } == 0 : undef;
}

sub is_edge_separable {
    &edges >= 2 ? @{ (&biconnectivity)[2] } > 0 : undef;
}

sub articulation_points {
    @{ (&biconnectivity)[0] };
}

*cut_vertices = \&articulation_points;

sub biconnected_components {
    @{ (&biconnectivity)[1] };
}

sub biconnected_component_by_index {
    my ($i) = splice @_, 1, 1;
    (&biconnectivity)[1]->[ $i ];
}

sub biconnected_component_by_vertex {
    my ($v) = splice @_, 1, 1;
    my $v2bc = (&biconnectivity)[3];
    splice @_, 1, 0, $v;
    my $V = $_[0]->[ _V ];
    ($v) = $V->get_ids_by_paths([$v]);
    return defined $v2bc->{ $v } ? keys %{ $v2bc->{ $v } } : ();
}

sub same_biconnected_components {
    my ($v2bc, $Z) =  (&biconnectivity)[4,5];
    my $V = $_[0]->[ _V ];
    my @vs = $V->get_ids_by_paths([@_[1..$#_]]);
    return 0 if grep !defined, my @vecs = @$v2bc{ @vs };
    my $accumulator = $vecs[0];
    $accumulator &= $_ for @vecs[1..$#vecs]; # accumulate 0s -> all in same
    $accumulator ne $Z;
}

sub biconnected_graph {
    my ($g, %opt) = @_;
    my $bc = (&biconnectivity)[1];
    my $bcg = Graph->new(directed => 0);
    my $sc_cb = $opt{super_component} || \&_super_component;
    my @s = map $sc_cb->(@$_), @$bc;
    $bcg->set_vertex_attribute($s[$_], 'subvertices', $bc->[$_]) for 0..$#$bc;
    my @edges;
    for my $i (0..$#$bc) {
	my @u = @{ $bc->[ $i ] };
	for my $j (0..$i-1) {
	    my %j; @j{ @{ $bc->[ $j ] } } = ();
	    next if !grep exists $j{ $_ }, @u;
	    push @edges, [ @s[$i, $j] ];
	}
    }
    $bcg->add_edges(@edges);
    return $bcg;
}

sub bridges {
    @{ (&biconnectivity)[2] || _empty_array };
}

###
# SPT.
#

sub _SPT_add {
    my ($g, $h, $HF, $r, $attr, $unseen, $etc) = @_;
    my $etc_r = $etc->{ $r } || 0;
    for my $s ( grep exists $unseen->{ $_ }, $g->successors( $r ) ) {
	my ($t) = sort {$a<=>$b} $g->get_edge_attribute_all($r, $s, $attr);
	$t = 1 unless defined $t;
	__carp_confess "Graph::SPT_Dijkstra: edge $r-$s is negative ($t)"
	    if $t < 0;
	if (!defined($etc->{ $s }) || ($etc_r + $t) < $etc->{ $s }) {
	    my $etc_s = $etc->{ $s } || 0;
	    $etc->{ $s } = $etc_r + $t;
	    # print "$r - $s : setting $s to $etc->{ $s } ($etc_r, $etc_s)\n";
	    $h->set_vertex_attributes($s, { $attr=>$etc->{ $s }, 'p', $r });
	    $HF->add( Graph::SPTHeapElem->new($r, $s, $etc->{ $s }) );
	}
    }
}

sub _SPT_Dijkstra_compute {
    require Graph::SPTHeapElem;
    my $sptg = $_[0]->_heap_walk($_[0]->new(multiedged=>0), \&_SPT_add, {}, @_[1..$#_]);
    $sptg->set_graph_attribute('SPT_Dijkstra_root', $_[4]);
    $sptg;
}

sub SPT_Dijkstra {
    my $g = $_[0];
    my @args = &_root_opt;
    _check_cache($g, 'SPT_Dijkstra', [$args[3]],
	\&_SPT_Dijkstra_compute, @args);
}

*SSSP_Dijkstra = \&SPT_Dijkstra;

*single_source_shortest_paths = \&SPT_Dijkstra;

sub SP_Dijkstra {
    my ($g, $u, $v) = @_;
    my $sptg = $g->SPT_Dijkstra(first_root => $u);
    my @path = ($v);
    require Set::Object;
    my $seen = Set::Object->new;
    my $V = $g->vertices;
    my $p;
    while (defined($p = $sptg->get_vertex_attribute($v, 'p'))) {
	last if $seen->contains($p);
	push @path, $p;
	$v = $p;
	$seen->insert($p);
	last if $seen->size == $V || $u eq $v;
    }
    return if !@path or $path[-1] ne $u;
    return reverse @path;
}

sub __SPT_Bellman_Ford {
    my ($g, $u, $v, $attr, $d, $p, $c0, $c1) = @_;
    return unless $c0->{ $u };
    my ($w) = sort {$a<=>$b} $g->get_edge_attribute_all($u, $v, $attr);
    $w = 1 unless defined $w;
    if (defined $d->{ $v }) {
	if (defined $d->{ $u }) {
	    if ($d->{ $v } > $d->{ $u } + $w) {
		$d->{ $v } = $d->{ $u } + $w;
		$p->{ $v } = $u;
		$c1->{ $v }++;
	    }
	} # else !defined $d->{ $u } &&  defined $d->{ $v }
    } else {
	if (defined $d->{ $u }) {
	    #  defined $d->{ $u } && !defined $d->{ $v }
	    $d->{ $v } = $d->{ $u } + $w;
	    $p->{ $v } = $u;
	    $c1->{ $v }++;
	} # else !defined $d->{ $u } && !defined $d->{ $v }
    }
}

sub _SPT_Bellman_Ford {
    my ($g, $opt, $unseenh, $unseena, $r, $next, $code, $attr) = @_;
    my %d;
    return unless defined $r;
    $d{ $r } = 0;
    my %p;
    my $V = $g->vertices;
    my %c0; # Changed during the last iteration?
    $c0{ $r }++;
    for (my $i = 0; $i < $V; $i++) {
	my %c1;
	for my $e ($g->edges) {
	    my ($u, $v) = @$e;
	    __SPT_Bellman_Ford($g, $u, $v, $attr, \%d, \%p, \%c0, \%c1);
	    __SPT_Bellman_Ford($g, $v, $u, $attr, \%d, \%p, \%c0, \%c1)
		if $g->undirected;
	}
	%c0 = %c1 unless $i == $V - 1;
    }

    for my $e ($g->edges) {
	my ($u, $v) = @$e;
	if (defined $d{ $u } && defined $d{ $v }) {
	    my ($d) = sort {$a<=>$b} $g->get_edge_attribute_all($u, $v, $attr);
	    __carp_confess "Graph::SPT_Bellman_Ford: negative cycle exists"
		if defined $d && $d{ $v } > $d{ $u } + $d;
	}
    }

    return (\%p, \%d);
}

sub _SPT_Bellman_Ford_compute {
    my ($g, @args) = @_;
    my ($p, $d) = $g->_SPT_Bellman_Ford(@args);
    my $h = $g->new(multiedged=>0);
    for my $v (keys %$p) {
	my $u = $p->{ $v };
	my ($w) = sort {$a<=>$b} $g->get_edge_attribute_all($u, $v, $args[6]);
	$h->set_edge_attribute( $u, $v, $args[6], $w);
	$h->set_vertex_attributes( $v, { $args[6], $d->{ $v }, p => $u } );
    }
    $h->set_graph_attribute('SPT_Bellman_Ford_root', $args[3]);
    $h;
}

sub SPT_Bellman_Ford {
    my @args = &_root_opt;
    _check_cache($_[0], 'SPT_Bellman_Ford', [$args[3]],
	\&_SPT_Bellman_Ford_compute, @args);
}

*SSSP_Bellman_Ford = \&SPT_Bellman_Ford;

sub SP_Bellman_Ford {
    my ($g, $u, $v) = @_;
    my $sptg = $g->SPT_Bellman_Ford(first_root => $u);
    my @path = ($v);
    require Set::Object;
    my $seen = Set::Object->new;
    my $V = $g->vertices;
    my $p;
    while (defined($p = $sptg->get_vertex_attribute($v, 'p'))) {
	last if $seen->contains($p);
	push @path, $p;
	$v = $p;
	$seen->insert($p);
	last if $seen->size == $V;
    }
    # @path = () if @path && "$path[-1]" ne "$u";
    return reverse @path;
}

###
# Transitive Closure.
#

sub TransitiveClosure_Floyd_Warshall {
    my $self = shift;
    require Graph::TransitiveClosure;
    Graph::TransitiveClosure->new($self, @_);
}

*transitive_closure = \&TransitiveClosure_Floyd_Warshall;

sub APSP_Floyd_Warshall {
    my $self = shift;
    require Graph::TransitiveClosure;
    Graph::TransitiveClosure->new($self, path => 1, @_);
}

*all_pairs_shortest_paths = \&APSP_Floyd_Warshall;

sub _transitive_closure_matrix_compute {
    &APSP_Floyd_Warshall->transitive_closure_matrix;
}

sub transitive_closure_matrix {
    _check_cache($_[0], 'transitive_closure_matrix', [],
	\&_transitive_closure_matrix_compute, @_[1..$#_]);
}

sub path_length {
    shift->transitive_closure_matrix->path_length(@_);
}

sub path_successor {
    shift->transitive_closure_matrix->path_successor(@_);
}

sub path_vertices {
    shift->transitive_closure_matrix->path_vertices(@_);
}

sub all_paths {
    shift->transitive_closure_matrix->all_paths(@_);
}

sub is_reachable {
    shift->transitive_closure_matrix->is_reachable(@_);
}

sub for_shortest_paths {
    my $g = shift;
    my $c = shift;
    my $t = $g->transitive_closure_matrix;
    my @v = $g->vertices;
    my $n = 0;
    for my $u (@v) {
	$c->($t, $u, $_, ++$n) for grep $t->is_reachable($u, $_), @v;
    }
    return $n;
}

sub _minmax_path {
    my $g = shift;
    my $min;
    my $max;
    my $minp;
    my $maxp;
    $g->for_shortest_paths(sub {
			       my ($t, $u, $v, $n) = @_;
			       my $l = $t->path_length($u, $v);
			       return unless defined $l;
			       my $p;
			       if ($u ne $v && (!defined $max || $l > $max)) {
				   $max = $l;
				   $maxp = $p = [ $t->path_vertices($u, $v) ];
			       }
			       if ($u ne $v && (!defined $min || $l < $min)) {
				   $min = $l;
				   $minp = $p || [ $t->path_vertices($u, $v) ];
			       }
			   });
    return ($min, $max, $minp, $maxp);
}

sub diameter {
    my $g = shift;
    my ($min, $max, $minp, $maxp) = $g->_minmax_path(@_);
    return defined $maxp ? (wantarray ? @$maxp : $max) : undef;
}

*graph_diameter = \&diameter;

sub longest_path {
    my ($g, $u, $v) = @_;
    my $t = $g->transitive_closure_matrix;
    if (defined $u) {
	return wantarray ? $t->path_vertices($u, $v) : $t->path_length($u, $v)
	    if defined $v;
	my $max;
	my @max;
	for my $v (grep $u ne $_, $g->vertices) {
	    my $l = $t->path_length($u, $v);
	    next if !(defined $l && (!defined $max || $l > $max));
	    $max = $l;
	    @max = $t->path_vertices($u, $v);
	}
	return wantarray ? @max : $max;
    }
    if (defined $v) {
	my $max;
	my @max;
	for my $u (grep $_ ne $v, $g->vertices) {
	    my $l = $t->path_length($u, $v);
	    next if !(defined $l && (!defined $max || $l > $max));
	    $max = $l;
	    @max = $t->path_vertices($u, $v);
	}
	return wantarray ? @max : @max - 1;
    }
    my ($min, $max, $minp, $maxp) = $g->_minmax_path(@_);
    return defined $maxp ? (wantarray ? @$maxp : $max) : undef;
}

sub vertex_eccentricity {
    &expect_undirected;
    my ($g, $u) = @_;
    return Infinity() if !&is_connected;
    my $max;
    for my $v (grep $u ne $_, $g->vertices) {
	my $l = $g->path_length($u, $v);
	next if !(defined $l && (!defined $max || $l > $max));
	$max = $l;
    }
    return defined $max ? $max : Infinity();
}

sub shortest_path {
    &expect_undirected;
    my ($g, $u, $v) = @_;
    my $t = $g->transitive_closure_matrix;
    if (defined $u) {
	return wantarray ? $t->path_vertices($u, $v) : $t->path_length($u, $v)
	    if defined $v;
	my $min;
	my @min;
	for my $v (grep $u ne $_, $g->vertices) {
	    my $l = $t->path_length($u, $v);
	    next if !(defined $l && (!defined $min || $l < $min));
	    $min = $l;
	    @min = $t->path_vertices($u, $v);
	}
	# print "min/1 = @min\n";
	return wantarray ? @min : $min;
    }
    if (defined $v) {
	my $min;
	my @min;
	for my $u (grep $_ ne $v, $g->vertices) {
	    my $l = $t->path_length($u, $v);
	    next if !(defined $l && (!defined $min || $l < $min));
	    $min = $l;
	    @min = $t->path_vertices($u, $v);
	}
	# print "min/2 = @min\n";
	return wantarray ? @min : $min;
    }
    my ($min, $max, $minp, $maxp) = $g->_minmax_path(@_);
    return if !defined $minp;
    wantarray ? @$minp : $min;
}

sub radius {
    &expect_undirected;
    my $g = shift;
    my ($center, $radius) = (undef, Infinity());
    for my $v ($g->vertices) {
	my $x = $g->vertex_eccentricity($v);
	($center, $radius) = ($v, $x) if defined $x && $x < $radius;
    }
    return $radius;
}

sub center_vertices {
    &expect_undirected;
    my ($g, $delta) = @_;
    $delta = 0 unless defined $delta;
    $delta = abs($delta);
    my @c;
    my $Inf = Infinity();
    my $r = $g->radius;
    if (defined $r && $r != $Inf) {
	for my $v ($g->vertices) {
	    my $e = $g->vertex_eccentricity($v);
	    next unless defined $e && $e != $Inf;
	    push @c, $v if abs($e - $r) <= $delta;
	}
    }
    return @c;
}

*centre_vertices = \&center_vertices;

sub average_path_length {
    my $g = shift;
    my @A = @_;
    my $d = 0;
    my $m = 0;
    $g->for_shortest_paths(sub {
        my ($t, $u, $v, $n) = @_;
        return unless my $l = $t->path_length($u, $v);
        return if defined $A[0] && $u ne $A[0];
        return if defined $A[1] && $v ne $A[1];
        $d += $l;
        $m++;
    });
    return $m ? $d / $m : undef;
}

###
# Simple tests.
#

sub is_multi_graph {
    return 0 unless &is_multiedged || &is_countedged;
    my $g = $_[0];
    my $multiedges = 0;
    for my $e (&_edges05) {
	my ($u, @v) = @$e;
	return 0 if grep $u eq $_, @v;
	$multiedges++ if $g->get_edge_count(@$e) > 1;
    }
    return $multiedges;
}

sub is_simple_graph {
    return 1 unless &is_multiedged || &is_countedged;
    my $g = $_[0];
    return 0 if grep $g->get_edge_count(@$_) > 1, &_edges05;
    return 1;
}

sub is_pseudo_graph {
    my $m = &is_countedged || &is_multiedged;
    my $g = $_[0];
    for my $e (&_edges05) {
	my ($u, @v) = @$e;
	return 1 if grep $u eq $_, @v;
	return 1 if $m && $g->get_edge_count($u, @v) > 1;
    }
    return 0;
}

###
# Rough isomorphism guess.
#

my %_factorial = (0 => 1, 1 => 1);

sub __factorial {
    my $n = shift;
    for (my $i = 2; $i <= $n; $i++) {
	next if exists $_factorial{$i};
	$_factorial{$i} = $i * $_factorial{$i - 1};
    }
    $_factorial{$n};
}

sub _factorial {
    my $n = int(shift);
    __carp_confess "factorial of a negative number" if $n < 0;
    __factorial($n) unless exists $_factorial{$n};
    return $_factorial{$n};
}

sub could_be_isomorphic {
    my ($g0, $g1) = @_;
    return 0 unless &vertices == $g1->vertices;
    return 0 unless &_edges05  == $g1->_edges05;
    my %d0;
    $d0{ $g0->in_degree($_) }{ $g0->out_degree($_) }++ for &vertices;
    my %d1;
    $d1{ $g1->in_degree($_) }{ $g1->out_degree($_) }++ for $g1->vertices;
    return 0 unless keys %d0 == keys %d1;
    for my $da (keys %d0) {
	return 0
	    unless exists $d1{$da} &&
		   keys %{ $d0{$da} } == keys %{ $d1{$da} };
	return 0
	    if grep !(exists $d1{$da}{$_} && $d0{$da}{$_} == $d1{$da}{$_}),
	    keys %{ $d0{$da} };
    }
    for my $da (keys %d0) {
	return 0 if grep $d1{$da}{$_} != $d0{$da}{$_}, keys %{ $d0{$da} };
	delete $d1{$da};
    }
    return 0 unless keys %d1 == 0;
    my $f = 1;
    for my $da (keys %d0) {
	$f *= _factorial(abs($d0{$da}{$_})) for keys %{ $d0{$da} };
    }
    return $f;
}

###
# Analysis functions.

sub subgraph_by_radius {
    $_[0]->subgraph([ @_[1..$#_-1], &reachable_by_radius ]);
}

sub clustering_coefficient {
    my ($g) = @_;
    return unless my @v = $g->vertices;
    require Set::Object;
    my %clustering;

    my $gamma = 0;

    for my $n (@v) {
	my $gamma_v = 0;
	my @neigh = $g->successors($n);
	my $c = Set::Object->new;
	for my $u (@neigh) {
	    for my $v (grep +(!$c->contains("$u-$_") && $g->has_edge($u, $_)), @neigh) {
		$gamma_v++;
		$c->insert("$u-$v");
		$c->insert("$v-$u");
	    }
	}
	if (@neigh > 1) {
	    $clustering{$n} = $gamma_v/(@neigh * (@neigh - 1) / 2);
	    $gamma += $gamma_v/(@neigh * (@neigh - 1) / 2);
	} else {
	    $clustering{$n} = 0;
	}
    }

    $gamma /= @v;

    return wantarray ? ($gamma, %clustering) : $gamma;
}

sub betweenness {
    my $g = shift;

    my @V = $g->vertices();

    my %Cb; # C_b{w} = 0

    @Cb{@V} = ();

    for my $s (@V) {
	my @S; # stack (unshift, shift)

	my %P; # P{w} = empty list
	$P{$_} = [] for @V;

	my %sigma; # \sigma{t} = 0
	$sigma{$_} = 0 for @V;
	$sigma{$s} = 1;

	my %d; # d{t} = -1;
	$d{$_} = -1 for @V;
	$d{$s} = 0;

	my @Q; # queue (push, shift)
	push @Q, $s;

	while (@Q) {
	    my $v = shift @Q;
	    unshift @S, $v;
	    for my $w ($g->successors($v)) {
		# w found for first time
		if ($d{$w} < 0) {
		    push @Q, $w;
		    $d{$w} = $d{$v} + 1;
		}
		# Shortest path to w via v
		if ($d{$w} == $d{$v} + 1) {
		    $sigma{$w} += $sigma{$v};
		    push @{ $P{$w} }, $v;
		}
	    }
	}

	my %delta;
	$delta{$_} = 0 for @V;

	while (@S) {
	    my $w = shift @S;
	    $delta{$_} += $sigma{$_}/$sigma{$w} * (1 + $delta{$w})
		for @{ $P{$w} };
	    $Cb{$w} += $delta{$w} if $w ne $s;
	}
    }

    return %Cb;
}

sub connected_subgraphs {
    my $g = shift;
    require Set::Object;
    my @subgraphs = ( [ map { Set::Object->new($_) } $g->vertices ] );
    for (2..scalar $g->vertices) {
        my %seen;
        for my $subgraph (@{$subgraphs[-1]}) {
            for my $neighbour ((Set::Object->new( map { $g->neighbours($_) } $subgraph->members ) - $subgraph)->members) {
                my $new_subgraph = Set::Object->new($subgraph->members, $neighbour);
                my $key = join '|', @$new_subgraph;
                next if exists $seen{$key};
                $seen{$key} = $new_subgraph;
            }
        }
        push @subgraphs, [values %seen];
    }
    return map { $g->subgraph([$_->members]) } map { @$_ } @subgraphs;
}

1;
