11from .utils import *
22from .vector import Vector
3+ import math
34import random
45import itertools
5- from typing import Callable , Dict , Iterable , List , Sequence , Tuple , TypeVar , Union , cast
6+ from typing import * # type: ignore
67
78__all__ = ["Edge" , "Graph" , "SwitchGraph" ]
89
@@ -36,8 +37,8 @@ def unweighted_edge(edge):
3637
3738
3839class SwitchGraph :
39- """A graph which can switch edges quickly
40- """
40+ """A graph which can switch edges quickly"""
41+
4142 directed : bool
4243 __edges : Dict [Tuple [int , int ], int ]
4344
@@ -48,6 +49,13 @@ def get_edges(self):
4849 ret .extend (itertools .repeat (k , self .__edges [k ]))
4950 return sorted (ret )
5051
52+ def edge_count (self ):
53+ val = 0
54+ for k in self .__edges :
55+ if k [0 ] <= k [1 ]:
56+ val += self .__edges [k ]
57+ return val
58+
5159 def __insert (self , u : int , v : int ):
5260 if (u , v ) not in self .__edges :
5361 self .__edges [(u , v )] = 0
@@ -59,22 +67,20 @@ def __remove(self, u: int, v: int):
5967 self .__edges .pop ((u , v ))
6068
6169 def insert (self , u : int , v : int ):
62- """Add edge (u, v)
63- """
70+ """Add edge (u, v)"""
6471 self .__insert (u , v )
6572 if not self .directed and u != v :
6673 self .__insert (v , u )
6774
6875 def remove (self , u : int , v : int ):
69- """Remove edge (u, v)
70- """
76+ """Remove edge (u, v)"""
7177 self .__remove (u , v )
7278 if not self .directed and u != v :
7379 self .__remove (v , u )
7480
75- def __init__ (self ,
76- E : Iterable [Union [Edge , Tuple [int , int ]]],
77- directed : bool = True ):
81+ def __init__ (
82+ self , E : Iterable [Union [Edge , Tuple [int , int ]]], directed : bool = True
83+ ):
7884 self .directed = directed
7985 self .__edges = {}
8086 for e in E :
@@ -92,9 +98,9 @@ def switch(self, *, self_loop: bool = False, repeated_edges: bool = False):
9298 Returns:
9399 If a switch was performed, then return True. If the switch was rejected, then return False.
94100 """
95- first , second = random .choices (list ( self . __edges . keys ()),
96- list (self .__edges .values ()),
97- k = 2 )
101+ first , second = random .choices (
102+ list ( self . __edges . keys ()), list (self .__edges .values ()), k = 2
103+ )
98104 x1 , y1 = first if self .directed else sorted (first )
99105 x2 , y2 = second if self .directed else sorted (second )
100106
@@ -105,7 +111,7 @@ def switch(self, *, self_loop: bool = False, repeated_edges: bool = False):
105111 if {x1 , y1 } & {x2 , y2 } != set ():
106112 return False
107113
108- if repeated_edges :
114+ if not repeated_edges :
109115 if (x1 , y2 ) in self .__edges or (x2 , y1 ) in self .__edges :
110116 return False
111117
@@ -118,11 +124,13 @@ def switch(self, *, self_loop: bool = False, repeated_edges: bool = False):
118124
119125 @staticmethod
120126 def from_directed_degree_sequence (
121- degree_sequence : Sequence [Tuple [int , int ]],
122- start_id : int = 1 ,
123- * ,
124- self_loop : bool = False ,
125- repeated_edges : bool = False ) -> "SwitchGraph" :
127+ degree_sequence : Sequence [Tuple [int , int ]],
128+ start_id : int = 1 ,
129+ * ,
130+ self_loop : bool = False ,
131+ repeated_edges : bool = False
132+ ):
133+ """Generate a directed graph greedily based on the degree sequence."""
126134 if any (x < 0 or y < 0 for (x , y ) in degree_sequence ):
127135 raise ValueError ("Degree sequence is not graphical." )
128136
@@ -135,8 +143,9 @@ def from_directed_degree_sequence(
135143 if len (degree_sequence ) == 0 :
136144 return ret
137145
138- degseq = [[sout , sin , vn ]
139- for vn , (sin , sout ) in enumerate (degree_sequence , start_id )]
146+ degseq = [
147+ [sout , sin , vn ] for vn , (sin , sout ) in enumerate (degree_sequence , start_id )
148+ ]
140149 degseq .sort (reverse = True )
141150
142151 try :
@@ -165,11 +174,13 @@ def from_directed_degree_sequence(
165174
166175 @staticmethod
167176 def from_undirected_degree_sequence (
168- degree_sequence : Sequence [int ],
169- start_id : int = 1 ,
170- * ,
171- self_loop : bool = False ,
172- repeated_edges : bool = False ) -> "SwitchGraph" :
177+ degree_sequence : Sequence [int ],
178+ start_id : int = 1 ,
179+ * ,
180+ self_loop : bool = False ,
181+ repeated_edges : bool = False
182+ ):
183+ """Generate an undirected graph greedily based on the degree sequence."""
173184 if any (x < 0 for x in degree_sequence ):
174185 raise ValueError ("Degree sequence is not graphical." )
175186
@@ -504,6 +515,58 @@ def graph(point_count, edge_count, **kwargs):
504515 i += 1
505516 return graph
506517
518+ @staticmethod
519+ def from_degree_sequence (
520+ degree_sequence : Union [Sequence [Tuple [int , int ]], Sequence [int ]],
521+ n_iter : Optional [int ] = None ,
522+ * ,
523+ self_loop : bool = False ,
524+ repeated_edges : bool = False ,
525+ weight_limit : Union [int , Tuple [int , int ]] = (1 , 1 ),
526+ weight_gen : Optional [Callable [[], int ]] = None ,
527+ iter_limit : int = int (1e6 )
528+ ):
529+ if len (degree_sequence ) == 0 :
530+ return Graph (0 )
531+ if isinstance (weight_limit , int ):
532+ weight_limit = (1 , weight_limit )
533+ if weight_gen is None :
534+ weight_gen = lambda : random .randint (* weight_limit )
535+ if isinstance (degree_sequence [0 ], int ):
536+ directed = False
537+ sg = SwitchGraph .from_undirected_degree_sequence (
538+ cast (Sequence [int ], degree_sequence ),
539+ self_loop = self_loop ,
540+ repeated_edges = repeated_edges ,
541+ )
542+ else :
543+ directed = True
544+ sg = SwitchGraph .from_directed_degree_sequence (
545+ cast (Sequence [Tuple [int , int ]], degree_sequence ),
546+ self_loop = self_loop ,
547+ repeated_edges = repeated_edges ,
548+ )
549+ point_cnt = len (degree_sequence )
550+ edge_cnt = sg .edge_count ()
551+ if n_iter is None :
552+ n_iter = int (
553+ Graph ._estimate_upperbound (
554+ point_cnt ,
555+ edge_cnt ,
556+ directed ,
557+ self_loop ,
558+ repeated_edges ,
559+ )
560+ / math .log (edge_cnt )
561+ )
562+ n_iter = min (n_iter , iter_limit )
563+ for _ in range (n_iter ):
564+ sg .switch (self_loop = self_loop , repeated_edges = repeated_edges )
565+ g = Graph (len (degree_sequence ), directed )
566+ for edge in sg .get_edges ():
567+ g .add_edge (* edge , weight = weight_gen ())
568+ return g
569+
507570 @staticmethod
508571 def DAG (point_count , edge_count , ** kwargs ):
509572 """DAG(point_count, edge_count, **kwargs) -> Graph
@@ -713,6 +776,31 @@ def _calc_max_edge(point_count, directed, self_loop):
713776 max_edge += point_count
714777 return max_edge
715778
779+ @staticmethod
780+ def _estimate_comb (n : int , k : int ):
781+ try :
782+ return float (sum (math .log (n - i ) - math .log (i + 1 ) for i in range (k )))
783+ except ValueError :
784+ return 0.0
785+
786+ @staticmethod
787+ def _estimate_upperbound (
788+ point_count : int ,
789+ edge_count : int ,
790+ directed : bool ,
791+ self_loop : bool ,
792+ repeated_edges : bool ,
793+ ):
794+ tot_edge = point_count * (point_count - 1 )
795+ if not directed :
796+ tot_edge //= 2
797+ if self_loop :
798+ tot_edge += point_count
799+ if repeated_edges :
800+ return Graph ._estimate_comb (edge_count + tot_edge - 1 , edge_count )
801+ else :
802+ return Graph ._estimate_comb (tot_edge , edge_count )
803+
716804 @staticmethod
717805 def forest (point_count , tree_count , ** kwargs ):
718806 """
0 commit comments