4
4
5
5
from dataclasses import dataclass
6
6
from typing import Optional
7
- from scipy .sparse import csc_matrix , csr_matrix
8
7
from timeit import default_timer as timer
9
8
10
9
from mac .utils .graphs import *
@@ -41,13 +40,23 @@ def __init__(self, fixed_edges, candidate_edges, num_nodes,
41
40
min_edge_selection_tol : float, optional
42
41
Tolerance for the minimum edge selection weight. Default is 1e-10.
43
42
"""
44
- if (num_nodes == 0 ):
45
- assert (len (fixed_edges ) == len (candidate_edges ) == 0 )
43
+ # Check that we at least *could* have a spanning tree in the set
44
+ # {fixed_edges U candidate_edges} This does not guarantee that a
45
+ # spanning tree exists, but it's a good basic test.
46
+ num_edges = len (fixed_edges ) + len (candidate_edges )
47
+ assert (num_nodes - 1 ) <= num_edges
48
+
49
+ # We also check that there aren't "too many" edges. The number of edges
50
+ # in the complete graph K(n) is equal to n * (n - 1) / 2, so we cannot
51
+ # possibly have more edges than this.
52
+ assert num_edges <= 0.5 * num_nodes * (num_nodes - 1 )
53
+
54
+ # Pre-compute the Laplacian for the subgraph comprised of the "fixed edges".
46
55
self .L_fixed = weight_graph_lap_from_edge_list (fixed_edges , num_nodes )
47
56
self .num_nodes = num_nodes
57
+
48
58
self .weights = []
49
59
self .edge_list = []
50
-
51
60
for edge in candidate_edges :
52
61
self .weights .append (edge .weight )
53
62
self .edge_list .append ((edge .i , edge .j ))
@@ -63,15 +72,15 @@ def __init__(self, fixed_edges, candidate_edges, num_nodes,
63
72
self .min_selection_weight_tol = min_selection_weight_tol
64
73
65
74
def laplacian (self , x ):
66
- """
67
- Construct the combined Laplacian (fixed edges plus candidate edges weighted by x).
68
-
75
+ """Construct the combined Laplacian (fixed edges plus candidate edges weighted by x).
69
76
x: An element of [0,1]^m; this is the edge selection to use
70
- tol: Tolerance for edges that are numerically zero. This improves speed
71
- in situations where edges are not *exactly* zero, but close enough that
72
- they have almost no influence on the graph.
73
77
74
- returns the matrix L(w)
78
+ The tolerance parameter `min_selection_weight_tol` is used to prune out
79
+ edges that are numerically zero. This improves speed in situations
80
+ where edges are not *exactly* zero, but close enough that they have
81
+ almost no influence on the graph.
82
+
83
+ returns the matrix L(x)
75
84
"""
76
85
idx = np .where (x > self .min_selection_weight_tol )
77
86
prod = x [idx ]* self .weights [idx ]
@@ -89,11 +98,16 @@ def evaluate_objective(self, x):
89
98
90
99
returns F(x) = lambda_2(L(x)).
91
100
"""
92
- return fiedler .find_fiedler_pair (L = self .laplacian (x ), method = self .fiedler_method , tol = self .fiedler_tol )[0 ]
101
+ return fiedler .find_fiedler_pair (L = self .laplacian (x ),
102
+ method = self .fiedler_method , tol = self .fiedler_tol )[0 ]
93
103
94
104
def problem (self , x , cache = None ):
95
- """
96
- Compute the algebraic connectivity of L(x) and a (super)gradient of the algebraic connectivity with respect to x.
105
+ """Compute the algebraic connectivity of L(x) and a (super)gradient of the
106
+ algebraic connectivity with respect to x.
107
+
108
+ x: Weights for each candidate edge (does not include fixed edges)
109
+ cache: Mutable `Cache` object. If a `Cache` object is provided in the `cache` field, it will be used
110
+ and updated, but not explicitly returned. Rather, it will be updated directly.
97
111
98
112
returns x, grad F(x).
99
113
"""
@@ -114,17 +128,18 @@ def problem(self, x, cache=None):
114
128
return f , gradf
115
129
116
130
def solve (self , k , x_init = None , rounding = "nearest" , fallback = False ,
117
- max_iters = 5 , relative_duality_gap_tol = 1e-4 ,
118
- grad_norm_tol = 1e-8 , random_rounding_max_iters = 1 , verbose = False , return_rounding_time = False , use_cache = False ):
131
+ max_iters = 5 , relative_duality_gap_tol = 1e-4 ,
132
+ grad_norm_tol = 1e-8 , random_rounding_max_iters = 1 ,
133
+ verbose = False , return_rounding_time = False , use_cache = False ):
119
134
"""Use the Frank-Wolfe method to solve the subset selection problem,.
120
135
121
136
Parameters
122
137
----------
123
- x_init : Array-like
124
- Initial weights for the candidate edges, must satisfy 0 <= w_i <= 1, |w| <= k. This
125
- is the starting point for the Frank-Wolfe algorithm. TODO(kevin): make optional
126
138
k : int
127
139
Number of edges to select.
140
+ x_init : optional, array-like
141
+ Initial weights for the candidate edges, must satisfy 0 <= w_i <= 1, |w| <= k. This
142
+ is the starting point for the Frank-Wolfe algorithm. TODO(kevin): make optional
128
143
rounding : str, optional
129
144
Rounding method to use. Options are "nearest" (default) and "madow"
130
145
(a random rounding procedure).
0 commit comments