Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

601

602

603

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

622

623

624

625

626

627

628

629

630

631

632

633

634

635

636

637

638

639

640

641

642

643

644

645

646

647

648

649

650

651

652

653

654

655

656

657

658

659

660

661

662

663

664

665

666

667

668

669

670

671

672

673

674

675

676

677

678

679

680

681

682

683

684

685

686

687

688

689

690

691

692

693

694

695

696

697

698

699

700

701

702

703

704

705

706

707

708

709

710

711

712

713

714

715

716

717

r""" 

Cutwidth 

  

This module implements several algorithms to compute the cutwidth of a graph and 

the corresponding ordering of the vertices. It also implements tests functions 

for evaluation the width of a linear ordering (or layout). 

  

Given an ordering 

`v_1,\cdots, v_n` of the vertices of `V(G)`, its *cost* is defined as: 

  

.. MATH:: 

  

c(v_1, ..., v_n) = \max_{1\leq i \leq n-1} c'(\{v_1, ..., v_i\}) 

  

Where 

  

.. MATH:: 

  

c'(S) = |\{(u,w)\in E(G)\mid u\in S\text{ and }w\in V(G)\backslash S\}| 

  

The *cutwidth* of a graph `G` is equal to the minimum cost of an ordering of its 

vertices. 

  

  

**This module contains the following methods** 

  

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

  

:meth:`cutwidth` | Return the cutwidth of the graph and the corresponding vertex ordering. 

:meth:`cutwidth_dyn` | Compute the cutwidth of `G` using an exponential time and space algorithm based on dynamic programming 

:meth:`cutwidth_MILP` | Compute the cutwidth of `G` and the optimal ordering of its vertices using an MILP formulation 

:meth:`width_of_cut_decomposition` | Return the width of the cut decomposition induced by the linear ordering `L` of the vertices of `G` 

  

  

Exponential algorithm for cutwidth 

---------------------------------- 

  

In order to find an optimal ordering of the vertices for the vertex separation, 

this algorithm tries to save time by computing the function `c'(S)` **at most 

once** once for each of the sets `S\subseteq V(G)`. These values are stored in 

an array of size `2^n` where reading the value of `c'(S)` or updating it can be 

done in constant time. 

  

Assuming that we can compute the cost of a set `S` and remember it, finding an 

optimal ordering is an easy task. Indeed, we can think of the sequence `v_1, 

..., v_n` of vertices as a sequence of *sets* `\{v_1\}, \{v_1,v_2\}, ..., 

\{v_1,...,v_n\}`, whose cost is precisely `\max c'(\{v_1\}), c'(\{v_1,v_2\}), 

... , c'(\{v_1,...,v_n\})`. Hence, when considering the digraph on the `2^n` 

sets `S\subseteq V(G)` where there is an arc from `S` to `S'` if `S'=S\cap 

\{v\}` for some `v` (that is, if the sets `S` and `S'` can be consecutive in a 

sequence), an ordering of the vertices of `G` corresponds to a *path* from 

`\emptyset` to `\{v_1,...,v_n\}`. In this setting, checking whether there exists 

a ordering of cost less than `k` can be achieved by checking whether there 

exists a directed path `\emptyset` to `\{v_1,...,v_n\}` using only sets of cost 

less than `k`. This is just a depth-first-search, for each `k`. 

  

**Lazy evaluation of** `c'` 

  

In the previous algorithm, most of the time is actually spent on the computation 

of `c'(S)` for each set `S\subseteq V(G)` -- i.e. `2^n` computations of 

neighborhoods. This can be seen as a huge waste of time when noticing that it is 

useless to know that the value `c'(S)` for a set `S` is less than `k` if all the 

paths leading to `S` have a cost greater than `k`. For this reason, the value of 

`c'(S)` is computed lazily during the depth-first search. Explanation : 

  

When the depth-first search discovers a set of size less than `k`, the costs of 

its out-neighbors (the potential sets that could follow it in the optimal 

ordering) are evaluated. When an out-neighbor is found that has a cost smaller 

than `k`, the depth-first search continues with this set, which is explored with 

the hope that it could lead to a path toward `\{v_1,...,v_n\}`. On the other 

hand, if an out-neighbour has a cost larger than `k` it is useless to attempt to 

build a cheap sequence going though this set, and the exploration stops 

there. This way, a large number of sets will never be evaluated and *a lot* of 

computational time is saved this way. 

  

Besides, some improvement is also made by "improving" the values found by 

`c'`. Indeed, `c'(S)` is a lower bound on the cost of a sequence containing the 

set `S`, but if all out-neighbors of `S` have a cost of `c'(S) + 5` then one 

knows that having `S` in a sequence means a total cost of at least `c'(S) + 

5`. For this reason, for each set `S` we store the value of `c'(S)`, and replace 

it by `\max (c'(S), \min_{\text{next}})` (where `\min_{\text{next}}` is the 

minimum of the costs of the out-neighbors of `S`) once the costs of these 

out-neighbors have been evaluated by the algorithm. 

  

This algorithm and its implementation are very similar to 

:meth:`sage.graphs.graph_decompositions.vertex_separation.vertex_separation_exp`. 

The main difference is in the computation of `c'(S)`. See the :mod:`vertex 

separation module's documentation 

<sage.graphs.graph_decompositions.vertex_separation>` for more details on this 

algorithm. 

  

.. NOTE:: 

  

Because of its current implementation, this algorithm only works on graphs 

on strictly less than 32 vertices. This can be changed to 64 if necessary, 

but 32 vertices already require 4GB of memory. 

  

  

MILP formulation for the cutwidth 

--------------------------------- 

  

We describe a mixed integer linear program (MILP) for determining an 

optimal layout for the cutwidth of `G`. 

  

**Variables:** 

  

- `x_v^k` -- Variable set to 1 if vertex `v` is placed in the ordering at 

position `i` with `i\leq k`, and 0 otherwise. 

  

- `y_{u,v}^{k}` -- Variable set to 1 if one of `u` or `v` is at a position 

`i\leq k` and the other is at a position `j>k`, and so we have to count edge 

`uv` at position `k`. Otherwise, `y_{u,v}^{k}=0`. The value of `y_{u,v}^{k}` 

is a xor of the values of `x_u^k` and `x_v^k`. 

  

- `z` -- Objective value to minimize. It is equal to the maximum over all 

position `k` of the number of edges with one extremity at position at most `k` 

and the other at position stricly more than `k`, that is `\sum_{uv\in 

E}y_{u,v}^{k}`. 

  

  

**MILP formulation:** 

  

.. MATH:: 

:nowrap: 

  

\begin{alignat*}{2} 

\intertext{Minimize:} 

&z&\\ 

\intertext{Subject to:} 

\sum_{i=0}^{k-1}x_v^i &\leq k*x_v^{k} & \forall v\in V,\ k\in[1,n-1] \quad(1)\\ 

x_v^n & =1 & \quad \forall v\in V \quad(2)\\ 

\sum_{v\in V}x_v^k & = k+1 &\quad \forall k\in[0,n-1] \quad(3)\\ 

x_u^k - x_v^k & \leq y_{u,v}^k &\quad \forall uv\in E,\ \forall k\in[0,n-1] \quad(4)\\ 

x_v^k - x_u^k & \leq y_{u,v}^k &\quad \forall uv\in E,\ \forall k\in[0,n-1] \quad(5)\\ 

\sum_{uv\in E}y_{u,v}^k &\leq z &\quad \forall k\in[0,n-1] \quad(6)\\ 

0 \leq z &\leq |E| 

\end{alignat*} 

  

Constraints (1)-(3) ensure that all vertices have a distinct position. 

Constraints (4)-(5) force variable `y_{u,v}^k` to 1 if the edge is in the cut. 

Constraint (6) count the number of edges starting at position at most `k` and 

ending at a position stricly larger than `k`. 

  

This formulation corresponds to method :meth:`cutwidth_MILP`. 

  

  

Authors 

------- 

  

- David Coudert (2015-06): Initial version 

  

  

Methods 

------- 

""" 

  

#***************************************************************************** 

# Copyright (C) 2015 David Coudert <david.coudert@inria.fr> 

# 

# This program is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 2 of the License, or 

# (at your option) any later version. 

# http://www.gnu.org/licenses/ 

#***************************************************************************** 

  

from __future__ import absolute_import, print_function 

  

from libc.stdint cimport uint8_t 

from libc.string cimport memset 

from cysignals.memory cimport check_allocarray, sig_free 

from cysignals.signals cimport sig_check 

  

from sage.graphs.graph_decompositions.fast_digraph cimport FastDigraph, popcount32 

from sage.graphs.graph_decompositions.vertex_separation import is_valid_ordering 

from sage.rings.integer_ring import ZZ 

  

  

################################################################################ 

# Measurement function of the width of some layout 

################################################################################ 

  

def width_of_cut_decomposition(G, L): 

r""" 

Returns the width of the cut decomposition induced by the linear ordering 

`L` of the vertices of `G`. 

  

If `G` is an instance of :mod:`Graph <sage.graphs.graph>`, this function 

returns the width `cw_L(G)` of the cut decomposition induced by the linear 

ordering `L` of the vertices of `G`. 

  

.. MATH:: 

  

cw_L(G) = \max_{0\leq i< |V|-1} |\{(u,w)\in E(G)\mid u\in L[:i]\text{ and }w\in V(G)\setminus L[:i]\}| 

  

INPUT: 

  

- ``G`` -- a Graph 

  

- ``L`` -- a linear ordering of the vertices of ``G`` 

  

EXAMPLES: 

  

Cut decomposition of a Cycle graph:: 

  

sage: from sage.graphs.graph_decompositions import cutwidth 

sage: G = graphs.CycleGraph(6) 

sage: L = G.vertices() 

sage: cutwidth.width_of_cut_decomposition(G, L) 

2 

  

Cut decomposition of a Path graph:: 

  

sage: from sage.graphs.graph_decompositions import cutwidth 

sage: P = graphs.PathGraph(6) 

sage: cutwidth.width_of_cut_decomposition(P, [0, 1, 2, 3, 4, 5]) 

1 

sage: cutwidth.width_of_cut_decomposition(P, [5, 0, 1, 2, 3, 4]) 

2 

sage: cutwidth.width_of_cut_decomposition(P, [0, 2, 4, 1, 3, 5]) 

5 

  

TESTS: 

  

Giving a wrong linear ordering:: 

  

sage: from sage.graphs.graph_decompositions import cutwidth 

sage: cutwidth.width_of_cut_decomposition(Graph(), ['a','b']) 

Traceback (most recent call last): 

... 

ValueError: The input linear vertex ordering L is not valid for G. 

""" 

if not is_valid_ordering(G, L): 

raise ValueError("The input linear vertex ordering L is not valid for G.") 

elif G.order()<=1: 

return 0 

  

position = {u:i for i,u in enumerate(L)} 

  

# We count for each position `i` the number of edges going from vertices at 

# positions in `0..i` to vertices at positions in `i+1..n-1`, for each 

# `x\leq i<n-1`. 

cpt = [0]*len(L) 

for u,v in G.edge_iterator(labels=None): 

x,y = position[u],position[v] 

if x>y: 

x,y = y,x 

# Edge (u,v) contributes 1 to the number of edges going from vertices at 

# positions `0..i` to vertices at positions `i+1..n-1` for each `x\leq 

# i<n-1`. 

for i in range(x,y): 

cpt[i] += 1 

  

# The width of L is the maximum computed value. 

return max(cpt) 

  

  

################################################################################ 

# Front end method for cutwidth 

################################################################################ 

  

def cutwidth(G, algorithm="exponential", cut_off=0, solver=None, verbose=False): 

r""" 

Return the cutwidth of the graph and the corresponding vertex ordering. 

  

INPUT: 

  

- ``G`` -- a Graph or a DiGraph 

  

- ``algorithm`` -- (default: ``"exponential"``) Specify the algorithm to use 

among 

  

- ``exponential`` -- Use an exponential time and space algorithm based on 

dynamic programming. This algorithm only works on graphs with strictly 

less than 32 vertices. 

  

- ``MILP`` -- Use a mixed integer linear programming formulation. This 

algorithm has no size restriction but could take a very long time. 

  

- ``cut_off`` -- (default: 0) This parameter is used to stop the search as 

soon as a solution with width at most ``cut_off`` is found, if any. If 

this bound cannot be reached, the best solution found is returned. 

  

- ``solver`` -- (default: ``None``) Specify a Linear Program (LP) solver to 

be used. If set to ``None``, the default one is used. This parameter is 

used only when ``algorithm='MILP'``. For more information on LP solvers 

and which default solver is used, see the method 

:meth:`solve<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the 

class 

:class:`MixedIntegerLinearProgram<sage.numerical.mip.MixedIntegerLinearProgram>`. 

  

- ``verbose`` (boolean) -- whether to display information on the 

computations. 

  

OUTPUT: 

  

A pair ``(cost, ordering)`` representing the optimal ordering of the 

vertices and its cost. 

  

EXAMPLES: 

  

Cutwidth of a Complete Graph:: 

  

sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth 

sage: G = graphs.CompleteGraph(5) 

sage: cw,L = cutwidth(G); cw 

6 

sage: K = graphs.CompleteGraph(6) 

sage: cw,L = cutwidth(K); cw 

9 

sage: cw,L = cutwidth(K+K); cw 

9 

  

The cutwidth of a `p\times q` Grid Graph with `p\leq q` is `p+1`:: 

  

sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth 

sage: G = graphs.Grid2dGraph(3,3) 

sage: cw,L = cutwidth(G); cw 

4 

sage: G = graphs.Grid2dGraph(3,5) 

sage: cw,L = cutwidth(G); cw 

4 

  

TESTS: 

  

Comparison of algorithms:: 

  

sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth 

sage: for i in range(2): # long time 

....: G = graphs.RandomGNP(7, 0.3) 

....: ve, le = cutwidth(G, algorithm="exponential") 

....: vm, lm = cutwidth(G, algorithm="MILP", solver='GLPK') 

....: if ve != vm: 

....: print("Something goes wrong!") 

  

Given a wrong algorithm:: 

  

sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth 

sage: cutwidth(graphs.PathGraph(2), algorithm="SuperFast") 

Traceback (most recent call last): 

... 

ValueError: Algorithm "SuperFast" has not been implemented yet. Please contribute. 

  

Given anything else than a Graph:: 

  

sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth 

sage: cutwidth(range(4)) 

Traceback (most recent call last): 

... 

ValueError: The parameter must be a Graph. 

  

Giving a wrong type cut off:: 

  

sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth 

sage: cutwidth(Graph(), cut_off='toto') 

Traceback (most recent call last): 

... 

ValueError: The specified cut off parameter must be an integer. 

""" 

from sage.graphs.graph import Graph 

  

if not isinstance(G, Graph): 

raise ValueError('The parameter must be a Graph.') 

  

if not cut_off in ZZ: 

raise ValueError("The specified cut off parameter must be an integer.") 

elif G.size() <= cut_off: 

# We have a trivial solution 

return width_of_cut_decomposition(G, G.vertices()), G.vertices() 

  

if not G.is_connected(): 

# The graph has several connected components. We solve the problem on 

# each of them and concatenate the partial orderings. The cutwidth is 

# the maximum over all these subgraphs. 

cw, L = 0, [] 

for V in G.connected_components(): 

  

# We build the connected subgraph 

H = G.subgraph(V) 

if H.size() <= max(cw, cut_off): 

# We can directly add these vertices to the solution 

L.extend(V) 

  

else: 

# We do a recursive call on H 

cwH,LH = cutwidth(H, algorithm = algorithm, 

cut_off = max(cut_off,cw), 

solver = solver, verbose = verbose) 

  

# We update the cutwidth and ordering 

cw = max(cw, cwH) 

L.extend(LH) 

  

return cw, L 

  

# We have a connected graph and we call the desired algorithm 

if algorithm == "exponential": 

return cutwidth_dyn(G, lower_bound=cut_off) 

  

elif algorithm == "MILP": 

return cutwidth_MILP(G, lower_bound=cut_off, solver=solver, verbose=verbose) 

  

else: 

raise ValueError('Algorithm "{}" has not been implemented yet. Please contribute.'.format(algorithm)) 

  

  

################################################################################ 

# Dynamic Programming algorithm for cutwidth 

################################################################################ 

from sage.graphs.graph_decompositions.vertex_separation cimport find_order 

  

def cutwidth_dyn(G, lower_bound=0): 

r""" 

Dynamic programming algorithm for the cutwidth of a Graph. 

  

This function uses dynamic programming algorithm for determining an optimal 

layout for the cutwidth of `G`. See the :mod:`module's documentation 

<sage.graphs.graph_decompositions.cutwidth>` for more details on this 

method. 

  

INPUT: 

  

- ``G`` -- a Graph 

  

- ``lower_bound`` -- (default: 0) the algorithm returns immediately if it 

finds a solution lower or equal to ``lower_bound`` (in which case it may 

not be optimal). 

  

OUTPUT: 

  

A pair ``(cost, ordering)`` representing the optimal ordering of the 

vertices and its cost. 

  

.. NOTE:: 

  

Because of its current implementation, this algorithm only works on 

graphs on strictly less than 32 vertices. This can be changed to 63 if 

necessary, but 32 vertices already require 4GB of memory. 

  

TESTS: 

  

Giving anything else than a Graph:: 

  

sage: from sage.graphs.graph_decompositions import cutwidth 

sage: cutwidth.cutwidth_dyn([]) 

Traceback (most recent call last): 

... 

ValueError: The parameter must be a Graph. 

  

Giving a too large Graph:: 

  

sage: from sage.graphs.graph_decompositions import cutwidth 

sage: cutwidth.cutwidth_dyn(graphs.PathGraph(40)) 

Traceback (most recent call last): 

... 

ValueError: The graph should have at most 31 vertices ! 

  

Giving a wrong type lower bound:: 

  

sage: from sage.graphs.graph_decompositions import cutwidth 

sage: cutwidth.cutwidth_dyn(Graph(), lower_bound='toto') 

Traceback (most recent call last): 

... 

ValueError: The specified lower bound must be an integer. 

  

""" 

from sage.graphs.graph import Graph 

if not isinstance(G, Graph): 

raise ValueError("The parameter must be a Graph.") 

  

if G.order() >= 32: 

raise ValueError("The graph should have at most 31 vertices !") 

  

if not lower_bound in ZZ: 

raise ValueError("The specified lower bound must be an integer.") 

  

cdef FastDigraph g = FastDigraph(G) 

  

cdef unsigned int mem = 1 << g.n 

cdef uint8_t * neighborhoods = <uint8_t *> check_allocarray(mem, sizeof(uint8_t)) 

  

memset(neighborhoods, <uint8_t> -1, mem) 

  

cdef int i, k 

  

try: 

for k in range(lower_bound, G.size()): 

for i in range(g.n): 

sig_check() 

if exists(g, neighborhoods, 0, 0, i, k) <= k: 

order = find_order(g, neighborhoods, k) 

return k, [g.int_to_vertices[i] for i in order] 

finally: 

sig_free(neighborhoods) 

  

order = find_order(g, neighborhoods, k) 

return k, [g.int_to_vertices[i] for i in order] 

  

cdef inline int exists(FastDigraph g, uint8_t * neighborhoods, int S, int cost_S, int v, int k): 

""" 

Check whether an ordering with the given cost `k` exists, and updates data 

in the neighborhoods array at the same time. See the module's documentation. 

  

INPUT: 

  

- ``g`` -- a FastDiGraph 

  

- ``neighborhoods`` -- an array of size `2^(g.n)` storing for each subset 

`X\subseteq V` of vertices of the graph the number of edges from `X` to 

`V\setminus X`. 

  

- ``S`` -- an integer encoding the predecessor subset of vertices (from 

which is issued the current call). 

  

- ``cost_S`` -- the number of edges from `S` to `V\setminus S`. 

  

- ``v`` -- a vertex such that the current subset of vertices is 

`current==S\cup\{v\}`. 

  

- ``k`` -- the maximum admissible cost for a solution. 

""" 

cdef int current = S | 1<<v 

# If this is true, it means the set has not been evaluated yet 

if neighborhoods[current] == <uint8_t>-1: 

# The number of edges from `current` to `V\setminus current` is the 

# number of edges from `S` to `V\setminus S`, minus the number of edges 

# from `S` to vertex `v`, plus the number of edges from `v` to 

# `V\setminus (S\cup \{v\})`. This can be computed adding the degree of 

# `c` to `cost_S`, and then removing twice the number of edges from `S` 

# to `v`. 

neighborhoods[current] = cost_S + g.degree[v] - 2*popcount32(S&g.graph[v]) 

  

# If the cost of this set is too high, there is no point in going further. 

# Same thing if the current set is the whole vertex set. 

if neighborhoods[current] > k or (current == (1<<g.n)-1): 

return neighborhoods[current] 

  

# Minimum of the costs of the outneighbors, initialized with large constant. 

cdef int mini = (<uint8_t> -1) 

  

cdef int i 

cdef int next_set 

  

# For each possible extension of the current set witha vertex, check whether 

# there exists a cheap path toward {1..n}, and update the cost. 

for i in range(g.n): 

if (current >> i)&1: # if i in S 

continue 

  

mini = min(mini, exists(g, neighborhoods, current, neighborhoods[current], i, k)) 

  

# We have found a path ! 

if mini <= k: 

return mini 

  

# Updating the cost of the current set with the minimum of the cost of its 

# outneighbors. 

neighborhoods[current] = mini 

  

return neighborhoods[current] 

  

################################################################################ 

# MILP formulations for cutwidth 

################################################################################ 

  

def cutwidth_MILP(G, lower_bound=0, solver=None, verbose=0): 

r""" 

MILP formulation for the cutwidth of a Graph. 

  

This method uses a mixed integer linear program (MILP) for determining an 

optimal layout for the cutwidth of `G`. See the :mod:`module's documentation 

<sage.graphs.graph_decompositions.cutwidth>` for more details on this MILP 

formulation. 

  

INPUT: 

  

- ``G`` -- a Graph 

  

- ``lower_bound`` -- (default: 0) the algorithm searches for a solution with 

cost larger or equal to ``lower_bound``. If the given bound is larger than 

the optimal solution the returned solution might not be optimal. If the 

given bound is too high, the algorithm might not be able to find a 

feasible solution. 

  

- ``solver`` -- (default: ``None``) Specify a Linear Program (LP) solver to 

be used. If set to ``None``, the default one is used. For more information 

on LP solvers and which default solver is used, see the method 

:meth:`solve<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the 

class 

:class:`MixedIntegerLinearProgram<sage.numerical.mip.MixedIntegerLinearProgram>`. 

  

- ``verbose`` -- integer (default: ``0``). Sets the level of verbosity. Set 

to 0 by default, which means quiet. 

  

OUTPUT: 

  

A pair ``(cost, ordering)`` representing the optimal ordering of the 

vertices and its cost. 

  

EXAMPLES: 

  

Cutwidth of a Cycle graph:: 

  

sage: from sage.graphs.graph_decompositions import cutwidth 

sage: G = graphs.CycleGraph(5) 

sage: cw, L = cutwidth.cutwidth_MILP(G); cw 

2 

sage: cw == cutwidth.width_of_cut_decomposition(G, L) 

True 

sage: cwe, Le = cutwidth.cutwidth_dyn(G); cwe 

2 

  

Cutwidth of a Complete graph:: 

  

sage: from sage.graphs.graph_decompositions import cutwidth 

sage: G = graphs.CompleteGraph(4) 

sage: cw, L = cutwidth.cutwidth_MILP(G); cw 

4 

sage: cw == cutwidth.width_of_cut_decomposition(G, L) 

True 

  

Cutwidth of a Path graph:: 

  

sage: from sage.graphs.graph_decompositions import cutwidth 

sage: G = graphs.PathGraph(3) 

sage: cw, L = cutwidth.cutwidth_MILP(G); cw 

1 

sage: cw == cutwidth.width_of_cut_decomposition(G, L) 

True 

  

TESTS: 

  

Comparison with exponential algorithm:: 

  

sage: from sage.graphs.graph_decompositions import cutwidth 

sage: for i in range(2): # long time 

....: G = graphs.RandomGNP(7, 0.3) 

....: ve, le = cutwidth.cutwidth_dyn(G) 

....: vm, lm = cutwidth.cutwidth_MILP(G, solver='GLPK') 

....: if ve != vm: 

....: print("The solution is not optimal!") 

  

Giving a too large lower bound:: 

  

sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth_MILP 

sage: G = graphs.CycleGraph(3) 

sage: cutwidth_MILP(G, lower_bound=G.size()+1) 

Traceback (most recent call last): 

... 

MIPSolverException: ... 

  

Giving anything else than a Graph:: 

  

sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth_MILP 

sage: cutwidth_MILP([]) 

Traceback (most recent call last): 

... 

ValueError: The first input parameter must be a Graph. 

""" 

from sage.graphs.graph import Graph 

if not isinstance(G, Graph): 

raise ValueError("The first input parameter must be a Graph.") 

  

from sage.numerical.mip import MixedIntegerLinearProgram 

p = MixedIntegerLinearProgram( maximization = False, solver = solver ) 

  

# Declaration of variables. 

x = p.new_variable(binary=True, nonnegative=True) 

y = p.new_variable(binary=True, nonnegative=True) 

z = p.new_variable(integer=True, nonnegative=True) 

  

N = G.order() 

V = G.vertices() 

  

# All vertices at different positions 

for v in V: 

for k in range(N-1): 

p.add_constraint( p.sum( x[v,i] for i in range(k) ) <= k*x[v,k] ) 

p.add_constraint( x[v,N-1] == 1 ) 

for k in range(N): 

p.add_constraint( p.sum( x[v,k] for v in V ) == k+1 ) 

  

# Edge uv counts at position i if one of u or v is placed at a position in 

# [0,i] and the other is placed at a position in [i+1,n]. 

for u,v in G.edge_iterator(labels=None): 

for i in range(N): 

p.add_constraint( x[u,i] - x[v,i] <= y[u,v,i] ) 

p.add_constraint( x[v,i] - x[u,i] <= y[u,v,i] ) 

  

# Lower bound on the solution 

p.add_constraint( lower_bound <= z['z'] ) 

  

# Objective 

p.add_constraint( z['z'] <= G.size() ) 

for i in range(N): 

p.add_constraint( p.sum( y[u,v,i] for u,v in G.edge_iterator(labels=None) ) <= z['z'] ) 

  

p.set_objective( z['z'] ) 

  

obj = p.solve( log=verbose ) 

  

# We now extract the ordering and the cost of the solution 

cw = int( p.get_values(z)['z'] ) 

val_x = p.get_values( x ) 

seq = [] 

to_see = set(V) 

for k in range(N): 

for u in to_see: 

if val_x[u,k] > 0: 

seq.append(u) 

to_see.discard(u) 

break 

  

return cw, seq