Skip to content

Commit 8ba127f

Browse files
authored
Merge pull request #52 from harshangrjn/cheeger_update
Cheeger update
2 parents bb084de + 25a9f37 commit 8ba127f

11 files changed

Lines changed: 205 additions & 231 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
LaplacianOpt.jl Change Log
22
=========================
33

4+
### v0.7.1
5+
- Clean up of `round_zeros_ones!`, `laplacian_matrix`, `_violated_eigen_vector`, `weighted_adjacency_matrix`, `edge_combinations`, `optimal_graph_edges` functions
6+
- Added support for cheeger constant of graph based on set cardinality (updated MILP formulation) and set volume definitions
7+
48
### v0.7.0
59
- Solver logging option added in `optimizers.jl`
610
- Added option `minors_on_augment_edges` to include principal minor cuts only corresponding to vertices with augmentable edges - helps in reducing run times

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "LaplacianOpt"
22
uuid = "bb20392f-64fb-4001-92e8-14b3aedd5a9e"
33
authors = ["Harsha Nagarajan"]
4-
version = "0.7.0"
4+
version = "0.7.1"
55

66
[deps]
77
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ Please report any issues via the Github **[issue tracker](https://github.com/har
3737
This work was supported by Los Alamos National Laboratory (LANL)'s LDRD Early Career Research Award (20190590ECR) and [LANL-TAMU's collaborative research project](https://nationallabsoffice.tamus.edu/the-texas-am-university-system-and-los-alamos-national-laboratory-partner-to-design-robust-networks/) grant. The primary developer of this package is [Harsha Nagarajan](http://harshanagarajan.com) ([@harshangrjn](https://github.com/harshangrjn)).
3838

3939
## Citing LaplacianOpt
40-
If you find LaplacianOpt.jl useful in your work, we request you to cite the following papers [\[link-1\]](https://doi.org/10.1109/ECC.2015.7330770) [\[link-2\]](https://arxiv.org/abs/2304.08571):
40+
If you find LaplacianOpt.jl useful in your work, we request you to cite the following papers [\[link-1\]](https://doi.org/10.1109/ECC.2015.7330770) [\[link-2\]](https://doi.org/10.1109/TCNS.2024.3431408):
4141
```bibtex
4242
@article{LOpt_TCNS2024,
43-
title={Optimal Robust Network Design: Formulations and Algorithms for Maximizing Algebraic Connectivity},
43+
title={Optimal robust network design: Formulations and algorithms for maximizing algebraic connectivity},
4444
author={Somisetty, Neelkamal and Nagarajan, Harsha and Darbha, Swaroop},
45-
journal={IEEE Transactions on Control of Network Systems (accepted)},
46-
url = {https://arxiv.org/abs/2304.08571},
45+
journal={IEEE Transactions on Control of Network Systems},
46+
url = {https://doi.org/10.1109/TCNS.2024.3431408},
4747
year={2024},
4848
publisher={IEEE}
4949
}

docs/src/index.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ Pkg.test("LaplacianOpt")
3434
```
3535

3636
## Citing LaplacianOpt
37-
If you find LaplacianOpt.jl useful in your work, we request you to cite the following papers [\[link-1\]](https://doi.org/10.1109/ECC.2015.7330770) [\[link-2\]](https://arxiv.org/abs/2304.08571):
37+
If you find LaplacianOpt.jl useful in your work, we request you to cite the following papers [\[link-1\]](https://doi.org/10.1109/ECC.2015.7330770) [\[link-2\]](https://doi.org/10.1109/TCNS.2024.3431408):
3838
```bibtex
3939
@article{LOpt_TCNS2024,
40-
title={Optimal Robust Network Design: Formulations and Algorithms for Maximizing Algebraic Connectivity},
40+
title={Optimal robust network design: Formulations and algorithms for maximizing algebraic connectivity},
4141
author={Somisetty, Neelkamal and Nagarajan, Harsha and Darbha, Swaroop},
42-
journal={IEEE Transactions on Control of Network Systems (accepted)},
43-
url = {https://arxiv.org/abs/2304.08571},
42+
journal={IEEE Transactions on Control of Network Systems},
43+
url = {https://doi.org/10.1109/TCNS.2024.3431408},
4444
year={2024},
4545
publisher={IEEE}
4646
}

examples/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[deps]
22
CPLEX = "a076750e-1247-5638-91d2-ce28b192dca0"
33
GLPK = "60bf3e95-4087-53dc-ae20-288a0d20c6a6"
4+
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
45
Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b"
56
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
67
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"

examples/optimizers.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
#=====================================#
22
# MIP solvers (commercial, but fast) #
33
#=====================================#
4-
function get_gurobi(; solver_log = true)
4+
function get_gurobi(; solver_log = true, time_limit = 1E5)
55
GRB_ENV = Gurobi.Env()
66
return optimizer_with_attributes(
77
() -> Gurobi.Optimizer(GRB_ENV),
88
MOI.Silent() => !solver_log,
99
# "MIPFocus" => 3, # Focus on optimality over feasibility
1010
"Presolve" => 1,
11+
"TimeLimit" => time_limit,
12+
# "NonConvex" => 2
1113
)
1214
end
1315

examples/run_examples.jl

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import LaplacianOpt as LOpt
22
using JuMP
33
using CPLEX
4+
using Gurobi
45
# using GLPK
56

67
include("optimizers.jl")
78

8-
#-----------------------------------#
9-
# MIP solver #
10-
# (> Cplex 22.1 performs the best) #
11-
#-----------------------------------#
12-
lopt_optimizer = get_cplex(solver_log = true)
9+
#------------------------------------#
10+
# MIP solver #
11+
# (Gurobi 11.0.1+ performs the best) #
12+
#------------------------------------#
13+
lopt_optimizer = get_gurobi(solver_log = true)
1314

1415
#-------------------------------------#
1516
# User-defined input graphs #

src/constraints.jl

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,11 +272,19 @@ function constraint_cheeger_cuts(
272272
lom::LaplacianOptModel,
273273
optimizer::MOI.OptimizerWithAttributes,
274274
)
275+
ac_lower_bound = floor(lom.options.best_lower_bound, digits = 3)
276+
277+
if isapprox(ac_lower_bound, 0, atol = 1E-4)
278+
Memento.info(
279+
_LOGGER,
280+
"Cheeger cuts may be ineffective: best λ₂ lower bound is approximately 0.",
281+
)
282+
return
283+
end
284+
275285
result_dict =
276286
LOpt.cheeger_constant(z_val .* lom.data["adjacency_augment_graph"], optimizer)
277287

278-
ac_lower_bound = floor(lom.options.best_lower_bound, digits = 3)
279-
280288
if !(isapprox(result_dict["cheeger_constant"], 0, atol = 1E-5)) && (
281289
result_dict["cheeger_constant"] <
282290
(ac_lower_bound * lom.options.cheeger_cuts_factor)

src/data.jl

Lines changed: 57 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -129,63 +129,52 @@ function parse_file(file_path::String)
129129
end
130130

131131
function _pre_process_data(data_dict::Dict{String,Any})
132-
num_edges_existing = 0
133-
num_edges_to_augment = 0
134-
135132
num_nodes = data_dict["num_nodes"]
136133
adjacency_base_graph = data_dict["adjacency_base_graph"]
137134
adjacency_augment_graph = data_dict["adjacency_augment_graph"]
138135

139-
for i in 1:(num_nodes-1)
140-
for j in (i+1):num_nodes
141-
if isapprox(abs(adjacency_base_graph[i, j]), 0, atol = 1E-6)
142-
adjacency_base_graph[i, j] = 0
143-
end
144-
if isapprox(abs(adjacency_augment_graph[i, j]), 0, atol = 1E-6)
145-
adjacency_augment_graph[i, j] = 0
146-
end
147-
if !(isapprox(
148-
adjacency_base_graph[i, j],
149-
adjacency_base_graph[j, i],
150-
atol = 1E-5,
151-
))
152-
Memento.error("Adjacency matrix of the base graph has to be symmetric")
153-
end
154-
if !(isapprox(
155-
adjacency_augment_graph[i, j],
156-
adjacency_augment_graph[j, i],
157-
atol = 1E-5,
158-
))
159-
Memento.error("Adjacency matrix of the augment graph has to be symmetric")
160-
end
161-
if !(isapprox(adjacency_base_graph[i, j], 0, atol = 1E-6))
162-
num_edges_existing += 1
163-
end
164-
if !(isapprox(adjacency_augment_graph[i, j], 0, atol = 1E-6))
165-
num_edges_to_augment += 1
166-
end
136+
num_edges_existing, num_edges_to_augment = 0, 0
137+
138+
for i in 1:num_nodes-1, j in i+1:num_nodes
139+
adjacency_base_graph[i, j] =
140+
isapprox(abs(adjacency_base_graph[i, j]), 0, atol = 1E-6) ? 0 :
141+
adjacency_base_graph[i, j]
142+
adjacency_augment_graph[i, j] =
143+
isapprox(abs(adjacency_augment_graph[i, j]), 0, atol = 1E-6) ? 0 :
144+
adjacency_augment_graph[i, j]
145+
146+
if !isapprox(adjacency_base_graph[i, j], adjacency_base_graph[j, i], atol = 1E-5)
147+
Memento.error(_LOGGER, "Adjacency matrix of the base graph must be symmetric")
148+
end
149+
if !isapprox(
150+
adjacency_augment_graph[i, j],
151+
adjacency_augment_graph[j, i],
152+
atol = 1E-5,
153+
)
154+
Memento.error(
155+
_LOGGER,
156+
"Adjacency matrix of the augment graph must be symmetric",
157+
)
167158
end
168-
end
169159

170-
# Base graph connectivity
171-
is_base_graph_connected = false
172-
if num_edges_existing > 0
173-
!(isapprox(
174-
abs(LOpt.algebraic_connectivity(adjacency_base_graph)),
175-
0,
176-
atol = 1E-6,
177-
)) && (is_base_graph_connected = true)
160+
num_edges_existing +=
161+
!isapprox(adjacency_base_graph[i, j], 0, atol = 1E-6) ? 1 : 0
162+
num_edges_to_augment +=
163+
!isapprox(adjacency_augment_graph[i, j], 0, atol = 1E-6) ? 1 : 0
178164
end
179165

180-
data_dict_new = Dict{String,Any}()
181-
data_dict_new["num_nodes"] = num_nodes
182-
data_dict_new["adjacency_base_graph"] = adjacency_base_graph
183-
data_dict_new["adjacency_augment_graph"] = adjacency_augment_graph
184-
data_dict_new["num_edges_existing"] = num_edges_existing
185-
data_dict_new["num_edges_to_augment"] = num_edges_to_augment
186-
data_dict_new["is_base_graph_connected"] = is_base_graph_connected
166+
is_base_graph_connected =
167+
(num_edges_existing > 0) &&
168+
!isapprox(abs(LOpt.algebraic_connectivity(adjacency_base_graph)), 0, atol = 1E-6)
187169

188-
return data_dict_new
170+
return Dict{String,Any}(
171+
"num_nodes" => num_nodes,
172+
"adjacency_base_graph" => adjacency_base_graph,
173+
"adjacency_augment_graph" => adjacency_augment_graph,
174+
"num_edges_existing" => num_edges_existing,
175+
"num_edges_to_augment" => num_edges_to_augment,
176+
"is_base_graph_connected" => is_base_graph_connected,
177+
)
189178
end
190179

191180
"""
@@ -213,57 +202,56 @@ end
213202
Given the pre-processed data dictionary, this function detects any infeasibility before
214203
building the optimization model.
215204
"""
205+
216206
function _detect_infeasbility_in_data(data::Dict{String,Any})
217207
num_nodes = data["num_nodes"]
208+
num_edges_existing = data["num_edges_existing"]
209+
num_edges_to_augment = data["num_edges_to_augment"]
210+
augment_budget = data["augment_budget"]
218211

219-
if data["num_edges_to_augment"] == 0
220-
Memento.error(
221-
_LOGGER,
222-
"At least one edge has to be specified to be able to augment to the base graph",
223-
)
224-
# Assuming undirected graph
225-
elseif data["num_edges_existing"] == num_nodes * (num_nodes - 1) / 2
212+
if num_edges_to_augment == 0
213+
Memento.error(_LOGGER, "At least one edge is required for augmentation.")
214+
elseif num_edges_existing == num_nodes * (num_nodes - 1) / 2
226215
Memento.error(
227216
_LOGGER,
228-
"Input graph is already a complete graph; augmentation is unnecessary",
217+
"The graph is already complete; augmentation is unnecessary.",
229218
)
230-
elseif (data["num_edges_existing"] == 0) && (data["augment_budget"] < (num_nodes - 1))
219+
elseif num_edges_existing == 0 && augment_budget < (num_nodes - 1)
231220
Memento.error(
232221
_LOGGER,
233-
"Detected trivial solutions with disconnected graphs. `augment_budget` may be insufficient.",
222+
"Graph is disconnected; `augment_budget` may be insufficient.",
234223
)
235-
elseif !isinteger(data["augment_budget"]) || (data["augment_budget"] < -1E-6)
236-
Memento.error(_LOGGER, "Edge augmentation budget has to be a positive integer")
224+
elseif !isinteger(augment_budget) || augment_budget < 0
225+
Memento.error(_LOGGER, "Augmentation budget must be a positive integer.")
237226
end
238227

239-
# Detect mutually exclusive sets of edges between base and augment graph
228+
# Check mutually exclusive edge sets
240229
if maximum(
241230
(data["adjacency_augment_graph"] .> 0) + (data["adjacency_base_graph"] .> 0),
242231
) > 1
243232
Memento.error(
244233
_LOGGER,
245-
"Edge sets of base and augment graphs have to be mutually exclusive",
234+
"Edge sets of base and augment graphs must be mutually exclusive.",
246235
)
247236
end
248237

249-
# Detect free vertices
238+
# Check for free vertices
250239
A = data["adjacency_augment_graph"] + data["adjacency_base_graph"]
251240
for i in 1:num_nodes
252241
if isapprox(sum(A[i, :]), 0, atol = 1E-6)
253242
Memento.error(
254243
_LOGGER,
255-
"Detected trivial solutions with disconnected graphs due to free vertices.",
244+
"Free vertices detected, leading to disconnected graphs.",
256245
)
257246
end
258247
end
259248

260-
# Detect tour infeasibility
261-
if (data["graph_type"] == "hamiltonian_cycle") && (data["num_edges_existing"] == 0)
262-
if !(data["num_edges_to_augment"] >= data["num_nodes"]) ||
263-
!(data["augment_budget"] == data["num_nodes"])
249+
# Check Hamiltonian cycle feasibility
250+
if data["graph_type"] == "hamiltonian_cycle" && num_edges_existing == 0
251+
if !(num_edges_to_augment >= num_nodes && augment_budget == num_nodes)
264252
Memento.error(
265253
_LOGGER,
266-
"Detected infeasibility due to the number of augmentation edges incompatible for a hamiltonian cycle",
254+
"Infeasibility due to incompatible augmentation for Hamiltonian cycle.",
267255
)
268256
end
269257
end

0 commit comments

Comments
 (0)