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

718

719

720

721

722

723

724

725

726

727

728

729

730

731

732

733

734

735

736

737

738

739

740

741

742

743

744

745

746

747

748

749

750

751

752

753

754

755

756

757

758

759

760

761

762

763

764

765

766

767

768

769

770

771

772

773

774

775

776

777

778

779

780

781

782

783

784

785

786

787

788

789

790

791

792

793

794

795

796

797

798

799

800

801

802

803

804

805

806

807

808

809

810

811

812

813

814

815

816

817

818

819

820

821

822

823

824

825

826

827

828

829

830

831

832

833

834

835

836

837

838

839

840

841

842

843

844

845

846

847

848

849

850

851

852

853

854

855

856

857

858

859

860

861

862

863

864

865

866

867

868

869

870

871

872

873

874

875

876

877

878

879

880

881

882

883

884

885

886

887

888

889

890

891

892

893

894

895

896

897

898

899

900

901

902

903

904

905

906

907

908

909

910

911

912

913

914

915

916

917

918

919

920

921

922

923

924

925

926

927

928

929

930

931

932

933

934

935

936

937

938

939

940

941

942

943

944

945

946

947

948

949

950

951

952

953

954

955

956

957

958

959

960

961

962

963

964

965

966

967

968

969

970

971

972

973

974

975

976

977

978

979

980

981

982

983

984

985

986

987

988

989

990

991

992

993

994

995

996

997

998

999

1000

1001

1002

1003

1004

1005

1006

1007

1008

1009

1010

1011

1012

1013

1014

1015

1016

1017

1018

1019

1020

1021

1022

1023

1024

1025

1026

1027

1028

1029

1030

1031

1032

1033

1034

1035

1036

1037

1038

1039

1040

1041

1042

1043

1044

1045

1046

1047

1048

1049

1050

1051

1052

1053

1054

1055

1056

1057

1058

1059

1060

1061

1062

1063

1064

1065

1066

1067

1068

1069

1070

1071

1072

1073

1074

1075

1076

1077

1078

1079

1080

1081

1082

1083

1084

1085

1086

1087

1088

1089

1090

1091

1092

1093

1094

1095

1096

1097

1098

1099

1100

1101

1102

1103

1104

1105

1106

1107

1108

1109

1110

1111

1112

1113

1114

1115

1116

1117

1118

1119

1120

1121

1122

1123

1124

1125

1126

1127

1128

1129

1130

1131

1132

1133

1134

1135

1136

1137

1138

1139

1140

1141

1142

1143

1144

1145

1146

1147

1148

1149

1150

1151

1152

1153

1154

1155

1156

1157

1158

1159

1160

1161

1162

1163

1164

1165

1166

1167

1168

1169

1170

1171

1172

1173

1174

1175

1176

1177

1178

1179

1180

1181

1182

1183

1184

1185

1186

1187

1188

1189

1190

1191

1192

1193

1194

1195

1196

1197

1198

1199

1200

1201

1202

1203

1204

1205

1206

1207

1208

1209

1210

1211

1212

1213

1214

1215

1216

1217

1218

1219

1220

1221

1222

1223

1224

1225

1226

1227

1228

1229

1230

1231

1232

1233

1234

1235

1236

1237

1238

1239

1240

1241

1242

1243

1244

1245

1246

1247

1248

1249

1250

1251

1252

1253

1254

1255

1256

1257

1258

1259

1260

1261

1262

1263

1264

1265

1266

1267

1268

1269

1270

1271

1272

1273

1274

1275

1276

1277

1278

1279

1280

1281

1282

1283

1284

1285

1286

1287

1288

1289

1290

1291

1292

1293

1294

1295

1296

1297

1298

1299

1300

1301

1302

1303

1304

1305

1306

1307

1308

1309

1310

1311

1312

1313

1314

1315

1316

1317

1318

1319

1320

1321

1322

1323

1324

1325

1326

1327

1328

1329

1330

1331

1332

1333

1334

1335

1336

1337

1338

1339

1340

1341

1342

1343

1344

1345

1346

1347

1348

1349

1350

1351

1352

1353

1354

1355

1356

1357

1358

1359

1360

1361

1362

1363

1364

1365

1366

1367

1368

1369

1370

1371

1372

1373

1374

1375

1376

1377

1378

1379

1380

1381

1382

1383

1384

1385

1386

1387

1388

1389

1390

1391

1392

1393

1394

1395

1396

1397

1398

1399

1400

1401

1402

1403

1404

1405

1406

1407

1408

1409

1410

1411

1412

1413

1414

1415

1416

1417

1418

1419

1420

1421

1422

1423

1424

1425

1426

1427

1428

1429

1430

1431

1432

1433

1434

1435

1436

1437

1438

1439

1440

1441

1442

1443

1444

1445

1446

1447

1448

1449

1450

1451

1452

1453

1454

1455

1456

1457

1458

1459

1460

1461

1462

1463

1464

1465

1466

1467

1468

1469

1470

1471

1472

1473

1474

1475

1476

1477

1478

1479

1480

1481

1482

1483

1484

1485

1486

1487

1488

1489

1490

1491

1492

1493

1494

1495

1496

1497

1498

1499

1500

1501

1502

1503

1504

1505

1506

1507

1508

1509

1510

1511

1512

1513

1514

1515

1516

1517

1518

1519

1520

1521

1522

1523

1524

1525

1526

1527

1528

1529

1530

1531

1532

1533

1534

1535

1536

1537

1538

1539

1540

1541

1542

1543

1544

1545

1546

1547

1548

1549

1550

1551

1552

1553

1554

1555

1556

1557

1558

1559

1560

1561

1562

1563

1564

1565

1566

1567

1568

1569

1570

1571

1572

1573

1574

1575

1576

1577

1578

1579

1580

1581

1582

1583

1584

1585

1586

1587

1588

1589

1590

1591

1592

1593

1594

1595

1596

1597

1598

1599

1600

1601

1602

1603

1604

1605

1606

1607

1608

1609

1610

1611

1612

1613

1614

1615

1616

1617

1618

1619

1620

1621

1622

1623

1624

1625

1626

1627

1628

1629

1630

1631

1632

1633

1634

1635

1636

1637

1638

1639

1640

1641

1642

1643

1644

1645

1646

1647

1648

1649

1650

1651

1652

1653

1654

1655

1656

1657

1658

1659

1660

1661

1662

1663

1664

1665

1666

1667

1668

1669

1670

1671

1672

1673

1674

1675

1676

1677

1678

1679

1680

1681

1682

1683

1684

1685

1686

1687

1688

1689

1690

1691

1692

1693

1694

1695

1696

1697

1698

1699

1700

1701

1702

1703

1704

1705

1706

1707

1708

1709

1710

1711

1712

1713

1714

1715

1716

1717

1718

1719

1720

1721

1722

1723

1724

1725

1726

1727

1728

1729

1730

1731

1732

1733

1734

1735

1736

1737

1738

1739

1740

1741

1742

1743

1744

1745

1746

1747

1748

1749

1750

1751

1752

1753

1754

1755

1756

1757

1758

1759

1760

1761

1762

1763

1764

1765

1766

1767

1768

1769

1770

1771

1772

1773

1774

1775

1776

1777

1778

1779

1780

1781

1782

1783

1784

1785

1786

1787

1788

1789

1790

1791

1792

1793

1794

1795

1796

1797

1798

1799

1800

1801

1802

1803

1804

1805

1806

1807

1808

1809

1810

1811

1812

1813

1814

1815

1816

1817

1818

1819

1820

1821

1822

1823

1824

1825

1826

1827

1828

1829

1830

1831

1832

1833

1834

1835

1836

1837

1838

1839

1840

1841

1842

1843

1844

1845

1846

1847

1848

1849

1850

1851

1852

1853

1854

1855

1856

1857

1858

1859

1860

1861

1862

1863

1864

1865

1866

1867

1868

1869

1870

1871

1872

1873

1874

1875

1876

1877

1878

1879

1880

1881

1882

1883

1884

1885

1886

1887

1888

1889

1890

1891

1892

1893

1894

1895

1896

1897

1898

1899

1900

1901

1902

1903

1904

1905

1906

1907

1908

1909

1910

1911

1912

1913

1914

1915

1916

1917

1918

1919

1920

1921

1922

1923

1924

1925

1926

1927

1928

1929

1930

1931

1932

1933

1934

1935

1936

1937

1938

1939

1940

1941

1942

1943

1944

1945

1946

1947

1948

1949

1950

1951

1952

1953

1954

1955

1956

1957

1958

1959

1960

1961

1962

1963

1964

1965

1966

1967

1968

1969

1970

1971

1972

1973

1974

1975

1976

1977

1978

1979

1980

1981

1982

1983

1984

1985

1986

1987

1988

1989

1990

1991

1992

1993

1994

1995

1996

1997

1998

1999

2000

2001

2002

2003

2004

2005

2006

2007

2008

2009

2010

2011

2012

2013

2014

2015

2016

2017

2018

2019

2020

2021

2022

2023

2024

2025

2026

2027

2028

2029

2030

2031

2032

2033

2034

2035

2036

2037

2038

2039

2040

2041

2042

2043

2044

2045

2046

2047

2048

2049

2050

2051

2052

2053

2054

2055

2056

2057

2058

2059

2060

2061

2062

2063

2064

2065

2066

2067

2068

2069

2070

2071

2072

2073

2074

2075

2076

2077

2078

2079

2080

2081

2082

2083

2084

2085

2086

2087

2088

2089

2090

2091

2092

2093

2094

2095

2096

2097

2098

2099

2100

2101

2102

2103

2104

2105

2106

2107

2108

2109

2110

2111

2112

2113

2114

2115

2116

2117

2118

2119

2120

2121

2122

2123

2124

2125

2126

2127

2128

2129

2130

2131

2132

2133

2134

2135

2136

2137

2138

2139

2140

2141

2142

2143

2144

2145

2146

2147

2148

2149

2150

2151

2152

2153

2154

2155

2156

2157

2158

2159

2160

2161

2162

2163

2164

2165

2166

2167

2168

2169

2170

2171

2172

2173

2174

2175

2176

2177

2178

2179

2180

2181

2182

2183

2184

2185

2186

2187

2188

2189

2190

2191

2192

2193

2194

2195

2196

2197

2198

2199

2200

2201

2202

2203

2204

2205

2206

2207

2208

2209

2210

2211

2212

2213

2214

2215

2216

2217

2218

2219

2220

2221

2222

2223

2224

2225

2226

2227

2228

2229

2230

2231

2232

2233

2234

2235

2236

2237

2238

2239

2240

2241

2242

2243

2244

2245

2246

2247

2248

2249

2250

2251

2252

2253

2254

2255

2256

2257

2258

2259

2260

2261

2262

2263

2264

2265

2266

2267

2268

2269

2270

2271

2272

2273

2274

2275

2276

2277

2278

2279

2280

2281

2282

2283

2284

2285

2286

2287

2288

2289

2290

2291

2292

2293

2294

2295

2296

2297

2298

2299

2300

2301

2302

2303

2304

2305

2306

2307

2308

2309

2310

2311

2312

2313

2314

2315

2316

2317

2318

2319

2320

2321

2322

2323

2324

2325

2326

2327

2328

2329

2330

2331

2332

2333

2334

2335

2336

2337

2338

2339

2340

2341

2342

2343

2344

2345

2346

2347

2348

2349

2350

2351

2352

2353

2354

2355

2356

2357

2358

2359

2360

2361

2362

2363

2364

2365

2366

2367

2368

2369

2370

2371

2372

2373

2374

2375

2376

2377

2378

2379

2380

2381

2382

2383

2384

2385

2386

2387

2388

2389

2390

2391

2392

2393

2394

2395

2396

2397

2398

2399

2400

2401

2402

2403

2404

2405

2406

2407

2408

2409

2410

2411

2412

2413

2414

2415

2416

2417

2418

2419

2420

2421

2422

2423

2424

2425

2426

2427

2428

2429

2430

2431

2432

2433

2434

2435

2436

2437

2438

2439

2440

2441

2442

2443

2444

2445

2446

2447

2448

2449

2450

2451

2452

2453

2454

2455

2456

2457

2458

2459

2460

2461

2462

2463

2464

2465

2466

2467

2468

2469

2470

2471

2472

2473

2474

2475

2476

2477

2478

2479

2480

2481

2482

2483

2484

2485

2486

2487

2488

2489

2490

2491

2492

2493

2494

2495

2496

2497

2498

2499

2500

2501

2502

2503

2504

2505

2506

2507

2508

2509

2510

2511

2512

2513

2514

2515

2516

2517

2518

2519

2520

2521

2522

2523

2524

2525

2526

2527

2528

2529

2530

2531

2532

2533

2534

2535

2536

2537

2538

2539

2540

2541

2542

2543

2544

2545

2546

2547

2548

2549

2550

2551

2552

2553

2554

2555

2556

2557

2558

2559

2560

2561

2562

2563

2564

2565

2566

2567

2568

2569

2570

2571

2572

2573

2574

2575

2576

2577

2578

2579

2580

2581

2582

2583

2584

2585

2586

2587

2588

2589

2590

2591

2592

2593

2594

2595

2596

2597

2598

2599

2600

2601

2602

2603

2604

2605

2606

2607

2608

2609

2610

2611

2612

2613

2614

2615

2616

2617

2618

2619

2620

2621

2622

2623

2624

2625

2626

2627

2628

2629

2630

2631

2632

2633

2634

2635

2636

2637

2638

2639

2640

2641

2642

2643

2644

2645

2646

2647

2648

2649

2650

2651

2652

2653

2654

2655

2656

2657

2658

2659

2660

2661

2662

2663

2664

2665

2666

2667

2668

2669

2670

2671

2672

2673

2674

2675

2676

2677

2678

2679

2680

2681

2682

2683

2684

2685

2686

2687

2688

2689

2690

2691

2692

2693

2694

2695

2696

2697

2698

2699

2700

2701

2702

2703

2704

2705

2706

2707

2708

2709

2710

2711

2712

2713

2714

2715

2716

2717

2718

2719

2720

2721

2722

2723

2724

2725

2726

2727

2728

2729

2730

2731

2732

2733

2734

2735

2736

2737

2738

2739

2740

2741

2742

2743

2744

2745

2746

2747

2748

2749

2750

2751

2752

2753

2754

2755

2756

2757

2758

2759

2760

2761

2762

2763

2764

2765

2766

2767

2768

2769

2770

2771

2772

2773

2774

2775

2776

2777

2778

2779

2780

2781

2782

2783

2784

2785

2786

2787

2788

2789

2790

2791

2792

2793

2794

2795

2796

2797

2798

2799

2800

2801

2802

2803

2804

2805

2806

2807

2808

2809

2810

2811

2812

2813

2814

2815

2816

2817

2818

2819

2820

2821

2822

2823

2824

2825

2826

2827

2828

2829

2830

2831

2832

2833

2834

2835

2836

2837

2838

2839

2840

2841

2842

2843

2844

2845

2846

2847

2848

2849

2850

2851

2852

2853

2854

2855

2856

2857

2858

2859

2860

2861

2862

2863

2864

2865

2866

2867

2868

2869

2870

2871

2872

2873

2874

2875

2876

2877

2878

2879

2880

2881

2882

2883

2884

2885

2886

2887

2888

2889

2890

2891

2892

2893

2894

2895

2896

2897

2898

2899

2900

2901

2902

2903

2904

2905

2906

2907

2908

2909

2910

2911

2912

2913

2914

2915

2916

2917

2918

2919

2920

2921

2922

2923

2924

2925

2926

2927

2928

2929

2930

2931

2932

2933

2934

2935

2936

2937

2938

2939

2940

2941

2942

2943

2944

2945

2946

2947

2948

2949

2950

2951

2952

2953

2954

2955

2956

2957

2958

2959

2960

2961

2962

2963

2964

2965

2966

2967

2968

2969

2970

2971

2972

2973

2974

2975

2976

2977

2978

2979

2980

2981

2982

2983

2984

2985

2986

2987

2988

2989

2990

2991

2992

2993

2994

2995

2996

2997

2998

2999

3000

3001

3002

3003

3004

3005

3006

3007

3008

3009

3010

3011

3012

3013

3014

3015

3016

3017

3018

3019

3020

3021

3022

3023

3024

3025

3026

3027

3028

3029

3030

3031

3032

3033

3034

3035

3036

3037

3038

3039

3040

3041

3042

3043

3044

3045

3046

3047

3048

3049

3050

3051

3052

3053

3054

3055

3056

3057

3058

3059

3060

3061

3062

3063

3064

3065

3066

3067

3068

3069

3070

3071

3072

3073

3074

3075

3076

3077

3078

3079

3080

3081

3082

3083

3084

3085

3086

3087

3088

3089

3090

3091

3092

3093

3094

3095

3096

3097

3098

3099

3100

3101

3102

3103

3104

3105

3106

3107

3108

3109

3110

3111

3112

3113

3114

3115

3116

3117

3118

3119

3120

3121

3122

3123

3124

3125

3126

3127

3128

3129

3130

3131

3132

3133

3134

3135

3136

3137

3138

3139

3140

3141

3142

3143

3144

3145

3146

3147

3148

3149

3150

3151

3152

3153

3154

3155

3156

3157

3158

3159

3160

3161

3162

3163

3164

3165

3166

3167

3168

3169

3170

3171

3172

3173

3174

3175

3176

3177

3178

3179

3180

3181

3182

3183

3184

3185

3186

3187

3188

3189

3190

3191

3192

3193

3194

3195

3196

3197

3198

3199

3200

3201

3202

3203

3204

3205

3206

3207

3208

3209

3210

3211

3212

3213

3214

3215

3216

3217

3218

3219

3220

3221

3222

3223

3224

3225

3226

3227

3228

3229

3230

3231

3232

3233

3234

3235

3236

3237

3238

3239

3240

3241

3242

3243

3244

3245

3246

3247

3248

3249

3250

3251

3252

3253

3254

3255

3256

3257

3258

3259

3260

3261

3262

3263

3264

3265

3266

3267

3268

3269

3270

3271

3272

3273

3274

3275

3276

3277

3278

3279

3280

3281

3282

3283

3284

3285

3286

3287

3288

3289

3290

3291

3292

3293

3294

3295

3296

3297

3298

3299

3300

3301

3302

3303

3304

3305

3306

3307

3308

3309

3310

3311

3312

3313

3314

3315

3316

3317

3318

3319

3320

3321

3322

3323

3324

3325

3326

3327

3328

3329

3330

3331

3332

3333

3334

3335

3336

3337

3338

3339

3340

3341

3342

3343

3344

3345

3346

3347

3348

3349

3350

3351

3352

3353

3354

3355

3356

3357

3358

3359

3360

3361

3362

3363

3364

3365

3366

3367

3368

3369

3370

3371

3372

3373

3374

3375

3376

3377

3378

3379

3380

3381

3382

3383

3384

3385

3386

3387

3388

3389

3390

3391

3392

3393

3394

3395

3396

3397

3398

3399

3400

3401

3402

3403

3404

3405

3406

3407

3408

3409

3410

3411

3412

3413

3414

3415

3416

3417

3418

3419

3420

3421

3422

3423

3424

3425

3426

3427

3428

3429

3430

3431

3432

3433

3434

3435

3436

3437

3438

3439

3440

3441

3442

3443

3444

3445

3446

3447

3448

3449

3450

3451

3452

3453

3454

3455

3456

3457

3458

3459

3460

3461

3462

3463

3464

3465

3466

3467

3468

3469

3470

3471

3472

3473

3474

3475

3476

3477

3478

3479

3480

3481

3482

3483

3484

3485

3486

3487

3488

3489

3490

3491

3492

3493

3494

3495

3496

3497

3498

3499

3500

3501

3502

3503

3504

3505

3506

3507

3508

3509

3510

3511

3512

3513

3514

3515

3516

3517

3518

3519

3520

3521

3522

3523

3524

3525

3526

3527

3528

3529

3530

3531

3532

3533

3534

3535

3536

3537

3538

3539

3540

3541

3542

3543

3544

3545

3546

3547

3548

3549

3550

3551

3552

3553

3554

3555

3556

3557

3558

3559

3560

3561

3562

3563

3564

3565

3566

3567

3568

3569

3570

3571

3572

3573

3574

3575

3576

3577

3578

3579

3580

3581

3582

3583

3584

3585

3586

3587

3588

3589

3590

3591

3592

3593

3594

3595

3596

3597

3598

3599

3600

3601

3602

3603

3604

3605

3606

3607

3608

3609

3610

3611

3612

3613

3614

3615

3616

3617

3618

3619

3620

3621

3622

3623

3624

3625

3626

3627

3628

3629

3630

3631

3632

3633

3634

3635

3636

3637

3638

3639

3640

3641

3642

3643

3644

3645

3646

3647

3648

3649

3650

3651

3652

3653

3654

3655

3656

3657

3658

3659

3660

3661

3662

3663

3664

3665

3666

3667

3668

3669

3670

3671

3672

3673

3674

3675

3676

3677

3678

3679

3680

3681

3682

3683

3684

3685

3686

3687

3688

3689

3690

3691

3692

3693

3694

3695

3696

3697

3698

3699

3700

3701

3702

3703

3704

3705

3706

3707

3708

3709

3710

3711

3712

3713

3714

3715

3716

3717

3718

3719

3720

3721

3722

3723

3724

3725

3726

3727

3728

3729

3730

3731

3732

3733

3734

3735

3736

3737

3738

3739

3740

3741

3742

3743

3744

3745

3746

3747

3748

3749

3750

3751

3752

3753

3754

3755

3756

3757

3758

3759

3760

3761

3762

3763

3764

3765

3766

3767

3768

3769

3770

3771

3772

3773

3774

3775

3776

3777

3778

3779

3780

3781

3782

3783

3784

3785

3786

3787

3788

3789

3790

3791

3792

3793

3794

3795

3796

3797

3798

3799

3800

3801

3802

3803

3804

3805

3806

3807

3808

3809

3810

3811

3812

3813

3814

3815

3816

3817

3818

3819

3820

3821

3822

3823

3824

3825

3826

3827

3828

3829

3830

3831

3832

3833

3834

3835

3836

3837

3838

3839

3840

3841

3842

3843

3844

3845

3846

3847

3848

3849

3850

3851

3852

3853

3854

3855

3856

3857

3858

3859

3860

3861

3862

3863

3864

3865

3866

3867

3868

3869

3870

3871

3872

3873

3874

3875

3876

3877

3878

3879

3880

3881

3882

3883

3884

3885

3886

3887

3888

3889

3890

3891

3892

3893

3894

3895

3896

3897

3898

3899

3900

3901

3902

3903

3904

3905

3906

3907

3908

3909

3910

3911

3912

3913

3914

3915

3916

3917

3918

3919

3920

3921

3922

3923

3924

3925

3926

3927

3928

3929

3930

3931

3932

3933

3934

3935

3936

3937

3938

3939

3940

3941

3942

3943

3944

3945

3946

3947

3948

3949

3950

3951

3952

3953

3954

3955

3956

3957

3958

3959

3960

3961

3962

3963

3964

3965

3966

3967

3968

3969

3970

3971

3972

3973

3974

3975

3976

3977

3978

3979

3980

3981

3982

3983

3984

3985

3986

3987

3988

3989

3990

3991

3992

3993

3994

3995

3996

3997

3998

3999

4000

4001

4002

4003

4004

4005

4006

4007

4008

4009

4010

4011

4012

4013

4014

4015

4016

4017

4018

4019

4020

4021

4022

4023

4024

4025

4026

4027

4028

4029

4030

4031

4032

4033

4034

4035

4036

4037

4038

4039

4040

4041

4042

4043

4044

4045

4046

4047

4048

4049

4050

4051

4052

4053

4054

4055

4056

4057

4058

4059

4060

4061

4062

4063

4064

4065

4066

4067

4068

4069

4070

4071

4072

4073

4074

4075

4076

4077

4078

4079

4080

4081

4082

4083

4084

4085

4086

4087

4088

4089

4090

4091

4092

4093

4094

4095

4096

4097

4098

4099

4100

4101

4102

4103

4104

4105

4106

4107

4108

4109

4110

4111

4112

4113

4114

4115

4116

4117

4118

4119

4120

4121

4122

4123

4124

4125

4126

4127

4128

4129

4130

4131

4132

4133

4134

4135

4136

4137

4138

4139

4140

4141

4142

4143

4144

4145

4146

4147

4148

4149

4150

4151

4152

4153

4154

4155

4156

4157

4158

4159

4160

4161

4162

4163

4164

4165

4166

4167

4168

4169

4170

4171

4172

4173

4174

4175

4176

4177

4178

4179

4180

4181

4182

4183

4184

4185

4186

4187

4188

4189

4190

4191

4192

4193

4194

4195

4196

4197

4198

4199

4200

4201

4202

4203

4204

4205

4206

4207

4208

4209

4210

4211

4212

4213

4214

4215

4216

4217

4218

4219

4220

4221

4222

4223

4224

4225

4226

4227

4228

4229

4230

4231

4232

4233

4234

4235

4236

4237

4238

4239

4240

4241

4242

4243

4244

4245

4246

4247

4248

4249

4250

4251

4252

4253

4254

4255

4256

4257

4258

4259

4260

4261

4262

4263

4264

4265

4266

4267

4268

4269

4270

4271

4272

4273

4274

4275

4276

4277

4278

4279

4280

4281

4282

4283

4284

4285

4286

4287

4288

4289

4290

4291

4292

4293

4294

4295

4296

4297

4298

4299

4300

4301

4302

4303

4304

4305

4306

4307

4308

4309

4310

4311

4312

4313

4314

4315

4316

4317

4318

4319

4320

4321

4322

4323

4324

4325

4326

4327

4328

4329

4330

4331

4332

4333

4334

4335

4336

4337

4338

4339

4340

4341

4342

4343

4344

4345

4346

4347

4348

4349

4350

4351

4352

4353

4354

4355

4356

4357

4358

4359

4360

4361

4362

4363

4364

4365

4366

4367

4368

4369

4370

4371

4372

4373

4374

4375

4376

4377

4378

4379

4380

4381

4382

4383

4384

4385

4386

4387

4388

4389

4390

4391

4392

4393

4394

4395

4396

4397

4398

4399

4400

4401

4402

4403

4404

4405

4406

4407

4408

4409

4410

4411

4412

4413

4414

4415

4416

4417

4418

4419

4420

4421

4422

4423

4424

4425

4426

4427

4428

4429

4430

4431

4432

4433

4434

4435

4436

4437

4438

4439

4440

4441

4442

4443

4444

4445

4446

4447

4448

4449

4450

4451

4452

4453

4454

4455

4456

4457

4458

4459

4460

4461

4462

4463

4464

4465

4466

4467

4468

4469

4470

4471

4472

4473

4474

4475

4476

4477

4478

4479

4480

4481

4482

4483

4484

4485

4486

4487

4488

4489

4490

4491

4492

4493

4494

4495

4496

4497

4498

4499

4500

4501

4502

4503

4504

4505

4506

4507

4508

4509

4510

4511

4512

4513

4514

4515

4516

4517

4518

4519

4520

4521

4522

4523

4524

4525

4526

4527

4528

4529

4530

4531

4532

4533

4534

4535

4536

4537

4538

4539

4540

4541

4542

4543

4544

4545

4546

4547

4548

4549

4550

4551

4552

4553

4554

4555

4556

4557

4558

4559

4560

4561

4562

4563

4564

4565

4566

4567

4568

4569

4570

4571

4572

4573

4574

4575

4576

4577

4578

4579

4580

4581

4582

4583

4584

4585

4586

4587

4588

4589

4590

4591

4592

4593

4594

4595

4596

4597

4598

4599

4600

4601

4602

4603

4604

4605

4606

4607

4608

4609

4610

4611

4612

4613

4614

4615

4616

4617

4618

4619

4620

4621

4622

4623

4624

4625

4626

4627

4628

4629

4630

4631

4632

4633

4634

4635

4636

4637

4638

4639

4640

4641

4642

4643

4644

4645

4646

4647

4648

4649

4650

4651

4652

4653

4654

4655

4656

4657

4658

4659

4660

4661

4662

4663

4664

4665

4666

4667

4668

4669

4670

4671

4672

4673

4674

4675

4676

4677

4678

4679

4680

4681

4682

4683

4684

4685

4686

4687

4688

4689

4690

4691

4692

4693

4694

4695

4696

4697

4698

4699

4700

4701

4702

4703

4704

4705

4706

4707

4708

4709

4710

4711

4712

4713

4714

4715

4716

4717

4718

4719

4720

4721

4722

4723

4724

4725

4726

4727

4728

4729

4730

4731

4732

4733

4734

4735

4736

4737

4738

4739

4740

4741

4742

4743

4744

4745

4746

4747

4748

4749

4750

4751

4752

4753

4754

4755

4756

4757

4758

4759

4760

4761

4762

4763

4764

4765

4766

4767

4768

4769

4770

4771

4772

4773

4774

4775

4776

4777

4778

4779

4780

4781

4782

4783

4784

4785

4786

4787

4788

4789

4790

4791

4792

4793

4794

4795

4796

4797

4798

4799

4800

4801

4802

4803

4804

4805

4806

4807

4808

4809

4810

4811

4812

4813

4814

4815

4816

4817

4818

4819

4820

4821

4822

4823

4824

4825

4826

4827

4828

4829

4830

4831

4832

4833

4834

4835

4836

4837

4838

4839

4840

4841

4842

4843

4844

4845

4846

4847

4848

4849

4850

4851

4852

4853

4854

4855

4856

4857

4858

4859

4860

4861

4862

4863

4864

4865

4866

4867

4868

4869

4870

4871

4872

4873

4874

4875

4876

4877

4878

4879

4880

4881

4882

4883

4884

4885

4886

4887

4888

4889

4890

4891

4892

4893

4894

4895

4896

4897

4898

4899

4900

4901

4902

4903

4904

4905

4906

4907

4908

4909

4910

4911

4912

4913

4914

4915

4916

4917

4918

4919

4920

4921

4922

4923

4924

4925

4926

4927

4928

4929

4930

4931

4932

4933

4934

4935

4936

4937

4938

4939

4940

4941

4942

4943

4944

4945

4946

4947

4948

4949

4950

4951

4952

4953

4954

4955

4956

4957

4958

4959

4960

4961

4962

4963

4964

4965

4966

4967

4968

4969

4970

4971

4972

4973

4974

4975

4976

4977

4978

4979

4980

4981

4982

4983

4984

4985

4986

4987

4988

4989

4990

4991

4992

4993

4994

4995

4996

4997

4998

4999

5000

5001

5002

5003

5004

5005

5006

5007

5008

5009

5010

5011

5012

5013

5014

5015

5016

5017

5018

5019

5020

5021

5022

5023

5024

5025

5026

5027

5028

5029

5030

5031

5032

5033

5034

5035

5036

5037

5038

5039

5040

5041

5042

5043

5044

5045

5046

5047

5048

5049

5050

5051

5052

5053

5054

5055

5056

5057

5058

5059

5060

5061

5062

5063

5064

5065

5066

5067

5068

5069

5070

5071

5072

5073

5074

5075

5076

5077

5078

5079

5080

5081

5082

5083

5084

5085

5086

5087

5088

5089

5090

5091

5092

5093

5094

5095

5096

5097

5098

5099

5100

5101

5102

5103

5104

5105

5106

5107

5108

5109

5110

5111

5112

5113

5114

5115

5116

5117

5118

5119

5120

5121

5122

5123

5124

5125

5126

5127

5128

5129

5130

5131

5132

5133

5134

5135

5136

5137

5138

5139

5140

5141

5142

5143

5144

5145

5146

5147

5148

5149

5150

5151

5152

5153

5154

5155

5156

5157

5158

5159

5160

5161

5162

5163

5164

5165

5166

5167

5168

5169

5170

5171

5172

5173

5174

5175

5176

5177

5178

5179

5180

5181

5182

5183

5184

5185

5186

5187

5188

5189

5190

5191

5192

5193

5194

5195

5196

5197

5198

5199

5200

5201

5202

5203

5204

5205

5206

5207

5208

5209

5210

5211

5212

5213

5214

5215

5216

5217

5218

5219

5220

5221

5222

5223

5224

5225

5226

5227

5228

5229

5230

5231

5232

5233

5234

5235

5236

5237

5238

5239

5240

5241

5242

5243

5244

5245

5246

5247

5248

5249

5250

5251

5252

5253

5254

5255

5256

5257

5258

5259

5260

5261

5262

5263

5264

5265

5266

5267

5268

5269

5270

5271

5272

5273

5274

5275

5276

5277

5278

5279

5280

5281

5282

5283

5284

5285

5286

5287

5288

5289

5290

5291

5292

5293

5294

5295

5296

5297

5298

5299

5300

5301

5302

5303

5304

5305

5306

5307

5308

5309

5310

5311

5312

5313

5314

5315

5316

5317

5318

5319

5320

5321

5322

5323

5324

5325

5326

5327

5328

5329

5330

5331

5332

5333

5334

5335

5336

5337

5338

5339

5340

5341

5342

5343

5344

5345

5346

5347

5348

5349

5350

5351

5352

5353

5354

5355

5356

5357

5358

5359

5360

5361

5362

5363

5364

5365

5366

5367

5368

5369

5370

5371

5372

5373

5374

5375

5376

5377

5378

5379

5380

5381

5382

5383

5384

5385

5386

5387

5388

5389

5390

5391

5392

5393

5394

5395

5396

5397

5398

5399

5400

5401

5402

5403

5404

5405

5406

5407

5408

5409

5410

5411

5412

5413

5414

5415

5416

5417

5418

5419

5420

5421

5422

5423

5424

5425

5426

5427

5428

5429

5430

5431

5432

5433

5434

5435

5436

5437

5438

5439

5440

5441

5442

5443

5444

5445

5446

5447

5448

5449

5450

5451

5452

5453

5454

5455

5456

5457

5458

5459

5460

5461

5462

5463

5464

5465

5466

5467

5468

5469

5470

5471

5472

5473

5474

5475

5476

5477

5478

5479

5480

5481

5482

5483

5484

5485

5486

5487

5488

5489

5490

5491

5492

5493

5494

5495

5496

5497

5498

5499

5500

5501

5502

5503

5504

5505

5506

5507

5508

5509

5510

5511

5512

5513

5514

5515

5516

5517

5518

5519

5520

5521

5522

5523

5524

5525

5526

5527

5528

5529

5530

5531

5532

5533

5534

5535

5536

5537

5538

5539

5540

5541

5542

5543

5544

5545

5546

5547

5548

5549

5550

5551

5552

5553

5554

5555

5556

5557

5558

5559

5560

5561

5562

5563

5564

5565

5566

5567

5568

5569

5570

5571

5572

5573

5574

5575

5576

5577

5578

5579

5580

5581

5582

5583

5584

5585

5586

5587

5588

5589

5590

5591

5592

5593

5594

5595

5596

5597

5598

5599

5600

5601

5602

5603

5604

5605

5606

5607

5608

5609

5610

5611

5612

5613

5614

5615

5616

5617

5618

5619

5620

5621

5622

5623

5624

5625

5626

5627

5628

5629

5630

5631

5632

5633

5634

5635

5636

5637

5638

5639

5640

5641

5642

5643

5644

5645

5646

5647

5648

5649

5650

5651

5652

5653

5654

5655

5656

5657

5658

5659

5660

5661

5662

5663

5664

5665

5666

5667

5668

5669

5670

5671

5672

5673

5674

5675

5676

5677

5678

5679

5680

5681

5682

5683

5684

5685

5686

5687

5688

5689

5690

5691

5692

5693

5694

5695

5696

5697

5698

5699

5700

5701

5702

5703

5704

5705

5706

5707

5708

5709

5710

5711

5712

5713

5714

5715

5716

5717

5718

5719

5720

5721

5722

5723

5724

5725

5726

5727

5728

5729

5730

5731

5732

5733

5734

5735

5736

5737

5738

5739

5740

5741

5742

5743

5744

5745

5746

5747

5748

5749

5750

5751

5752

5753

5754

5755

5756

5757

5758

5759

5760

5761

5762

5763

5764

5765

5766

5767

5768

5769

5770

5771

5772

5773

5774

5775

5776

5777

5778

5779

5780

5781

5782

5783

5784

5785

5786

5787

5788

5789

5790

5791

5792

5793

5794

5795

5796

5797

5798

5799

5800

5801

5802

5803

5804

5805

5806

5807

5808

5809

5810

5811

5812

5813

5814

5815

5816

5817

5818

5819

5820

5821

5822

5823

5824

5825

5826

5827

5828

5829

5830

5831

5832

5833

5834

5835

5836

5837

5838

5839

5840

5841

5842

5843

5844

5845

5846

5847

5848

5849

5850

5851

5852

5853

5854

5855

5856

5857

5858

5859

5860

5861

5862

5863

5864

5865

5866

5867

5868

5869

5870

5871

5872

5873

5874

5875

5876

5877

5878

5879

5880

5881

5882

5883

5884

5885

5886

5887

5888

5889

5890

5891

5892

5893

5894

5895

5896

5897

5898

5899

5900

5901

5902

5903

5904

5905

5906

5907

5908

5909

5910

5911

5912

5913

5914

5915

5916

5917

5918

5919

5920

5921

5922

5923

5924

5925

5926

5927

5928

5929

5930

5931

5932

5933

5934

5935

5936

5937

5938

5939

5940

5941

5942

5943

5944

5945

5946

5947

5948

5949

5950

5951

5952

5953

5954

5955

5956

5957

5958

5959

5960

5961

5962

5963

5964

5965

5966

5967

5968

5969

5970

5971

5972

5973

5974

5975

5976

5977

5978

5979

5980

5981

5982

5983

5984

5985

5986

5987

5988

5989

5990

5991

5992

5993

5994

5995

5996

5997

5998

5999

6000

6001

6002

6003

6004

6005

6006

6007

6008

6009

6010

6011

6012

6013

6014

6015

6016

6017

6018

6019

6020

6021

6022

6023

6024

6025

6026

6027

6028

6029

6030

6031

6032

6033

6034

6035

6036

6037

6038

6039

6040

6041

6042

6043

6044

6045

6046

6047

6048

6049

6050

6051

6052

6053

6054

6055

6056

6057

6058

6059

6060

6061

6062

6063

6064

6065

6066

6067

6068

6069

6070

6071

6072

6073

6074

6075

6076

6077

6078

6079

6080

6081

6082

6083

6084

6085

6086

6087

6088

6089

6090

6091

6092

6093

6094

6095

6096

6097

6098

6099

6100

6101

6102

6103

6104

6105

6106

6107

6108

6109

6110

6111

6112

6113

6114

6115

6116

6117

6118

6119

6120

6121

6122

6123

6124

6125

6126

6127

6128

6129

6130

6131

6132

6133

6134

6135

6136

6137

6138

6139

6140

6141

6142

6143

6144

6145

6146

6147

6148

6149

6150

6151

6152

6153

6154

6155

6156

6157

6158

6159

6160

6161

6162

6163

6164

6165

6166

6167

6168

6169

6170

6171

6172

6173

6174

6175

6176

6177

6178

6179

6180

6181

6182

6183

6184

6185

6186

6187

6188

6189

6190

6191

6192

6193

6194

6195

6196

6197

6198

6199

6200

6201

6202

6203

6204

6205

6206

6207

6208

6209

6210

6211

6212

6213

6214

6215

6216

6217

6218

6219

6220

6221

6222

6223

6224

6225

6226

6227

6228

6229

6230

6231

6232

6233

6234

6235

6236

6237

6238

6239

6240

6241

6242

6243

6244

6245

6246

6247

6248

6249

6250

6251

6252

6253

6254

6255

6256

6257

6258

6259

6260

6261

6262

6263

6264

6265

6266

6267

6268

6269

6270

6271

6272

6273

6274

6275

6276

6277

6278

6279

6280

6281

6282

6283

6284

6285

6286

6287

6288

6289

6290

6291

6292

6293

6294

6295

6296

6297

6298

6299

6300

6301

6302

6303

6304

6305

6306

6307

6308

6309

6310

6311

6312

6313

6314

6315

6316

6317

6318

6319

6320

6321

6322

6323

6324

6325

6326

6327

6328

6329

6330

6331

6332

6333

6334

6335

6336

6337

6338

6339

6340

6341

6342

6343

6344

6345

6346

6347

6348

6349

6350

6351

6352

6353

6354

6355

6356

6357

6358

6359

6360

6361

6362

6363

6364

6365

6366

6367

6368

6369

6370

6371

6372

6373

6374

6375

6376

6377

6378

6379

6380

6381

6382

6383

6384

6385

6386

6387

6388

6389

6390

6391

6392

6393

6394

6395

6396

6397

6398

6399

6400

6401

6402

6403

6404

6405

6406

6407

6408

6409

6410

6411

6412

6413

6414

6415

6416

6417

6418

6419

6420

6421

6422

6423

6424

6425

6426

6427

6428

6429

6430

6431

6432

6433

6434

6435

6436

6437

6438

6439

6440

6441

6442

6443

6444

6445

6446

6447

6448

6449

6450

6451

6452

6453

6454

6455

6456

6457

6458

6459

6460

6461

6462

6463

6464

6465

6466

6467

6468

6469

6470

6471

6472

6473

6474

6475

6476

6477

6478

6479

6480

6481

6482

6483

6484

6485

6486

6487

6488

6489

6490

6491

6492

6493

6494

6495

6496

6497

6498

6499

6500

6501

6502

6503

6504

6505

6506

6507

6508

6509

6510

6511

6512

6513

6514

6515

6516

6517

6518

6519

6520

6521

6522

6523

6524

6525

6526

6527

6528

6529

6530

6531

6532

6533

6534

6535

6536

6537

6538

6539

6540

6541

6542

6543

6544

6545

6546

6547

6548

6549

6550

6551

6552

6553

6554

6555

6556

6557

6558

6559

6560

6561

6562

6563

6564

6565

6566

6567

6568

6569

6570

6571

6572

6573

6574

6575

6576

6577

6578

6579

6580

6581

6582

6583

6584

6585

6586

6587

6588

6589

6590

6591

6592

6593

6594

6595

6596

6597

6598

6599

6600

6601

6602

6603

6604

6605

6606

6607

6608

6609

6610

6611

6612

6613

6614

6615

6616

6617

6618

6619

6620

6621

6622

6623

6624

6625

6626

6627

6628

6629

6630

6631

6632

6633

6634

6635

6636

6637

6638

6639

6640

6641

6642

6643

6644

6645

6646

6647

6648

6649

6650

6651

6652

6653

6654

6655

6656

6657

6658

6659

6660

6661

6662

6663

6664

6665

6666

6667

6668

6669

6670

6671

6672

6673

6674

6675

6676

6677

6678

6679

6680

6681

6682

6683

6684

6685

6686

6687

6688

6689

6690

6691

6692

6693

6694

6695

6696

6697

6698

6699

6700

6701

6702

6703

6704

6705

6706

6707

6708

6709

6710

6711

6712

6713

6714

6715

6716

6717

6718

6719

6720

6721

6722

6723

6724

6725

6726

6727

6728

6729

6730

6731

6732

6733

6734

6735

6736

6737

6738

6739

6740

6741

6742

6743

6744

6745

6746

6747

6748

6749

6750

6751

6752

6753

6754

6755

6756

6757

6758

6759

6760

6761

6762

6763

6764

6765

6766

6767

6768

6769

6770

6771

6772

6773

6774

6775

6776

6777

6778

6779

6780

6781

6782

6783

6784

6785

6786

6787

6788

6789

6790

6791

6792

6793

6794

6795

6796

6797

6798

6799

6800

6801

6802

6803

6804

6805

6806

6807

6808

6809

6810

6811

6812

6813

6814

6815

6816

6817

6818

6819

6820

6821

6822

6823

6824

6825

6826

6827

6828

6829

6830

6831

6832

6833

6834

6835

6836

6837

6838

6839

6840

6841

6842

6843

6844

6845

6846

6847

6848

6849

6850

6851

6852

6853

6854

6855

6856

6857

6858

6859

6860

6861

6862

6863

6864

6865

6866

6867

6868

6869

6870

6871

6872

6873

6874

6875

6876

6877

6878

6879

6880

6881

6882

6883

6884

6885

6886

6887

6888

6889

6890

6891

6892

6893

6894

6895

6896

6897

6898

6899

6900

6901

6902

6903

6904

6905

6906

6907

6908

6909

6910

6911

6912

6913

6914

6915

6916

6917

6918

6919

6920

6921

6922

6923

6924

6925

6926

6927

6928

6929

6930

6931

6932

6933

6934

6935

6936

6937

6938

6939

6940

6941

6942

6943

6944

6945

6946

6947

6948

6949

6950

6951

6952

6953

6954

6955

6956

6957

6958

6959

6960

6961

6962

6963

6964

6965

6966

6967

6968

6969

6970

6971

6972

6973

6974

6975

6976

6977

6978

6979

6980

6981

6982

6983

6984

6985

6986

6987

6988

6989

6990

6991

6992

6993

6994

6995

6996

6997

6998

6999

7000

7001

7002

7003

7004

7005

7006

7007

7008

7009

7010

7011

7012

7013

7014

7015

7016

7017

7018

7019

7020

7021

7022

7023

7024

7025

7026

7027

7028

7029

7030

7031

7032

7033

7034

7035

7036

7037

7038

7039

7040

7041

7042

7043

7044

7045

7046

7047

7048

7049

7050

7051

7052

7053

7054

7055

7056

7057

7058

7059

7060

7061

7062

7063

7064

7065

7066

7067

7068

7069

7070

7071

7072

7073

7074

7075

7076

7077

7078

7079

7080

7081

7082

7083

7084

7085

7086

7087

7088

7089

7090

7091

7092

7093

7094

7095

7096

7097

7098

7099

7100

7101

7102

7103

7104

7105

7106

7107

7108

7109

7110

7111

7112

7113

7114

7115

7116

7117

7118

7119

7120

7121

7122

7123

7124

7125

7126

7127

7128

7129

7130

7131

7132

7133

7134

7135

7136

7137

7138

7139

7140

7141

7142

7143

7144

7145

7146

7147

7148

7149

7150

7151

7152

7153

7154

7155

7156

7157

7158

7159

7160

7161

7162

7163

7164

7165

7166

7167

7168

7169

7170

7171

7172

7173

7174

7175

7176

7177

7178

7179

7180

7181

7182

7183

7184

7185

7186

7187

7188

7189

7190

7191

7192

7193

7194

7195

7196

7197

7198

7199

7200

7201

7202

7203

7204

7205

7206

7207

7208

7209

7210

7211

7212

7213

7214

7215

7216

7217

7218

7219

7220

7221

7222

7223

7224

7225

7226

7227

7228

7229

7230

7231

7232

7233

7234

7235

7236

7237

7238

7239

7240

7241

7242

7243

7244

7245

7246

7247

7248

7249

7250

7251

7252

7253

7254

7255

7256

7257

7258

7259

7260

7261

7262

7263

7264

7265

7266

7267

7268

7269

7270

7271

7272

7273

7274

7275

7276

7277

7278

7279

7280

7281

7282

7283

7284

7285

7286

7287

7288

7289

7290

7291

7292

7293

7294

7295

7296

7297

7298

7299

7300

7301

7302

7303

7304

7305

7306

7307

7308

7309

7310

7311

7312

7313

7314

7315

7316

7317

7318

7319

7320

7321

7322

7323

7324

7325

7326

7327

7328

7329

7330

7331

7332

7333

7334

7335

7336

7337

7338

7339

7340

7341

7342

7343

7344

7345

7346

7347

7348

7349

7350

7351

7352

7353

7354

7355

7356

7357

7358

7359

7360

7361

7362

7363

7364

7365

7366

7367

7368

7369

7370

7371

7372

7373

7374

7375

7376

7377

7378

7379

7380

7381

7382

7383

7384

7385

7386

7387

7388

7389

7390

7391

7392

7393

7394

7395

7396

7397

7398

7399

7400

7401

7402

7403

7404

7405

7406

7407

7408

7409

7410

7411

7412

7413

7414

7415

7416

7417

7418

7419

7420

7421

7422

7423

7424

7425

7426

7427

7428

7429

7430

7431

7432

7433

7434

7435

7436

7437

7438

7439

7440

7441

7442

7443

7444

7445

7446

7447

7448

7449

7450

7451

7452

7453

7454

7455

7456

7457

7458

7459

7460

7461

7462

7463

7464

7465

7466

7467

7468

7469

7470

7471

7472

7473

7474

7475

7476

7477

7478

7479

7480

7481

7482

7483

7484

7485

7486

7487

7488

7489

7490

7491

7492

7493

7494

7495

7496

7497

7498

7499

7500

7501

7502

7503

7504

7505

7506

7507

7508

7509

7510

7511

7512

7513

7514

7515

7516

7517

7518

7519

7520

7521

7522

7523

7524

7525

7526

7527

7528

7529

7530

7531

7532

7533

7534

7535

7536

7537

7538

7539

7540

7541

7542

7543

7544

7545

7546

7547

7548

7549

7550

7551

7552

7553

7554

7555

7556

7557

7558

7559

7560

7561

7562

7563

7564

7565

7566

7567

7568

7569

7570

7571

7572

7573

7574

7575

7576

7577

7578

7579

7580

7581

7582

7583

7584

7585

7586

7587

7588

7589

7590

7591

7592

7593

7594

7595

7596

7597

7598

7599

7600

7601

7602

7603

7604

7605

7606

7607

7608

7609

7610

7611

7612

7613

7614

7615

7616

7617

7618

7619

7620

7621

7622

7623

7624

7625

7626

7627

7628

7629

7630

7631

7632

7633

7634

7635

7636

7637

7638

7639

7640

7641

7642

7643

7644

7645

7646

7647

7648

7649

7650

7651

7652

7653

7654

7655

7656

7657

7658

7659

7660

7661

7662

7663

7664

7665

7666

7667

7668

7669

7670

7671

7672

7673

7674

7675

7676

7677

7678

7679

7680

7681

7682

7683

7684

7685

7686

7687

7688

7689

7690

7691

7692

7693

7694

7695

7696

7697

7698

7699

7700

7701

7702

7703

7704

7705

7706

7707

7708

7709

7710

7711

7712

7713

7714

7715

7716

7717

7718

7719

7720

7721

7722

7723

7724

7725

7726

7727

7728

7729

7730

7731

7732

7733

7734

7735

7736

7737

7738

7739

7740

7741

7742

7743

7744

7745

7746

7747

7748

7749

7750

7751

7752

7753

7754

7755

7756

7757

7758

7759

7760

7761

7762

7763

7764

7765

7766

7767

7768

7769

7770

7771

7772

7773

7774

7775

7776

7777

7778

7779

7780

7781

7782

7783

7784

7785

7786

7787

7788

7789

7790

7791

7792

7793

7794

7795

7796

7797

7798

7799

7800

7801

7802

7803

7804

7805

7806

7807

7808

7809

7810

7811

7812

7813

7814

7815

7816

7817

7818

7819

7820

7821

7822

7823

7824

7825

7826

7827

7828

7829

7830

7831

7832

7833

7834

7835

7836

7837

7838

7839

7840

7841

7842

7843

7844

7845

7846

7847

7848

7849

7850

7851

7852

7853

7854

7855

7856

7857

7858

7859

7860

7861

7862

7863

7864

7865

7866

7867

7868

7869

7870

7871

7872

7873

7874

7875

7876

7877

7878

7879

7880

7881

7882

7883

7884

7885

7886

7887

7888

7889

7890

7891

7892

7893

7894

7895

7896

7897

7898

7899

7900

7901

7902

7903

7904

7905

7906

7907

7908

7909

7910

7911

7912

7913

7914

7915

7916

7917

7918

7919

7920

7921

7922

7923

7924

7925

7926

7927

7928

7929

7930

7931

7932

7933

7934

7935

7936

7937

7938

7939

7940

7941

7942

7943

7944

7945

7946

7947

7948

7949

7950

7951

7952

7953

7954

7955

7956

7957

7958

7959

7960

7961

7962

7963

7964

7965

7966

7967

7968

7969

7970

7971

7972

7973

7974

7975

7976

7977

7978

7979

7980

7981

7982

7983

7984

7985

7986

7987

7988

7989

7990

7991

7992

7993

7994

7995

7996

7997

7998

7999

8000

8001

8002

8003

8004

8005

8006

8007

8008

8009

8010

8011

8012

8013

8014

8015

8016

8017

8018

8019

8020

8021

8022

8023

8024

8025

8026

8027

8028

8029

8030

8031

8032

8033

8034

8035

8036

8037

8038

8039

8040

8041

8042

8043

8044

8045

8046

8047

8048

8049

8050

8051

8052

8053

8054

8055

8056

8057

8058

8059

8060

8061

8062

8063

8064

8065

8066

8067

8068

8069

8070

8071

8072

8073

8074

8075

8076

8077

8078

8079

8080

8081

8082

8083

8084

8085

8086

8087

8088

8089

8090

8091

8092

8093

8094

8095

8096

8097

8098

8099

8100

8101

8102

8103

8104

8105

8106

8107

8108

8109

8110

8111

8112

8113

8114

8115

8116

8117

8118

8119

8120

8121

8122

8123

8124

8125

8126

8127

8128

8129

8130

8131

8132

8133

8134

8135

8136

8137

8138

8139

8140

8141

8142

8143

8144

8145

8146

8147

8148

8149

8150

8151

8152

8153

8154

8155

8156

8157

8158

8159

8160

8161

8162

8163

8164

8165

8166

8167

8168

8169

8170

8171

8172

8173

8174

8175

8176

8177

8178

8179

8180

8181

8182

8183

8184

8185

8186

8187

8188

8189

8190

8191

8192

8193

8194

8195

8196

8197

8198

8199

8200

8201

8202

8203

8204

8205

8206

8207

8208

8209

8210

8211

8212

8213

8214

8215

8216

8217

8218

8219

8220

8221

8222

8223

8224

8225

8226

8227

8228

8229

8230

8231

8232

8233

8234

8235

8236

8237

8238

8239

8240

8241

8242

8243

8244

8245

8246

8247

8248

8249

8250

8251

8252

8253

8254

8255

8256

8257

8258

8259

8260

8261

8262

8263

8264

8265

8266

8267

8268

8269

8270

8271

8272

8273

8274

8275

8276

8277

8278

8279

8280

8281

8282

8283

8284

8285

8286

8287

8288

8289

8290

8291

8292

8293

8294

8295

8296

8297

8298

8299

8300

8301

8302

8303

8304

8305

8306

8307

8308

8309

8310

8311

8312

8313

8314

8315

8316

8317

8318

8319

8320

8321

8322

8323

8324

8325

8326

8327

8328

8329

8330

8331

8332

8333

8334

8335

8336

8337

8338

8339

8340

8341

8342

8343

8344

8345

8346

8347

8348

8349

8350

8351

8352

8353

8354

8355

8356

8357

8358

8359

8360

8361

8362

8363

8364

8365

8366

8367

8368

8369

8370

8371

8372

8373

8374

8375

8376

8377

8378

8379

8380

8381

8382

8383

8384

8385

8386

8387

8388

8389

8390

8391

8392

8393

8394

8395

8396

8397

8398

8399

8400

8401

8402

8403

8404

8405

8406

8407

8408

8409

8410

8411

8412

8413

8414

8415

8416

8417

8418

8419

8420

8421

8422

8423

8424

8425

8426

8427

8428

8429

8430

8431

8432

8433

8434

8435

8436

8437

8438

8439

8440

8441

8442

8443

8444

8445

8446

8447

8448

8449

8450

8451

8452

8453

8454

8455

8456

8457

8458

8459

8460

8461

8462

8463

8464

8465

8466

8467

8468

8469

8470

8471

8472

8473

8474

8475

8476

8477

8478

8479

8480

8481

8482

8483

8484

8485

8486

8487

8488

8489

8490

8491

8492

8493

8494

8495

8496

8497

8498

8499

8500

8501

8502

8503

8504

8505

8506

8507

8508

8509

8510

8511

8512

8513

8514

8515

8516

8517

8518

8519

8520

8521

8522

8523

8524

8525

8526

8527

8528

8529

8530

8531

8532

8533

8534

8535

8536

8537

8538

8539

8540

8541

8542

8543

8544

8545

8546

8547

8548

8549

8550

8551

8552

8553

8554

8555

8556

8557

8558

8559

8560

8561

8562

8563

8564

8565

8566

8567

8568

8569

8570

8571

8572

8573

8574

8575

8576

8577

8578

8579

8580

8581

8582

8583

8584

8585

8586

8587

8588

8589

8590

8591

8592

8593

8594

8595

8596

8597

8598

8599

8600

8601

8602

8603

8604

8605

8606

8607

8608

8609

8610

8611

8612

8613

8614

8615

8616

8617

8618

8619

8620

8621

8622

8623

8624

8625

8626

8627

8628

8629

8630

8631

8632

8633

8634

8635

8636

8637

8638

8639

8640

8641

8642

8643

8644

8645

8646

8647

8648

8649

8650

8651

8652

8653

8654

8655

8656

8657

8658

8659

8660

8661

8662

8663

8664

8665

8666

8667

8668

8669

8670

8671

8672

8673

8674

8675

8676

8677

8678

8679

8680

8681

8682

8683

8684

8685

8686

8687

8688

8689

8690

8691

8692

8693

8694

8695

8696

8697

8698

8699

8700

8701

8702

8703

8704

8705

8706

8707

8708

8709

8710

8711

8712

8713

8714

8715

8716

8717

8718

8719

8720

8721

8722

8723

8724

8725

8726

8727

8728

8729

8730

8731

8732

8733

8734

8735

8736

8737

8738

8739

8740

8741

8742

8743

8744

8745

8746

8747

8748

8749

8750

8751

8752

8753

8754

8755

8756

8757

8758

8759

8760

8761

8762

8763

8764

8765

8766

8767

8768

8769

8770

8771

8772

8773

8774

8775

8776

8777

8778

8779

8780

8781

8782

8783

8784

8785

8786

8787

8788

8789

8790

8791

8792

8793

8794

8795

8796

8797

8798

8799

8800

8801

8802

8803

8804

8805

8806

8807

8808

8809

8810

8811

8812

8813

8814

8815

8816

8817

8818

8819

8820

8821

8822

8823

8824

8825

8826

8827

8828

8829

8830

8831

8832

8833

8834

8835

8836

8837

8838

8839

8840

8841

8842

8843

8844

8845

8846

8847

8848

8849

8850

8851

8852

8853

8854

8855

8856

8857

8858

8859

8860

8861

8862

8863

8864

8865

8866

8867

8868

8869

8870

8871

8872

8873

8874

8875

8876

8877

8878

8879

8880

8881

8882

8883

8884

8885

8886

8887

8888

8889

8890

8891

8892

8893

8894

8895

8896

8897

8898

8899

8900

8901

8902

8903

8904

8905

8906

8907

8908

8909

8910

8911

8912

8913

8914

8915

8916

8917

8918

8919

8920

8921

8922

8923

8924

8925

8926

8927

8928

8929

8930

8931

8932

8933

8934

8935

8936

8937

8938

8939

8940

8941

8942

8943

8944

8945

8946

8947

8948

8949

8950

8951

8952

8953

8954

8955

8956

8957

8958

8959

8960

8961

8962

8963

8964

8965

8966

8967

8968

8969

8970

8971

8972

8973

8974

8975

8976

8977

8978

8979

8980

8981

8982

8983

8984

8985

8986

8987

8988

8989

8990

8991

8992

8993

8994

8995

8996

8997

8998

8999

9000

9001

9002

9003

9004

9005

9006

9007

9008

9009

9010

9011

9012

9013

9014

9015

9016

9017

9018

9019

9020

9021

9022

9023

9024

9025

9026

9027

9028

9029

9030

9031

9032

9033

9034

9035

9036

9037

9038

9039

9040

9041

9042

9043

9044

9045

9046

9047

9048

9049

9050

9051

9052

9053

9054

9055

9056

9057

9058

9059

9060

9061

9062

9063

9064

9065

9066

9067

9068

9069

9070

9071

9072

9073

9074

9075

9076

9077

9078

9079

9080

9081

9082

9083

9084

9085

9086

9087

9088

9089

9090

9091

9092

9093

9094

9095

9096

9097

9098

9099

9100

9101

9102

9103

9104

9105

9106

9107

9108

9109

9110

9111

9112

9113

9114

9115

9116

9117

9118

9119

9120

9121

9122

9123

9124

9125

9126

9127

9128

9129

9130

9131

9132

9133

9134

9135

9136

9137

9138

9139

9140

9141

9142

9143

9144

9145

9146

9147

9148

9149

9150

9151

9152

9153

9154

9155

9156

9157

9158

9159

9160

9161

9162

9163

9164

9165

9166

9167

9168

9169

9170

9171

9172

9173

9174

9175

9176

9177

9178

9179

9180

9181

9182

9183

9184

9185

9186

9187

9188

9189

9190

9191

9192

9193

9194

9195

9196

9197

9198

9199

9200

9201

9202

9203

9204

9205

9206

9207

9208

9209

9210

9211

9212

9213

9214

9215

9216

9217

9218

9219

9220

9221

9222

9223

9224

9225

9226

9227

9228

9229

9230

9231

9232

9233

9234

9235

9236

9237

9238

9239

9240

9241

9242

9243

9244

9245

9246

9247

9248

9249

9250

9251

9252

9253

9254

9255

9256

9257

9258

9259

9260

9261

9262

9263

9264

9265

9266

9267

9268

9269

9270

9271

9272

9273

9274

9275

9276

9277

9278

9279

9280

9281

9282

9283

9284

9285

9286

9287

9288

9289

9290

9291

9292

9293

9294

9295

9296

9297

9298

9299

9300

9301

9302

9303

9304

9305

9306

9307

9308

9309

9310

9311

9312

9313

9314

9315

9316

9317

9318

9319

9320

9321

9322

9323

9324

9325

9326

9327

9328

9329

9330

9331

9332

9333

9334

9335

9336

9337

9338

9339

9340

9341

9342

9343

9344

9345

9346

9347

9348

9349

9350

9351

9352

9353

9354

9355

9356

9357

9358

9359

9360

9361

9362

9363

9364

9365

9366

9367

9368

9369

9370

9371

9372

9373

9374

9375

9376

9377

9378

9379

9380

9381

9382

9383

9384

9385

9386

9387

9388

9389

9390

9391

9392

9393

9394

9395

9396

9397

9398

9399

9400

9401

9402

9403

9404

9405

9406

9407

9408

9409

9410

9411

9412

9413

9414

9415

9416

9417

9418

9419

9420

9421

9422

9423

9424

9425

9426

9427

9428

9429

9430

9431

9432

9433

9434

9435

9436

9437

9438

9439

9440

9441

9442

9443

9444

9445

9446

9447

9448

9449

9450

9451

9452

9453

9454

9455

9456

9457

9458

9459

9460

9461

9462

9463

9464

9465

9466

9467

9468

9469

9470

9471

9472

9473

9474

9475

9476

9477

9478

9479

9480

9481

9482

9483

9484

9485

9486

9487

9488

9489

9490

9491

9492

9493

9494

9495

9496

9497

9498

9499

9500

9501

9502

9503

9504

9505

9506

9507

9508

9509

9510

9511

9512

9513

9514

9515

9516

9517

9518

9519

9520

9521

9522

9523

9524

9525

9526

9527

9528

9529

9530

9531

9532

9533

9534

9535

9536

9537

9538

9539

9540

9541

9542

9543

9544

9545

9546

9547

9548

9549

9550

9551

9552

9553

9554

9555

9556

9557

9558

9559

9560

9561

9562

9563

9564

9565

9566

9567

9568

9569

9570

9571

9572

9573

9574

9575

9576

9577

9578

9579

9580

9581

9582

9583

9584

9585

9586

9587

9588

9589

9590

9591

9592

9593

9594

9595

9596

9597

9598

9599

9600

9601

9602

9603

9604

9605

9606

9607

9608

9609

9610

9611

9612

9613

9614

9615

9616

9617

9618

9619

9620

9621

9622

9623

9624

9625

9626

9627

9628

9629

9630

9631

9632

9633

9634

9635

9636

9637

9638

9639

9640

9641

9642

9643

9644

9645

9646

9647

9648

9649

9650

9651

9652

9653

9654

9655

9656

9657

9658

9659

9660

9661

9662

9663

9664

9665

9666

9667

9668

9669

9670

9671

9672

9673

9674

9675

9676

9677

9678

9679

9680

9681

9682

9683

9684

9685

9686

9687

9688

9689

9690

9691

9692

9693

9694

9695

9696

9697

9698

9699

9700

9701

9702

9703

9704

9705

9706

9707

9708

9709

9710

9711

9712

9713

9714

9715

9716

9717

9718

9719

9720

9721

9722

9723

9724

9725

9726

9727

9728

9729

9730

9731

9732

9733

9734

9735

9736

9737

9738

9739

9740

9741

9742

9743

9744

9745

9746

9747

9748

9749

9750

9751

9752

9753

9754

9755

9756

9757

9758

9759

9760

9761

9762

9763

9764

9765

9766

9767

9768

9769

9770

9771

9772

9773

9774

9775

9776

9777

9778

9779

9780

9781

9782

9783

9784

9785

9786

9787

9788

9789

9790

9791

9792

9793

9794

9795

9796

9797

9798

9799

9800

9801

9802

9803

9804

9805

9806

9807

9808

9809

9810

9811

9812

9813

9814

9815

9816

9817

9818

9819

9820

9821

9822

9823

9824

9825

9826

9827

9828

9829

9830

9831

9832

9833

9834

9835

9836

9837

9838

9839

9840

9841

9842

9843

9844

9845

9846

9847

9848

9849

9850

9851

9852

9853

9854

9855

9856

9857

9858

9859

9860

9861

9862

9863

9864

9865

9866

9867

9868

9869

9870

9871

9872

9873

9874

9875

9876

9877

9878

9879

9880

9881

9882

9883

9884

9885

9886

9887

9888

9889

9890

9891

9892

9893

9894

9895

9896

9897

9898

9899

9900

9901

9902

9903

9904

9905

9906

9907

9908

9909

9910

9911

9912

9913

9914

9915

9916

9917

9918

9919

9920

9921

9922

9923

9924

9925

9926

9927

9928

9929

9930

9931

9932

9933

9934

9935

9936

9937

9938

9939

9940

9941

9942

9943

9944

9945

9946

9947

9948

9949

9950

9951

9952

9953

9954

9955

9956

9957

9958

9959

9960

9961

9962

9963

9964

9965

9966

9967

9968

9969

9970

9971

9972

9973

9974

9975

9976

9977

9978

9979

9980

9981

9982

9983

9984

9985

9986

9987

9988

9989

9990

9991

9992

9993

9994

9995

9996

9997

9998

9999

10000

10001

10002

10003

10004

10005

10006

10007

10008

10009

10010

10011

10012

10013

10014

10015

10016

10017

10018

10019

10020

10021

10022

10023

10024

10025

10026

10027

10028

10029

10030

10031

10032

10033

10034

10035

10036

10037

10038

10039

10040

10041

10042

10043

10044

10045

10046

10047

10048

10049

10050

10051

10052

10053

10054

10055

10056

10057

10058

10059

10060

10061

10062

10063

10064

10065

10066

10067

10068

10069

10070

10071

10072

10073

10074

10075

10076

10077

10078

10079

10080

10081

10082

10083

10084

10085

10086

10087

10088

10089

10090

10091

10092

10093

10094

10095

10096

10097

10098

10099

10100

10101

10102

10103

10104

10105

10106

10107

10108

10109

10110

10111

10112

10113

10114

10115

10116

10117

10118

10119

10120

10121

10122

10123

10124

10125

10126

10127

10128

10129

10130

10131

10132

10133

10134

10135

10136

10137

10138

10139

10140

10141

10142

10143

10144

10145

10146

10147

10148

10149

10150

10151

10152

10153

10154

10155

10156

10157

10158

10159

10160

10161

10162

10163

10164

10165

10166

10167

10168

10169

10170

10171

10172

10173

10174

10175

10176

10177

10178

10179

10180

10181

10182

10183

10184

10185

10186

10187

10188

10189

10190

10191

10192

10193

10194

10195

10196

10197

10198

10199

10200

10201

10202

10203

10204

10205

10206

10207

10208

10209

10210

10211

10212

10213

10214

10215

10216

10217

10218

10219

10220

10221

10222

10223

10224

10225

10226

10227

10228

10229

10230

10231

10232

10233

10234

10235

10236

10237

10238

10239

10240

10241

10242

10243

10244

10245

10246

10247

10248

10249

10250

10251

10252

10253

10254

10255

10256

10257

10258

10259

10260

10261

10262

10263

10264

10265

10266

10267

10268

10269

10270

10271

10272

10273

10274

10275

10276

10277

10278

10279

10280

10281

10282

10283

10284

10285

10286

10287

10288

10289

10290

10291

10292

10293

10294

10295

10296

10297

10298

10299

10300

10301

10302

10303

10304

10305

10306

10307

10308

10309

10310

10311

10312

10313

10314

10315

10316

10317

10318

10319

10320

10321

10322

10323

10324

10325

10326

10327

10328

10329

10330

10331

10332

10333

10334

10335

10336

10337

10338

10339

10340

10341

10342

10343

10344

10345

10346

10347

10348

10349

10350

10351

10352

10353

10354

10355

10356

10357

10358

10359

10360

10361

10362

10363

10364

10365

10366

10367

10368

10369

10370

10371

10372

10373

10374

10375

10376

10377

10378

10379

10380

10381

10382

10383

10384

10385

10386

10387

10388

10389

10390

10391

10392

10393

10394

10395

10396

10397

10398

10399

10400

10401

10402

10403

10404

10405

10406

10407

10408

10409

10410

10411

10412

10413

10414

10415

10416

10417

10418

10419

10420

10421

10422

10423

10424

10425

10426

10427

10428

10429

10430

10431

10432

10433

10434

10435

10436

10437

10438

10439

10440

10441

10442

10443

10444

10445

10446

10447

10448

10449

10450

10451

10452

10453

10454

10455

10456

10457

10458

10459

10460

10461

10462

10463

10464

10465

10466

10467

10468

10469

10470

10471

10472

10473

10474

10475

10476

10477

10478

10479

10480

10481

10482

10483

10484

10485

10486

10487

10488

10489

10490

10491

10492

10493

10494

10495

10496

10497

10498

10499

10500

10501

10502

10503

10504

10505

10506

10507

10508

10509

10510

10511

10512

10513

10514

10515

10516

10517

10518

10519

10520

10521

10522

10523

10524

10525

10526

10527

10528

10529

10530

10531

10532

10533

10534

10535

10536

10537

10538

10539

10540

10541

10542

10543

10544

10545

10546

10547

10548

10549

10550

10551

10552

10553

10554

10555

10556

10557

10558

10559

10560

10561

10562

10563

10564

10565

10566

10567

10568

10569

10570

10571

10572

10573

10574

10575

10576

10577

10578

10579

10580

10581

10582

10583

10584

10585

10586

10587

10588

10589

10590

10591

10592

10593

10594

10595

10596

10597

10598

10599

10600

10601

10602

10603

10604

10605

10606

10607

10608

10609

10610

10611

10612

10613

10614

10615

10616

10617

10618

10619

10620

10621

10622

10623

10624

10625

10626

10627

10628

10629

10630

10631

10632

10633

10634

10635

10636

10637

10638

10639

10640

10641

10642

10643

10644

10645

10646

10647

10648

10649

10650

10651

10652

10653

10654

10655

10656

10657

10658

10659

10660

10661

10662

10663

10664

10665

10666

10667

10668

10669

10670

10671

10672

10673

10674

10675

10676

10677

10678

10679

10680

10681

10682

10683

10684

10685

10686

10687

10688

10689

10690

10691

10692

10693

10694

10695

10696

10697

10698

10699

10700

10701

10702

10703

10704

10705

10706

10707

10708

10709

10710

10711

10712

10713

10714

10715

10716

10717

10718

10719

10720

10721

10722

10723

10724

10725

10726

10727

10728

10729

10730

10731

10732

10733

10734

10735

10736

10737

10738

10739

10740

10741

10742

10743

10744

10745

10746

10747

10748

10749

10750

10751

10752

10753

10754

10755

10756

10757

10758

10759

10760

10761

10762

10763

10764

10765

10766

10767

10768

10769

10770

10771

10772

10773

10774

10775

10776

10777

10778

10779

10780

10781

10782

10783

10784

10785

10786

10787

10788

10789

10790

10791

10792

10793

10794

10795

10796

10797

10798

10799

10800

10801

10802

10803

10804

10805

10806

10807

10808

10809

10810

10811

10812

10813

10814

10815

10816

10817

10818

10819

10820

10821

10822

10823

10824

10825

10826

10827

10828

10829

10830

10831

10832

10833

10834

10835

10836

10837

10838

10839

10840

10841

10842

10843

10844

10845

10846

10847

10848

10849

10850

10851

10852

10853

10854

10855

10856

10857

10858

10859

10860

10861

10862

10863

10864

10865

10866

10867

10868

10869

10870

10871

10872

10873

10874

10875

10876

10877

10878

10879

10880

10881

10882

10883

10884

10885

10886

10887

10888

10889

10890

10891

10892

10893

10894

10895

10896

10897

10898

10899

10900

10901

10902

10903

10904

10905

10906

10907

10908

10909

10910

10911

10912

10913

10914

10915

10916

10917

10918

10919

10920

10921

10922

10923

10924

10925

10926

10927

10928

10929

10930

10931

10932

10933

10934

10935

10936

10937

10938

10939

10940

10941

10942

10943

10944

10945

10946

10947

10948

10949

10950

10951

10952

10953

10954

10955

10956

10957

10958

10959

10960

10961

10962

10963

10964

10965

10966

10967

10968

10969

10970

10971

10972

10973

10974

10975

10976

10977

10978

10979

10980

10981

10982

10983

10984

10985

10986

10987

10988

10989

10990

10991

10992

10993

10994

10995

10996

10997

10998

10999

11000

11001

11002

11003

11004

11005

11006

11007

11008

11009

11010

11011

11012

11013

11014

11015

11016

11017

11018

11019

11020

11021

11022

11023

11024

11025

11026

11027

11028

11029

11030

11031

11032

11033

11034

11035

11036

11037

11038

11039

11040

11041

11042

11043

11044

11045

11046

11047

11048

11049

11050

11051

11052

11053

11054

11055

11056

11057

11058

11059

11060

11061

11062

11063

11064

11065

11066

11067

11068

11069

11070

11071

11072

11073

11074

11075

11076

11077

11078

11079

11080

11081

11082

11083

11084

11085

11086

11087

11088

11089

11090

11091

11092

11093

11094

11095

11096

11097

11098

11099

11100

11101

11102

11103

11104

11105

11106

11107

11108

11109

11110

11111

11112

11113

11114

11115

11116

11117

11118

11119

11120

11121

11122

11123

11124

11125

11126

11127

11128

11129

11130

11131

11132

11133

11134

11135

11136

11137

11138

11139

11140

11141

11142

11143

11144

11145

11146

11147

11148

11149

11150

11151

11152

11153

11154

11155

11156

11157

11158

11159

11160

11161

11162

11163

11164

11165

11166

11167

11168

11169

11170

11171

11172

11173

11174

11175

11176

11177

11178

11179

11180

11181

11182

11183

11184

11185

11186

11187

11188

11189

11190

11191

11192

11193

11194

11195

11196

11197

11198

11199

11200

11201

11202

11203

11204

11205

11206

11207

11208

11209

11210

11211

11212

11213

11214

11215

11216

11217

11218

11219

11220

11221

11222

11223

11224

11225

11226

11227

11228

11229

11230

11231

11232

11233

11234

11235

11236

11237

11238

11239

11240

11241

11242

11243

11244

11245

11246

11247

11248

11249

11250

11251

11252

11253

11254

11255

11256

11257

11258

11259

11260

11261

11262

11263

11264

11265

11266

11267

11268

11269

11270

11271

11272

11273

11274

11275

11276

11277

11278

11279

11280

11281

11282

11283

11284

11285

11286

11287

11288

11289

11290

11291

11292

11293

11294

11295

11296

11297

11298

11299

11300

11301

11302

11303

11304

11305

11306

11307

11308

11309

11310

11311

11312

11313

11314

11315

11316

11317

11318

11319

11320

11321

11322

11323

11324

11325

11326

11327

11328

11329

11330

11331

11332

11333

11334

11335

11336

11337

11338

11339

11340

11341

11342

11343

11344

11345

11346

11347

11348

11349

11350

11351

11352

11353

11354

11355

11356

11357

11358

11359

11360

11361

11362

11363

11364

11365

11366

11367

11368

11369

11370

11371

11372

11373

11374

11375

11376

11377

11378

11379

11380

11381

11382

11383

11384

11385

11386

11387

11388

11389

11390

11391

11392

11393

11394

11395

11396

11397

11398

11399

11400

11401

11402

11403

11404

11405

11406

11407

11408

11409

11410

11411

11412

11413

11414

11415

11416

11417

11418

11419

11420

11421

11422

11423

11424

11425

11426

11427

11428

11429

11430

11431

11432

11433

11434

11435

11436

11437

11438

11439

11440

11441

11442

11443

11444

11445

11446

11447

11448

11449

11450

11451

11452

11453

11454

11455

11456

11457

11458

11459

11460

11461

11462

11463

11464

11465

11466

11467

11468

11469

11470

11471

11472

11473

11474

11475

11476

11477

11478

11479

11480

11481

11482

11483

11484

11485

11486

11487

11488

11489

11490

11491

11492

11493

11494

11495

11496

11497

11498

11499

11500

11501

11502

11503

11504

11505

11506

11507

11508

11509

11510

11511

11512

11513

11514

11515

11516

11517

11518

11519

11520

11521

11522

11523

11524

11525

11526

11527

11528

11529

11530

11531

11532

11533

11534

11535

11536

11537

11538

11539

11540

11541

11542

11543

11544

11545

11546

11547

11548

11549

11550

11551

11552

11553

11554

11555

11556

11557

11558

11559

11560

11561

11562

11563

11564

11565

11566

11567

11568

11569

11570

11571

11572

11573

11574

11575

11576

11577

11578

11579

11580

11581

11582

11583

11584

11585

11586

11587

11588

11589

11590

11591

11592

11593

11594

11595

11596

11597

11598

11599

11600

11601

11602

11603

11604

11605

11606

11607

11608

11609

11610

11611

11612

11613

11614

11615

11616

11617

11618

11619

11620

11621

11622

11623

11624

11625

11626

11627

11628

11629

11630

11631

11632

11633

11634

11635

11636

11637

11638

11639

11640

11641

11642

11643

11644

11645

11646

11647

11648

11649

11650

11651

11652

11653

11654

11655

11656

11657

11658

11659

11660

11661

11662

11663

11664

11665

11666

11667

11668

11669

11670

11671

11672

11673

11674

11675

11676

11677

11678

11679

11680

11681

11682

11683

11684

11685

11686

11687

11688

11689

11690

11691

11692

11693

11694

11695

11696

11697

11698

11699

11700

11701

11702

11703

11704

11705

11706

11707

11708

11709

11710

11711

11712

11713

11714

11715

11716

11717

11718

11719

11720

11721

11722

11723

11724

11725

11726

11727

11728

11729

11730

11731

11732

11733

11734

11735

11736

11737

11738

11739

11740

11741

11742

11743

11744

11745

11746

11747

11748

11749

11750

11751

11752

11753

11754

11755

11756

11757

11758

11759

11760

11761

11762

11763

11764

11765

11766

11767

11768

11769

11770

11771

11772

11773

11774

11775

11776

11777

11778

11779

11780

11781

11782

11783

11784

11785

11786

11787

11788

11789

11790

11791

11792

11793

11794

11795

11796

11797

11798

11799

11800

11801

11802

11803

11804

11805

11806

11807

11808

11809

11810

11811

11812

11813

11814

11815

11816

11817

11818

11819

11820

11821

11822

11823

11824

11825

11826

11827

11828

11829

11830

11831

11832

11833

11834

11835

11836

11837

11838

11839

11840

11841

11842

11843

11844

11845

11846

11847

11848

11849

11850

11851

11852

11853

11854

11855

11856

11857

11858

11859

11860

11861

11862

11863

11864

11865

11866

11867

11868

11869

11870

11871

11872

11873

11874

11875

11876

11877

11878

11879

11880

11881

11882

11883

11884

11885

11886

11887

11888

11889

11890

11891

11892

11893

11894

11895

11896

11897

11898

11899

11900

11901

11902

11903

11904

11905

11906

11907

11908

11909

11910

11911

11912

11913

11914

11915

11916

11917

11918

11919

11920

11921

11922

11923

11924

11925

11926

11927

11928

11929

11930

11931

11932

11933

11934

11935

11936

11937

11938

11939

11940

11941

11942

11943

11944

11945

11946

11947

11948

11949

11950

11951

11952

11953

11954

11955

11956

11957

11958

11959

11960

11961

11962

11963

11964

11965

11966

11967

11968

11969

11970

11971

11972

11973

11974

11975

11976

11977

11978

11979

11980

11981

11982

11983

11984

11985

11986

11987

11988

11989

11990

11991

11992

11993

11994

11995

11996

11997

11998

11999

12000

12001

12002

12003

12004

12005

12006

12007

12008

12009

12010

12011

12012

12013

12014

12015

12016

12017

12018

12019

12020

12021

12022

12023

12024

12025

12026

12027

12028

12029

12030

12031

12032

12033

12034

12035

12036

12037

12038

12039

12040

12041

12042

12043

12044

12045

12046

12047

12048

12049

12050

12051

12052

12053

12054

12055

12056

12057

12058

12059

12060

12061

12062

12063

12064

12065

12066

12067

12068

12069

12070

12071

12072

12073

12074

12075

12076

12077

12078

12079

12080

12081

12082

12083

12084

12085

12086

12087

12088

12089

12090

12091

12092

12093

12094

12095

12096

12097

12098

12099

12100

12101

12102

12103

12104

12105

12106

12107

12108

12109

12110

12111

12112

12113

12114

12115

12116

12117

12118

12119

12120

12121

12122

12123

12124

12125

12126

12127

12128

12129

12130

12131

12132

12133

12134

12135

12136

12137

12138

12139

12140

12141

12142

12143

12144

12145

12146

12147

12148

12149

12150

12151

12152

12153

12154

12155

12156

12157

12158

12159

12160

12161

12162

12163

12164

12165

12166

12167

12168

12169

12170

12171

12172

12173

12174

12175

12176

12177

12178

12179

12180

12181

12182

12183

12184

12185

12186

12187

12188

12189

12190

12191

12192

12193

12194

12195

12196

12197

12198

12199

12200

12201

12202

12203

12204

12205

12206

12207

12208

12209

12210

12211

12212

12213

12214

12215

12216

12217

12218

12219

12220

12221

12222

12223

12224

12225

12226

12227

12228

12229

12230

12231

12232

12233

12234

12235

12236

12237

12238

12239

12240

12241

12242

12243

12244

12245

12246

12247

12248

12249

12250

12251

12252

12253

12254

12255

12256

12257

12258

12259

12260

12261

12262

12263

12264

12265

12266

12267

12268

12269

12270

12271

12272

12273

12274

12275

12276

12277

12278

12279

12280

12281

12282

12283

12284

12285

12286

12287

12288

12289

12290

12291

12292

12293

12294

12295

12296

12297

12298

12299

12300

12301

12302

12303

12304

12305

12306

12307

12308

12309

12310

12311

12312

12313

12314

12315

12316

12317

12318

12319

12320

12321

12322

12323

12324

12325

12326

12327

12328

12329

12330

12331

12332

12333

12334

12335

12336

12337

12338

12339

12340

12341

12342

12343

12344

12345

12346

12347

12348

12349

12350

12351

12352

12353

12354

12355

12356

12357

12358

12359

12360

12361

12362

12363

12364

12365

12366

12367

12368

12369

12370

12371

12372

12373

12374

12375

12376

12377

12378

12379

12380

12381

12382

12383

12384

12385

12386

12387

12388

12389

12390

12391

12392

12393

12394

12395

12396

12397

12398

12399

12400

12401

12402

12403

12404

12405

12406

12407

12408

12409

12410

12411

12412

12413

12414

12415

12416

12417

12418

12419

12420

12421

12422

12423

12424

12425

12426

12427

12428

12429

12430

12431

12432

12433

12434

12435

12436

12437

12438

12439

12440

12441

12442

12443

12444

12445

12446

12447

12448

12449

12450

12451

12452

12453

12454

12455

12456

12457

12458

12459

12460

12461

12462

12463

12464

12465

12466

12467

12468

12469

12470

12471

12472

12473

12474

12475

12476

12477

12478

12479

12480

12481

12482

12483

12484

12485

12486

12487

12488

12489

12490

12491

12492

12493

12494

12495

12496

12497

12498

12499

12500

12501

12502

12503

12504

12505

12506

12507

12508

12509

12510

12511

12512

12513

12514

12515

12516

12517

12518

12519

12520

12521

12522

12523

12524

12525

12526

12527

12528

12529

12530

12531

12532

12533

12534

12535

12536

12537

12538

12539

12540

12541

12542

12543

12544

12545

12546

12547

12548

12549

12550

12551

12552

12553

12554

12555

12556

12557

12558

12559

12560

12561

12562

12563

12564

12565

12566

12567

12568

12569

12570

12571

12572

12573

12574

12575

12576

12577

12578

12579

12580

12581

12582

12583

12584

12585

12586

12587

12588

12589

12590

12591

12592

12593

12594

12595

12596

12597

12598

12599

12600

12601

12602

12603

12604

12605

12606

12607

12608

12609

12610

12611

12612

12613

12614

12615

12616

12617

12618

12619

12620

12621

12622

12623

12624

12625

12626

12627

12628

12629

12630

12631

12632

12633

12634

12635

12636

12637

12638

12639

12640

12641

12642

12643

12644

12645

12646

12647

12648

12649

12650

12651

12652

12653

12654

12655

12656

12657

12658

12659

12660

12661

12662

12663

12664

12665

12666

12667

12668

12669

12670

12671

12672

12673

12674

12675

12676

12677

12678

12679

12680

12681

12682

12683

12684

12685

12686

12687

12688

12689

12690

12691

12692

12693

12694

12695

12696

12697

12698

12699

12700

12701

12702

12703

12704

12705

12706

12707

12708

12709

12710

12711

12712

12713

12714

12715

12716

12717

12718

12719

12720

12721

12722

12723

12724

12725

12726

12727

12728

12729

12730

12731

12732

12733

12734

12735

12736

12737

12738

12739

12740

12741

12742

12743

12744

12745

12746

12747

12748

12749

12750

12751

12752

12753

12754

12755

12756

12757

12758

12759

12760

12761

12762

12763

12764

12765

12766

12767

12768

12769

12770

12771

12772

12773

12774

12775

12776

12777

12778

12779

12780

12781

12782

12783

12784

12785

12786

12787

12788

12789

12790

12791

12792

12793

12794

12795

12796

12797

12798

12799

12800

12801

12802

12803

12804

12805

12806

12807

12808

12809

12810

12811

12812

12813

12814

12815

12816

12817

12818

12819

12820

12821

12822

12823

12824

12825

12826

12827

12828

12829

12830

12831

12832

12833

12834

12835

12836

12837

12838

12839

12840

12841

12842

12843

12844

12845

12846

12847

12848

12849

12850

12851

12852

12853

12854

12855

12856

12857

12858

12859

12860

12861

12862

12863

12864

12865

12866

12867

12868

12869

12870

12871

12872

12873

12874

12875

12876

12877

12878

12879

12880

12881

12882

12883

12884

12885

12886

12887

12888

12889

12890

12891

12892

12893

12894

12895

12896

12897

12898

12899

12900

12901

12902

12903

12904

12905

12906

12907

12908

12909

12910

12911

12912

12913

12914

12915

12916

12917

12918

12919

12920

12921

12922

12923

12924

12925

12926

12927

12928

12929

12930

12931

12932

12933

12934

12935

12936

12937

12938

12939

12940

12941

12942

12943

12944

12945

12946

12947

12948

12949

12950

12951

12952

12953

12954

12955

12956

12957

12958

12959

12960

12961

12962

12963

12964

12965

12966

12967

12968

12969

12970

12971

12972

12973

12974

12975

12976

12977

12978

12979

12980

12981

12982

12983

12984

12985

12986

12987

12988

12989

12990

12991

12992

12993

12994

12995

12996

12997

12998

12999

13000

13001

13002

13003

13004

13005

13006

13007

13008

13009

13010

13011

13012

13013

13014

13015

13016

13017

13018

13019

13020

13021

13022

13023

13024

13025

13026

13027

13028

13029

13030

13031

13032

13033

13034

13035

13036

13037

13038

13039

13040

13041

13042

13043

13044

13045

13046

13047

13048

13049

13050

13051

13052

13053

13054

13055

13056

13057

13058

13059

13060

13061

13062

13063

13064

13065

13066

13067

13068

13069

13070

13071

13072

13073

13074

13075

13076

13077

13078

13079

13080

13081

13082

13083

13084

13085

13086

13087

13088

13089

13090

13091

13092

13093

13094

13095

13096

13097

13098

13099

13100

13101

13102

13103

13104

13105

13106

13107

13108

13109

13110

13111

13112

13113

13114

13115

13116

13117

13118

13119

13120

13121

13122

13123

13124

13125

13126

13127

13128

13129

13130

13131

13132

13133

13134

13135

13136

13137

13138

13139

13140

13141

13142

13143

13144

13145

13146

13147

13148

13149

13150

13151

13152

13153

13154

13155

13156

13157

13158

13159

13160

13161

13162

13163

13164

13165

13166

13167

13168

13169

13170

13171

13172

13173

13174

13175

13176

13177

13178

13179

13180

13181

13182

13183

13184

13185

13186

13187

13188

13189

13190

13191

13192

13193

13194

13195

13196

13197

13198

13199

13200

13201

13202

13203

13204

13205

13206

13207

13208

13209

13210

13211

13212

13213

13214

13215

13216

13217

13218

13219

13220

13221

13222

13223

13224

13225

13226

13227

13228

13229

13230

13231

13232

13233

13234

13235

13236

13237

13238

13239

13240

13241

13242

13243

13244

13245

13246

13247

13248

13249

13250

13251

13252

13253

13254

13255

13256

13257

13258

13259

13260

13261

13262

13263

13264

13265

13266

13267

13268

13269

13270

13271

13272

13273

13274

13275

13276

13277

13278

13279

13280

13281

13282

13283

13284

13285

13286

13287

13288

13289

13290

13291

13292

13293

13294

13295

13296

13297

13298

13299

13300

13301

13302

13303

13304

13305

13306

13307

13308

13309

13310

13311

13312

13313

13314

13315

13316

13317

13318

13319

13320

13321

13322

13323

13324

13325

13326

13327

13328

13329

13330

13331

13332

13333

13334

13335

13336

13337

13338

13339

13340

13341

13342

13343

13344

13345

13346

13347

13348

13349

13350

13351

13352

13353

13354

13355

13356

13357

13358

13359

13360

13361

13362

13363

13364

13365

13366

13367

13368

13369

13370

13371

13372

13373

13374

13375

13376

13377

13378

13379

13380

13381

13382

13383

13384

13385

13386

13387

13388

13389

13390

13391

13392

13393

13394

13395

13396

13397

13398

13399

13400

13401

13402

13403

13404

13405

13406

13407

13408

13409

13410

13411

13412

13413

13414

13415

13416

13417

13418

13419

13420

13421

13422

13423

13424

13425

13426

13427

13428

13429

13430

13431

13432

13433

13434

13435

13436

13437

13438

13439

13440

13441

13442

13443

13444

13445

13446

13447

13448

13449

13450

13451

13452

13453

13454

13455

13456

13457

13458

13459

13460

13461

13462

13463

13464

13465

13466

13467

13468

13469

13470

13471

13472

13473

13474

13475

13476

13477

13478

13479

13480

13481

13482

13483

13484

13485

13486

13487

13488

13489

13490

13491

13492

13493

13494

13495

13496

13497

13498

13499

13500

13501

13502

13503

13504

13505

13506

13507

13508

13509

13510

13511

13512

13513

13514

13515

13516

13517

13518

13519

13520

13521

13522

13523

13524

13525

13526

13527

13528

13529

13530

13531

13532

13533

13534

13535

13536

13537

13538

13539

13540

13541

13542

13543

13544

13545

13546

13547

13548

13549

13550

13551

13552

13553

13554

13555

13556

13557

13558

13559

13560

13561

13562

13563

13564

13565

13566

13567

13568

13569

13570

13571

13572

13573

13574

13575

13576

13577

13578

13579

13580

13581

13582

13583

13584

13585

13586

13587

13588

13589

13590

13591

13592

13593

13594

13595

13596

13597

13598

13599

13600

13601

13602

13603

13604

13605

13606

13607

13608

13609

13610

13611

13612

13613

13614

13615

13616

13617

13618

13619

13620

13621

13622

13623

13624

13625

13626

13627

13628

13629

13630

13631

13632

13633

13634

13635

13636

13637

13638

13639

13640

13641

13642

13643

13644

13645

13646

13647

13648

13649

13650

13651

13652

13653

13654

13655

13656

13657

13658

13659

13660

13661

13662

13663

13664

13665

13666

13667

13668

13669

13670

13671

13672

13673

13674

13675

13676

13677

13678

13679

13680

13681

13682

13683

13684

13685

13686

13687

13688

13689

13690

13691

13692

13693

13694

13695

13696

13697

13698

13699

13700

13701

13702

13703

13704

13705

13706

13707

13708

13709

13710

13711

13712

13713

13714

13715

13716

13717

13718

13719

13720

13721

13722

13723

13724

13725

13726

13727

13728

13729

13730

13731

13732

13733

13734

13735

13736

13737

13738

13739

13740

13741

13742

13743

13744

13745

13746

13747

13748

13749

13750

13751

13752

13753

13754

13755

13756

13757

13758

13759

13760

13761

13762

13763

13764

13765

13766

13767

13768

13769

13770

13771

13772

13773

13774

13775

13776

13777

13778

13779

13780

13781

13782

13783

13784

13785

13786

13787

13788

13789

13790

13791

13792

13793

13794

13795

13796

13797

13798

13799

13800

13801

13802

13803

13804

13805

13806

13807

13808

13809

13810

13811

13812

13813

13814

13815

13816

13817

13818

13819

13820

13821

13822

13823

13824

13825

13826

13827

13828

13829

13830

13831

13832

13833

13834

13835

13836

13837

13838

13839

13840

13841

13842

13843

13844

13845

13846

13847

13848

13849

13850

13851

13852

13853

13854

13855

13856

13857

13858

13859

13860

13861

13862

13863

13864

13865

13866

13867

13868

13869

13870

13871

13872

13873

13874

13875

13876

13877

13878

13879

13880

13881

13882

13883

13884

13885

13886

13887

13888

13889

13890

13891

13892

13893

13894

13895

13896

13897

13898

13899

13900

13901

13902

13903

13904

13905

13906

13907

13908

13909

13910

13911

13912

13913

13914

13915

13916

13917

13918

13919

13920

13921

13922

13923

13924

13925

13926

13927

13928

13929

13930

13931

13932

13933

13934

13935

13936

13937

13938

13939

13940

13941

13942

13943

13944

13945

13946

13947

13948

13949

13950

13951

13952

13953

13954

13955

13956

13957

13958

13959

13960

13961

13962

13963

13964

13965

13966

13967

13968

13969

13970

13971

13972

13973

13974

13975

13976

13977

13978

13979

13980

13981

13982

13983

13984

13985

13986

13987

13988

13989

13990

13991

13992

13993

13994

13995

13996

13997

13998

13999

14000

14001

14002

14003

14004

14005

14006

14007

14008

14009

14010

14011

14012

14013

14014

14015

14016

14017

14018

14019

14020

14021

14022

14023

14024

14025

14026

14027

14028

14029

14030

14031

14032

14033

14034

14035

14036

14037

14038

14039

14040

14041

14042

14043

14044

14045

14046

14047

14048

14049

14050

14051

14052

14053

14054

14055

14056

14057

14058

14059

14060

14061

14062

14063

14064

14065

14066

14067

14068

14069

14070

14071

14072

14073

14074

14075

14076

14077

14078

14079

14080

14081

14082

14083

14084

14085

14086

14087

14088

14089

14090

14091

14092

14093

14094

14095

14096

14097

14098

14099

14100

14101

14102

14103

14104

14105

14106

14107

14108

14109

14110

14111

14112

14113

14114

14115

14116

14117

14118

14119

14120

14121

14122

14123

14124

14125

14126

14127

14128

14129

14130

14131

14132

14133

14134

14135

14136

14137

14138

14139

14140

14141

14142

14143

14144

14145

14146

14147

14148

14149

14150

14151

14152

14153

14154

14155

14156

14157

14158

14159

14160

14161

14162

14163

14164

14165

14166

14167

14168

14169

14170

14171

14172

14173

14174

14175

14176

14177

14178

14179

14180

14181

14182

14183

14184

14185

14186

14187

14188

14189

14190

14191

14192

14193

14194

14195

14196

14197

14198

14199

14200

14201

14202

14203

14204

14205

14206

14207

14208

14209

14210

14211

14212

14213

14214

14215

14216

14217

14218

14219

14220

14221

14222

14223

14224

14225

14226

14227

14228

14229

14230

14231

14232

14233

14234

14235

14236

14237

14238

14239

14240

14241

14242

14243

14244

14245

14246

14247

14248

14249

14250

14251

14252

14253

14254

14255

14256

14257

14258

14259

14260

14261

14262

14263

14264

14265

14266

14267

14268

14269

14270

14271

14272

14273

14274

14275

14276

14277

14278

14279

14280

14281

14282

14283

14284

14285

14286

14287

14288

14289

14290

14291

14292

14293

14294

14295

14296

14297

14298

14299

14300

14301

14302

14303

14304

14305

14306

14307

14308

14309

14310

14311

14312

14313

14314

14315

14316

14317

14318

14319

14320

14321

14322

14323

14324

14325

14326

14327

14328

14329

14330

14331

14332

14333

14334

14335

14336

14337

14338

14339

14340

14341

14342

14343

14344

14345

14346

14347

14348

14349

14350

14351

14352

14353

14354

14355

14356

14357

14358

14359

14360

14361

14362

14363

14364

14365

14366

14367

14368

14369

14370

14371

14372

14373

14374

14375

14376

14377

14378

14379

14380

14381

14382

14383

14384

14385

14386

14387

14388

14389

14390

14391

14392

14393

14394

14395

14396

14397

14398

14399

14400

14401

14402

14403

14404

14405

14406

14407

14408

14409

14410

14411

14412

14413

14414

14415

14416

14417

14418

14419

14420

14421

14422

14423

14424

14425

14426

14427

14428

14429

14430

14431

14432

14433

14434

14435

14436

14437

14438

14439

14440

14441

14442

14443

14444

14445

14446

14447

14448

14449

14450

14451

14452

14453

14454

14455

14456

14457

14458

14459

14460

14461

14462

14463

14464

14465

14466

14467

14468

14469

14470

14471

14472

14473

14474

14475

14476

14477

14478

14479

14480

14481

14482

14483

14484

14485

14486

14487

14488

14489

14490

14491

14492

14493

14494

14495

14496

14497

14498

14499

14500

14501

14502

14503

14504

14505

14506

14507

14508

14509

14510

14511

14512

14513

14514

14515

14516

14517

14518

14519

14520

14521

14522

14523

14524

14525

14526

14527

14528

14529

14530

14531

14532

14533

14534

14535

14536

14537

14538

14539

14540

14541

14542

14543

14544

14545

14546

14547

14548

14549

14550

14551

14552

14553

14554

14555

14556

14557

14558

14559

14560

14561

14562

14563

14564

14565

14566

14567

14568

14569

14570

14571

14572

14573

14574

14575

14576

14577

14578

14579

14580

14581

14582

14583

14584

14585

14586

14587

14588

14589

14590

14591

14592

14593

14594

14595

14596

14597

14598

14599

14600

14601

14602

14603

14604

14605

14606

14607

14608

14609

14610

14611

14612

14613

14614

14615

14616

14617

14618

14619

14620

14621

14622

14623

14624

14625

14626

14627

14628

14629

14630

14631

14632

14633

14634

14635

14636

14637

14638

14639

14640

14641

14642

14643

14644

14645

14646

14647

14648

14649

14650

14651

14652

14653

14654

14655

14656

14657

14658

14659

14660

14661

14662

14663

14664

14665

14666

14667

14668

14669

14670

14671

14672

14673

14674

14675

14676

14677

14678

14679

14680

14681

14682

14683

14684

14685

14686

14687

14688

14689

14690

14691

14692

14693

14694

14695

14696

14697

14698

14699

14700

14701

14702

14703

14704

14705

14706

14707

14708

14709

14710

14711

14712

14713

14714

14715

14716

14717

14718

14719

14720

14721

14722

14723

14724

14725

14726

14727

14728

14729

14730

14731

14732

14733

14734

14735

14736

14737

14738

14739

14740

14741

14742

14743

14744

14745

14746

14747

14748

14749

14750

14751

14752

14753

14754

14755

14756

14757

14758

14759

14760

14761

14762

14763

14764

14765

14766

14767

14768

14769

14770

14771

14772

14773

14774

14775

14776

14777

14778

14779

14780

14781

14782

14783

14784

14785

14786

14787

14788

14789

14790

14791

14792

14793

14794

14795

14796

14797

14798

14799

14800

14801

14802

14803

14804

14805

14806

14807

14808

14809

14810

14811

14812

14813

14814

14815

14816

14817

14818

14819

14820

14821

14822

14823

14824

14825

14826

14827

14828

14829

14830

14831

14832

14833

14834

14835

14836

14837

14838

14839

14840

14841

14842

14843

14844

14845

14846

14847

14848

14849

14850

14851

14852

14853

14854

14855

14856

14857

14858

14859

14860

14861

14862

14863

14864

14865

14866

14867

14868

14869

14870

14871

14872

14873

14874

14875

14876

14877

14878

14879

14880

14881

14882

14883

14884

14885

14886

14887

14888

14889

14890

14891

14892

14893

14894

14895

14896

14897

14898

14899

14900

14901

14902

14903

14904

14905

14906

14907

14908

14909

14910

14911

14912

14913

14914

14915

14916

14917

14918

14919

14920

14921

14922

14923

14924

14925

14926

14927

14928

14929

14930

14931

14932

14933

14934

14935

14936

14937

14938

14939

14940

14941

14942

14943

14944

14945

14946

14947

14948

14949

14950

14951

14952

14953

14954

14955

14956

14957

14958

14959

14960

14961

14962

14963

14964

14965

14966

14967

14968

14969

14970

14971

14972

14973

14974

14975

14976

14977

14978

14979

14980

14981

14982

14983

14984

14985

14986

14987

14988

14989

14990

14991

14992

14993

14994

14995

14996

14997

14998

14999

15000

15001

15002

15003

15004

15005

15006

15007

15008

15009

15010

15011

15012

15013

15014

15015

15016

15017

15018

15019

15020

15021

15022

15023

15024

15025

15026

15027

15028

15029

15030

15031

15032

15033

15034

15035

15036

15037

15038

15039

15040

15041

15042

15043

15044

15045

15046

15047

15048

15049

15050

15051

15052

15053

15054

15055

15056

15057

15058

15059

15060

15061

15062

15063

15064

15065

15066

15067

15068

15069

15070

15071

15072

15073

15074

15075

15076

15077

15078

15079

15080

15081

15082

15083

15084

15085

15086

15087

15088

15089

15090

15091

15092

15093

15094

15095

15096

15097

15098

15099

15100

15101

15102

15103

15104

15105

15106

15107

15108

15109

15110

15111

15112

15113

15114

15115

15116

15117

15118

15119

15120

15121

15122

15123

15124

15125

15126

15127

15128

15129

15130

15131

15132

15133

15134

15135

15136

15137

15138

15139

15140

15141

15142

15143

15144

15145

15146

15147

15148

15149

15150

15151

15152

15153

15154

15155

15156

15157

15158

15159

15160

15161

15162

15163

15164

15165

15166

15167

15168

15169

15170

15171

15172

15173

15174

15175

15176

15177

15178

15179

15180

15181

15182

15183

15184

15185

15186

15187

15188

15189

15190

15191

15192

15193

15194

15195

15196

15197

15198

15199

15200

15201

15202

15203

15204

15205

15206

15207

15208

15209

15210

15211

15212

15213

15214

15215

15216

15217

15218

15219

15220

15221

15222

15223

15224

15225

15226

15227

15228

15229

15230

15231

15232

15233

15234

15235

15236

15237

15238

15239

15240

15241

15242

15243

15244

15245

15246

15247

15248

15249

15250

15251

15252

15253

15254

15255

15256

15257

15258

15259

15260

15261

15262

15263

15264

15265

15266

15267

15268

15269

15270

15271

15272

15273

15274

15275

15276

15277

15278

15279

15280

15281

15282

15283

15284

15285

15286

15287

15288

15289

15290

15291

15292

15293

15294

15295

15296

15297

15298

15299

15300

15301

15302

15303

15304

15305

15306

15307

15308

15309

15310

15311

15312

15313

15314

15315

15316

15317

15318

15319

15320

15321

15322

15323

15324

15325

15326

15327

15328

15329

15330

15331

15332

15333

15334

15335

15336

15337

15338

15339

15340

15341

15342

15343

15344

15345

15346

15347

15348

15349

15350

15351

15352

15353

15354

15355

15356

15357

15358

15359

15360

15361

15362

15363

15364

15365

15366

15367

15368

15369

15370

15371

15372

15373

15374

15375

15376

15377

15378

15379

15380

15381

15382

15383

15384

15385

15386

15387

15388

15389

15390

15391

15392

15393

15394

15395

15396

15397

15398

15399

15400

15401

15402

15403

15404

15405

15406

15407

15408

15409

15410

15411

15412

15413

15414

15415

15416

15417

15418

15419

15420

15421

15422

15423

15424

15425

15426

15427

15428

15429

15430

15431

15432

15433

15434

15435

15436

15437

15438

15439

15440

15441

15442

15443

15444

15445

15446

15447

15448

15449

15450

15451

15452

15453

15454

15455

15456

15457

15458

15459

15460

15461

15462

15463

15464

15465

15466

15467

15468

15469

15470

15471

15472

15473

15474

15475

15476

15477

15478

15479

15480

15481

15482

15483

15484

15485

15486

15487

15488

15489

15490

15491

15492

15493

15494

15495

15496

15497

15498

15499

15500

15501

15502

15503

15504

15505

15506

15507

15508

15509

15510

15511

15512

15513

15514

15515

15516

15517

15518

15519

15520

15521

15522

15523

15524

15525

15526

15527

15528

15529

15530

15531

15532

15533

15534

15535

15536

15537

15538

15539

15540

15541

15542

15543

15544

15545

15546

15547

15548

15549

15550

15551

15552

15553

15554

15555

15556

15557

15558

15559

15560

15561

15562

15563

15564

15565

15566

15567

15568

15569

15570

15571

15572

15573

15574

15575

15576

15577

15578

15579

15580

15581

15582

15583

15584

15585

15586

15587

15588

15589

15590

15591

15592

15593

15594

15595

15596

15597

15598

15599

15600

15601

15602

15603

15604

15605

15606

15607

15608

15609

15610

15611

15612

15613

15614

15615

15616

15617

15618

15619

15620

15621

15622

15623

15624

15625

15626

15627

15628

15629

15630

15631

15632

15633

15634

15635

15636

15637

15638

15639

15640

15641

15642

15643

15644

15645

15646

15647

15648

15649

15650

15651

15652

15653

15654

15655

15656

15657

15658

15659

15660

15661

15662

15663

15664

15665

15666

15667

15668

15669

15670

15671

15672

15673

15674

15675

15676

15677

15678

15679

15680

15681

15682

15683

15684

15685

15686

15687

15688

15689

15690

15691

15692

15693

15694

15695

15696

15697

15698

15699

15700

15701

15702

15703

15704

15705

15706

15707

15708

15709

15710

15711

15712

15713

15714

15715

15716

15717

15718

15719

15720

15721

15722

15723

15724

15725

15726

15727

15728

15729

15730

15731

15732

15733

15734

15735

15736

15737

15738

15739

15740

15741

15742

15743

15744

15745

15746

15747

15748

15749

15750

15751

15752

15753

15754

15755

15756

15757

15758

15759

15760

15761

15762

15763

15764

15765

15766

15767

15768

15769

15770

15771

15772

15773

15774

15775

15776

15777

15778

15779

15780

15781

15782

15783

15784

15785

15786

15787

15788

15789

15790

15791

15792

15793

15794

15795

15796

15797

15798

15799

15800

15801

15802

15803

15804

15805

15806

15807

15808

15809

15810

15811

15812

15813

15814

15815

15816

15817

15818

15819

15820

15821

15822

15823

15824

15825

15826

15827

15828

15829

15830

15831

15832

15833

15834

15835

15836

15837

15838

15839

15840

15841

15842

15843

15844

15845

15846

15847

15848

15849

15850

15851

15852

15853

15854

15855

15856

15857

15858

15859

15860

15861

15862

15863

15864

15865

15866

15867

15868

15869

15870

15871

15872

15873

15874

15875

15876

15877

15878

15879

15880

15881

15882

15883

15884

15885

15886

15887

15888

15889

15890

15891

15892

15893

15894

15895

15896

15897

15898

15899

15900

15901

15902

15903

15904

15905

15906

15907

15908

15909

15910

15911

15912

15913

15914

15915

15916

15917

15918

15919

15920

15921

15922

15923

15924

15925

15926

15927

15928

15929

15930

15931

15932

15933

15934

15935

15936

15937

15938

15939

15940

15941

15942

15943

15944

15945

15946

15947

15948

15949

15950

15951

15952

15953

15954

15955

15956

15957

15958

15959

15960

15961

15962

15963

15964

15965

15966

15967

15968

15969

15970

15971

15972

15973

15974

15975

15976

15977

15978

15979

15980

15981

15982

15983

15984

15985

15986

15987

15988

15989

15990

15991

15992

15993

15994

15995

15996

15997

15998

15999

16000

16001

16002

16003

16004

16005

16006

16007

16008

16009

16010

16011

16012

16013

16014

16015

16016

16017

16018

16019

16020

16021

16022

16023

16024

16025

16026

16027

16028

16029

16030

16031

16032

16033

16034

16035

16036

16037

16038

16039

16040

16041

16042

16043

16044

16045

16046

16047

16048

16049

16050

16051

16052

16053

16054

16055

16056

16057

16058

16059

16060

16061

16062

16063

16064

16065

16066

16067

16068

16069

16070

16071

16072

16073

16074

16075

16076

16077

16078

16079

16080

16081

16082

16083

16084

16085

16086

16087

16088

16089

16090

16091

16092

16093

16094

16095

16096

16097

16098

16099

16100

16101

16102

16103

16104

16105

16106

16107

16108

16109

16110

16111

16112

16113

16114

16115

16116

16117

16118

16119

16120

16121

16122

16123

16124

16125

16126

16127

16128

16129

16130

16131

16132

16133

16134

16135

16136

16137

16138

16139

16140

16141

16142

16143

16144

16145

16146

16147

16148

16149

16150

16151

16152

16153

16154

16155

16156

16157

16158

16159

16160

16161

16162

16163

16164

16165

16166

16167

16168

16169

16170

16171

16172

16173

16174

16175

16176

16177

16178

16179

16180

16181

16182

16183

16184

16185

16186

16187

16188

16189

16190

16191

16192

16193

16194

16195

16196

16197

16198

16199

16200

16201

16202

16203

16204

16205

16206

16207

16208

16209

16210

16211

16212

16213

16214

16215

16216

16217

16218

16219

16220

16221

16222

16223

16224

16225

16226

16227

16228

16229

16230

16231

16232

16233

16234

16235

16236

16237

16238

16239

16240

16241

16242

16243

16244

16245

16246

16247

16248

16249

16250

16251

16252

16253

16254

16255

16256

16257

16258

16259

16260

16261

16262

16263

16264

16265

16266

16267

16268

16269

16270

16271

16272

16273

16274

16275

16276

16277

16278

16279

16280

16281

16282

16283

16284

16285

16286

16287

16288

16289

16290

16291

16292

16293

16294

16295

16296

16297

16298

16299

16300

16301

16302

16303

16304

16305

16306

16307

16308

16309

16310

16311

16312

16313

16314

16315

16316

16317

16318

16319

16320

16321

16322

16323

16324

16325

16326

16327

16328

16329

16330

16331

16332

16333

16334

16335

16336

16337

16338

16339

16340

16341

16342

16343

16344

16345

16346

16347

16348

16349

16350

16351

16352

16353

16354

16355

16356

16357

16358

16359

16360

16361

16362

16363

16364

16365

16366

16367

16368

16369

16370

16371

16372

16373

16374

16375

16376

16377

16378

16379

16380

16381

16382

16383

16384

16385

16386

16387

16388

16389

16390

16391

16392

16393

16394

16395

16396

16397

16398

16399

16400

16401

16402

16403

16404

16405

16406

16407

16408

16409

16410

16411

16412

16413

16414

16415

16416

16417

16418

16419

16420

16421

16422

16423

16424

16425

16426

16427

16428

16429

16430

16431

16432

16433

16434

16435

16436

16437

16438

16439

16440

16441

16442

16443

16444

16445

16446

16447

16448

16449

16450

16451

16452

16453

16454

16455

16456

16457

16458

16459

16460

16461

16462

16463

16464

16465

16466

16467

16468

16469

16470

16471

16472

16473

16474

16475

16476

16477

16478

16479

16480

16481

16482

16483

16484

16485

16486

16487

16488

16489

16490

16491

16492

16493

16494

16495

16496

16497

16498

16499

16500

16501

16502

16503

16504

16505

16506

16507

16508

16509

16510

16511

16512

16513

16514

16515

16516

16517

16518

16519

16520

16521

16522

16523

16524

16525

16526

16527

16528

16529

16530

16531

16532

16533

16534

16535

16536

16537

16538

16539

16540

16541

16542

16543

16544

16545

16546

16547

16548

16549

16550

16551

16552

16553

16554

16555

16556

16557

16558

16559

16560

16561

16562

16563

16564

16565

16566

16567

16568

16569

16570

16571

16572

16573

16574

16575

16576

16577

16578

16579

16580

16581

16582

16583

16584

16585

16586

16587

16588

16589

16590

16591

16592

16593

16594

16595

16596

16597

16598

16599

16600

16601

16602

16603

16604

16605

16606

16607

16608

16609

16610

16611

16612

16613

16614

16615

16616

16617

16618

16619

16620

16621

16622

16623

16624

16625

16626

16627

16628

16629

16630

16631

16632

16633

16634

16635

16636

16637

16638

16639

16640

16641

16642

16643

16644

16645

16646

16647

16648

16649

16650

16651

16652

16653

16654

16655

16656

16657

16658

16659

16660

16661

16662

16663

16664

16665

16666

16667

16668

16669

16670

16671

16672

16673

16674

16675

16676

16677

16678

16679

16680

16681

16682

16683

16684

16685

16686

16687

16688

16689

16690

16691

16692

16693

16694

16695

16696

16697

16698

16699

16700

16701

16702

16703

16704

16705

16706

16707

16708

16709

16710

16711

16712

16713

16714

16715

16716

16717

16718

16719

16720

16721

16722

16723

16724

16725

16726

16727

16728

16729

16730

16731

16732

16733

16734

16735

16736

16737

16738

16739

16740

16741

16742

16743

16744

16745

16746

16747

16748

16749

16750

16751

16752

16753

16754

16755

16756

16757

16758

16759

16760

16761

16762

16763

16764

16765

16766

16767

16768

16769

16770

16771

16772

16773

16774

16775

16776

16777

16778

16779

16780

16781

16782

16783

16784

16785

16786

16787

16788

16789

16790

16791

16792

16793

16794

16795

16796

16797

16798

16799

16800

16801

16802

16803

16804

16805

16806

16807

16808

16809

16810

16811

16812

16813

16814

16815

16816

16817

16818

16819

16820

16821

16822

16823

16824

16825

16826

16827

16828

16829

16830

16831

16832

16833

16834

16835

16836

16837

16838

16839

16840

16841

16842

16843

16844

16845

16846

16847

16848

16849

16850

16851

16852

16853

16854

16855

16856

16857

16858

16859

16860

16861

16862

16863

16864

16865

16866

16867

16868

16869

16870

16871

16872

16873

16874

16875

16876

16877

16878

16879

16880

16881

16882

16883

16884

16885

16886

16887

16888

16889

16890

16891

16892

16893

16894

16895

16896

16897

16898

16899

16900

16901

16902

16903

16904

16905

16906

16907

16908

16909

16910

16911

16912

16913

16914

16915

16916

16917

16918

16919

16920

16921

16922

16923

16924

16925

16926

16927

16928

16929

16930

16931

16932

16933

16934

16935

16936

16937

16938

16939

16940

16941

16942

16943

16944

16945

16946

16947

16948

16949

16950

16951

16952

16953

16954

16955

16956

16957

16958

16959

16960

16961

16962

16963

16964

16965

16966

16967

16968

16969

16970

16971

16972

16973

16974

16975

16976

16977

16978

16979

16980

16981

16982

16983

16984

16985

16986

16987

16988

16989

16990

16991

16992

16993

16994

16995

16996

16997

16998

16999

17000

17001

17002

17003

17004

17005

17006

17007

17008

17009

17010

17011

17012

17013

17014

17015

17016

17017

17018

17019

17020

17021

17022

17023

17024

17025

17026

17027

17028

17029

17030

17031

17032

17033

17034

17035

17036

17037

17038

17039

17040

17041

17042

17043

17044

17045

17046

17047

17048

17049

17050

17051

17052

17053

17054

17055

17056

17057

17058

17059

17060

17061

17062

17063

17064

17065

17066

17067

17068

17069

17070

17071

17072

17073

17074

17075

17076

17077

17078

17079

17080

17081

17082

17083

17084

17085

17086

17087

17088

17089

17090

17091

17092

17093

17094

17095

17096

17097

17098

17099

17100

17101

17102

17103

17104

17105

17106

17107

17108

17109

17110

17111

17112

17113

17114

17115

17116

17117

17118

17119

17120

17121

17122

17123

17124

17125

17126

17127

17128

17129

17130

17131

17132

17133

17134

17135

17136

17137

17138

17139

17140

17141

17142

17143

17144

17145

17146

17147

17148

17149

17150

17151

17152

17153

17154

17155

17156

17157

17158

17159

17160

17161

17162

17163

17164

17165

17166

17167

17168

17169

17170

17171

17172

17173

17174

17175

17176

17177

17178

17179

17180

17181

17182

17183

17184

17185

17186

17187

17188

17189

17190

17191

17192

17193

17194

17195

17196

17197

17198

17199

17200

17201

17202

17203

17204

17205

17206

17207

17208

17209

17210

17211

17212

17213

17214

17215

17216

17217

17218

17219

17220

17221

17222

17223

17224

17225

17226

17227

17228

17229

17230

17231

17232

17233

17234

17235

17236

17237

17238

17239

17240

17241

17242

17243

17244

17245

17246

17247

17248

17249

17250

17251

17252

17253

17254

17255

17256

17257

17258

17259

17260

17261

17262

17263

17264

17265

17266

17267

17268

17269

17270

17271

17272

17273

17274

17275

17276

17277

17278

17279

17280

17281

17282

17283

17284

17285

17286

17287

17288

17289

17290

17291

17292

17293

17294

17295

17296

17297

17298

17299

17300

17301

17302

17303

17304

17305

17306

17307

17308

17309

17310

17311

17312

17313

17314

17315

17316

17317

17318

17319

17320

17321

17322

17323

17324

17325

17326

17327

17328

17329

17330

17331

17332

17333

17334

17335

17336

17337

17338

17339

17340

17341

17342

17343

17344

17345

17346

17347

17348

17349

17350

17351

17352

17353

17354

17355

17356

17357

17358

17359

17360

17361

17362

17363

17364

17365

17366

17367

17368

17369

17370

17371

17372

17373

17374

17375

17376

17377

17378

17379

17380

17381

17382

17383

17384

17385

17386

17387

17388

17389

17390

17391

17392

17393

17394

17395

17396

17397

17398

17399

17400

17401

17402

17403

17404

17405

17406

17407

17408

17409

17410

17411

17412

17413

17414

17415

17416

17417

17418

17419

17420

17421

17422

17423

17424

17425

17426

17427

17428

17429

17430

17431

17432

17433

17434

17435

17436

17437

17438

17439

17440

17441

17442

17443

17444

17445

17446

17447

17448

17449

17450

17451

17452

17453

17454

17455

17456

17457

17458

17459

17460

17461

17462

17463

17464

17465

17466

17467

17468

17469

17470

17471

17472

17473

17474

17475

17476

17477

17478

17479

17480

17481

17482

17483

17484

17485

17486

17487

17488

17489

17490

17491

17492

17493

17494

17495

17496

17497

17498

17499

17500

17501

17502

17503

17504

17505

17506

17507

17508

17509

17510

17511

17512

17513

17514

17515

17516

17517

17518

17519

17520

17521

17522

17523

17524

17525

17526

17527

17528

17529

17530

17531

17532

17533

17534

17535

17536

17537

17538

17539

17540

17541

17542

17543

17544

17545

17546

17547

17548

17549

17550

17551

17552

17553

17554

17555

17556

17557

17558

17559

17560

17561

17562

17563

17564

17565

17566

17567

17568

17569

17570

17571

17572

17573

17574

17575

17576

17577

17578

17579

17580

17581

17582

17583

17584

17585

17586

17587

17588

17589

17590

17591

17592

17593

17594

17595

17596

17597

17598

17599

17600

17601

17602

17603

17604

17605

17606

17607

17608

17609

17610

17611

17612

17613

17614

17615

17616

17617

17618

17619

17620

17621

17622

17623

17624

17625

17626

17627

17628

17629

17630

17631

17632

17633

17634

17635

17636

17637

17638

17639

17640

17641

17642

17643

17644

17645

17646

17647

17648

17649

17650

17651

17652

17653

17654

17655

17656

17657

17658

17659

17660

17661

17662

17663

17664

17665

17666

17667

17668

17669

17670

17671

17672

17673

17674

17675

17676

17677

17678

17679

17680

17681

17682

17683

17684

17685

17686

17687

17688

17689

17690

17691

17692

17693

17694

17695

17696

17697

17698

17699

17700

17701

17702

17703

17704

17705

17706

17707

17708

17709

17710

17711

17712

17713

17714

17715

17716

17717

17718

17719

17720

17721

17722

17723

17724

17725

17726

17727

17728

17729

17730

17731

17732

17733

17734

17735

17736

17737

17738

17739

17740

17741

17742

17743

17744

17745

17746

17747

17748

17749

17750

17751

17752

17753

17754

17755

17756

17757

17758

17759

17760

17761

17762

17763

17764

17765

17766

17767

17768

17769

17770

17771

17772

17773

17774

17775

17776

17777

17778

17779

17780

17781

17782

17783

17784

17785

17786

17787

17788

17789

17790

17791

17792

17793

17794

17795

17796

17797

17798

17799

17800

17801

17802

17803

17804

17805

17806

17807

17808

17809

17810

17811

17812

17813

17814

17815

17816

17817

17818

17819

17820

17821

17822

17823

17824

17825

17826

17827

17828

17829

17830

17831

17832

17833

17834

17835

17836

17837

17838

17839

17840

17841

17842

17843

17844

17845

17846

17847

17848

17849

17850

17851

17852

17853

17854

17855

17856

17857

17858

17859

17860

17861

17862

17863

17864

17865

17866

17867

17868

17869

17870

17871

17872

17873

17874

17875

17876

17877

17878

17879

17880

17881

17882

17883

17884

17885

17886

17887

17888

17889

17890

17891

17892

17893

17894

17895

17896

17897

17898

17899

17900

17901

17902

17903

17904

17905

17906

17907

17908

17909

17910

17911

17912

17913

17914

17915

17916

17917

17918

17919

17920

17921

17922

17923

17924

17925

17926

17927

17928

17929

17930

17931

17932

17933

17934

17935

17936

17937

17938

17939

17940

17941

17942

17943

17944

17945

17946

17947

17948

17949

17950

17951

17952

17953

17954

17955

17956

17957

17958

17959

17960

17961

17962

17963

17964

17965

17966

17967

17968

17969

17970

17971

17972

17973

17974

17975

17976

17977

17978

17979

17980

17981

17982

17983

17984

17985

17986

17987

17988

17989

17990

17991

17992

17993

17994

17995

17996

17997

17998

17999

18000

18001

18002

18003

18004

18005

18006

18007

18008

18009

18010

18011

18012

18013

18014

18015

18016

18017

18018

18019

18020

18021

18022

18023

18024

18025

18026

18027

18028

18029

18030

18031

18032

18033

18034

18035

18036

18037

18038

18039

18040

18041

18042

18043

18044

18045

18046

18047

18048

18049

18050

18051

18052

18053

18054

18055

18056

18057

18058

18059

18060

18061

18062

18063

18064

18065

18066

18067

18068

18069

18070

18071

18072

18073

18074

18075

18076

18077

18078

18079

18080

18081

18082

18083

18084

18085

18086

18087

18088

18089

18090

18091

18092

18093

18094

18095

18096

18097

18098

18099

18100

18101

18102

18103

18104

18105

18106

18107

18108

18109

18110

18111

18112

18113

18114

18115

18116

18117

18118

18119

18120

18121

18122

18123

18124

18125

18126

18127

18128

18129

18130

18131

18132

18133

18134

18135

18136

18137

18138

18139

18140

18141

18142

18143

18144

18145

18146

18147

18148

18149

18150

18151

18152

18153

18154

18155

18156

18157

18158

18159

18160

18161

18162

18163

18164

18165

18166

18167

18168

18169

18170

18171

18172

18173

18174

18175

18176

18177

18178

18179

18180

18181

18182

18183

18184

18185

18186

18187

18188

18189

18190

18191

18192

18193

18194

18195

18196

18197

18198

18199

18200

18201

18202

18203

18204

18205

18206

18207

18208

18209

18210

18211

18212

18213

18214

18215

18216

18217

18218

18219

18220

18221

18222

18223

18224

18225

18226

18227

18228

18229

18230

18231

18232

18233

18234

18235

18236

18237

18238

18239

18240

18241

18242

18243

18244

18245

18246

18247

18248

18249

18250

18251

18252

18253

18254

18255

18256

18257

18258

18259

18260

18261

18262

18263

18264

18265

18266

18267

18268

18269

18270

18271

18272

18273

18274

18275

18276

18277

18278

18279

18280

18281

18282

18283

18284

18285

18286

18287

18288

18289

18290

18291

18292

18293

18294

18295

18296

18297

18298

18299

18300

18301

18302

18303

18304

18305

18306

18307

18308

18309

18310

18311

18312

18313

18314

18315

18316

18317

18318

18319

18320

18321

18322

18323

18324

18325

18326

18327

18328

18329

18330

18331

18332

18333

18334

18335

18336

18337

18338

18339

18340

18341

18342

18343

18344

18345

18346

18347

18348

18349

18350

18351

18352

18353

18354

18355

18356

18357

18358

18359

18360

18361

18362

18363

18364

18365

18366

18367

18368

18369

18370

18371

18372

18373

18374

18375

18376

18377

18378

18379

18380

18381

18382

18383

18384

18385

18386

18387

18388

18389

18390

18391

18392

18393

18394

18395

18396

18397

18398

18399

18400

18401

18402

18403

18404

18405

18406

18407

18408

18409

18410

18411

18412

18413

18414

18415

18416

18417

18418

18419

18420

18421

18422

18423

18424

18425

18426

18427

18428

18429

18430

18431

18432

18433

18434

18435

18436

18437

18438

18439

18440

18441

18442

18443

18444

18445

18446

18447

18448

18449

18450

18451

18452

18453

18454

18455

18456

18457

18458

18459

18460

18461

18462

18463

18464

18465

18466

18467

18468

18469

18470

18471

18472

18473

18474

18475

18476

18477

18478

18479

18480

18481

18482

18483

18484

18485

18486

18487

18488

18489

18490

18491

18492

18493

18494

18495

18496

18497

18498

18499

18500

18501

18502

18503

18504

18505

18506

18507

18508

18509

18510

18511

18512

18513

18514

18515

18516

18517

18518

18519

18520

18521

18522

18523

18524

18525

18526

18527

18528

18529

18530

18531

18532

18533

18534

18535

18536

18537

18538

18539

18540

18541

18542

18543

18544

18545

18546

18547

18548

18549

18550

18551

18552

18553

18554

18555

18556

18557

18558

18559

18560

18561

18562

18563

18564

18565

18566

18567

18568

18569

18570

18571

18572

18573

18574

18575

18576

18577

18578

18579

18580

18581

18582

18583

18584

18585

18586

18587

18588

18589

18590

18591

18592

18593

18594

18595

18596

18597

18598

18599

18600

18601

18602

18603

18604

18605

18606

18607

18608

18609

18610

18611

18612

18613

18614

18615

18616

18617

18618

18619

18620

18621

18622

18623

18624

18625

18626

18627

18628

18629

18630

18631

18632

18633

18634

18635

18636

18637

18638

18639

18640

18641

18642

18643

18644

18645

18646

18647

18648

18649

18650

18651

18652

18653

18654

18655

18656

18657

18658

18659

18660

18661

18662

18663

18664

18665

18666

18667

18668

18669

18670

18671

18672

18673

18674

18675

18676

18677

18678

18679

18680

18681

18682

18683

18684

18685

18686

18687

18688

18689

18690

18691

18692

18693

18694

18695

18696

18697

18698

18699

18700

18701

18702

18703

18704

18705

18706

18707

18708

18709

18710

18711

18712

18713

18714

18715

18716

18717

18718

18719

18720

18721

18722

18723

18724

18725

18726

18727

18728

18729

18730

18731

18732

18733

18734

18735

18736

18737

18738

18739

18740

18741

18742

18743

18744

18745

18746

18747

18748

18749

18750

18751

18752

18753

18754

18755

18756

18757

18758

18759

18760

18761

18762

18763

18764

18765

18766

18767

18768

18769

18770

18771

18772

18773

18774

18775

18776

18777

18778

18779

18780

18781

18782

18783

18784

18785

18786

18787

18788

18789

18790

18791

18792

18793

18794

18795

18796

18797

18798

18799

18800

18801

18802

18803

18804

18805

18806

18807

18808

18809

18810

18811

18812

18813

18814

18815

18816

18817

18818

18819

18820

18821

18822

18823

18824

18825

18826

18827

18828

18829

18830

18831

18832

18833

18834

18835

18836

18837

18838

18839

18840

18841

18842

18843

18844

18845

18846

18847

18848

18849

18850

18851

18852

18853

18854

18855

18856

18857

18858

18859

18860

18861

18862

18863

18864

18865

18866

18867

18868

18869

18870

18871

18872

18873

18874

18875

18876

18877

18878

18879

18880

18881

18882

18883

18884

18885

18886

18887

18888

18889

18890

18891

18892

18893

18894

18895

18896

18897

18898

18899

18900

18901

18902

18903

18904

18905

18906

18907

18908

18909

18910

18911

18912

18913

18914

18915

18916

18917

18918

18919

18920

18921

18922

18923

18924

18925

18926

18927

18928

18929

18930

18931

18932

18933

18934

18935

18936

18937

18938

18939

18940

18941

18942

18943

18944

18945

18946

18947

18948

18949

18950

18951

18952

18953

18954

18955

18956

18957

18958

18959

18960

18961

18962

18963

18964

18965

18966

18967

18968

18969

18970

18971

18972

18973

18974

18975

18976

18977

18978

18979

18980

18981

18982

18983

18984

18985

18986

18987

18988

18989

18990

18991

18992

18993

18994

18995

18996

18997

18998

18999

19000

19001

19002

19003

19004

19005

19006

19007

19008

19009

19010

19011

19012

19013

19014

19015

19016

19017

19018

19019

19020

19021

19022

19023

19024

19025

19026

19027

19028

19029

19030

19031

19032

19033

19034

19035

19036

19037

19038

19039

19040

19041

19042

19043

19044

19045

19046

19047

19048

19049

19050

19051

19052

19053

19054

19055

19056

19057

19058

19059

19060

19061

19062

19063

19064

19065

19066

19067

19068

19069

19070

19071

19072

19073

19074

19075

19076

19077

19078

19079

19080

19081

19082

19083

19084

19085

19086

19087

19088

19089

19090

19091

19092

19093

19094

19095

19096

19097

19098

19099

19100

19101

19102

19103

19104

19105

19106

19107

19108

19109

19110

19111

19112

19113

19114

19115

19116

19117

19118

19119

19120

19121

19122

19123

19124

19125

19126

19127

19128

19129

19130

19131

19132

19133

19134

19135

19136

19137

19138

19139

19140

19141

19142

19143

19144

19145

19146

19147

19148

19149

19150

19151

19152

19153

19154

19155

19156

19157

19158

19159

19160

19161

19162

19163

19164

19165

19166

19167

19168

19169

19170

19171

19172

19173

19174

19175

19176

19177

19178

19179

19180

19181

19182

19183

19184

19185

19186

19187

19188

19189

19190

19191

19192

19193

19194

19195

19196

19197

19198

19199

19200

19201

19202

19203

19204

19205

19206

19207

19208

19209

19210

19211

19212

19213

19214

19215

19216

19217

19218

19219

19220

19221

19222

19223

19224

19225

19226

19227

19228

19229

19230

19231

19232

19233

19234

19235

19236

19237

19238

19239

19240

19241

19242

19243

19244

19245

19246

19247

19248

19249

19250

19251

19252

19253

19254

19255

19256

19257

19258

19259

19260

19261

19262

19263

19264

19265

19266

19267

19268

19269

19270

19271

19272

19273

19274

19275

19276

19277

19278

19279

19280

19281

19282

19283

19284

19285

19286

19287

19288

19289

19290

19291

19292

19293

19294

19295

19296

19297

19298

19299

19300

19301

19302

19303

19304

19305

19306

19307

19308

19309

19310

19311

19312

19313

19314

19315

19316

19317

19318

19319

19320

19321

19322

19323

19324

19325

19326

19327

19328

19329

19330

19331

19332

19333

19334

19335

19336

19337

19338

19339

19340

19341

19342

19343

19344

19345

19346

19347

19348

19349

19350

19351

19352

19353

19354

19355

19356

19357

19358

19359

19360

19361

19362

19363

19364

19365

19366

19367

19368

19369

19370

19371

19372

19373

19374

19375

19376

19377

19378

19379

19380

19381

19382

19383

19384

19385

19386

19387

19388

19389

19390

19391

19392

19393

19394

19395

19396

19397

19398

19399

19400

19401

19402

19403

19404

19405

19406

19407

19408

19409

19410

19411

19412

19413

19414

19415

19416

19417

19418

19419

19420

19421

19422

19423

19424

19425

19426

19427

19428

19429

19430

19431

19432

19433

19434

19435

19436

19437

19438

19439

19440

19441

19442

19443

19444

19445

19446

19447

19448

19449

19450

19451

19452

19453

19454

19455

19456

19457

19458

19459

19460

19461

19462

19463

19464

19465

19466

19467

19468

19469

19470

19471

19472

19473

19474

19475

19476

19477

19478

19479

19480

19481

19482

19483

19484

19485

19486

19487

19488

19489

19490

19491

19492

19493

19494

19495

19496

19497

19498

19499

19500

19501

19502

19503

19504

19505

19506

19507

19508

19509

19510

19511

19512

19513

19514

19515

19516

19517

19518

19519

19520

19521

19522

19523

19524

19525

19526

19527

19528

19529

19530

19531

19532

19533

19534

19535

19536

19537

19538

19539

19540

19541

19542

19543

19544

19545

19546

19547

19548

19549

19550

19551

19552

19553

19554

19555

19556

19557

19558

19559

19560

19561

19562

19563

19564

19565

19566

19567

19568

19569

19570

19571

19572

19573

19574

19575

19576

19577

19578

19579

19580

19581

19582

19583

19584

19585

19586

19587

19588

19589

19590

19591

19592

19593

19594

19595

19596

19597

19598

19599

19600

19601

19602

19603

19604

19605

19606

19607

19608

19609

19610

19611

19612

19613

19614

19615

19616

19617

19618

19619

19620

19621

19622

19623

19624

19625

19626

19627

19628

19629

19630

19631

19632

19633

19634

19635

19636

19637

19638

19639

19640

19641

19642

19643

19644

19645

19646

19647

19648

19649

19650

19651

19652

19653

19654

19655

19656

19657

19658

19659

19660

19661

19662

19663

19664

19665

19666

19667

19668

19669

19670

19671

19672

19673

19674

19675

19676

19677

19678

19679

19680

19681

19682

19683

19684

19685

19686

19687

19688

19689

19690

19691

19692

19693

19694

19695

19696

19697

19698

19699

19700

19701

19702

19703

19704

19705

19706

19707

19708

19709

19710

19711

19712

19713

19714

19715

19716

19717

19718

19719

19720

19721

19722

19723

19724

19725

19726

19727

19728

19729

19730

19731

19732

19733

19734

19735

19736

19737

19738

19739

19740

19741

19742

19743

19744

19745

19746

19747

19748

19749

19750

19751

19752

19753

19754

19755

19756

19757

19758

19759

19760

19761

19762

19763

19764

19765

19766

19767

19768

19769

19770

19771

19772

19773

19774

19775

19776

19777

19778

19779

19780

19781

19782

19783

19784

19785

19786

19787

19788

19789

19790

19791

19792

19793

19794

19795

19796

19797

19798

19799

19800

19801

19802

19803

19804

19805

19806

19807

19808

19809

19810

19811

19812

19813

19814

19815

19816

19817

19818

19819

19820

19821

19822

19823

19824

19825

19826

19827

19828

19829

19830

19831

19832

19833

19834

19835

19836

19837

19838

19839

19840

19841

19842

19843

19844

19845

19846

19847

19848

19849

19850

19851

19852

19853

19854

19855

19856

19857

19858

19859

19860

19861

19862

19863

19864

19865

19866

19867

19868

19869

19870

19871

19872

19873

19874

19875

19876

19877

19878

19879

19880

19881

19882

19883

19884

19885

19886

19887

19888

19889

19890

19891

19892

19893

19894

19895

19896

19897

19898

19899

19900

19901

19902

19903

19904

19905

19906

19907

19908

19909

19910

19911

19912

19913

19914

19915

19916

19917

19918

19919

19920

19921

19922

19923

19924

19925

19926

19927

19928

19929

19930

19931

19932

19933

19934

19935

19936

19937

19938

19939

19940

19941

19942

19943

19944

19945

19946

19947

19948

19949

19950

19951

19952

19953

19954

19955

19956

19957

19958

19959

19960

19961

19962

19963

19964

19965

19966

19967

19968

19969

19970

19971

19972

19973

19974

19975

19976

19977

19978

19979

19980

19981

19982

19983

19984

19985

19986

19987

19988

19989

19990

19991

19992

19993

19994

19995

19996

19997

19998

19999

20000

20001

20002

20003

20004

20005

20006

20007

20008

20009

20010

20011

20012

20013

20014

20015

20016

20017

20018

20019

20020

20021

20022

20023

20024

20025

20026

20027

20028

20029

20030

20031

20032

20033

20034

20035

20036

20037

20038

20039

20040

20041

20042

20043

20044

20045

20046

20047

20048

20049

20050

20051

20052

20053

20054

20055

20056

20057

20058

20059

20060

20061

20062

20063

20064

20065

20066

20067

20068

20069

20070

20071

20072

20073

20074

20075

20076

20077

20078

20079

20080

20081

20082

20083

20084

20085

20086

20087

20088

20089

20090

20091

20092

20093

20094

20095

20096

20097

20098

20099

20100

20101

20102

20103

20104

20105

20106

20107

20108

20109

20110

20111

20112

20113

20114

20115

20116

20117

20118

20119

20120

20121

20122

20123

20124

20125

20126

20127

20128

20129

20130

20131

20132

20133

20134

20135

20136

20137

20138

20139

20140

20141

20142

20143

20144

20145

20146

20147

20148

20149

20150

20151

20152

20153

20154

20155

20156

20157

20158

20159

20160

20161

20162

20163

20164

20165

20166

20167

20168

20169

20170

20171

20172

20173

20174

20175

20176

20177

20178

20179

20180

20181

20182

20183

20184

20185

20186

20187

20188

20189

20190

20191

20192

20193

20194

20195

20196

20197

20198

20199

20200

20201

20202

20203

20204

20205

20206

20207

20208

20209

20210

20211

20212

20213

20214

20215

20216

20217

20218

20219

20220

20221

20222

20223

20224

20225

20226

20227

20228

20229

20230

20231

20232

20233

20234

20235

20236

20237

20238

20239

20240

20241

20242

20243

20244

20245

20246

20247

20248

20249

20250

20251

20252

20253

20254

20255

20256

20257

20258

20259

20260

20261

20262

20263

20264

20265

20266

20267

20268

20269

20270

20271

20272

20273

20274

20275

20276

20277

20278

20279

20280

20281

20282

20283

20284

20285

20286

20287

20288

20289

20290

20291

20292

20293

20294

20295

20296

20297

20298

20299

20300

20301

20302

20303

20304

20305

20306

20307

20308

20309

20310

20311

20312

20313

20314

20315

20316

20317

20318

20319

20320

20321

20322

20323

20324

20325

20326

20327

20328

20329

20330

20331

20332

20333

20334

20335

20336

20337

20338

20339

20340

20341

20342

20343

20344

20345

20346

20347

20348

20349

20350

20351

20352

20353

20354

20355

20356

20357

20358

20359

20360

20361

20362

20363

20364

20365

20366

20367

20368

20369

20370

20371

20372

20373

20374

20375

20376

20377

20378

20379

20380

20381

20382

20383

20384

20385

20386

20387

20388

20389

20390

20391

20392

20393

20394

20395

20396

20397

20398

20399

20400

20401

20402

20403

20404

20405

20406

20407

20408

20409

20410

20411

20412

20413

20414

20415

20416

20417

20418

20419

20420

20421

20422

20423

20424

20425

20426

20427

20428

20429

20430

20431

20432

20433

20434

20435

20436

20437

20438

20439

20440

20441

20442

20443

20444

20445

20446

20447

20448

20449

20450

20451

20452

20453

20454

20455

20456

20457

20458

20459

20460

20461

20462

20463

20464

20465

20466

20467

20468

20469

20470

20471

20472

20473

20474

20475

20476

20477

20478

20479

20480

20481

20482

20483

20484

20485

20486

20487

20488

20489

20490

20491

20492

20493

20494

20495

20496

20497

20498

20499

20500

20501

20502

20503

20504

20505

20506

20507

20508

20509

20510

20511

20512

20513

20514

20515

20516

20517

20518

20519

20520

20521

20522

20523

20524

20525

20526

20527

20528

20529

20530

20531

20532

20533

20534

20535

20536

20537

20538

20539

20540

20541

20542

20543

20544

20545

20546

20547

20548

20549

20550

20551

20552

20553

20554

20555

20556

20557

20558

20559

20560

20561

20562

20563

20564

20565

20566

20567

20568

20569

20570

20571

20572

20573

20574

20575

20576

20577

20578

20579

20580

20581

20582

20583

20584

20585

20586

20587

20588

20589

20590

20591

20592

20593

20594

20595

20596

20597

20598

20599

20600

20601

20602

20603

20604

20605

20606

20607

20608

20609

20610

20611

20612

20613

20614

20615

20616

20617

20618

20619

20620

20621

20622

20623

20624

20625

20626

20627

20628

20629

20630

20631

20632

20633

20634

20635

20636

20637

20638

20639

20640

20641

20642

20643

20644

20645

20646

20647

20648

20649

20650

20651

20652

20653

20654

20655

20656

20657

20658

20659

20660

20661

20662

20663

20664

20665

20666

20667

20668

20669

20670

20671

20672

20673

20674

20675

20676

20677

20678

20679

20680

20681

20682

20683

20684

20685

20686

20687

20688

20689

20690

20691

20692

20693

20694

20695

20696

20697

20698

20699

20700

20701

20702

20703

20704

20705

20706

20707

20708

20709

20710

20711

20712

20713

20714

20715

20716

20717

20718

20719

20720

20721

20722

20723

20724

20725

20726

20727

20728

20729

20730

20731

20732

20733

20734

20735

20736

20737

20738

20739

20740

20741

20742

20743

20744

20745

20746

20747

20748

20749

20750

20751

20752

20753

20754

20755

20756

20757

20758

20759

20760

20761

20762

20763

20764

20765

20766

20767

20768

20769

20770

20771

20772

20773

20774

20775

20776

20777

20778

20779

20780

20781

20782

20783

20784

20785

20786

20787

20788

20789

20790

20791

20792

20793

20794

20795

20796

20797

20798

20799

20800

20801

20802

20803

20804

20805

20806

20807

20808

20809

20810

20811

20812

20813

20814

20815

20816

20817

20818

20819

20820

20821

20822

20823

20824

20825

20826

20827

20828

20829

20830

20831

20832

20833

20834

20835

20836

20837

20838

20839

20840

20841

20842

20843

20844

20845

20846

20847

20848

20849

20850

20851

20852

20853

20854

20855

20856

20857

20858

20859

20860

20861

20862

20863

20864

20865

20866

20867

20868

20869

20870

20871

20872

20873

20874

20875

20876

20877

20878

20879

20880

20881

20882

20883

20884

20885

20886

20887

20888

20889

20890

20891

20892

20893

20894

20895

20896

20897

20898

20899

20900

20901

20902

20903

20904

20905

20906

20907

20908

20909

20910

20911

20912

20913

20914

20915

20916

20917

20918

20919

20920

20921

20922

20923

20924

20925

20926

20927

20928

20929

20930

20931

20932

20933

20934

20935

20936

20937

20938

20939

20940

20941

20942

20943

20944

20945

20946

20947

20948

20949

20950

20951

20952

20953

20954

20955

20956

20957

20958

20959

20960

20961

20962

20963

20964

20965

20966

20967

20968

20969

20970

20971

20972

20973

20974

20975

20976

20977

20978

20979

20980

20981

20982

20983

20984

20985

20986

20987

20988

20989

20990

20991

20992

20993

20994

20995

20996

20997

20998

20999

21000

21001

21002

21003

21004

21005

21006

21007

21008

21009

21010

21011

21012

21013

21014

21015

21016

21017

21018

21019

21020

21021

21022

21023

21024

21025

21026

21027

21028

21029

21030

21031

21032

21033

21034

21035

21036

21037

21038

21039

21040

21041

21042

21043

21044

21045

21046

21047

21048

21049

21050

21051

21052

21053

21054

21055

21056

21057

21058

21059

21060

21061

21062

21063

21064

21065

21066

21067

21068

21069

21070

21071

21072

21073

21074

21075

21076

21077

21078

21079

21080

21081

21082

21083

21084

21085

21086

21087

21088

21089

21090

21091

21092

21093

21094

21095

21096

21097

21098

21099

21100

21101

21102

21103

21104

21105

21106

21107

21108

21109

21110

21111

21112

21113

21114

21115

21116

21117

21118

21119

21120

21121

21122

21123

21124

21125

21126

21127

21128

21129

21130

21131

21132

21133

21134

21135

21136

21137

21138

21139

21140

21141

21142

21143

21144

21145

21146

21147

21148

21149

21150

21151

21152

21153

21154

21155

21156

21157

21158

21159

21160

21161

21162

21163

21164

21165

21166

21167

21168

21169

21170

21171

21172

21173

21174

21175

21176

21177

21178

21179

21180

21181

21182

21183

21184

21185

21186

21187

21188

21189

21190

21191

21192

21193

21194

21195

21196

21197

21198

21199

21200

21201

21202

21203

21204

21205

21206

21207

21208

21209

21210

21211

21212

21213

21214

21215

21216

21217

21218

21219

21220

21221

21222

21223

21224

21225

21226

21227

21228

21229

21230

21231

21232

21233

21234

21235

21236

21237

21238

21239

21240

21241

21242

21243

21244

21245

21246

21247

21248

21249

21250

21251

21252

21253

21254

21255

21256

21257

21258

21259

21260

21261

21262

21263

21264

21265

21266

21267

21268

21269

21270

21271

21272

21273

21274

21275

21276

21277

21278

21279

21280

21281

21282

21283

21284

21285

21286

21287

21288

21289

21290

21291

21292

21293

21294

21295

21296

21297

21298

21299

21300

21301

21302

21303

21304

21305

21306

21307

21308

21309

21310

21311

21312

21313

21314

21315

21316

21317

21318

21319

21320

21321

21322

21323

21324

21325

21326

21327

21328

21329

21330

21331

21332

21333

21334

21335

21336

21337

21338

21339

21340

21341

21342

21343

21344

21345

21346

21347

21348

21349

21350

21351

21352

21353

21354

21355

21356

21357

21358

21359

21360

21361

21362

21363

21364

21365

21366

21367

21368

21369

21370

21371

21372

21373

21374

21375

21376

21377

21378

21379

21380

21381

21382

21383

21384

21385

21386

21387

21388

21389

21390

21391

21392

21393

21394

21395

21396

21397

21398

21399

21400

21401

21402

21403

21404

21405

21406

21407

21408

21409

21410

21411

21412

21413

21414

21415

21416

21417

21418

21419

21420

21421

21422

21423

21424

21425

21426

21427

21428

21429

21430

21431

21432

21433

21434

21435

21436

21437

21438

21439

21440

21441

21442

21443

21444

21445

21446

21447

21448

21449

21450

21451

21452

21453

21454

21455

21456

21457

21458

21459

21460

21461

21462

21463

21464

21465

21466

21467

21468

21469

21470

21471

21472

21473

21474

21475

21476

21477

21478

21479

21480

21481

21482

21483

21484

21485

21486

21487

21488

21489

21490

21491

21492

21493

21494

21495

21496

21497

21498

21499

21500

21501

21502

21503

21504

21505

21506

21507

21508

21509

21510

21511

21512

21513

21514

21515

21516

21517

21518

21519

21520

21521

21522

21523

21524

21525

21526

21527

21528

21529

21530

21531

21532

21533

21534

21535

21536

21537

21538

21539

21540

21541

21542

21543

21544

21545

21546

21547

21548

21549

21550

21551

21552

21553

21554

21555

21556

21557

21558

21559

21560

21561

21562

21563

21564

21565

21566

21567

21568

21569

21570

21571

21572

21573

21574

21575

21576

21577

21578

21579

21580

21581

21582

21583

21584

21585

21586

21587

21588

21589

21590

21591

21592

21593

21594

21595

21596

21597

21598

21599

21600

21601

21602

21603

21604

21605

21606

21607

21608

21609

21610

21611

21612

21613

21614

21615

21616

21617

21618

21619

21620

21621

21622

21623

21624

21625

21626

21627

21628

21629

21630

21631

21632

21633

21634

21635

21636

21637

21638

21639

21640

21641

21642

21643

21644

21645

21646

21647

21648

21649

21650

21651

21652

21653

21654

21655

21656

21657

21658

21659

21660

21661

21662

21663

21664

21665

21666

21667

21668

21669

21670

21671

21672

21673

21674

21675

21676

21677

21678

21679

21680

21681

21682

21683

21684

21685

21686

21687

21688

21689

21690

21691

21692

21693

21694

21695

21696

21697

21698

21699

21700

21701

21702

21703

21704

21705

21706

21707

21708

21709

21710

21711

21712

21713

21714

21715

21716

21717

21718

21719

21720

21721

21722

21723

21724

21725

21726

21727

21728

21729

21730

21731

21732

21733

21734

21735

21736

21737

21738

21739

21740

21741

21742

21743

21744

21745

21746

21747

21748

21749

21750

21751

21752

21753

21754

21755

21756

21757

21758

21759

21760

21761

21762

21763

21764

21765

21766

21767

21768

21769

21770

21771

21772

21773

21774

21775

21776

21777

21778

21779

21780

21781

21782

21783

21784

21785

21786

21787

21788

21789

21790

21791

21792

21793

21794

21795

21796

21797

21798

21799

21800

21801

21802

21803

21804

21805

21806

21807

21808

21809

21810

21811

21812

21813

21814

21815

21816

21817

21818

21819

21820

21821

21822

21823

21824

21825

21826

21827

21828

21829

21830

21831

21832

21833

21834

21835

21836

21837

21838

21839

21840

21841

21842

21843

21844

21845

21846

21847

21848

21849

21850

21851

21852

21853

21854

21855

21856

21857

21858

21859

21860

21861

21862

21863

21864

21865

21866

21867

21868

21869

21870

21871

21872

21873

21874

21875

21876

21877

21878

21879

21880

21881

21882

21883

21884

21885

21886

21887

21888

21889

21890

21891

21892

21893

21894

21895

21896

21897

21898

21899

21900

21901

21902

21903

21904

21905

21906

21907

21908

21909

21910

21911

21912

21913

21914

21915

21916

21917

21918

21919

21920

21921

21922

21923

21924

21925

21926

21927

21928

21929

21930

21931

21932

21933

21934

21935

21936

21937

21938

21939

21940

21941

21942

21943

21944

21945

21946

21947

21948

21949

21950

21951

21952

21953

21954

21955

21956

21957

21958

21959

21960

21961

21962

21963

21964

21965

21966

21967

21968

21969

21970

21971

21972

21973

21974

21975

21976

21977

21978

21979

21980

21981

21982

21983

21984

21985

21986

21987

21988

21989

21990

21991

21992

21993

21994

21995

21996

21997

21998

21999

22000

22001

22002

22003

22004

22005

22006

22007

22008

22009

22010

22011

22012

22013

22014

22015

22016

22017

22018

22019

22020

22021

22022

22023

22024

22025

22026

22027

22028

22029

22030

22031

22032

22033

22034

22035

22036

22037

22038

22039

22040

22041

22042

22043

22044

22045

22046

22047

22048

22049

22050

22051

22052

22053

22054

22055

22056

22057

22058

22059

22060

22061

22062

22063

22064

22065

22066

22067

22068

22069

22070

22071

22072

22073

22074

22075

22076

22077

22078

22079

22080

22081

22082

22083

22084

22085

22086

22087

22088

22089

22090

22091

22092

22093

22094

22095

22096

22097

22098

22099

22100

22101

22102

22103

22104

22105

22106

22107

22108

22109

22110

22111

22112

22113

22114

22115

22116

22117

22118

22119

22120

22121

22122

22123

22124

22125

22126

22127

22128

22129

22130

22131

22132

22133

22134

22135

22136

22137

22138

22139

22140

22141

22142

22143

22144

22145

22146

22147

22148

22149

22150

22151

22152

22153

22154

22155

22156

22157

22158

22159

22160

22161

22162

22163

22164

22165

22166

22167

22168

22169

22170

22171

22172

22173

22174

22175

22176

22177

22178

22179

22180

22181

22182

22183

22184

22185

22186

22187

22188

22189

22190

22191

22192

22193

22194

22195

22196

22197

22198

22199

22200

22201

22202

22203

22204

22205

22206

22207

22208

22209

22210

22211

22212

22213

22214

22215

22216

22217

22218

22219

22220

22221

22222

22223

22224

22225

22226

22227

22228

22229

22230

22231

22232

22233

22234

22235

22236

22237

22238

22239

22240

22241

22242

22243

22244

22245

22246

22247

22248

22249

22250

22251

22252

22253

22254

22255

22256

22257

22258

22259

22260

22261

22262

22263

22264

22265

22266

22267

22268

22269

22270

22271

22272

22273

22274

22275

22276

22277

22278

22279

22280

22281

22282

22283

22284

22285

22286

22287

22288

22289

22290

22291

22292

22293

22294

22295

22296

22297

22298

22299

22300

22301

22302

22303

22304

22305

22306

22307

22308

22309

22310

22311

22312

22313

22314

22315

22316

22317

22318

22319

22320

22321

22322

22323

22324

22325

22326

22327

22328

22329

22330

22331

22332

22333

22334

22335

22336

22337

22338

22339

22340

22341

22342

22343

22344

22345

22346

22347

22348

22349

22350

22351

22352

22353

22354

22355

22356

22357

22358

22359

22360

22361

22362

22363

22364

22365

22366

22367

22368

22369

22370

22371

22372

22373

22374

22375

22376

22377

22378

22379

22380

22381

22382

22383

22384

22385

22386

22387

22388

22389

22390

22391

22392

22393

22394

22395

22396

22397

22398

22399

22400

22401

22402

22403

22404

22405

22406

22407

22408

22409

22410

22411

22412

22413

22414

22415

22416

22417

22418

22419

22420

22421

22422

22423

22424

22425

22426

22427

22428

22429

22430

22431

22432

22433

22434

22435

22436

22437

22438

22439

22440

22441

22442

22443

22444

22445

22446

22447

22448

22449

22450

22451

22452

22453

22454

22455

22456

22457

22458

22459

22460

22461

22462

22463

22464

22465

22466

22467

22468

22469

22470

22471

22472

22473

22474

22475

22476

22477

22478

22479

22480

22481

22482

22483

22484

22485

22486

22487

22488

22489

22490

22491

22492

22493

22494

22495

22496

22497

22498

22499

22500

22501

22502

22503

22504

22505

22506

22507

22508

22509

22510

22511

22512

22513

22514

22515

22516

22517

22518

22519

22520

22521

22522

22523

22524

22525

22526

22527

22528

22529

22530

22531

22532

22533

22534

22535

22536

22537

22538

22539

22540

22541

22542

22543

22544

22545

22546

22547

22548

22549

22550

22551

22552

22553

22554

22555

22556

22557

22558

22559

22560

22561

22562

22563

22564

22565

22566

22567

22568

22569

22570

22571

22572

22573

22574

22575

22576

22577

22578

22579

22580

22581

22582

22583

22584

22585

22586

22587

22588

22589

22590

22591

22592

22593

22594

22595

22596

22597

22598

22599

22600

22601

22602

22603

22604

22605

22606

22607

22608

22609

22610

22611

22612

22613

22614

22615

22616

22617

22618

22619

22620

22621

22622

22623

22624

22625

22626

22627

22628

22629

22630

22631

22632

22633

22634

22635

22636

22637

22638

22639

22640

22641

22642

22643

22644

22645

22646

22647

22648

22649

22650

22651

22652

22653

22654

22655

22656

22657

22658

22659

22660

22661

22662

22663

22664

22665

22666

22667

22668

22669

22670

22671

22672

22673

22674

22675

22676

22677

22678

22679

22680

22681

22682

22683

22684

22685

22686

22687

22688

22689

22690

22691

22692

22693

22694

22695

22696

22697

22698

22699

22700

22701

22702

22703

22704

22705

22706

22707

22708

22709

22710

22711

22712

22713

22714

22715

22716

22717

22718

22719

22720

22721

22722

22723

22724

22725

22726

22727

22728

22729

22730

22731

22732

22733

22734

22735

22736

22737

22738

22739

22740

22741

22742

22743

22744

22745

22746

22747

22748

22749

22750

22751

22752

22753

22754

22755

22756

22757

22758

22759

22760

22761

22762

22763

22764

22765

22766

22767

22768

22769

22770

22771

22772

22773

22774

22775

22776

22777

22778

22779

22780

22781

22782

22783

22784

22785

22786

22787

22788

22789

22790

22791

22792

22793

22794

22795

22796

22797

22798

22799

22800

22801

22802

22803

22804

22805

22806

22807

22808

22809

22810

22811

22812

22813

22814

22815

22816

22817

22818

22819

22820

22821

22822

22823

22824

22825

22826

22827

22828

22829

22830

22831

22832

22833

22834

22835

22836

22837

22838

22839

22840

22841

22842

22843

22844

22845

22846

22847

22848

22849

22850

22851

22852

22853

22854

22855

22856

22857

22858

22859

22860

22861

22862

22863

22864

22865

22866

22867

22868

22869

22870

22871

22872

22873

22874

22875

22876

22877

22878

22879

22880

22881

22882

22883

22884

22885

22886

22887

22888

22889

22890

22891

22892

22893

22894

22895

22896

22897

22898

22899

22900

22901

22902

22903

22904

22905

22906

22907

22908

22909

22910

22911

22912

22913

22914

22915

22916

22917

22918

22919

22920

22921

22922

22923

22924

22925

22926

22927

22928

22929

22930

22931

22932

22933

22934

22935

22936

22937

22938

22939

22940

22941

22942

22943

22944

22945

22946

22947

22948

22949

22950

22951

22952

22953

22954

22955

22956

22957

22958

22959

22960

22961

22962

22963

22964

22965

22966

22967

22968

22969

22970

22971

22972

22973

22974

22975

22976

22977

22978

22979

22980

22981

22982

22983

22984

22985

22986

22987

22988

22989

22990

22991

22992

22993

22994

22995

22996

22997

22998

22999

23000

23001

23002

23003

23004

23005

23006

23007

23008

23009

23010

23011

23012

23013

23014

23015

23016

23017

23018

23019

23020

23021

23022

23023

23024

23025

23026

23027

23028

23029

23030

23031

23032

23033

23034

23035

23036

23037

23038

23039

23040

23041

23042

23043

23044

23045

23046

23047

23048

23049

23050

23051

23052

23053

23054

23055

23056

23057

23058

23059

23060

23061

23062

23063

23064

23065

23066

23067

23068

23069

23070

23071

23072

23073

23074

23075

23076

23077

23078

23079

23080

23081

23082

23083

23084

23085

23086

23087

23088

23089

23090

23091

23092

23093

23094

23095

23096

23097

23098

23099

23100

23101

23102

23103

23104

23105

23106

23107

23108

23109

23110

23111

23112

23113

23114

23115

23116

23117

23118

23119

23120

23121

23122

23123

23124

23125

23126

23127

23128

23129

23130

23131

23132

23133

23134

23135

23136

23137

23138

23139

23140

23141

23142

23143

23144

23145

23146

23147

23148

23149

23150

23151

23152

23153

23154

23155

23156

23157

23158

23159

23160

23161

23162

23163

23164

23165

23166

23167

23168

23169

23170

23171

23172

23173

23174

23175

23176

23177

23178

23179

23180

23181

23182

23183

23184

23185

23186

23187

23188

23189

23190

23191

23192

23193

23194

23195

23196

23197

23198

23199

23200

23201

23202

23203

23204

23205

23206

# -*- coding: utf-8 -*- 

r""" 

Generic graphs (common to directed/undirected) 

 

This module implements the base class for graphs and digraphs, and methods that 

can be applied on both. Here is what it can do: 

 

**Basic Graph operations:** 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~GenericGraph.networkx_graph` | Create a new NetworkX graph from the Sage graph 

:meth:`~GenericGraph.igraph_graph` | Create a new igraph graph from the Sage graph 

:meth:`~GenericGraph.to_dictionary` | Create a dictionary encoding the graph. 

:meth:`~GenericGraph.copy` | Return a copy of the graph. 

:meth:`~GenericGraph.export_to_file` | Export the graph to a file. 

:meth:`~GenericGraph.adjacency_matrix` | Return the adjacency matrix of the (di)graph. 

:meth:`~GenericGraph.incidence_matrix` | Return an incidence matrix of the (di)graph 

:meth:`~GenericGraph.distance_matrix` | Return the distance matrix of the (strongly) connected (di)graph 

:meth:`~GenericGraph.weighted_adjacency_matrix` | Return the weighted adjacency matrix of the graph 

:meth:`~GenericGraph.kirchhoff_matrix` | Return the Kirchhoff matrix (a.k.a. the Laplacian) of the graph. 

:meth:`~GenericGraph.has_loops` | Return whether there are loops in the (di)graph 

:meth:`~GenericGraph.allows_loops` | Return whether loops are permitted in the (di)graph 

:meth:`~GenericGraph.allow_loops` | Change whether loops are permitted in the (di)graph 

:meth:`~GenericGraph.loops` | Return a list of all loops in the (di)graph 

:meth:`~GenericGraph.loop_edges` | Return a list of all loops in the (di)graph 

:meth:`~GenericGraph.number_of_loops` | Return the number of edges that are loops 

:meth:`~GenericGraph.loop_vertices` | Return a list of vertices with loops 

:meth:`~GenericGraph.remove_loops` | Remove loops on vertices in vertices. If vertices is None, removes all loops. 

:meth:`~GenericGraph.has_multiple_edges` | Return whether there are multiple edges in the (di)graph. 

:meth:`~GenericGraph.allows_multiple_edges` | Return whether multiple edges are permitted in the (di)graph. 

:meth:`~GenericGraph.allow_multiple_edges` | Change whether multiple edges are permitted in the (di)graph. 

:meth:`~GenericGraph.multiple_edges` | Return any multiple edges in the (di)graph. 

:meth:`~GenericGraph.name` | Return or sets the graph's name. 

:meth:`~GenericGraph.is_immutable` | Return whether the graph is immutable. 

:meth:`~GenericGraph.weighted` | Whether the (di)graph is to be considered as a weighted (di)graph. 

:meth:`~GenericGraph.antisymmetric` | Test whether the graph is antisymmetric 

:meth:`~GenericGraph.density` | Return the density 

:meth:`~GenericGraph.order` | Return the number of vertices. 

:meth:`~GenericGraph.size` | Return the number of edges. 

:meth:`~GenericGraph.add_vertex` | Create an isolated vertex. 

:meth:`~GenericGraph.add_vertices` | Add vertices to the (di)graph from an iterable container 

:meth:`~GenericGraph.delete_vertex` | Delete a vertex, removing all incident edges. 

:meth:`~GenericGraph.delete_vertices` | Remove vertices from the (di)graph taken from an iterable container of vertices. 

:meth:`~GenericGraph.has_vertex` | Return ``True`` if vertex is one of the vertices of this graph. 

:meth:`~GenericGraph.random_vertex` | Return a random vertex of self. 

:meth:`~GenericGraph.random_vertex_iterator` | Return an iterator over random vertices of self. 

:meth:`~GenericGraph.random_edge` | Return a random edge of self. 

:meth:`~GenericGraph.random_edge_iterator` | Return an iterator over random edges of self. 

:meth:`~GenericGraph.vertex_boundary` | Return a list of all vertices in the external boundary of vertices1, intersected with vertices2. 

:meth:`~GenericGraph.set_vertices` | Associate arbitrary objects with each vertex 

:meth:`~GenericGraph.set_vertex` | Associate an arbitrary object with a vertex. 

:meth:`~GenericGraph.get_vertex` | Retrieve the object associated with a given vertex. 

:meth:`~GenericGraph.get_vertices` | Return a dictionary of the objects associated to each vertex. 

:meth:`~GenericGraph.vertex_iterator` | Return an iterator over the vertices. 

:meth:`~GenericGraph.neighbor_iterator` | Return an iterator over neighbors of vertex. 

:meth:`~GenericGraph.vertices` | Return a list of the vertices. 

:meth:`~GenericGraph.neighbors` | Return a list of neighbors (in and out if directed) of vertex. 

:meth:`~GenericGraph.merge_vertices` | Merge vertices. 

:meth:`~GenericGraph.add_edge` | Add an edge from u and v. 

:meth:`~GenericGraph.add_edges` | Add edges from an iterable container. 

:meth:`~GenericGraph.subdivide_edge` | Subdivide an edge `k` times. 

:meth:`~GenericGraph.subdivide_edges` | Subdivide k times edges from an iterable container. 

:meth:`~GenericGraph.delete_edge` | Delete the edge from u to v 

:meth:`~GenericGraph.delete_edges` | Delete edges from an iterable container. 

:meth:`~GenericGraph.contract_edge` | Contract an edge from `u` to `v`. 

:meth:`~GenericGraph.contract_edges` | Contract edges from an iterable container. 

:meth:`~GenericGraph.delete_multiedge` | Delete all edges from u and v. 

:meth:`~GenericGraph.set_edge_label` | Set the edge label of a given edge. 

:meth:`~GenericGraph.has_edge` | Return True if (u, v) is an edge, False otherwise. 

:meth:`~GenericGraph.edges` | Return a list of edges. 

:meth:`~GenericGraph.edge_boundary` | Return a list of edges `(u,v,l)` with `u` in ``vertices1`` 

:meth:`~GenericGraph.edge_iterator` | Return an iterator over edges. 

:meth:`~GenericGraph.edges_incident` | Return incident edges to some vertices. 

:meth:`~GenericGraph.edge_label` | Return the label of an edge. 

:meth:`~GenericGraph.edge_labels` | Return a list of edge labels. 

:meth:`~GenericGraph.remove_multiple_edges` | Remove all multiple edges, retaining one edge for each. 

:meth:`~GenericGraph.clear` | Empty the graph of vertices and edges and removes name, associated objects, and position information. 

:meth:`~GenericGraph.degree` | Return the degree (in + out for digraphs) of a vertex or of vertices. 

:meth:`~GenericGraph.average_degree` | Return the average degree of the graph. 

:meth:`~GenericGraph.degree_histogram` | Return a list, whose ith entry is the frequency of degree i. 

:meth:`~GenericGraph.degree_iterator` | Return an iterator over the degrees of the (di)graph. 

:meth:`~GenericGraph.degree_sequence` | Return the degree sequence of this (di)graph. 

:meth:`~GenericGraph.random_subgraph` | Return a random subgraph that contains each vertex with prob. p. 

:meth:`~GenericGraph.add_clique` | Add a clique to the graph with the given vertices. 

:meth:`~GenericGraph.add_cycle` | Add a cycle to the graph with the given vertices. 

:meth:`~GenericGraph.add_path` | Add a cycle to the graph with the given vertices. 

:meth:`~GenericGraph.complement` | Return the complement of the (di)graph. 

:meth:`~GenericGraph.line_graph` | Return the line graph of the (di)graph. 

:meth:`~GenericGraph.to_simple` | Return a simple version of itself (i.e., undirected and loops and multiple edges are removed). 

:meth:`~GenericGraph.disjoint_union` | Return the disjoint union of self and other. 

:meth:`~GenericGraph.union` | Return the union of self and other. 

:meth:`~GenericGraph.relabel` | Relabel the vertices of ``self`` 

:meth:`~GenericGraph.degree_to_cell` | Return the number of edges from vertex to an edge in cell. 

:meth:`~GenericGraph.subgraph` | Return the subgraph containing the given vertices and edges. 

:meth:`~GenericGraph.is_subgraph` | Test whether self is a subgraph of other. 

 

**Graph products:** 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~GenericGraph.cartesian_product` | Return the Cartesian product of self and other. 

:meth:`~GenericGraph.tensor_product` | Return the tensor product, also called the categorical product, of self and other. 

:meth:`~GenericGraph.lexicographic_product` | Return the lexicographic product of self and other. 

:meth:`~GenericGraph.strong_product` | Return the strong product of self and other. 

:meth:`~GenericGraph.disjunctive_product` | Return the disjunctive product of self and other. 

 

**Paths and cycles:** 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~GenericGraph.eulerian_orientation` | Return a DiGraph which is an Eulerian orientation of the current graph. 

:meth:`~GenericGraph.eulerian_circuit` | Return a list of edges forming an Eulerian circuit if one exists. 

:meth:`~GenericGraph.cycle_basis` | Return a list of cycles which form a basis of the cycle space of ``self``. 

:meth:`~GenericGraph.all_paths` | Return a list of all paths (also lists) between a pair of vertices in the (di)graph. 

:meth:`~GenericGraph.triangles_count` | Return the number of triangles in the (di)graph. 

 

**Linear algebra:** 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~GenericGraph.spectrum` | Return a list of the eigenvalues of the adjacency matrix. 

:meth:`~GenericGraph.eigenvectors` | Return the *right* eigenvectors of the adjacency matrix of the graph. 

:meth:`~GenericGraph.eigenspaces` | Return the *right* eigenspaces of the adjacency matrix of the graph. 

 

**Some metrics:** 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~GenericGraph.cluster_triangles` | Return the number of triangles for nbunch of vertices as a dictionary keyed by vertex. 

:meth:`~GenericGraph.clustering_average` | Return the average clustering coefficient. 

:meth:`~GenericGraph.clustering_coeff` | Return the clustering coefficient for each vertex in nbunch 

:meth:`~GenericGraph.cluster_transitivity` | Return the transitivity (fraction of transitive triangles) of the graph. 

:meth:`~GenericGraph.szeged_index` | Return the Szeged index of the graph. 

 

 

**Automorphism group:** 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~GenericGraph.coarsest_equitable_refinement` | Return the coarsest partition which is finer than the input partition, and equitable with respect to self. 

:meth:`~GenericGraph.automorphism_group` | Return the largest subgroup of the automorphism group of the (di)graph whose orbit partition is finer than the partition given. 

:meth:`~GenericGraph.is_vertex_transitive` | Return whether the automorphism group of self is transitive within the partition provided 

:meth:`~GenericGraph.is_isomorphic` | Test for isomorphism between self and other. 

:meth:`~GenericGraph.canonical_label` | Return the canonical graph. 

:meth:`~GenericGraph.is_cayley` | Check whether the graph is a Cayley graph. 

 

**Graph properties:** 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~GenericGraph.is_eulerian` | Return ``True`` if the graph has a (closed) tour that visits each edge exactly once. 

:meth:`~GenericGraph.is_planar` | Test whether the graph is planar. 

:meth:`~GenericGraph.is_circular_planar` | Test whether the graph is circular planar (outerplanar) 

:meth:`~GenericGraph.is_regular` | Return ``True`` if this graph is (`k`-)regular. 

:meth:`~GenericGraph.is_chordal` | Test whether the given graph is chordal. 

:meth:`~GenericGraph.is_circulant` | Test whether the graph is a circulant graph. 

:meth:`~GenericGraph.is_interval` | Check whether the graph is an interval graph. 

:meth:`~GenericGraph.is_gallai_tree` | Return whether the current graph is a Gallai tree. 

:meth:`~GenericGraph.is_clique` | Test whether a set of vertices is a clique 

:meth:`~GenericGraph.is_cycle` | Test whether self is a (directed) cycle graph. 

:meth:`~GenericGraph.is_independent_set` | Test whether a set of vertices is an independent set 

:meth:`~GenericGraph.is_transitively_reduced` | Test whether the digraph is transitively reduced. 

:meth:`~GenericGraph.is_equitable` | Check whether the given partition is equitable with respect to self. 

:meth:`~GenericGraph.is_self_complementary` | Check whether the graph is self-complementary. 

 

**Traversals:** 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~GenericGraph.breadth_first_search` | Return an iterator over the vertices in a breadth-first ordering. 

:meth:`~GenericGraph.depth_first_search` | Return an iterator over the vertices in a depth-first ordering. 

:meth:`~GenericGraph.lex_BFS` | Perform a Lex BFS on the graph. 

 

**Distances:** 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~GenericGraph.centrality_betweenness` | Return the betweenness centrality 

:meth:`~GenericGraph.centrality_closeness` | Returns the closeness centrality (1/average distance to all vertices) 

:meth:`~GenericGraph.distance` | Return the (directed) distance from u to v in the (di)graph 

:meth:`~GenericGraph.distance_all_pairs` | Return the distances between all pairs of vertices. 

:meth:`~GenericGraph.distances_distribution` | Return the distances distribution of the (di)graph in a dictionary. 

:meth:`~GenericGraph.eccentricity` | Return the eccentricity of vertex (or vertices) v. 

:meth:`~GenericGraph.radius` | Return the radius of the (di)graph. 

:meth:`~GenericGraph.center` | Return the set of vertices in the center of the graph 

:meth:`~GenericGraph.diameter` | Return the largest distance between any two vertices. 

:meth:`~GenericGraph.distance_graph` | Return the graph on the same vertex set as the original graph but vertices are adjacent in the returned graph if and only if they are at specified distances in the original graph. 

:meth:`~GenericGraph.girth` | Compute the girth of the graph. 

:meth:`~GenericGraph.periphery` | Return the set of vertices in the periphery 

:meth:`~GenericGraph.shortest_path` | Return a list of vertices representing some shortest path from `u` to `v` 

:meth:`~GenericGraph.shortest_path_length` | Return the minimal length of paths from u to v 

:meth:`~GenericGraph.shortest_paths` | Return a dictionary associating to each vertex v a shortest path from u to v, if it exists. 

:meth:`~GenericGraph.shortest_path_lengths` | Return a dictionary of shortest path lengths keyed by targets that are connected by a path from u. 

:meth:`~GenericGraph.shortest_path_all_pairs` | Compute a shortest path between each pair of vertices. 

:meth:`~GenericGraph.wiener_index` | Return the Wiener index of the graph. 

:meth:`~GenericGraph.average_distance` | Return the average distance between vertices of the graph. 

 

**Flows, connectivity, trees:** 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~GenericGraph.is_connected` | Test whether the (di)graph is connected. 

:meth:`~GenericGraph.connected_components` | Return the list of connected components 

:meth:`~GenericGraph.connected_components_number` | Return the number of connected components. 

:meth:`~GenericGraph.connected_components_subgraphs` | Return a list of connected components as graph objects. 

:meth:`~GenericGraph.connected_component_containing_vertex` | Return a list of the vertices connected to vertex. 

:meth:`~GenericGraph.connected_components_sizes` | Return the sizes of the connected components as a list. 

:meth:`~GenericGraph.blocks_and_cut_vertices` | Compute the blocks and cut vertices of the graph. 

:meth:`~GenericGraph.blocks_and_cuts_tree` | Compute the blocks-and-cuts tree of the graph. 

:meth:`~GenericGraph.is_cut_edge` | Return True if the input edge is a cut-edge or a bridge. 

:meth:`~GenericGraph.is_cut_vertex` | Return True if the input vertex is a cut-vertex. 

:meth:`~GenericGraph.edge_cut` | Return a minimum edge cut between vertices `s` and `t` 

:meth:`~GenericGraph.vertex_cut` | Return a minimum vertex cut between non-adjacent vertices `s` and `t` 

:meth:`~GenericGraph.flow` | Return a maximum flow in the graph from ``x`` to ``y`` 

:meth:`~GenericGraph.nowhere_zero_flow` | Return a `k`-nowhere zero flow of the (di)graph. 

:meth:`~GenericGraph.edge_disjoint_paths` | Returns a list of edge-disjoint paths between two vertices 

:meth:`~GenericGraph.vertex_disjoint_paths` | Return a list of vertex-disjoint paths between two vertices as given by Menger's theorem. 

:meth:`~GenericGraph.edge_connectivity` | Return the edge connectivity of the graph. 

:meth:`~GenericGraph.vertex_connectivity` | Return the vertex connectivity of the graph. 

:meth:`~GenericGraph.transitive_closure` | Compute the transitive closure of a graph and returns it. 

:meth:`~GenericGraph.transitive_reduction` | Return a transitive reduction of a graph. 

:meth:`~GenericGraph.min_spanning_tree` | Return the edges of a minimum spanning tree. 

:meth:`~GenericGraph.spanning_trees_count` | Return the number of spanning trees in a graph. 

:meth:`~GenericGraph.dominator_tree` | Returns a dominator tree of the graph. 

 

**Plot/embedding-related methods:** 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~GenericGraph.set_embedding` | Set a combinatorial embedding dictionary to ``_embedding`` attribute. 

:meth:`~GenericGraph.get_embedding` | Return the attribute _embedding if it exists. 

:meth:`~GenericGraph.faces` | Return the faces of an embedded graph. 

:meth:`~GenericGraph.planar_dual` | Return the planar dual of an embedded graph. 

:meth:`~GenericGraph.get_pos` | Return the position dictionary 

:meth:`~GenericGraph.set_pos` | Set the position dictionary. 

:meth:`~GenericGraph.set_planar_positions` | Compute a planar layout for self using Schnyder's algorithm 

:meth:`~GenericGraph.layout_planar` | Use Schnyder's algorithm to compute a planar layout for self. 

:meth:`~GenericGraph.is_drawn_free_of_edge_crossings` | Test whether the position dictionary gives a planar embedding. 

:meth:`~GenericGraph.latex_options` | Return an instance of :class:`~sage.graphs.graph_latex.GraphLatex` for the graph. 

:meth:`~GenericGraph.set_latex_options` | Set multiple options for rendering a graph with LaTeX. 

:meth:`~GenericGraph.layout` | Return a layout for the vertices of this graph. 

:meth:`~GenericGraph.layout_spring` | Compute a spring layout for this graph 

:meth:`~GenericGraph.layout_ranked` | Compute a ranked layout for this graph 

:meth:`~GenericGraph.layout_extend_randomly` | Extend randomly a partial layout 

:meth:`~GenericGraph.layout_circular` | Compute a circular layout for this graph 

:meth:`~GenericGraph.layout_tree` | Compute an ordered tree layout for this graph, which should be a tree (no non-oriented cycles). 

:meth:`~GenericGraph.layout_graphviz` | Call ``graphviz`` to compute a layout of the vertices of this graph. 

:meth:`~GenericGraph.graphplot` | Return a GraphPlot object. 

:meth:`~GenericGraph.plot` | Return a graphics object representing the (di)graph. 

:meth:`~GenericGraph.show` | Show the (di)graph. 

:meth:`~GenericGraph.plot3d` | Plot the graph in three dimensions. 

:meth:`~GenericGraph.show3d` | Plot the graph using Tachyon, and shows the resulting plot. 

:meth:`~GenericGraph.graphviz_string` | Return a representation in the dot language. 

:meth:`~GenericGraph.graphviz_to_file_named` | Write a representation in the dot in a file. 

 

**Algorithmically hard stuff:** 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~GenericGraph.steiner_tree` | Return a tree of minimum weight connecting the given set of vertices. 

:meth:`~GenericGraph.edge_disjoint_spanning_trees` | Return the desired number of edge-disjoint spanning trees/arborescences. 

:meth:`~GenericGraph.feedback_vertex_set` | Compute the minimum feedback vertex set of a (di)graph. 

:meth:`~GenericGraph.multiway_cut` | Return a minimum edge multiway cut 

:meth:`~GenericGraph.max_cut` | Return a maximum edge cut of the graph. 

:meth:`~GenericGraph.longest_path` | Return a longest path of ``self``. 

:meth:`~GenericGraph.traveling_salesman_problem` | Solve the traveling salesman problem (TSP) 

:meth:`~GenericGraph.is_hamiltonian` | Test whether the current graph is Hamiltonian. 

:meth:`~GenericGraph.hamiltonian_cycle` | Return a Hamiltonian cycle/circuit of the current graph/digraph 

:meth:`~GenericGraph.hamiltonian_path` | Return a Hamiltonian path of the current graph/digraph 

:meth:`~GenericGraph.multicommodity_flow` | Solve a multicommodity flow problem. 

:meth:`~GenericGraph.disjoint_routed_paths` | Return a set of disjoint routed paths. 

:meth:`~GenericGraph.dominating_set` | Return a minimum dominating set of the graph 

:meth:`~GenericGraph.subgraph_search` | Return a copy of ``G`` in ``self``. 

:meth:`~GenericGraph.subgraph_search_count` | Return the number of labelled occurrences of ``G`` in ``self``. 

:meth:`~GenericGraph.subgraph_search_iterator` | Return an iterator over the labelled copies of ``G`` in ``self``. 

:meth:`~GenericGraph.characteristic_polynomial` | Return the characteristic polynomial of the adjacency matrix of the (di)graph. 

:meth:`~GenericGraph.genus` | Return the minimal genus of the graph. 

:meth:`~GenericGraph.crossing_number` | Return the minimum number of edge crossings needed to draw the graph. 

 

Methods 

------- 

""" 

from __future__ import print_function, absolute_import, division 

from six.moves import range, zip 

from six import itervalues, iteritems, integer_types 

 

from copy import copy 

from sage.misc.decorators import options 

from sage.misc.cachefunc import cached_method 

from sage.misc.prandom import random 

from sage.rings.integer_ring import ZZ 

from sage.rings.integer import Integer 

from sage.rings.rational import Rational 

from .generic_graph_pyx import GenericGraph_pyx, spring_layout_fast 

from sage.graphs.dot2tex_utils import assert_have_dot2tex 

from sage.misc.superseded import deprecation, deprecated_function_alias 

 

class GenericGraph(GenericGraph_pyx): 

""" 

Base class for graphs and digraphs. 

 

.. automethod:: __eq__ 

""" 

 

# Nice defaults for plotting arrays of graphs (see sage.misc.functional.show) 

graphics_array_defaults = {'layout': 'circular', 'vertex_size':50, 'vertex_labels':False, 'graph_border':True} 

 

def __init__(self): 

r""" 

Every graph carries a dictionary of options, which is set 

here to ``None``. Some options are added to the global 

:data:`sage.misc.latex.latex` instance which will insure 

that if LaTeX is used to render the graph, 

then the right packages are loaded and MathJax reacts 

properly. 

 

Most other initialization is done in the directed 

and undirected subclasses. 

 

TESTS:: 

 

sage: g = Graph() 

sage: g 

Graph on 0 vertices 

""" 

self._latex_opts = None 

 

def __setstate__(self,state): 

r""" 

Set the state from a pickle dict 

 

Also converts old NetworkX backends into a more recent one. 

""" 

for k,v in iteritems(state): 

self.__dict__[k] = v 

from sage.graphs.base.graph_backends import NetworkXGraphBackend 

if isinstance(self._backend, NetworkXGraphBackend): 

from sage.misc.superseded import deprecation 

deprecation(1000,"You unpickled an object which relies on an old " 

"data structure. Save it again to update it, for it " 

"may break in the future.") 

g = self._backend._nxg 

if g.is_directed(): 

from sage.graphs.digraph import DiGraph as constructor 

else: 

from sage.graphs.graph import Graph as constructor 

self._backend = constructor(g)._backend 

 

def __add__(self, other): 

""" 

Return a graph isomorphic to disjoint union of this graph with `other`. 

 

Labels of the resulting graph will always be consecutive integers 

starting from zero. 

 

.. SEEALSO:: :meth:`disjoint_union` 

 

EXAMPLES:: 

 

sage: G = Graph({'a': ['b', 'c']}) 

sage: H = Graph({'c': ['d', 'e', 'f']}) 

sage: J = G + H; J 

Graph on 3 vertices disjoint_union Graph on 4 vertices: Graph on 7 vertices 

sage: J.vertices() 

[0, 1, 2, 3, 4, 5, 6] 

 

TESTS:: 

 

sage: G = Graph({'a': ['b', 'c']}) 

sage: E = Graph() 

sage: G+E 

Graph on 3 vertices disjoint_union Graph on 0 vertices: Graph on 3 vertices 

sage: E+G 

Graph on 0 vertices disjoint_union Graph on 3 vertices: Graph on 3 vertices 

sage: E+E 

Graph on 0 vertices disjoint_union Graph on 0 vertices: Graph on 0 vertices 

sage: G+42 

Traceback (most recent call last): 

... 

TypeError: adding <class 'sage.graphs.graph.Graph'> and <type 'sage.rings.integer.Integer'> is not defined 

""" 

if isinstance(other, GenericGraph): 

return self.disjoint_union(other, labels='integers') 

raise TypeError("adding {} and {} is not defined".format(type(self), type(other))) 

 

def __eq__(self, other): 

""" 

Compare self and other for equality. 

 

Do not call this method directly. That is, for ``G.__eq__(H)`` 

write ``G == H``. 

 

Two graphs are considered equal if the following hold: 

- they are either both directed, or both undirected; 

- they have the same settings for loops, multiedges, and 

weightedness; 

- they have the same set of vertices; 

- they have the same (multi)set of arrows/edges, where labels 

of arrows/edges are taken into account if *and only if* 

the graphs are considered weighted. See 

:meth:`~GenericGraph.weighted`. 

 

Note that this is *not* an isomorphism test. 

 

EXAMPLES:: 

 

sage: G = graphs.EmptyGraph() 

sage: H = Graph() 

sage: G == H 

True 

sage: G.to_directed() == H.to_directed() 

True 

sage: G = graphs.RandomGNP(8,.9999) 

sage: H = graphs.CompleteGraph(8) 

sage: G == H # most often true 

True 

sage: G = Graph( {0:[1,2,3,4,5,6,7]} ) 

sage: H = Graph( {1:[0], 2:[0], 3:[0], 4:[0], 5:[0], 6:[0], 7:[0]} ) 

sage: G == H 

True 

sage: G.allow_loops(True) 

sage: G == H 

False 

sage: G = graphs.RandomGNP(9,.3).to_directed() 

sage: H = graphs.RandomGNP(9,.3).to_directed() 

sage: G == H # most often false 

False 

sage: G = Graph(multiedges=True, sparse=True) 

sage: G.add_edge(0,1) 

sage: H = copy(G) 

sage: H.add_edge(0,1) 

sage: G == H 

False 

 

Note that graphs must be considered weighted, or Sage will not pay 

attention to edge label data in equality testing:: 

 

sage: foo = Graph(sparse=True) 

sage: foo.add_edges([(0, 1, 1), (0, 2, 2)]) 

sage: bar = Graph(sparse=True) 

sage: bar.add_edges([(0, 1, 2), (0, 2, 1)]) 

sage: foo == bar 

True 

sage: foo.weighted(True) 

sage: foo == bar 

False 

sage: bar.weighted(True) 

sage: foo == bar 

False 

 

""" 

# inputs must be (di)graphs: 

if not isinstance(other, GenericGraph): 

return False 

from sage.graphs.all import Graph 

g1_is_graph = isinstance(self, Graph) # otherwise, DiGraph 

g2_is_graph = isinstance(other, Graph) # otherwise, DiGraph 

# Fast checks 

if (g1_is_graph != g2_is_graph or 

self.allows_multiple_edges() != other.allows_multiple_edges() or 

self.allows_loops() != other.allows_loops() or 

self.order() != other.order() or 

self.size() != other.size() or 

self.weighted() != other.weighted()): 

return False 

# Vertices 

if any(x not in other for x in self): 

return False 

# Finally, we are prepared to check edges: 

if not self.allows_multiple_edges(): 

return all(other.has_edge(*edge) 

for edge in self.edge_iterator(labels=self._weighted)) 

# The problem with multiple edges is that labels may not have total 

# ordering, which makes it difficult to compare lists of labels. 

last_i = last_j = None 

for i, j in self.edge_iterator(labels=False): 

if i == last_i and j == last_j: 

continue 

last_i, last_j = i, j 

# All labels between i and j 

labels1 = self.edge_label(i, j) 

try: 

labels2 = other.edge_label(i, j) 

except LookupError: 

return False 

if len(labels1) != len(labels2): 

return False 

if self._weighted: 

# If there is total ordering, sorting will speed up things 

labels1.sort() 

labels2.sort() 

for l in labels1: 

try: 

labels2.remove(l) 

except ValueError: 

return False 

return True 

 

@cached_method 

def __hash__(self): 

""" 

Computes a hash for self, if self is immutable. 

 

Only immutable graphs are hashable. The resulting value is cached. 

 

EXAMPLES:: 

 

sage: G = graphs.PetersenGraph() 

sage: {G:1}[G] 

Traceback (most recent call last): 

... 

TypeError: This graph is mutable, and thus not hashable. Create 

an immutable copy by `g.copy(immutable=True)` 

sage: G_imm = Graph(G, data_structure="static_sparse") 

sage: G_imm == G 

True 

sage: {G_imm:1}[G_imm] # indirect doctest 

1 

sage: G_imm.__hash__() is G_imm.__hash__() 

True 

 

TESTS: 

 

Equality and hash do not depend on ordering of vertices. In other words, 

`G1==G2` can be `True` even when `G1.vertices() == G2.vertices()` is 

`False`. This is parts 1 and 2 of ticket :trac:`17086`. :: 

 

sage: import functools 

sage: @functools.total_ordering 

....: class C: 

....: order = ((0,0), (0,1), (1,1), (1,0)) 

....: # Hasse diagram: 

....: # 0,0 < 0,1 

....: # ^ ^ 

....: # 1,0 > 1,1 

....: def __init__(self, x): 

....: assert x in self.order 

....: self.x = x 

....: def __repr__(self): 

....: return 'C(%r)' % (self.x,) 

....: # ordering depends on full self.x 

....: def __lt__(self, other): 

....: return self.order.index(self.x) < self.order.index(other.x) 

....: # equality depends only on the second coordinate. 

....: def __eq__(self, other): 

....: return self.x[1] == other.x[1] 

....: def __hash__(self): 

....: return hash(self.x[1]) 

sage: G1 = Graph({C((0, 0)): [], C((0, 1)): []}, immutable=True) 

sage: G2 = Graph({C((1, 0)): [], C((1, 1)): []}, immutable=True) 

sage: (G1.vertices(), G2.vertices()) 

([C((0, 0)), C((0, 1))], [C((1, 1)), C((1, 0))]) 

sage: G1 == G2 

True 

sage: G1.__hash__() == G2.__hash__() 

True 

 

Hash of unweighted graphs does not depend on edge labels. That is, 

part 3 of ticket :trac:`17086` is fixed :: 

 

sage: G1 = Graph({0: {1: 'edge label A'}}, immutable=True) 

sage: G2 = Graph({0: {1: 'edge label B'}}, immutable=True) 

sage: G1 == G2 

True 

sage: G1.__hash__() == G2.__hash__() 

True 

 

""" 

if self.is_immutable(): 

edge_items = self.edge_iterator(labels = self._weighted) 

if self.allows_multiple_edges(): 

from collections import Counter 

edge_items = Counter(edge_items).items() 

return hash((frozenset(self.vertex_iterator()), 

self._weighted, 

frozenset(edge_items))) 

raise TypeError("This graph is mutable, and thus not hashable. " 

"Create an immutable copy by `g.copy(immutable=True)`") 

 

def __mul__(self, n): 

""" 

Returns the sum of a graph with itself n times. 

 

EXAMPLES:: 

 

sage: G = graphs.CycleGraph(3) 

sage: H = G*3; H 

Cycle graph disjoint_union Cycle graph disjoint_union Cycle graph: Graph on 9 vertices 

sage: H.vertices() 

[0, 1, 2, 3, 4, 5, 6, 7, 8] 

sage: H = G*1; H 

Cycle graph: Graph on 3 vertices 

""" 

if isinstance(n, integer_types + (Integer,)): 

if n < 1: 

raise TypeError('multiplication of a graph and a nonpositive integer is not defined') 

if n == 1: 

return copy(self) 

return sum([self]*(n-1), self) 

else: 

raise TypeError('multiplication of a graph and something other than an integer is not defined') 

 

def __ne__(self, other): 

""" 

Tests for inequality, complement of __eq__. 

 

EXAMPLES:: 

 

sage: g = Graph() 

sage: g2 = copy(g) 

sage: g == g 

True 

sage: g != g 

False 

sage: g2 == g 

True 

sage: g2 != g 

False 

sage: g is g 

True 

sage: g2 is g 

False 

""" 

return (not (self == other)) 

 

def __rmul__(self, n): 

""" 

Returns the sum of a graph with itself n times. 

 

EXAMPLES:: 

 

sage: G = graphs.CycleGraph(3) 

sage: H = int(3)*G; H 

Cycle graph disjoint_union Cycle graph disjoint_union Cycle graph: Graph on 9 vertices 

sage: H.vertices() 

[0, 1, 2, 3, 4, 5, 6, 7, 8] 

""" 

return self*n 

 

def __str__(self): 

""" 

str(G) returns the name of the graph, unless the name is the empty 

string, in which case it returns the default representation. 

 

EXAMPLES:: 

 

sage: G = graphs.PetersenGraph() 

sage: str(G) 

'Petersen graph' 

""" 

if self.name(): 

return self.name() 

else: 

return repr(self) 

 

def _bit_vector(self): 

""" 

Returns a string representing the edges of the (simple) graph for 

graph6 and dig6 strings. 

 

EXAMPLES:: 

 

sage: G = graphs.PetersenGraph() 

sage: G._bit_vector() 

'101001100110000010000001001000010110000010110' 

sage: len([a for a in G._bit_vector() if a == '1']) 

15 

sage: G.num_edges() 

15 

""" 

self._scream_if_not_simple() 

n = self.order() 

if self._directed: 

total_length = n*n 

bit = lambda x,y : x*n + y 

else: 

total_length = (n*(n - 1))//2 

n_ch_2 = lambda b : int(b*(b-1))//2 

bit = lambda x,y : n_ch_2(max([x,y])) + min([x,y]) 

bit_vector = set() 

 

v_to_int = {v: i for i, v in enumerate(self.vertices())} 

for u,v,_ in self.edge_iterator(): 

bit_vector.add(bit(v_to_int[u], v_to_int[v])) 

bit_vector = sorted(bit_vector) 

s = [] 

j = 0 

for i in bit_vector: 

s.append( '0'*(i - j) + '1' ) 

j = i + 1 

s = "".join(s) 

s += '0'*(total_length-len(s)) 

return s 

 

def _latex_(self): 

r""" 

 

Returns a string to render the graph using LaTeX. 

 

To adjust the string, use the 

:meth:`set_latex_options` method to set options, 

or call the :meth:`latex_options` method to 

get a :class:`~sage.graphs.graph_latex.GraphLatex` 

object that may be used to also customize the 

output produced here. Possible options are documented at 

:meth:`sage.graphs.graph_latex.GraphLatex.set_option`. 

 

EXAMPLES:: 

 

sage: from sage.graphs.graph_latex import check_tkz_graph 

sage: check_tkz_graph() # random - depends on TeX installation 

sage: g = graphs.CompleteGraph(2) 

sage: print(g._latex_()) 

\begin{tikzpicture} 

\definecolor{cv0}{rgb}{0.0,0.0,0.0} 

\definecolor{cfv0}{rgb}{1.0,1.0,1.0} 

\definecolor{clv0}{rgb}{0.0,0.0,0.0} 

\definecolor{cv1}{rgb}{0.0,0.0,0.0} 

\definecolor{cfv1}{rgb}{1.0,1.0,1.0} 

\definecolor{clv1}{rgb}{0.0,0.0,0.0} 

\definecolor{cv0v1}{rgb}{0.0,0.0,0.0} 

% 

\Vertex[style={minimum size=1.0cm,draw=cv0,fill=cfv0,text=clv0,shape=circle},LabelOut=false,L=\hbox{$0$},x=5.0cm,y=5.0cm]{v0} 

\Vertex[style={minimum size=1.0cm,draw=cv1,fill=cfv1,text=clv1,shape=circle},LabelOut=false,L=\hbox{$1$},x=0.0cm,y=0.0cm]{v1} 

% 

\Edge[lw=0.1cm,style={color=cv0v1,},](v0)(v1) 

% 

\end{tikzpicture} 

""" 

from sage.graphs.graph_latex import setup_latex_preamble 

setup_latex_preamble() 

 

return self.latex_options().latex() 

 

def _matrix_(self, R=None): 

""" 

Returns the adjacency matrix of the graph over the specified ring. 

 

EXAMPLES:: 

 

sage: G = graphs.CompleteBipartiteGraph(2,3) 

sage: m = matrix(G); m.parent() 

Full MatrixSpace of 5 by 5 dense matrices over Integer Ring 

sage: m 

[0 0 1 1 1] 

[0 0 1 1 1] 

[1 1 0 0 0] 

[1 1 0 0 0] 

[1 1 0 0 0] 

sage: G._matrix_() 

[0 0 1 1 1] 

[0 0 1 1 1] 

[1 1 0 0 0] 

[1 1 0 0 0] 

[1 1 0 0 0] 

sage: factor(m.charpoly()) 

x^3 * (x^2 - 6) 

""" 

if R is None: 

return self.am() 

else: 

return self.am().change_ring(R) 

 

def _repr_(self): 

""" 

Return a string representation of self. 

 

EXAMPLES:: 

 

sage: G = graphs.PetersenGraph() 

sage: G._repr_() 

'Petersen graph: Graph on 10 vertices' 

""" 

name = "" 

if self.allows_loops(): 

name += "looped " 

if self.allows_multiple_edges(): 

name += "multi-" 

if self._directed: 

name += "di" 

name += "graph on %d vert"%self.order() 

if self.order() == 1: 

name += "ex" 

else: 

name += "ices" 

name = name.capitalize() 

if self.name() != '': 

name = self.name() + ": " + name 

return name 

 

def is_immutable(self): 

""" 

Returns whether the graph is immutable. 

 

EXAMPLES:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.is_immutable() 

False 

sage: Graph(G, immutable=True).is_immutable() 

True 

""" 

return getattr(self, '_immutable', False) 

 

### Formats 

 

def copy(self, weighted=None, implementation='c_graph', data_structure=None, 

sparse=None, immutable=None): 

""" 

Change the graph implementation 

 

INPUT: 

 

- ``weighted`` boolean (default: ``None``) -- weightedness for 

the copy. Might change the equality class if not ``None``. 

 

- ``sparse`` (boolean) -- ``sparse=True`` is an alias for 

``data_structure="sparse"``, and ``sparse=False`` is an alias for 

``data_structure="dense"``. Only used when 

``implementation='c_graph'`` and ``data_structure=None``. 

 

- ``data_structure`` -- one of ``"sparse"``, ``"static_sparse"``, or 

``"dense"``. See the documentation of :class:`Graph` or 

:class:`DiGraph`. Only used when ``implementation='c_graph'``. 

 

- ``immutable`` (boolean) -- whether to create a mutable/immutable 

copy. Only used when ``implementation='c_graph'`` and 

``data_structure=None``. 

 

* ``immutable=None`` (default) means that the graph and its copy will 

behave the same way. 

 

* ``immutable=True`` is a shortcut for 

``data_structure='static_sparse'`` and ``implementation='c_graph'`` 

 

* ``immutable=False`` sets ``implementation`` to ``'c_graph'``. When 

``immutable=False`` is used to copy an immutable graph, the data 

structure used is ``"sparse"`` unless anything else is specified. 

 

.. NOTE:: 

 

If the graph uses 

:class:`~sage.graphs.base.static_sparse_backend.StaticSparseBackend` 

and the ``_immutable`` flag, then ``self`` is returned rather than a 

copy (unless one of the optional arguments is used). 

 

OUTPUT: 

 

A Graph object. 

 

.. warning:: 

 

Please use this method only if you need to copy but change the 

underlying implementation or weightedness. Otherwise simply 

do ``copy(g)`` instead of ``g.copy()``. 

 

.. warning:: 

 

If ``weighted`` is passed and is not the weightedness of the 

original, then the copy will not equal the original. 

 

EXAMPLES:: 

 

sage: g=Graph({0:[0,1,1,2]},loops=True,multiedges=True,sparse=True) 

sage: g==copy(g) 

True 

sage: g=DiGraph({0:[0,1,1,2],1:[0,1]},loops=True,multiedges=True,sparse=True) 

sage: g==copy(g) 

True 

 

Note that vertex associations are also kept:: 

 

sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), 2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() } 

sage: T = graphs.TetrahedralGraph() 

sage: T.set_vertices(d) 

sage: T2 = copy(T) 

sage: T2.get_vertex(0) 

Dodecahedron: Graph on 20 vertices 

 

Notice that the copy is at least as deep as the objects:: 

 

sage: T2.get_vertex(0) is T.get_vertex(0) 

False 

 

Examples of the keywords in use:: 

 

sage: G = graphs.CompleteGraph(19) 

sage: H = G.copy(implementation='c_graph') 

sage: H == G; H is G 

True 

False 

sage: G1 = G.copy(sparse=True) 

sage: G1==G 

True 

sage: G1 is G 

False 

sage: G2 = copy(G) 

sage: G2 is G 

False 

 

Argument ``weighted`` affects the equality class:: 

 

sage: G = graphs.CompleteGraph(5) 

sage: H1 = G.copy(weighted=False) 

sage: H2 = G.copy(weighted=True) 

sage: [G.weighted(), H1.weighted(), H2.weighted()] 

[False, False, True] 

sage: [G == H1, G == H2, H1 == H2] 

[True, False, False] 

sage: G.weighted(True) 

sage: [G == H1, G == H2, H1 == H2] 

[False, True, False] 

 

TESTS: 

 

We make copies of the ``_pos`` attribute:: 

 

sage: g = graphs.PathGraph(3) 

sage: h = copy(g) 

sage: h._pos is g._pos 

False 

 

We make sure that one can make immutable copies by providing the 

``data_structure`` optional argument, and that copying an immutable graph 

returns the graph:: 

 

sage: G = graphs.PetersenGraph() 

sage: hash(G) 

Traceback (most recent call last): 

... 

TypeError: This graph is mutable, and thus not hashable. Create an 

immutable copy by `g.copy(immutable=True)` 

sage: g = G.copy(immutable=True) 

sage: hash(g) # random 

1833517720 

sage: g==G 

True 

sage: (g is g.copy()) and (g is not copy(g)) 

True 

 

``immutable=True`` is a short-cut for ``data_structure='static_sparse'``:: 

 

sage: g is g.copy(data_structure='static_sparse') is g.copy(immutable=True) 

True 

 

If a graph pretends to be immutable, but does not use the 

static sparse backend, then the copy is not identical with the 

graph, even though it is considered to be hashable:: 

 

sage: P = Poset(([1,2,3,4], [[1,3],[1,4],[2,3]]), linear_extension=True, facade = False) 

sage: H = P.hasse_diagram() 

sage: H._immutable = True 

sage: hash(H) # random 

-1843552882 

sage: copy(H) is H 

False 

 

Bad input:: 

 

sage: G.copy(data_structure="sparse", sparse=False) 

Traceback (most recent call last): 

... 

ValueError: You cannot define 'immutable' or 'sparse' when 'data_structure' has a value. 

sage: G.copy(data_structure="sparse", immutable=True) 

Traceback (most recent call last): 

... 

ValueError: You cannot define 'immutable' or 'sparse' when 'data_structure' has a value. 

sage: G.copy(immutable=True, sparse=False) 

Traceback (most recent call last): 

... 

ValueError: There is no dense immutable backend at the moment. 

 

Which backend? :: 

 

sage: G.copy(data_structure="sparse")._backend 

<sage.graphs.base.sparse_graph.SparseGraphBackend object at ...> 

sage: G.copy(data_structure="dense")._backend 

<sage.graphs.base.dense_graph.DenseGraphBackend object at ...> 

sage: G.copy(data_structure="static_sparse")._backend 

<sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...> 

sage: G.copy(immutable=True)._backend 

<sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...> 

sage: G.copy(immutable=True, sparse=True)._backend 

<sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...> 

sage: G.copy(immutable=False, sparse=True)._backend 

<sage.graphs.base.sparse_graph.SparseGraphBackend object at ...> 

sage: G.copy(immutable=False, sparse=False)._backend 

<sage.graphs.base.sparse_graph.SparseGraphBackend object at ...> 

 

Fake immutable graphs:: 

 

sage: G._immutable = True 

sage: G.copy()._backend 

<sage.graphs.base.sparse_graph.SparseGraphBackend object at ...> 

""" 

# Which data structure should be used ? 

if implementation != 'c_graph': 

# We do not care about the value of data_structure. But let's check 

# the user did not define too much. 

if data_structure is not None or immutable is not None or sparse is not None: 

raise ValueError("'data_structure' 'immutable' and 'sparse' can" 

" only be defined when 'implementation'='c_graph'") 

elif data_structure is not None: 

# data_structure is already defined so there is nothing left to do 

# here ! Did the user try to define too much ? 

if immutable is not None or sparse is not None: 

raise ValueError("You cannot define 'immutable' or 'sparse' " 

"when 'data_structure' has a value.") 

# At this point : 

# - implementation is 'c_graph' 

# - data_structure is None. 

elif immutable is True: 

data_structure = 'static_sparse' 

if sparse is False: 

raise ValueError("There is no dense immutable backend at the moment.") 

elif immutable is False: 

# If the users requests a mutable graph and input is immutable, we 

# choose the 'sparse' cgraph backend. Unless the user explicitly 

# asked for something different. 

if self.is_immutable(): 

data_structure = 'dense' if sparse is False else 'sparse' 

elif sparse is True: 

data_structure = "sparse" 

elif sparse is False: 

data_structure = "dense" 

 

# Immutable copy of an immutable graph ? return self ! 

# (if okay for weightedness) 

if (self.is_immutable() and 

(weighted is None or self._weighted == weighted)): 

from sage.graphs.base.static_sparse_backend import StaticSparseBackend 

if (isinstance(self._backend, StaticSparseBackend) and 

implementation=='c_graph' and 

(data_structure=='static_sparse' or data_structure is None)): 

return self 

 

if data_structure is None: 

from sage.graphs.base.dense_graph import DenseGraphBackend 

if isinstance(self._backend, DenseGraphBackend): 

data_structure = "dense" 

else: 

data_structure = "sparse" 

 

G = self.__class__(self, name=self.name(), pos=copy(self._pos), 

weighted=weighted, 

implementation=implementation, 

data_structure=data_structure) 

 

attributes_to_copy = ('_assoc', '_embedding') 

for attr in attributes_to_copy: 

if hasattr(self, attr): 

copy_attr = {} 

old_attr = getattr(self, attr) 

if isinstance(old_attr, dict): 

for v, value in iteritems(old_attr): 

try: 

copy_attr[v] = value.copy() 

except AttributeError: 

copy_attr[v] = copy(value) 

setattr(G, attr, copy_attr) 

else: 

setattr(G, attr, copy(old_attr)) 

 

return G 

 

def __copy__(self): 

""" 

Copy the graph. 

 

OUTPUT: 

 

A new graph instance that is as close as possible to the 

original graph. The output is always mutable. 

 

EXAMPLES: 

 

sage: g = Graph({0:[1,2,3], 2:[4]}, immutable=True) 

sage: g.weighted(list(range(5))) 

Traceback (most recent call last): 

... 

TypeError: This graph is immutable and can thus not be changed. 

Create a mutable copy, e.g., by `copy(g)` 

sage: h = copy(g) # indirect doctest 

sage: h.add_vertex() 

5 

""" 

return self.copy(immutable=False) 

 

def export_to_file(self, filename, format=None, **kwds): 

r""" 

Export the graph to a file. 

 

INPUT: 

 

- ``filename`` (string) -- a file name. 

 

- ``format`` (string) -- select the output format explicitly. If set to 

``None`` (default), the format is set to be the file extension of 

``filename``. Admissible formats are: ``adjlist``, ``dot``, 

``edgelist``, ``gexf``, ``gml``, ``graphml``, ``multiline_adjlist``, 

``pajek``, ``yaml``. 

 

- All other arguments are forwarded to the subfunction. For more 

information, see their respective documentation: 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

``adjlist`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.adjlist.write_adjlist.html 

``dot`` | https://networkx.github.io/documentation/latest/reference/generated/networkx.drawing.nx_pydot.write_dot.html 

``edgelist`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.edgelist.write_edgelist.html 

``gexf`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.gexf.write_gexf.html 

``gml`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.gml.write_gml.html 

``graphml`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.graphml.write_graphml.html 

``multiline_adjlist`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.multiline_adjlist.write_multiline_adjlist.html 

``pajek`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.pajek.write_pajek.html 

``yaml`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.nx_yaml.write_yaml.html 

 

.. SEEALSO:: 

 

* :meth:`~sage.structure.sage_object.SageObject.save` -- save a Sage 

object to a 'sobj' file (preserves all its attributes). 

 

.. NOTE:: 

 

This functions uses the ``write_*`` functions defined in NetworkX 

(see http://networkx.lanl.gov/reference/readwrite.html). 

 

EXAMPLES:: 

 

sage: g = graphs.PetersenGraph() 

sage: filename = tmp_filename(ext=".pajek") 

sage: g.export_to_file(filename) 

sage: import networkx 

sage: G_networkx = networkx.read_pajek(filename) 

sage: Graph(G_networkx).is_isomorphic(g) 

True 

sage: filename = tmp_filename(ext=".edgelist") 

sage: g.export_to_file(filename, data=False) 

sage: h = Graph(networkx.read_edgelist(filename)) 

sage: g.is_isomorphic(h) 

True 

 

TESTS:: 

 

sage: g.export_to_file("hey",format="When I feel heavy metaaaaaallll...") 

Traceback (most recent call last): 

... 

ValueError: Format 'When I feel heavy metaaaaaallll...' unknown. 

sage: g.export_to_file("my_file.Yeeeeppeeeeee") 

Traceback (most recent call last): 

... 

RuntimeError: The file format could not be guessed from 'my_file.Yeeeeppeeeeee' 

""" 

import networkx 

 

formats = {"adjlist" : networkx.write_adjlist, 

"dot" : networkx.drawing.nx_pydot.write_dot, 

"edgelist" : networkx.write_edgelist, 

"gexf" : networkx.write_gexf, 

"gml" : networkx.write_gml, 

"graphml" : networkx.write_graphml, 

"multiline_adjlist" : networkx.write_multiline_adjlist, 

"pajek" : networkx.write_pajek, 

"yaml" : networkx.write_yaml} 

 

if format is None: 

ext = filename[1+filename.rfind("."):] 

if ext not in formats: 

raise RuntimeError("The file format could not be guessed from '{}'".format(filename)) 

format = ext 

 

if format not in formats: 

raise ValueError("Format '{}' unknown.".format(format)) 

 

formats[format](self.networkx_graph(),filename,**kwds) 

 

def _scream_if_not_simple(self, allow_loops=False, allow_multiple_edges=False): 

r""" 

Raises an exception if the graph is not simple. 

 

This function is called by some functions of the Graph library when they 

have been written for simple graphs only (i.e. neither loops nor multiple 

edges). It raises an exception inviting the user to convert the graph to 

a simple graph first, before calling the function again. 

 

Note that this function does not check the existence of loops or 

multiple edges, which would take linear time: it merely checks that the 

graph *does not allow* multiple edges nor loops, which takes a constant 

time. 

 

INPUT: 

 

- ``allow_loops`` (boolean) -- whether to tolerate loops. Set to 

``False`` by default. 

 

- ``allow_multiple_edges`` (boolean) -- whether to tolerate multiple 

edges. Set to ``False`` by default. 

 

.. SEEALSO:: 

 

* :meth:`allow_loops` 

* :meth:`allow_multiple_edges` 

 

EXAMPLES:: 

 

sage: g = graphs.PetersenGraph() 

 

No scream:: 

 

sage: from itertools import product 

sage: for p,q in product((True,False),repeat=2): 

....: g.allow_loops(p) 

....: g.allow_multiple_edges(q) 

....: g._scream_if_not_simple(p,q) 

 

A lot of them:: 

 

sage: g.allow_loops(True); g.allow_multiple_edges(True) 

sage: g._scream_if_not_simple() 

Traceback (most recent call last): 

... 

ValueError: This method is not known to work on graphs with multiedges/loops. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow multiedges/loops using allow_multiple_edges() and allow_loops(). 

sage: g._scream_if_not_simple(allow_loops=True) 

Traceback (most recent call last): 

... 

ValueError: This method is not known to work on graphs with multiedges. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow multiedges using allow_multiple_edges(). 

sage: g._scream_if_not_simple(allow_multiple_edges=True) 

Traceback (most recent call last): 

... 

ValueError: This method is not known to work on graphs with loops. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow loops using allow_loops(). 

 

sage: g.allow_loops(True); g.allow_multiple_edges(False) 

sage: g._scream_if_not_simple() 

Traceback (most recent call last): 

... 

ValueError: This method is not known to work on graphs with loops. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow loops using allow_loops(). 

sage: g._scream_if_not_simple(allow_multiple_edges=True) 

Traceback (most recent call last): 

... 

ValueError: This method is not known to work on graphs with loops. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow loops using allow_loops(). 

""" 

pb_with_loops = not allow_loops and self.allows_loops() 

pb_with_multiple_edges = not allow_multiple_edges and self.allows_multiple_edges() 

if pb_with_loops or pb_with_multiple_edges: 

if pb_with_loops and pb_with_multiple_edges: 

name = "multiedges/loops" 

functions = "allow_multiple_edges() and allow_loops()" 

elif pb_with_loops: 

name = "loops" 

functions = "allow_loops()" 

elif pb_with_multiple_edges: 

name = "multiedges" 

functions = "allow_multiple_edges()" 

msg = ("This method is not known to work on graphs with "+name+". "+ 

"Perhaps this method can be updated to handle them, but in the "+ 

"meantime if you want to use it please disallow "+name+" using "+ 

functions+".") 

raise ValueError(msg) 

 

def networkx_graph(self, copy=True): 

""" 

Creates a new NetworkX graph from the Sage graph. 

 

INPUT: 

 

 

- ``copy`` - if False, and the underlying 

implementation is a NetworkX graph, then the actual object itself 

is returned. 

 

 

EXAMPLES:: 

 

sage: G = graphs.TetrahedralGraph() 

sage: N = G.networkx_graph() 

sage: type(N) 

<class 'networkx.classes.graph.Graph'> 

""" 

 

try: 

if copy: 

return self._backend._nxg.copy() 

else: 

return self._backend._nxg 

except Exception: 

import networkx 

if self._directed and self.allows_multiple_edges(): 

class_type = networkx.MultiDiGraph 

elif self._directed: 

class_type = networkx.DiGraph 

elif self.allows_multiple_edges(): 

class_type = networkx.MultiGraph 

else: 

class_type = networkx.Graph 

N = class_type(selfloops=self.allows_loops(), multiedges=self.allows_multiple_edges(), 

name=self.name()) 

N.add_nodes_from(self.vertices()) 

for u,v,l in self.edges(): 

if l is None: 

N.add_edge(u,v) 

else: 

from networkx import NetworkXError 

try: 

N.add_edge(u,v,l) 

except (TypeError, ValueError, NetworkXError): 

N.add_edge(u,v,weight=l) 

return N 

 

def igraph_graph(self, vertex_attrs={}, edge_attrs={}): 

r""" 

Converts the graph into an igraph graph. 

 

Optionally, it is possible to add vertex attributes and edge attributes 

to the output graph. 

 

.. NOTE:: 

 

This routine needs the optional package igraph to be installed: 

to do so, it is enough to 

run ``sage -i python_igraph``. For 

more information on the Python version of igraph, see 

http://igraph.org/python/. 

 

INPUT: 

 

- ``vertex_attrs`` (dictionary) - a dictionary where the key is a string 

(the attribute name), and the value is an iterable containing in 

position i the label of the ith vertex returned by :meth:`vertices` 

(see http://igraph.org/python/doc/igraph.Graph-class.html#__init__ for 

more information). 

 

- ``edge_attrs`` (dictionary) - a dictionary where the key is a string 

(the attribute name), and the value is an iterable containing in 

position i the label of the ith edge in the list outputted by 

:meth:`edge_iterator` (see 

http://igraph.org/python/doc/igraph.Graph-class.html#__init__ for more 

information). 

 

.. NOTE:: 

 

In igraph, a graph is weighted if the edge labels have attribute 

``weight``. Hence, to create a weighted graph, it is enough to add 

this attribute. 

 

.. NOTE:: 

 

Often, Sage uses its own defined types for integer/floats. These 

types may not be igraph-compatible (see example below). 

 

EXAMPLES: 

 

Standard conversion:: 

 

sage: G = graphs.TetrahedralGraph() # optional - python_igraph 

sage: H = G.igraph_graph() # optional - python_igraph 

sage: H.summary() # optional - python_igraph 

'IGRAPH U--- 4 6 -- ' 

sage: G = digraphs.Path(3) # optional - python_igraph 

sage: H = G.igraph_graph() # optional - python_igraph 

sage: H.summary() # optional - python_igraph 

'IGRAPH D--- 3 2 -- ' 

 

Adding edge attributes:: 

 

sage: G = Graph([(1,2,'a'),(2,3,'b')]) # optional - python_igraph 

sage: H = G.igraph_graph(edge_attrs = {'label':[e[2] for e in G.edges()]}) # optional - python_igraph 

sage: H.es['label'] # optional - python_igraph 

['a', 'b'] 

 

 

If edges have an attribute ``weight``, the igraph graph is considered 

weighted:: 

 

sage: G = Graph([(1,2,{'weight':1}),(2,3,{'weight':2})]) # optional - python_igraph 

sage: H = G.igraph_graph(edge_attrs = {'weight':[e[2]['weight'] for e in G.edges()]}) # optional - python_igraph 

sage: H.is_weighted() # optional - python_igraph 

True 

sage: H.es['weight'] # optional - python_igraph 

[1, 2] 

 

Adding vertex attributes:: 

 

sage: G = graphs.GridGraph([2,2]) # optional - python_igraph 

sage: H = G.igraph_graph(vertex_attrs={'name':G.vertices()}) # optional - python_igraph 

sage: H.vs()['name'] # optional - python_igraph 

[(0, 0), (0, 1), (1, 0), (1, 1)] 

 

Sometimes, Sage integer/floats are not compatible with igraph:: 

 

sage: G = Graph([(0,1,2)]) # optional - python_igraph 

sage: H = G.igraph_graph(edge_attrs = {'capacity':[e[2] for e in G.edges()]}) # optional - python_igraph 

sage: H.maxflow_value(0, 1, 'capacity') # optional - python_igraph 

1.0 

sage: H = G.igraph_graph(edge_attrs = {'capacity':[float(e[2]) for e in G.edges()]}) # optional - python_igraph 

sage: H.maxflow_value(0, 1, 'capacity') # optional - python_igraph 

2.0 

 

TESTS: 

 

Converting a DiGraph back and forth:: 

 

sage: G = DiGraph([('a','b',{'w':1}),('b','c',{'w':2})]) # optional - python_igraph 

sage: vertex_attrs={'name':G.vertices()} # optional - python_igraph 

sage: edge_attrs={'w':[e[2]['w'] for e in G.edges()]} # optional - python_igraph 

sage: H = DiGraph(G.igraph_graph(vertex_attrs, edge_attrs)) # optional - python_igraph 

sage: G == H # optional - python_igraph 

True 

sage: G.edges() == H.edges() # optional - python_igraph 

True 

sage: H = DiGraph(G.igraph_graph(edge_attrs=edge_attrs)) # optional - python_igraph 

sage: G == H # optional - python_igraph 

False 

 

When checking for equality, edge labels are not taken into account:: 

 

sage: H = DiGraph(G.igraph_graph(vertex_attrs)) # optional - python_igraph 

sage: G == H # optional - python_igraph 

True 

sage: G.edges() == H.edges() # optional - python_igraph 

False 

 

Converting a Graph back and forth:: 

 

sage: G = Graph([('a','b',{'w':1}),('b','c',{'w':2})]) # optional - python_igraph 

sage: vertex_attrs={'name':G.vertices()} # optional - python_igraph 

sage: edge_attrs={'w':[e[2]['w'] for e in G.edges()]} # optional - python_igraph 

sage: H = Graph(G.igraph_graph(vertex_attrs, edge_attrs)) # optional - python_igraph 

sage: G == H # optional - python_igraph 

True 

sage: G.edges() == H.edges() # optional - python_igraph 

True 

sage: H = Graph(G.igraph_graph(edge_attrs=edge_attrs)) # optional - python_igraph 

sage: G == H # optional - python_igraph 

False 

 

When checking for equality, edge labels are not taken into account:: 

 

sage: H = Graph(G.igraph_graph(vertex_attrs)) # optional - python_igraph 

sage: G == H # optional - python_igraph 

True 

sage: G.edges() == H.edges() # optional - python_igraph 

False 

""" 

try: 

import igraph 

except ImportError: 

raise PackageNotFoundError("python_igraph") 

 

v_to_int = {v: i for i, v in enumerate(self.vertices())} 

edges = [(v_to_int[v], v_to_int[w]) for v,w in self.edge_iterator(labels=False)] 

 

return igraph.Graph(n = self.num_verts(), 

edges = edges, 

directed=self.is_directed(), 

vertex_attrs = vertex_attrs, 

edge_attrs = edge_attrs) 

 

def to_dictionary(self, edge_labels=False, multiple_edges=False): 

r""" 

Returns the graph as a dictionary. 

 

INPUT: 

 

- ``edge_labels`` (boolean) -- whether to include edge labels in the 

output. 

 

- ``multiple_edges`` (boolean) -- whether to include multiple edges in 

the output. 

 

OUTPUT: 

 

The output depends on the input: 

 

* If ``edge_labels == False`` and ``multiple_edges == False``, the 

output is a dictionary associating to each vertex the list of its 

neighbors. 

 

* If ``edge_labels == False`` and ``multiple_edges == True``, the output 

is a dictionary the same as previously with one difference: the 

neighbors are listed with multiplicity. 

 

* If ``edge_labels == True`` and ``multiple_edges == False``, the output 

is a dictionary associating to each vertex `u` [a dictionary 

associating to each vertex `v` incident to `u` the label of edge 

`(u,v)`]. 

 

* If ``edge_labels == True`` and ``multiple_edges == True``, the output 

is a dictionary associating to each vertex `u` [a dictionary 

associating to each vertex `v` incident to `u` [the list of labels of 

all edges between `u` and `v`]]. 

 

.. NOTE:: 

 

When used on directed graphs, the explanations above can be understood 

by replacing the word "neighbors" by "out-neighbors" 

 

EXAMPLES:: 

 

sage: g = graphs.PetersenGraph().to_dictionary() 

sage: [(key, sorted(g[key])) for key in g] 

[(0, [1, 4, 5]), 

(1, [0, 2, 6]), 

(2, [1, 3, 7]), 

(3, [2, 4, 8]), 

(4, [0, 3, 9]), 

(5, [0, 7, 8]), 

(6, [1, 8, 9]), 

(7, [2, 5, 9]), 

(8, [3, 5, 6]), 

(9, [4, 6, 7])] 

sage: graphs.PetersenGraph().to_dictionary(multiple_edges=True) 

{0: [1, 4, 5], 1: [0, 2, 6], 

2: [1, 3, 7], 3: [2, 4, 8], 

4: [0, 3, 9], 5: [0, 7, 8], 

6: [1, 8, 9], 7: [2, 5, 9], 

8: [3, 5, 6], 9: [4, 6, 7]} 

sage: graphs.PetersenGraph().to_dictionary(edge_labels=True) 

{0: {1: None, 4: None, 5: None}, 

1: {0: None, 2: None, 6: None}, 

2: {1: None, 3: None, 7: None}, 

3: {2: None, 4: None, 8: None}, 

4: {0: None, 3: None, 9: None}, 

5: {0: None, 7: None, 8: None}, 

6: {1: None, 8: None, 9: None}, 

7: {2: None, 5: None, 9: None}, 

8: {3: None, 5: None, 6: None}, 

9: {4: None, 6: None, 7: None}} 

sage: graphs.PetersenGraph().to_dictionary(edge_labels=True,multiple_edges=True) 

{0: {1: [None], 4: [None], 5: [None]}, 

1: {0: [None], 2: [None], 6: [None]}, 

2: {1: [None], 3: [None], 7: [None]}, 

3: {2: [None], 4: [None], 8: [None]}, 

4: {0: [None], 3: [None], 9: [None]}, 

5: {0: [None], 7: [None], 8: [None]}, 

6: {1: [None], 8: [None], 9: [None]}, 

7: {2: [None], 5: [None], 9: [None]}, 

8: {3: [None], 5: [None], 6: [None]}, 

9: {4: [None], 6: [None], 7: [None]}} 

""" 

 

# Returning the resuls as a dictionary of lists 

# 

# dictionary : 

# {vertex : [list of (out-)neighbors]} 

 

if not edge_labels and not multiple_edges: 

d = {} 

 

if self.is_directed(): 

for u in self: 

d[u]=self.neighbors_out(u) 

else: 

for u in self: 

d[u]=self.neighbors(u) 

 

 

# Returning the result as a dictionary of lists 

# 

# dictionary : 

# {vertex : [list of (out-)neighbors, with multiplicity]} 

elif not edge_labels and multiple_edges: 

d={v:[] for v in self} 

 

if self.is_directed(): 

for u,v in self.edge_iterator(labels = False): 

d[u].append(v) 

 

else: 

for u,v in self.edge_iterator(labels = False): 

d[u].append(v) 

d[v].append(u) 

 

# Returning the result as a dictionary of dictionaries 

# 

# Each vertex is associated with the dictionary associating to each of 

# its neighbors the corresponding edge label. 

# 

# dictionary : 

# {v : dictionary } 

# {neighbor u of v : label of edge u,v} 

 

elif edge_labels and not multiple_edges: 

d={v:{} for v in self} 

 

if self.is_directed(): 

for u,v,l in self.edge_iterator(): 

d[u][v] = l 

 

else: 

for u,v,l in self.edge_iterator(): 

d[u][v] = l 

d[v][u] = l 

 

# Returning the result as a dictionary of dictionaries 

# 

# Each vertex is associated with the dictionary associating to each of 

# its neighbors the list of edge labels between the two vertices 

# 

# dictionary : 

# {v : dictionary } 

# {neighbor u of v : [labels of edges between u and v]} 

 

elif edge_labels and multiple_edges: 

d={v:{} for v in self} 

 

if self.is_directed(): 

for u,v,l in self.edge_iterator(): 

if not v in d[u]: 

d[u][v] = [] 

d[u][v].append(l) 

 

else: 

for u,v,l in self.edge_iterator(): 

if not v in d[u]: 

d[u][v] = [] 

d[v][u] = [] 

 

d[u][v].append(l) 

d[v][u].append(l) 

 

return d 

 

def adjacency_matrix(self, sparse=None, vertices=None): 

r""" 

Returns the adjacency matrix of the (di)graph. 

 

The matrix returned is over the integers. If a different ring 

is desired, use either 

:meth:`sage.matrix.matrix0.Matrix.change_ring` method or 

:class:`matrix <sage.matrix.constructor.MatrixFactory>` 

function. 

 

INPUT: 

 

- ``sparse`` - whether to represent with a sparse matrix 

 

- ``vertices`` (list) -- the ordering of the vertices defining how they 

should appear in the matrix. By default, the ordering given by 

:meth:`GenericGraph.vertices` is used. 

 

EXAMPLES:: 

 

sage: G = graphs.CubeGraph(4) 

sage: G.adjacency_matrix() 

[0 1 1 0 1 0 0 0 1 0 0 0 0 0 0 0] 

[1 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0] 

[1 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0] 

[0 1 1 0 0 0 0 1 0 0 0 1 0 0 0 0] 

[1 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0] 

[0 1 0 0 1 0 0 1 0 0 0 0 0 1 0 0] 

[0 0 1 0 1 0 0 1 0 0 0 0 0 0 1 0] 

[0 0 0 1 0 1 1 0 0 0 0 0 0 0 0 1] 

[1 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0] 

[0 1 0 0 0 0 0 0 1 0 0 1 0 1 0 0] 

[0 0 1 0 0 0 0 0 1 0 0 1 0 0 1 0] 

[0 0 0 1 0 0 0 0 0 1 1 0 0 0 0 1] 

[0 0 0 0 1 0 0 0 1 0 0 0 0 1 1 0] 

[0 0 0 0 0 1 0 0 0 1 0 0 1 0 0 1] 

[0 0 0 0 0 0 1 0 0 0 1 0 1 0 0 1] 

[0 0 0 0 0 0 0 1 0 0 0 1 0 1 1 0] 

 

:: 

 

sage: matrix(GF(2),G) # matrix over GF(2) 

[0 1 1 0 1 0 0 0 1 0 0 0 0 0 0 0] 

[1 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0] 

[1 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0] 

[0 1 1 0 0 0 0 1 0 0 0 1 0 0 0 0] 

[1 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0] 

[0 1 0 0 1 0 0 1 0 0 0 0 0 1 0 0] 

[0 0 1 0 1 0 0 1 0 0 0 0 0 0 1 0] 

[0 0 0 1 0 1 1 0 0 0 0 0 0 0 0 1] 

[1 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0] 

[0 1 0 0 0 0 0 0 1 0 0 1 0 1 0 0] 

[0 0 1 0 0 0 0 0 1 0 0 1 0 0 1 0] 

[0 0 0 1 0 0 0 0 0 1 1 0 0 0 0 1] 

[0 0 0 0 1 0 0 0 1 0 0 0 0 1 1 0] 

[0 0 0 0 0 1 0 0 0 1 0 0 1 0 0 1] 

[0 0 0 0 0 0 1 0 0 0 1 0 1 0 0 1] 

[0 0 0 0 0 0 0 1 0 0 0 1 0 1 1 0] 

 

:: 

 

sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } ) 

sage: D.adjacency_matrix() 

[0 1 1 1 0 0] 

[1 0 1 0 0 0] 

[0 0 0 1 0 0] 

[0 0 0 0 1 0] 

[1 0 0 0 0 1] 

[0 1 0 0 0 0] 

 

A different ordering of the vertices:: 

 

sage: graphs.PathGraph(5).adjacency_matrix(vertices=[2,4,1,3,0]) 

[0 0 1 1 0] 

[0 0 0 1 0] 

[1 0 0 0 1] 

[1 1 0 0 0] 

[0 0 1 0 0] 

 

 

TESTS:: 

 

sage: graphs.CubeGraph(8).adjacency_matrix().parent() 

Full MatrixSpace of 256 by 256 dense matrices over Integer Ring 

sage: graphs.CubeGraph(9).adjacency_matrix().parent() 

Full MatrixSpace of 512 by 512 sparse matrices over Integer Ring 

sage: Graph([(i,i+1) for i in range(500)]+[(0,1),], multiedges=True).adjacency_matrix().parent() 

Full MatrixSpace of 501 by 501 dense matrices over Integer Ring 

sage: graphs.PathGraph(5).adjacency_matrix(vertices=[0,0,0,0,0]) 

Traceback (most recent call last): 

... 

ValueError: ``vertices`` must be a permutation of the vertices 

sage: graphs.PathGraph(5).adjacency_matrix(vertices=[1,2,3]) 

Traceback (most recent call last): 

... 

ValueError: ``vertices`` must be a permutation of the vertices 

""" 

n = self.order() 

if sparse is None: 

sparse=True 

if self.has_multiple_edges() or n <= 256 or self.density() > 0.05: 

sparse=False 

 

if vertices is None: 

vertices = self.vertices() 

elif (len(vertices) != n or 

set(vertices) != set(self.vertices())): 

raise ValueError("``vertices`` must be a permutation of the vertices") 

 

new_indices = dict((v,i) for i,v in enumerate(vertices)) 

D = {} 

directed = self._directed 

multiple_edges = self.allows_multiple_edges() 

for i,j,l in self.edge_iterator(): 

i = new_indices[i] 

j = new_indices[j] 

if multiple_edges and (i,j) in D: 

D[(i,j)] += 1 

if not directed and i != j: 

D[(j,i)] += 1 

else: 

D[(i,j)] = 1 

if not directed and i != j: 

D[(j,i)] = 1 

from sage.matrix.constructor import matrix 

M = matrix(ZZ, n, n, D, sparse=sparse) 

return M 

 

am = adjacency_matrix # shorter call makes life easier 

 

def incidence_matrix(self, oriented=None, sparse=True): 

""" 

Return the incidence matrix of the (di)graph. 

 

Each row is a vertex, and each column is an edge. The vertices as 

ordered as obtained by the method :meth:`vertices` and the edges as 

obtained by the method :meth:`edge_iterator`. 

 

If the graph is not directed, then return a matrix with entries in 

`\{0,1,2\}`. Each column will either contain two `1` (at the position of 

the endpoint of the edge), or one `2` (if the corresponding edge is a 

loop). 

 

If the graph is directed return a matrix in `\{-1,0,1\}` where `-1` and 

`+1` correspond respectively to the source and the target of the edge. A 

loop will correspond to a zero column. In particular, it is not possible 

to recover the loops of an oriented graph from its incidence matrix. 

 

See :wikipedia:`Incidence_Matrix` for more informations. 

 

INPUT: 

 

- ``oriented`` -- an optional boolean. If set to ``True``, the matrix 

will be oriented (i.e. with entries in `-1`, `0`, `1`) and if set to 

``False`` the matrix will be not oriented (i.e. with entries in `0`, 

`1`, `2`). By default, this argument is inferred from the graph type. 

Note that in the case the graph is not directed and with the option 

``directed=True``, a somewhat random direction is chosen for each 

edge. 

 

- ``sparse`` -- default to ``True``, whether to use a sparse or a dense 

matrix. 

 

EXAMPLES:: 

 

sage: G = graphs.CubeGraph(3) 

sage: G.incidence_matrix() 

[0 1 0 0 0 1 0 1 0 0 0 0] 

[0 0 0 1 0 1 1 0 0 0 0 0] 

[1 1 1 0 0 0 0 0 0 0 0 0] 

[1 0 0 1 1 0 0 0 0 0 0 0] 

[0 0 0 0 0 0 0 1 0 0 1 1] 

[0 0 0 0 0 0 1 0 0 1 0 1] 

[0 0 1 0 0 0 0 0 1 0 1 0] 

[0 0 0 0 1 0 0 0 1 1 0 0] 

sage: G.incidence_matrix(oriented=True) 

[ 0 -1 0 0 0 -1 0 -1 0 0 0 0] 

[ 0 0 0 -1 0 1 -1 0 0 0 0 0] 

[-1 1 -1 0 0 0 0 0 0 0 0 0] 

[ 1 0 0 1 -1 0 0 0 0 0 0 0] 

[ 0 0 0 0 0 0 0 1 0 0 -1 -1] 

[ 0 0 0 0 0 0 1 0 0 -1 0 1] 

[ 0 0 1 0 0 0 0 0 -1 0 1 0] 

[ 0 0 0 0 1 0 0 0 1 1 0 0] 

 

sage: G = digraphs.Circulant(4, [1,3]) 

sage: G.incidence_matrix() 

[-1 -1 1 0 0 0 1 0] 

[ 1 0 -1 -1 1 0 0 0] 

[ 0 0 0 1 -1 -1 0 1] 

[ 0 1 0 0 0 1 -1 -1] 

 

sage: graphs.CompleteGraph(3).incidence_matrix() 

[1 1 0] 

[1 0 1] 

[0 1 1] 

sage: G = Graph([(0,0),(0,1),(0,1)], loops=True, multiedges=True) 

sage: G.incidence_matrix(oriented=False) 

[2 1 1] 

[0 1 1] 

 

A well known result states that the product of the (oriented) incidence 

matrix with its transpose of a (non-oriented graph) is in fact the 

Kirchhoff matrix:: 

 

sage: G = graphs.PetersenGraph() 

sage: m = G.incidence_matrix(oriented=True) 

sage: m * m.transpose() == G.kirchhoff_matrix() 

True 

 

sage: K = graphs.CompleteGraph(3) 

sage: m = K.incidence_matrix(oriented=True) 

sage: m * m.transpose() == K.kirchhoff_matrix() 

True 

 

sage: H = Graph([(0,0),(0,1),(0,1)], loops=True, multiedges=True) 

sage: m = H.incidence_matrix(oriented=True) 

sage: m * m.transpose() == H.kirchhoff_matrix() 

True 

""" 

if oriented is None: 

oriented = self.is_directed() 

from sage.matrix.constructor import matrix 

from sage.rings.integer_ring import ZZ 

m = matrix(ZZ, self.num_verts(), self.num_edges(), sparse=sparse) 

verts = {v: i for i, v in enumerate(self.vertices())} 

 

if oriented: 

for e, (i, j) in enumerate(self.edge_iterator(labels=False)): 

if i != j: 

m[verts[i],e] = -1 

m[verts[j],e] = +1 

else: 

for e, (i, j) in enumerate(self.edge_iterator(labels=False)): 

m[verts[i],e] += 1 

m[verts[j],e] += 1 

 

return m 

 

def distance_matrix(self): 

""" 

Returns the distance matrix of the (strongly) connected (di)graph. 

 

The distance matrix of a (strongly) connected (di)graph is a matrix whose 

rows and columns are indexed with the vertices of the (di) graph. The 

intersection of a row and column contains the respective distance between 

the vertices indexed at these position. 

 

.. WARNING:: 

 

The ordering of vertices in the matrix has no reason to correspond 

to the order of vertices in 

:meth:`~sage.graphs.generic_graph.GenericGraph.vertices`. In 

particular, if two integers `i,j` are vertices of a graph `G` with 

distance matrix ``M``, then ``M[i][i]`` is not necessarily the 

distance between vertices `i` and `j`. 

 

EXAMPLES:: 

 

sage: G = graphs.CubeGraph(3) 

sage: G.distance_matrix() 

[0 1 1 2 1 2 2 3] 

[1 0 2 1 2 1 3 2] 

[1 2 0 1 2 3 1 2] 

[2 1 1 0 3 2 2 1] 

[1 2 2 3 0 1 1 2] 

[2 1 3 2 1 0 2 1] 

[2 3 1 2 1 2 0 1] 

[3 2 2 1 2 1 1 0] 

 

The well known result of Graham and Pollak states that the determinant of 

the distance matrix of any tree of order n is (-1)^{n-1}(n-1)2^{n-2} :: 

 

sage: all(T.distance_matrix().det() == (-1)^9*(9)*2^8 for T in graphs.trees(10)) 

True 

 

.. SEEALSO:: 

 

* :meth:`~sage.graphs.generic_graph.GenericGraph.distance_all_pairs` 

-- computes the distance between any two vertices. 

""" 

 

from sage.matrix.constructor import matrix 

 

n = self.order() 

ret = matrix(n,n) 

V = self.vertices() 

 

dist = self.distance_all_pairs() 

 

for i in range(n): 

for j in range(i+1,n): 

d = (dist[V[i]])[V[j]] 

if d > n : 

raise ValueError("Input (di)graph must be (strongly) connected.") 

ret[i,j] = ret[j,i] = d 

 

return ret 

 

def weighted_adjacency_matrix(self, sparse=True): 

""" 

Returns the weighted adjacency matrix of the graph. 

 

Each vertex is represented by its position in the list returned by the 

vertices() function. 

 

EXAMPLES:: 

 

sage: G = Graph(sparse=True, weighted=True) 

sage: G.add_edges([(0,1,1),(1,2,2),(0,2,3),(0,3,4)]) 

sage: M = G.weighted_adjacency_matrix(); M 

[0 1 3 4] 

[1 0 2 0] 

[3 2 0 0] 

[4 0 0 0] 

sage: H = Graph(data=M, format='weighted_adjacency_matrix', sparse=True) 

sage: H == G 

True 

 

TESTS: 

 

The following doctest verifies that :trac:`4888` is fixed:: 

 

sage: G = DiGraph({0:{}, 1:{0:1}, 2:{0:1}}, weighted = True,sparse=True) 

sage: G.weighted_adjacency_matrix() 

[0 0 0] 

[1 0 0] 

[1 0 0] 

""" 

if self.has_multiple_edges(): 

raise NotImplementedError("don't know how to represent weights for a multigraph") 

 

verts = self.vertices() 

new_indices = dict((v,i) for i,v in enumerate(verts)) 

 

D = {} 

if self._directed: 

for i,j,l in self.edge_iterator(): 

i = new_indices[i] 

j = new_indices[j] 

D[(i,j)] = l 

else: 

for i,j,l in self.edge_iterator(): 

i = new_indices[i] 

j = new_indices[j] 

D[(i,j)] = l 

D[(j,i)] = l 

from sage.matrix.constructor import matrix 

M = matrix(self.num_verts(), D, sparse=sparse) 

return M 

 

def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, **kwds): 

""" 

Returns the Kirchhoff matrix (a.k.a. the Laplacian) of the graph. 

 

The Kirchhoff matrix is defined to be `D - M`, where `D` is 

the diagonal degree matrix (each diagonal entry is the degree 

of the corresponding vertex), and `M` is the adjacency matrix. 

If ``normalized`` is ``True``, then the returned matrix is 

`D^{-1/2}(D-M)D^{-1/2}`. 

 

( In the special case of DiGraphs, `D` is defined as the diagonal 

in-degree matrix or diagonal out-degree matrix according to the 

value of ``indegree``) 

 

INPUT: 

 

- ``weighted`` -- Binary variable : 

- If ``True``, the weighted adjacency matrix is used for `M`, 

and the diagonal matrix `D` takes into account the weight of edges 

(replace in the definition "degree" by "sum of the incident edges" ). 

- Else, each edge is assumed to have weight 1. 

 

Default is to take weights into consideration if and only if the graph is 

weighted. 

 

- ``indegree`` -- Binary variable : 

- If ``True``, each diagonal entry of `D` is equal to the 

in-degree of the corresponding vertex. 

- Else, each diagonal entry of `D` is equal to the 

out-degree of the corresponding vertex. 

 

By default, ``indegree`` is set to ``True`` 

 

( This variable only matters when the graph is a digraph ) 

 

- ``normalized`` -- Binary variable : 

 

- If ``True``, the returned matrix is 

`D^{-1/2}(D-M)D^{-1/2}`, a normalized version of the 

Laplacian matrix. 

(More accurately, the normalizing matrix used is equal to `D^{-1/2}` 

only for non-isolated vertices. If vertex `i` is isolated, then 

diagonal entry `i` in the matrix is 1, rather than a division by 

zero.) 

- Else, the matrix `D-M` is returned 

 

Note that any additional keywords will be passed on to either 

the ``adjacency_matrix`` or ``weighted_adjacency_matrix`` method. 

 

AUTHORS: 

 

- Tom Boothby 

- Jason Grout 

 

EXAMPLES:: 

 

sage: G = Graph(sparse=True) 

sage: G.add_edges([(0,1,1),(1,2,2),(0,2,3),(0,3,4)]) 

sage: M = G.kirchhoff_matrix(weighted=True); M 

[ 8 -1 -3 -4] 

[-1 3 -2 0] 

[-3 -2 5 0] 

[-4 0 0 4] 

sage: M = G.kirchhoff_matrix(); M 

[ 3 -1 -1 -1] 

[-1 2 -1 0] 

[-1 -1 2 0] 

[-1 0 0 1] 

sage: M = G.laplacian_matrix(normalized=True); M 

[ 1 -1/6*sqrt(3)*sqrt(2) -1/6*sqrt(3)*sqrt(2) -1/3*sqrt(3)] 

[-1/6*sqrt(3)*sqrt(2) 1 -1/2 0] 

[-1/6*sqrt(3)*sqrt(2) -1/2 1 0] 

[ -1/3*sqrt(3) 0 0 1] 

 

sage: Graph({0:[],1:[2]}).laplacian_matrix(normalized=True) 

[ 0 0 0] 

[ 0 1 -1] 

[ 0 -1 1] 

 

A weighted directed graph with loops, changing the variable ``indegree`` :: 

 

sage: G = DiGraph({1:{1:2,2:3}, 2:{1:4}}, weighted=True,sparse=True) 

sage: G.laplacian_matrix() 

[ 4 -3] 

[-4 3] 

 

:: 

 

sage: G = DiGraph({1:{1:2,2:3}, 2:{1:4}}, weighted=True,sparse=True) 

sage: G.laplacian_matrix(indegree=False) 

[ 3 -3] 

[-4 4] 

""" 

from sage.matrix.constructor import diagonal_matrix 

from sage.functions.all import sqrt 

 

if weighted is None: 

weighted = self._weighted 

 

if weighted: 

M = self.weighted_adjacency_matrix(**kwds) 

else: 

M = self.adjacency_matrix(**kwds) 

 

D = M.parent(0) 

 

if M.is_sparse(): 

row_sums = {} 

if indegree: 

for (i,j), entry in iteritems(M.dict()): 

row_sums[j] = row_sums.get(j, 0) + entry 

else: 

for (i,j), entry in iteritems(M.dict()): 

row_sums[i] = row_sums.get(i, 0) + entry 

 

 

for i in range(M.nrows()): 

D[i,i] += row_sums.get(i, 0) 

 

else: 

if indegree: 

col_sums=[sum(v) for v in M.columns()] 

for i in range(M.nrows()): 

D[i,i] += col_sums[i] 

else: 

row_sums=[sum(v) for v in M.rows()] 

for i in range(M.nrows()): 

D[i,i] += row_sums[i] 

 

if normalized: 

Dsqrt = diagonal_matrix([1/sqrt(D[i,i]) if D[i,i]>0 else 1 \ 

for i in range(D.nrows())]) 

return Dsqrt*(D-M)*Dsqrt 

else: 

return D-M 

 

laplacian_matrix = kirchhoff_matrix 

 

### Attributes 

 

def set_embedding(self, embedding): 

""" 

Sets a combinatorial embedding dictionary to ``_embedding`` attribute. 

 

Dictionary is organized with vertex labels as keys and a list of 

each vertex's neighbors in clockwise order. 

 

Dictionary is error-checked for validity. 

 

INPUT: 

 

- ``embedding`` - a dictionary 

 

EXAMPLES:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.set_embedding({0: [1, 5, 4], 1: [0, 2, 6], 2: [1, 3, 7], 3: [8, 2, 4], 4: [0, 9, 3], 5: [0, 8, 7], 6: [8, 1, 9], 7: [9, 2, 5], 8: [3, 5, 6], 9: [4, 6, 7]}) 

sage: G.set_embedding({'s': [1, 5, 4], 1: [0, 2, 6], 2: [1, 3, 7], 3: [8, 2, 4], 4: [0, 9, 3], 5: [0, 8, 7], 6: [8, 1, 9], 7: [9, 2, 5], 8: [3, 5, 6], 9: [4, 6, 7]}) 

Traceback (most recent call last): 

... 

ValueError: The following vertices from the embedding do not belong to the graph: ['s'] 

""" 

self._check_embedding_validity(embedding,boolean=False) 

self._embedding = embedding 

 

def get_embedding(self): 

""" 

Returns the attribute _embedding if it exists. 

 

``_embedding`` is a dictionary organized with vertex labels as keys and a 

list of each vertex's neighbors in clockwise order. 

 

Error-checked to insure valid embedding is returned. 

 

EXAMPLES:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.genus() 

1 

sage: G.get_embedding() 

{0: [1, 4, 5], 1: [0, 2, 6], 2: [1, 3, 7], 3: [2, 4, 8], 4: [0, 3, 9], 5: [0, 7, 8], 6: [1, 9, 8], 7: [2, 5, 9], 8: [3, 6, 5], 9: [4, 6, 7]} 

""" 

if self._check_embedding_validity(): 

return self._embedding 

else: 

raise ValueError('%s has been modified and the embedding is no longer valid'%self) 

 

def _check_embedding_validity(self, embedding=None, boolean=True): 

""" 

Checks whether an _embedding attribute is well defined. 

 

INPUT: 

 

- ``embedding`` -- the embedding to test. If set to ``None`` (default), 

the test is performed on ``_embedding`` 

 

- ``boolean`` (boolean; ``True``) -- whether to return a boolean answer 

or raise a ``ValueError`` exception if the embedding is invalid. 

 

EXAMPLES:: 

 

sage: d = {0: [1, 5, 4], 1: [0, 2, 6], 2: [1, 3, 7], 3: [8, 2, 4], 4: [0, 9, 3], 5: [0, 8, 7], 6: [8, 1, 9], 7: [9, 2, 5], 8: [3, 5, 6], 9: [4, 6, 7]} 

sage: G = graphs.PetersenGraph() 

sage: G._check_embedding_validity(d) 

True 

 

Exceptions:: 

 

sage: g = graphs.PathGraph(2) 

sage: g._check_embedding_validity(boolean=False) 

Traceback (most recent call last): 

... 

ValueError: No embedding has been defined 

sage: g._check_embedding_validity({8:[],9:[]},boolean=False) 

Traceback (most recent call last): 

... 

ValueError: The following vertices from the embedding do not belong to the graph: [8, 9] 

sage: g._check_embedding_validity({0:[],1:[0]},boolean=False) 

Traceback (most recent call last): 

... 

ValueError: The list associated with vertex 0 has length 0 but d(0)=1 

sage: g._check_embedding_validity({0:[5],1:[0]},boolean=False) 

Traceback (most recent call last): 

... 

ValueError: 5 and 0 are not neighbors but 5 is in the list associated with 0 

sage: graphs.PathGraph(3)._check_embedding_validity({0:[1],1:[0,0],2:[1]},boolean=False) 

Traceback (most recent call last): 

... 

ValueError: The list associated with vertex 1 contains >1 occurrences of: [0] 

 

""" 

if embedding is None: 

embedding = getattr(self, '_embedding', None) 

if embedding is None: 

if boolean: 

return False 

raise ValueError("No embedding has been defined") 

 

if set(embedding) != set(self): 

if boolean: 

return False 

if set(embedding).difference(self): 

raise ValueError("The following vertices from the embedding do not belong to the graph: {}".format(list(set(embedding).difference(self)))) 

else: 

raise ValueError("The following vertices have no corresponding entry in the embedding: {}".format(list(set(self).difference(embedding)))) 

 

if self._directed: 

connected = lambda u,v : self.has_edge(u,v) or self.has_edge(v,u) 

else: 

connected = lambda u,v : self.has_edge(u,v) 

for v in embedding: 

if len(embedding[v]) != self.degree(v): 

if boolean: 

return False 

raise ValueError("The list associated with vertex {} has length {} but d({})={}".format(v,len(embedding[v]),v,self.degree(v))) 

if len(embedding[v]) != len(set(embedding[v])): 

if boolean: 

return False 

raise ValueError("The list associated with vertex {} contains >1 occurrences of: {}".format(v,[x for x in set(embedding[v]) if embedding[v].count(x)>1])) 

for u in embedding[v]: 

if not connected(v,u): 

if boolean: 

return False 

raise ValueError("{} and {} are not neighbors but {} is in the list associated with {}".format(u,v,u,v)) 

return True 

 

def has_loops(self): 

""" 

Return whether there are loops in the (di)graph 

 

EXAMPLES:: 

 

sage: G = Graph(loops=True); G 

Looped graph on 0 vertices 

sage: G.has_loops() 

False 

sage: G.allows_loops() 

True 

sage: G.add_edge((0,0)) 

sage: G.has_loops() 

True 

sage: G.loops() 

[(0, 0, None)] 

sage: G.allow_loops(False); G 

Graph on 1 vertex 

sage: G.has_loops() 

False 

sage: G.edges() 

[] 

 

sage: D = DiGraph(loops=True); D 

Looped digraph on 0 vertices 

sage: D.has_loops() 

False 

sage: D.allows_loops() 

True 

sage: D.add_edge((0,0)) 

sage: D.has_loops() 

True 

sage: D.loops() 

[(0, 0, None)] 

sage: D.allow_loops(False); D 

Digraph on 1 vertex 

sage: D.has_loops() 

False 

sage: D.edges() 

[] 

""" 

if self.allows_loops(): 

for v in self: 

if self.has_edge(v,v): 

return True 

return False 

 

def allows_loops(self): 

""" 

Return whether loops are permitted in the (di)graph 

 

EXAMPLES:: 

 

sage: G = Graph(loops=True); G 

Looped graph on 0 vertices 

sage: G.has_loops() 

False 

sage: G.allows_loops() 

True 

sage: G.add_edge((0,0)) 

sage: G.has_loops() 

True 

sage: G.loops() 

[(0, 0, None)] 

sage: G.allow_loops(False); G 

Graph on 1 vertex 

sage: G.has_loops() 

False 

sage: G.edges() 

[] 

 

sage: D = DiGraph(loops=True); D 

Looped digraph on 0 vertices 

sage: D.has_loops() 

False 

sage: D.allows_loops() 

True 

sage: D.add_edge((0,0)) 

sage: D.has_loops() 

True 

sage: D.loops() 

[(0, 0, None)] 

sage: D.allow_loops(False); D 

Digraph on 1 vertex 

sage: D.has_loops() 

False 

sage: D.edges() 

[] 

""" 

return self._backend.loops(None) 

 

def allow_loops(self, new, check=True): 

""" 

Change whether loops are permitted in the (di)graph 

 

INPUT: 

 

- ``new`` - boolean. 

 

EXAMPLES:: 

 

sage: G = Graph(loops=True); G 

Looped graph on 0 vertices 

sage: G.has_loops() 

False 

sage: G.allows_loops() 

True 

sage: G.add_edge((0,0)) 

sage: G.has_loops() 

True 

sage: G.loops() 

[(0, 0, None)] 

sage: G.allow_loops(False); G 

Graph on 1 vertex 

sage: G.has_loops() 

False 

sage: G.edges() 

[] 

 

sage: D = DiGraph(loops=True); D 

Looped digraph on 0 vertices 

sage: D.has_loops() 

False 

sage: D.allows_loops() 

True 

sage: D.add_edge((0,0)) 

sage: D.has_loops() 

True 

sage: D.loops() 

[(0, 0, None)] 

sage: D.allow_loops(False); D 

Digraph on 1 vertex 

sage: D.has_loops() 

False 

sage: D.edges() 

[] 

""" 

if new is False and check: 

self.remove_loops() 

self._backend.loops(new) 

 

def loop_edges(self, labels=True): 

""" 

Return a list of all loops in the (di)graph 

 

INPUT: 

 

- ``labels`` -- whether returned edges have labels (``(u,v,l)``) or not 

(``(u,v)``) 

 

EXAMPLES:: 

 

sage: G = Graph(loops=True); G 

Looped graph on 0 vertices 

sage: G.has_loops() 

False 

sage: G.allows_loops() 

True 

sage: G.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] ) 

sage: G.loop_edges() 

[(0, 0, None), (1, 1, None), (2, 2, None), (3, 3, None)] 

sage: G.loop_edges(labels=False) 

[(0, 0), (1, 1), (2, 2), (3, 3)] 

sage: G.allows_loops() 

True 

sage: G.has_loops() 

True 

sage: G.allow_loops(False) 

sage: G.has_loops() 

False 

sage: G.loop_edges() 

[] 

sage: G.edges() 

[(2, 3, None)] 

 

sage: D = DiGraph(loops=True); D 

Looped digraph on 0 vertices 

sage: D.has_loops() 

False 

sage: D.allows_loops() 

True 

sage: D.add_edge((0,0)) 

sage: D.has_loops() 

True 

sage: D.loops() 

[(0, 0, None)] 

sage: D.allow_loops(False); D 

Digraph on 1 vertex 

sage: D.has_loops() 

False 

sage: D.edges() 

[] 

 

sage: G = graphs.PetersenGraph() 

sage: G.loops() 

[] 

 

:: 

 

sage: D = DiGraph(4, loops=True) 

sage: D.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] ) 

sage: D.loop_edges() 

[(0, 0, None), (1, 1, None), (2, 2, None), (3, 3, None)] 

 

:: 

 

sage: G = Graph(4, loops=True, multiedges=True, sparse=True) 

sage: G.add_edges([(i,i) for i in range(4)]) 

sage: G.loop_edges() 

[(0, 0, None), (1, 1, None), (2, 2, None), (3, 3, None)] 

sage: G.add_edges([(0, 0), (1, 1)]) 

sage: G.loop_edges(labels=False) 

[(0, 0), (0, 0), (1, 1), (1, 1), (2, 2), (3, 3)] 

""" 

if self.allows_multiple_edges(): 

if labels: 

return [(v,v,l) for v in self.loop_vertices() for l in self.edge_label(v,v)] 

else: 

return [(v,v) for v in self.loop_vertices() for l in self.edge_label(v,v)] 

elif labels: 

return [(v,v,self.edge_label(v,v)) for v in self.loop_vertices()] 

else: 

return [(v,v) for v in self.loop_vertices()] 

 

# As discussed in trac 22911, we make method loops an alias for loop_edges 

loops = loop_edges 

 

def number_of_loops(self): 

""" 

Return the number of edges that are loops 

 

EXAMPLES:: 

 

sage: G = Graph(4, loops=True) 

sage: G.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] ) 

sage: G.edges(labels=False) 

[(0, 0), (1, 1), (2, 2), (2, 3), (3, 3)] 

sage: G.number_of_loops() 

4 

 

:: 

 

sage: D = DiGraph(4, loops=True) 

sage: D.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] ) 

sage: D.edges(labels=False) 

[(0, 0), (1, 1), (2, 2), (2, 3), (3, 3)] 

sage: D.number_of_loops() 

4 

""" 

return len(self.loop_edges()) 

 

def loop_vertices(self): 

""" 

Return a list of vertices with loops 

 

EXAMPLES:: 

 

sage: G = Graph({0 : [0], 1: [1,2,3], 2: [3]}, loops=True) 

sage: G.loop_vertices() 

[0, 1] 

""" 

if self.allows_loops(): 

return [v for v in self if self.has_edge(v,v)] 

else: 

return [] 

 

def has_multiple_edges(self, to_undirected=False): 

""" 

Returns whether there are multiple edges in the (di)graph. 

 

INPUT: 

 

- ``to_undirected`` -- (default: False) If True, runs the test on the undirected version of a DiGraph. 

Otherwise, treats DiGraph edges (u,v) and (v,u) as unique individual edges. 

 

EXAMPLES:: 

 

sage: G = Graph(multiedges=True,sparse=True); G 

Multi-graph on 0 vertices 

sage: G.has_multiple_edges() 

False 

sage: G.allows_multiple_edges() 

True 

sage: G.add_edges([(0,1)]*3) 

sage: G.has_multiple_edges() 

True 

sage: G.multiple_edges() 

[(0, 1, None), (0, 1, None), (0, 1, None)] 

sage: G.allow_multiple_edges(False); G 

Graph on 2 vertices 

sage: G.has_multiple_edges() 

False 

sage: G.edges() 

[(0, 1, None)] 

 

sage: D = DiGraph(multiedges=True,sparse=True); D 

Multi-digraph on 0 vertices 

sage: D.has_multiple_edges() 

False 

sage: D.allows_multiple_edges() 

True 

sage: D.add_edges([(0,1)]*3) 

sage: D.has_multiple_edges() 

True 

sage: D.multiple_edges() 

[(0, 1, None), (0, 1, None), (0, 1, None)] 

sage: D.allow_multiple_edges(False); D 

Digraph on 2 vertices 

sage: D.has_multiple_edges() 

False 

sage: D.edges() 

[(0, 1, None)] 

 

sage: G = DiGraph({1:{2: 'h'}, 2:{1:'g'}},sparse=True) 

sage: G.has_multiple_edges() 

False 

sage: G.has_multiple_edges(to_undirected=True) 

True 

sage: G.multiple_edges() 

[] 

sage: G.multiple_edges(to_undirected=True) 

[(1, 2, 'h'), (2, 1, 'g')] 

 

A loop is not a multiedge:: 

 

sage: g = Graph(loops=True,multiedges=True) 

sage: g.add_edge(0,0) 

sage: g.has_multiple_edges() 

False 

""" 

if self.allows_multiple_edges() or (self._directed and to_undirected): 

if self._directed: 

for u in self: 

s = set() 

for a,b,c in self.outgoing_edge_iterator(u): 

if b in s: 

return True 

s.add(b) 

if to_undirected: 

for a,b,c in self.incoming_edge_iterator(u): 

if a in s: 

return True 

s.add(a) 

else: 

for u in self: 

s = set() 

for a,b,c in self.edge_iterator(u): 

if a is u: 

if b in s: 

return True 

s.add(b) 

elif b is u: 

if a in s: 

return True 

s.add(a) 

return False 

 

def allows_multiple_edges(self): 

""" 

Returns whether multiple edges are permitted in the (di)graph. 

 

EXAMPLES:: 

 

sage: G = Graph(multiedges=True,sparse=True); G 

Multi-graph on 0 vertices 

sage: G.has_multiple_edges() 

False 

sage: G.allows_multiple_edges() 

True 

sage: G.add_edges([(0,1)]*3) 

sage: G.has_multiple_edges() 

True 

sage: G.multiple_edges() 

[(0, 1, None), (0, 1, None), (0, 1, None)] 

sage: G.allow_multiple_edges(False); G 

Graph on 2 vertices 

sage: G.has_multiple_edges() 

False 

sage: G.edges() 

[(0, 1, None)] 

 

sage: D = DiGraph(multiedges=True,sparse=True); D 

Multi-digraph on 0 vertices 

sage: D.has_multiple_edges() 

False 

sage: D.allows_multiple_edges() 

True 

sage: D.add_edges([(0,1)]*3) 

sage: D.has_multiple_edges() 

True 

sage: D.multiple_edges() 

[(0, 1, None), (0, 1, None), (0, 1, None)] 

sage: D.allow_multiple_edges(False); D 

Digraph on 2 vertices 

sage: D.has_multiple_edges() 

False 

sage: D.edges() 

[(0, 1, None)] 

""" 

return self._backend.multiple_edges(None) 

 

def allow_multiple_edges(self, new, check=True, keep_label='any'): 

""" 

Changes whether multiple edges are permitted in the (di)graph. 

 

INPUT: 

 

- ``new`` (boolean): if ``True``, the new graph will allow multiple 

edges. 

 

- ``check`` (boolean): if ``True`` and ``new`` is ``False``, we remove 

all multiple edges from the graph. 

 

- ``keep_label`` (``'any','min','max'``): used only if ``new`` is 

``False`` and ``check`` is ``True``. If there are multiple edges with 

different labels, this variable defines which label should be kept: 

any label (``'any'``), the smallest label (``'min'``), or the largest 

(``'max'``). 

 

EXAMPLES: 

 

The standard behavior with undirected graphs:: 

 

sage: G = Graph(multiedges=True,sparse=True); G 

Multi-graph on 0 vertices 

sage: G.has_multiple_edges() 

False 

sage: G.allows_multiple_edges() 

True 

sage: G.add_edges([(0,1,1), (0,1,2), (0,1,3)]) 

sage: G.has_multiple_edges() 

True 

sage: G.multiple_edges() 

[(0, 1, 1), (0, 1, 2), (0, 1, 3)] 

sage: G.allow_multiple_edges(False); G 

Graph on 2 vertices 

sage: G.has_multiple_edges() 

False 

sage: G.edges() 

[(0, 1, 1)] 

 

If we ask for the minimum label:: 

 

sage: G = Graph([(0, 1, 1), (0, 1, 2), (0, 1, 3)], multiedges=True,sparse=True) 

sage: G.allow_multiple_edges(False, keep_label='min') 

sage: G.edges() 

[(0, 1, 1)] 

 

If we ask for the maximum label:: 

 

sage: G = Graph([(0, 1, 1), (0, 1, 2), (0, 1, 3)], multiedges=True,sparse=True) 

sage: G.allow_multiple_edges(False, keep_label='max') 

sage: G.edges() 

[(0, 1, 3)] 

 

The standard behavior with digraphs:: 

 

sage: D = DiGraph(multiedges=True,sparse=True); D 

Multi-digraph on 0 vertices 

sage: D.has_multiple_edges() 

False 

sage: D.allows_multiple_edges() 

True 

sage: D.add_edges([(0,1)]*3) 

sage: D.has_multiple_edges() 

True 

sage: D.multiple_edges() 

[(0, 1, None), (0, 1, None), (0, 1, None)] 

sage: D.allow_multiple_edges(False); D 

Digraph on 2 vertices 

sage: D.has_multiple_edges() 

False 

sage: D.edges() 

[(0, 1, None)] 

""" 

seen = dict() 

 

if not keep_label in ['any','min','max']: 

raise ValueError("Variable keep_label must be 'any', 'min', or 'max'") 

 

# TODO: this should be much faster for c_graphs, but for now we just do this 

if self.allows_multiple_edges() and new is False and check: 

for u,v,l in self.multiple_edges(): 

if not (u,v) in seen: 

# This is the first time we see this edge 

seen[(u,v)] = l 

else: 

# This edge has already been seen: we have to remove 

# something from the graph. 

oldl = seen[(u,v)] 

if ((keep_label=='min' and l<oldl) or (keep_label=='max' and l>oldl)): 

# Keep the new edge, delete the old one 

self.delete_edge((u,v,oldl)) 

seen[(u,v)] = l 

else: 

# Delete the new edge 

self.delete_edge((u,v,l)) 

 

self._backend.multiple_edges(new) 

 

def multiple_edges(self, to_undirected=False, labels=True): 

""" 

Returns any multiple edges in the (di)graph. 

 

EXAMPLES:: 

 

sage: G = Graph(multiedges=True,sparse=True); G 

Multi-graph on 0 vertices 

sage: G.has_multiple_edges() 

False 

sage: G.allows_multiple_edges() 

True 

sage: G.add_edges([(0,1)]*3) 

sage: G.has_multiple_edges() 

True 

sage: G.multiple_edges() 

[(0, 1, None), (0, 1, None), (0, 1, None)] 

sage: G.allow_multiple_edges(False); G 

Graph on 2 vertices 

sage: G.has_multiple_edges() 

False 

sage: G.edges() 

[(0, 1, None)] 

 

sage: D = DiGraph(multiedges=True,sparse=True); D 

Multi-digraph on 0 vertices 

sage: D.has_multiple_edges() 

False 

sage: D.allows_multiple_edges() 

True 

sage: D.add_edges([(0,1)]*3) 

sage: D.has_multiple_edges() 

True 

sage: D.multiple_edges() 

[(0, 1, None), (0, 1, None), (0, 1, None)] 

sage: D.allow_multiple_edges(False); D 

Digraph on 2 vertices 

sage: D.has_multiple_edges() 

False 

sage: D.edges() 

[(0, 1, None)] 

 

sage: G = DiGraph({1:{2: 'h'}, 2:{1:'g'}},sparse=True) 

sage: G.has_multiple_edges() 

False 

sage: G.has_multiple_edges(to_undirected=True) 

True 

sage: G.multiple_edges() 

[] 

sage: G.multiple_edges(to_undirected=True) 

[(1, 2, 'h'), (2, 1, 'g')] 

""" 

multi_edges = [] 

if self._directed and not to_undirected: 

for v in self: 

for u in self.neighbor_in_iterator(v): 

edges = self.edge_boundary([u], [v], labels) 

if len(edges) > 1: 

multi_edges += edges 

else: 

to_undirected *= self._directed 

for v in self: 

for u in self.neighbor_iterator(v): 

if hash(u) >= hash(v): 

edges = self.edge_boundary([v], [u], labels) 

if to_undirected: 

edges += self.edge_boundary([u],[v], labels) 

if len(edges) > 1: 

multi_edges += edges 

return multi_edges 

 

def name(self, new=None): 

""" 

Returns or sets the graph's name. 

 

INPUT: 

 

- ``new`` - if not None, then this becomes the new name of the (di)graph. 

(if new == '', removes any name) 

 

EXAMPLES:: 

 

sage: d = {0: [1,4,5], 1: [2,6], 2: [3,7], 3: [4,8], 4: [9], 5: [7, 8], 6: [8,9], 7: [9]} 

sage: G = Graph(d); G 

Graph on 10 vertices 

sage: G.name("Petersen Graph"); G 

Petersen Graph: Graph on 10 vertices 

sage: G.name(new=""); G 

Graph on 10 vertices 

sage: G.name() 

'' 

 

TESTS: 

 

Name of an immutable graph :trac:`15681` :: 

 

sage: g = graphs.PetersenGraph() 

sage: gi = g.copy(immutable=True) 

sage: gi.name() 

'Petersen graph' 

sage: gi.name("Hey") 

Traceback (most recent call last): 

... 

NotImplementedError: An immutable graph does not change name 

""" 

if new is None: 

return getattr(self, '_name', "") 

 

if self.is_immutable(): 

raise NotImplementedError("An immutable graph does not change name") 

 

self._name = str(new) 

 

def get_pos(self, dim = 2): 

""" 

Returns the position dictionary, a dictionary specifying the 

coordinates of each vertex. 

 

EXAMPLES: By default, the position of a graph is None:: 

 

sage: G = Graph() 

sage: G.get_pos() 

sage: G.get_pos() is None 

True 

sage: P = G.plot(save_pos=True) 

sage: G.get_pos() 

{} 

 

Some of the named graphs come with a pre-specified positioning:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.get_pos() 

{0: (...e-17, 1.0), 

... 

9: (0.475..., 0.154...)} 

""" 

if dim == 2: 

return self._pos 

elif dim == 3: 

return getattr(self, "_pos3d", None) 

else: 

raise ValueError("dim must be 2 or 3") 

 

def _check_pos_validity(self, pos=None, dim = 2): 

r""" 

Checks whether pos specifies two (resp. 3) coordinates for every vertex (and no more vertices). 

 

INPUT: 

 

- ``pos`` - a position dictionary for a set of vertices 

- ``dim`` - 2 or 3 (default: 3 

 

OUTPUT: 

 

If ``pos`` is ``None`` then the position dictionary of ``self`` is 

investigated, otherwise the position dictionary provided in ``pos`` is 

investigated. The function returns ``True`` if the dictionary is of the 

correct form for ``self``. 

 

EXAMPLES:: 

 

sage: p = {0: [1, 5], 1: [0, 2], 2: [1, 3], 3: [8, 2], 4: [0, 9], 5: [0, 8], 6: [8, 1], 7: [9, 5], 8: [3, 5], 9: [6, 7]} 

sage: G = graphs.PetersenGraph() 

sage: G._check_pos_validity(p) 

True 

""" 

if pos is None: 

pos = self.get_pos(dim = dim) 

if pos is None: 

return False 

if len(pos) != self.order(): 

return False 

for v in pos: 

if not self.has_vertex(v): 

return False 

if len(pos[v]) != dim: 

return False 

return True 

 

def set_pos(self, pos, dim = 2): 

""" 

Sets the position dictionary, a dictionary specifying the 

coordinates of each vertex. 

 

EXAMPLES: Note that set_pos will allow you to do ridiculous things, 

which will not blow up until plotting:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.get_pos() 

{0: (..., ...), 

... 

9: (..., ...)} 

 

:: 

 

sage: G.set_pos('spam') 

sage: P = G.plot() 

Traceback (most recent call last): 

... 

TypeError: string indices must be integers, not str 

""" 

if dim == 2: 

self._pos = pos 

elif dim == 3: 

self._pos3d = pos 

else: 

raise ValueError("dim must be 2 or 3") 

 

def weighted(self, new=None): 

""" 

Whether the (di)graph is to be considered as a weighted (di)graph. 

 

INPUT: 

 

- ``new`` (optional bool): If it is provided, then the weightedness 

flag is set accordingly. This is not allowed for immutable graphs. 

 

.. NOTE:: 

 

Changing the weightedness flag changes the ``==``-class of 

a graph and is thus not allowed for immutable graphs. 

 

Edge weightings can still exist for (di)graphs ``G`` where 

``G.weighted()`` is ``False``. 

 

EXAMPLES: 

 

Here we have two graphs with different labels, but ``weighted()`` is 

``False`` for both, so we just check for the presence of edges:: 

 

sage: G = Graph({0:{1:'a'}}, sparse=True) 

sage: H = Graph({0:{1:'b'}}, sparse=True) 

sage: G == H 

True 

 

Now one is weighted and the other is not, and thus the graphs are 

not equal:: 

 

sage: G.weighted(True) 

sage: H.weighted() 

False 

sage: G == H 

False 

 

However, if both are weighted, then we finally compare 'a' to 'b':: 

 

sage: H.weighted(True) 

sage: G == H 

False 

 

TESTS: 

 

Ensure that :trac:`10490` is fixed: allows a weighted graph to be 

set as unweighted. :: 

 

sage: G = Graph({1:{2:3}}) 

sage: G.weighted() 

False 

sage: G.weighted(True) 

sage: G.weighted() 

True 

sage: G.weighted(False) 

sage: G.weighted() 

False 

 

Ensure that graphs using the static sparse backend can not be mutated 

using this method, as fixed in :trac:`15278`:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.weighted() 

False 

sage: H = copy(G) 

sage: H == G 

True 

sage: H.weighted(True) 

sage: H == G 

False 

sage: G_imm = Graph(G, data_structure="static_sparse") 

sage: G_imm == G 

True 

sage: G_imm.weighted() 

False 

sage: G_imm.weighted(True) 

Traceback (most recent call last): 

... 

TypeError: This graph is immutable and can thus not be changed. 

Create a mutable copy, e.g., by `copy(g)` 

sage: G_mut = copy(G) 

sage: G_mut == G_imm 

True 

sage: G_mut.weighted(True) 

sage: G_mut == G_imm 

False 

sage: G_mut == H 

True 

 

""" 

if new is not None: 

if self.is_immutable(): 

raise TypeError("This graph is immutable and can thus not be changed. " 

"Create a mutable copy, e.g., by `copy(g)`") 

if new in [True, False]: 

self._weighted = new 

else: 

raise ValueError("'new' must be a boolean") 

else: 

return bool(self._weighted) 

 

### Properties 

 

def antisymmetric(self): 

r""" 

Tests whether the graph is antisymmetric. 

 

A graph represents an antisymmetric relation if there being a path 

from a vertex x to a vertex y implies that there is not a path from 

y to x unless x=y. 

 

A directed acyclic graph is antisymmetric. An undirected graph is 

never antisymmetric unless it is just a union of isolated 

vertices. 

 

:: 

 

sage: graphs.RandomGNP(20,0.5).antisymmetric() 

False 

sage: digraphs.RandomDirectedGNR(20,0.5).antisymmetric() 

True 

""" 

if not self._directed: 

if self.size()-len(self.loop_edges())>0: 

return False 

else: 

return True 

g = copy(self) 

g.allow_multiple_edges(False) 

g.allow_loops(False) 

g = g.transitive_closure() 

gpaths = g.edges(labels=False) 

for e in gpaths: 

if (e[1],e[0]) in gpaths: 

return False 

return True 

 

def density(self): 

""" 

Returns the density (number of edges divided by number of possible 

edges). 

 

In the case of a multigraph, raises an error, since there is an 

infinite number of possible edges. 

 

EXAMPLES:: 

 

sage: d = {0: [1,4,5], 1: [2,6], 2: [3,7], 3: [4,8], 4: [9], 5: [7, 8], 6: [8,9], 7: [9]} 

sage: G = Graph(d); G.density() 

1/3 

sage: G = Graph({0:[1,2], 1:[0] }); G.density() 

2/3 

sage: G = DiGraph({0:[1,2], 1:[0] }); G.density() 

1/2 

 

Note that there are more possible edges on a looped graph:: 

 

sage: G.allow_loops(True) 

sage: G.density() 

1/3 

""" 

if self.has_multiple_edges(): 

raise TypeError("Density is not well-defined for multigraphs.") 

n = self.order() 

if self.allows_loops(): 

if n == 0: 

return Rational(0) 

if self._directed: 

return Rational(self.size())/Rational(n**2) 

else: 

return Rational(self.size())/Rational((n**2 + n)/2) 

else: 

if n < 2: 

return Rational(0) 

if self._directed: 

return Rational(self.size())/Rational((n**2 - n)) 

else: 

return Rational(self.size())/Rational((n**2 - n)/2) 

 

def is_eulerian(self, path=False): 

r""" 

Return true if the graph has a (closed) tour that visits each edge exactly 

once. 

 

INPUT: 

 

- ``path`` -- by default this function finds if the graph contains a closed 

tour visiting each edge once, i.e. an Eulerian cycle. If you want to test 

the existence of an Eulerian path, set this argument to ``True``. Graphs 

with this property are sometimes called semi-Eulerian. 

 

OUTPUT: 

 

``True`` or ``False`` for the closed tour case. For an open tour search 

(``path``=``True``) the function returns ``False`` if the graph is not 

semi-Eulerian, or a tuple (u, v) in the other case. This tuple defines the 

edge that would make the graph Eulerian, i.e. close an existing open tour. 

This edge may or may not be already present in the graph. 

 

EXAMPLES:: 

 

sage: graphs.CompleteGraph(4).is_eulerian() 

False 

sage: graphs.CycleGraph(4).is_eulerian() 

True 

sage: g = DiGraph({0:[1,2], 1:[2]}); g.is_eulerian() 

False 

sage: g = DiGraph({0:[2], 1:[3], 2:[0,1], 3:[2]}); g.is_eulerian() 

True 

sage: g = DiGraph({0:[1], 1:[2], 2:[0], 3:[]}); g.is_eulerian() 

True 

sage: g = Graph([(1,2), (2,3), (3,1), (4,5), (5,6), (6,4)]); g.is_eulerian() 

False 

 

:: 

 

sage: g = DiGraph({0: [1]}); g.is_eulerian(path=True) 

(1, 0) 

sage: graphs.CycleGraph(4).is_eulerian(path=True) 

False 

sage: g = DiGraph({0: [1], 1: [2,3], 2: [4]}); g.is_eulerian(path=True) 

False 

 

:: 

 

sage: g = Graph({0:[1,2,3], 1:[2,3], 2:[3,4], 3:[4]}, multiedges=True) 

sage: g.is_eulerian() 

False 

sage: e = g.is_eulerian(path=True); e 

(0, 1) 

sage: g.add_edge(e) 

sage: g.is_eulerian(path=False) 

True 

sage: g.is_eulerian(path=True) 

False 

 

TESTS:: 

 

sage: g = Graph({0:[], 1:[], 2:[], 3:[]}); g.is_eulerian() 

True 

""" 

 

# unconnected graph can still be Eulerian if all components 

# up to one doesn't contain any edge 

nontrivial_components = 0 

for cc in self.connected_components(): 

if len(cc) > 1: 

nontrivial_components += 1 

if nontrivial_components > 1: 

return False 

 

uv = [None, None] 

if self._directed: 

for v in self.vertex_iterator(): 

# loops don't matter since they count in both the in and out degree. 

if self.in_degree(v) != self.out_degree(v): 

if path: 

diff = self.out_degree(v) - self.in_degree(v) 

if abs(diff) > 1: 

return False 

else: 

# if there was another vertex with the same sign of difference... 

if uv[(diff+1)//2] is not None: 

return False # ... the graph is not semi-Eulerian 

else: 

uv[(diff+1)//2] = v 

else: 

return False 

else: 

for v in self.vertex_iterator(): 

# loops don't matter since they add an even number to the degree 

if self.degree(v) % 2 != 0: 

if not path: 

return False 

else: 

if uv[0] is None or uv[1] is None: 

uv[0 if uv[0] is None else 1] = v 

else: 

return False 

 

if path and (uv[0] is None or uv[1] is None): 

return False 

 

return True if not path else tuple(uv) 

 

def order(self): 

""" 

Returns the number of vertices. Note that len(G) returns the number 

of vertices in G also. 

 

EXAMPLES:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.order() 

10 

 

:: 

 

sage: G = graphs.TetrahedralGraph() 

sage: len(G) 

4 

""" 

return self._backend.num_verts() 

 

__len__ = order 

 

num_verts = order 

 

def size(self): 

""" 

Returns the number of edges. 

 

EXAMPLES:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.size() 

15 

""" 

return self._backend.num_edges(self._directed) 

 

num_edges = size 

 

### Orientations 

 

def eulerian_orientation(self): 

r""" 

Returns a DiGraph which is an Eulerian orientation of the current graph. 

 

An Eulerian graph being a graph such that any vertex has an even degree, 

an Eulerian orientation of a graph is an orientation of its edges such 

that each vertex `v` verifies `d^+(v)=d^-(v)=d(v)/2`, where `d^+` and 

`d^-` respectively represent the out-degree and the in-degree of a vertex. 

 

If the graph is not Eulerian, the orientation verifies for any vertex `v` 

that `| d^+(v)-d^-(v) | \leq 1`. 

 

ALGORITHM: 

 

This algorithm is a random walk through the edges of the graph, which 

orients the edges according to the walk. When a vertex is reached which 

has no non-oriented edge ( this vertex must have odd degree ), the 

walk resumes at another vertex of odd degree, if any. 

 

This algorithm has complexity `O(m)`, where `m` is the number of edges 

in the graph. 

 

EXAMPLES: 

 

The CubeGraph with parameter 4, which is regular of even degree, has an 

Eulerian orientation such that `d^+=d^-`:: 

 

sage: g=graphs.CubeGraph(4) 

sage: g.degree() 

[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4] 

sage: o=g.eulerian_orientation() 

sage: o.in_degree() 

[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] 

sage: o.out_degree() 

[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] 

 

Secondly, the Petersen Graph, which is 3 regular has an orientation 

such that the difference between `d^+` and `d^-` is at most 1:: 

 

sage: g=graphs.PetersenGraph() 

sage: o=g.eulerian_orientation() 

sage: o.in_degree() 

[2, 2, 2, 2, 2, 1, 1, 1, 1, 1] 

sage: o.out_degree() 

[1, 1, 1, 1, 1, 2, 2, 2, 2, 2] 

 

TESTS:: 

 

sage: E0 = Graph(); E4 = Graph(4) # See trac #21741 

sage: E0.eulerian_orientation() 

Digraph on 0 vertices 

sage: E4.eulerian_orientation() 

Digraph on 4 vertices 

""" 

from sage.graphs.digraph import DiGraph 

 

if self.size() == 0: 

return DiGraph(self.order()) 

 

g=copy(self) 

d=DiGraph() 

d.add_vertices(g.vertex_iterator()) 

 

 

# list of vertices of odd degree 

odd = [x for (x, deg) in zip(g.vertex_iterator(), g.degree_iterator()) 

if deg % 2 == 1] 

 

# Picks the first vertex, which is preferably an odd one 

if odd: 

v = odd.pop() 

else: 

v = next(g.edge_iterator(labels=None))[0] 

odd.append(v) 

# Stops when there is no edge left 

while True: 

 

# If there is an edge adjacent to the current one 

if g.degree(v)>0: 

e = next(g.edge_iterator(v)) 

g.delete_edge(e) 

if e[0]!=v: 

e=(e[1],e[0],e[2]) 

d.add_edge(e) 

v=e[1] 

 

# The current vertex is isolated 

else: 

odd.remove(v) 

 

# jumps to another odd vertex if possible 

if len(odd)>0: 

v=odd.pop() 

# Else jumps to an ever vertex which is not isolated 

elif g.size()>0: 

v = next(g.edge_iterator())[0] 

odd.append(v) 

# If there is none, we are done ! 

else: 

return d 

 

def eulerian_circuit(self, return_vertices=False, labels=True, path=False): 

r""" 

Return a list of edges forming an Eulerian circuit if one exists. 

Otherwise return False. 

 

This is implemented using Hierholzer's algorithm. 

 

INPUT: 

 

- ``return_vertices`` -- (default: ``False``) optionally provide a list of 

vertices for the path 

 

- ``labels`` -- (default: ``True``) whether to return edges with labels 

(3-tuples) 

 

- ``path`` -- (default: ``False``) find an Eulerian path instead 

 

OUTPUT: 

 

either ([edges], [vertices]) or [edges] of an Eulerian circuit (or path) 

 

EXAMPLES:: 

 

sage: g=graphs.CycleGraph(5); 

sage: g.eulerian_circuit() 

[(0, 4, None), (4, 3, None), (3, 2, None), (2, 1, None), (1, 0, None)] 

sage: g.eulerian_circuit(labels=False) 

[(0, 4), (4, 3), (3, 2), (2, 1), (1, 0)] 

 

:: 

 

sage: g = graphs.CompleteGraph(7) 

sage: edges, vertices = g.eulerian_circuit(return_vertices=True) 

sage: vertices 

[0, 6, 5, 4, 6, 3, 5, 2, 4, 3, 2, 6, 1, 5, 0, 4, 1, 3, 0, 2, 1, 0] 

 

:: 

 

sage: graphs.CompleteGraph(4).eulerian_circuit() 

False 

 

A disconnected graph can be Eulerian:: 

 

sage: g = Graph({0: [], 1: [2], 2: [3], 3: [1], 4: []}) 

sage: g.eulerian_circuit(labels=False) 

[(1, 3), (3, 2), (2, 1)] 

 

:: 

 

sage: g = DiGraph({0: [1], 1: [2, 4], 2:[3], 3:[1]}) 

sage: g.eulerian_circuit(labels=False, path=True) 

[(0, 1), (1, 2), (2, 3), (3, 1), (1, 4)] 

 

:: 

 

sage: g = Graph({0:[1,2,3], 1:[2,3], 2:[3,4], 3:[4]}) 

sage: g.is_eulerian(path=True) 

(0, 1) 

sage: g.eulerian_circuit(labels=False, path=True) 

[(1, 3), (3, 4), (4, 2), (2, 3), (3, 0), (0, 2), (2, 1), (1, 0)] 

 

TESTS:: 

 

sage: Graph({'H': ['G','L','L','D'], 'L': ['G','D']}).eulerian_circuit(labels=False) 

[('H', 'D'), ('D', 'L'), ('L', 'G'), ('G', 'H'), ('H', 'L'), ('L', 'H')] 

sage: Graph({0: [0, 1, 1, 1, 1]}).eulerian_circuit(labels=False) 

[(0, 1), (1, 0), (0, 1), (1, 0), (0, 0)] 

""" 

# trivial case 

if self.order() == 0: 

return ([], []) if return_vertices else [] 

 

# check if the graph has proper properties to be Eulerian 

edge = self.is_eulerian(path=path) 

if not edge: 

return False 

if path: 

start_vertex = edge[0] 

 

edges = [] 

vertices = [] 

 

# we'll remove edges as we go, so let's preserve the graph structure 

if self.is_directed(): 

g = self.reverse() # so the output will be in the proper order 

else: 

g = copy(self) 

 

if not path: 

# get the first vertex with degree>0 

start_vertex = None 

for v in g.vertex_iterator(): 

if g.degree(v) != 0: 

start_vertex = v 

break 

 

# (where to return?, what was the way?) 

stack = [ (start_vertex, None) ] 

 

while len(stack) != 0: 

v, e = stack.pop() 

 

degr = g.out_degree(v) if self.is_directed() else g.degree(v) 

if degr == 0: 

vertices.append(v) 

if e is not None: 

edges.append(e if labels else (e[0], e[1])) 

else: 

if self.is_directed(): 

next_edge = next(g.outgoing_edge_iterator(v)) 

else: 

next_edge = next(g.edge_iterator(v)) 

 

if next_edge[0] == v: # in the undirected case we want to 

# save the direction of traversal 

next_edge_new = (next_edge[1], next_edge[0], next_edge[2]) 

else: 

next_edge_new = next_edge 

next_vertex = next_edge_new[0] 

 

stack.append((v, e)) 

stack.append((next_vertex, next_edge_new)) 

 

g.delete_edge(next_edge) 

 

if return_vertices: 

return edges, vertices 

else: 

return edges 

 

def min_spanning_tree(self, 

weight_function=None, 

algorithm="Prim_Boost", 

starting_vertex=None, 

check=False): 

r""" 

Returns the edges of a minimum spanning tree. 

 

At the moment, no algorithm for directed graph is implemented: if the 

graph is directed, a minimum spanning tree of the corresponding 

undirected graph is returned. 

 

We expect all weights of the graph to be convertible to float. 

Otherwise, an exception is raised. 

 

INPUT: 

 

- ``weight_function`` (function) - a function that inputs an edge ``e`` 

and outputs its weight. An edge has the form ``(u,v,l)``, where ``u`` 

and ``v`` are vertices, ``l`` is a label (that can be of any kind). 

The ``weight_function`` can be used to transform the label into a 

weight (note that, if the weight returned is not convertible to a 

float, an error is raised). In particular: 

 

- if ``weight_function`` is not ``None``, the weight of an edge ``e`` 

is ``weight_function(e)``; 

 

- if ``weight_function`` is ``None`` (default) and ``g`` is weighted 

(that is, ``g.weighted()==True``), for each edge ``e=(u,v,l)``, we 

set weight ``l``; 

 

- if ``weight_function`` is ``None`` and ``g`` is not weighted, we set 

all weights to 1 (hence, the output can be any spanning tree). 

 

- ``algorithm`` -- The algorithm to use in computing a minimum spanning 

tree of ``G``. The following algorithms are supported: 

 

- ``"Prim_Boost"`` (default) -- Prim's algorithm 

(Boost implementation). 

 

- ``"Prim_fringe"`` -- a variant of Prim's algorithm. 

``"Prim_fringe"`` ignores the labels on the edges. 

 

- ``"Prim_edge"`` -- a variant of Prim's algorithm. 

 

- ``"Kruskal"`` -- Kruskal's algorithm. 

 

- ``"Kruskal_Boost"`` -- Kruskal's algorithm (Boost implementation). 

 

- ``NetworkX`` -- Uses NetworkX's minimum spanning tree 

implementation. 

 

- ``starting_vertex`` -- The vertex from which to begin the search 

for a minimum spanning tree (available only for ``Prim_fringe`` and 

``Prim_edge``). 

 

- ``check`` -- Boolean; default: ``False``. Whether to first perform 

sanity checks on the input graph ``G``. If appropriate, ``check`` 

is passed on to any minimum spanning tree functions that are 

invoked from the current method. See the documentation of the 

corresponding functions for details on what sort of sanity checks 

will be performed. 

 

OUTPUT: 

 

The edges of a minimum spanning tree of ``G``, if one exists, otherwise 

returns the empty list. 

 

.. SEEALSO:: 

 

- :func:`sage.graphs.spanning_tree.kruskal` 

- :func:`sage.graphs.base.boost_graph.min_spanning_tree` 

 

EXAMPLES: 

 

Kruskal's algorithm:: 

 

sage: g = graphs.CompleteGraph(5) 

sage: len(g.min_spanning_tree()) 

4 

sage: weight = lambda e: 1 / ((e[0] + 1) * (e[1] + 1)) 

sage: g.min_spanning_tree(weight_function=weight) 

[(0, 4, None), (1, 4, None), (2, 4, None), (3, 4, None)] 

sage: g.min_spanning_tree(weight_function=weight, algorithm='Kruskal_Boost') 

[(0, 4, None), (1, 4, None), (2, 4, None), (3, 4, None)] 

sage: g = graphs.PetersenGraph() 

sage: g.allow_multiple_edges(True) 

sage: g.add_edges(g.edges()) 

sage: g.min_spanning_tree() 

[(0, 1, None), (0, 4, None), (0, 5, None), (1, 2, None), (1, 6, None), (3, 8, None), (5, 7, None), (5, 8, None), (6, 9, None)] 

 

Prim's algorithm:: 

 

sage: g = graphs.CompleteGraph(5) 

sage: g.min_spanning_tree(algorithm='Prim_edge', starting_vertex=2, weight_function=weight) 

[(0, 4, None), (1, 4, None), (2, 4, None), (3, 4, None)] 

sage: g.min_spanning_tree(algorithm='Prim_fringe', starting_vertex=2, weight_function=weight) 

[(0, 4, None), (1, 4, None), (2, 4, None), (3, 4, None)] 

sage: g.min_spanning_tree(weight_function=weight, algorithm='Prim_Boost') 

[(0, 4, None), (1, 4, None), (2, 4, None), (3, 4, None)] 

 

NetworkX algorithm:: 

 

sage: g.min_spanning_tree(algorithm='NetworkX') 

[(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None)] 

 

More complicated weights:: 

 

sage: G = Graph([(0,1,{'name':'a','weight':1}), (0,2,{'name':'b','weight':3}), (1,2,{'name':'b','weight':1})]) 

sage: G.min_spanning_tree(weight_function=lambda e: e[2]['weight']) 

[(0, 1, {'name': 'a', 'weight': 1}), (1, 2, {'name': 'b', 'weight': 1})] 

 

If the graph is not weighted, edge labels are not considered, even if 

they are numbers:: 

 

sage: g = Graph([[1,2,1], [1,3,2], [2,3,1]]) 

sage: g.min_spanning_tree() 

[(1, 2, 1), (1, 3, 2)] 

 

In order to use weights, we need to set variable ``weighted`` to 

``True``:: 

 

sage: g.weighted(True) 

sage: g.min_spanning_tree() 

[(1, 2, 1), (2, 3, 1)] 

 

TESTS: 

 

Check that, if ``weight_function`` is not provided, then edge weights 

are used:: 

 

sage: g = Graph(weighted=True) 

sage: g.add_edges([[0,1,1],[1,2,1],[2,0,10]]) 

sage: g.min_spanning_tree() 

[(0, 1, 1), (1, 2, 1)] 

sage: g.min_spanning_tree(algorithm='Kruskal_Boost') 

[(0, 1, 1), (1, 2, 1)] 

sage: g.min_spanning_tree(algorithm='Prim_fringe') 

[(0, 1, 1), (1, 2, 1)] 

sage: g.min_spanning_tree(algorithm='Prim_edge') 

[(0, 1, 1), (1, 2, 1)] 

sage: g.min_spanning_tree(algorithm='Prim_Boost') 

[(0, 1, 1), (1, 2, 1)] 

sage: g.min_spanning_tree(algorithm='NetworkX') 

[(0, 1, 1), (1, 2, 1)] 

 

Check that, if ``weight_function`` is provided, it overrides edge 

weights:: 

 

sage: g = Graph([[0,1,1],[1,2,1],[2,0,10]], weighted=True) 

sage: weight = lambda e:3-e[0]-e[1] 

sage: g.min_spanning_tree(weight_function=weight) 

[(0, 2, 10), (1, 2, 1)] 

sage: g.min_spanning_tree(algorithm='Kruskal_Boost', weight_function=weight) 

[(0, 2, 10), (1, 2, 1)] 

sage: g.min_spanning_tree(algorithm='Prim_fringe', weight_function=weight) 

[(0, 2, 10), (1, 2, 1)] 

sage: g.min_spanning_tree(algorithm='Prim_edge', weight_function=weight) 

[(0, 2, 10), (1, 2, 1)] 

sage: g.min_spanning_tree(algorithm='Prim_Boost', weight_function=weight) 

[(0, 2, 10), (1, 2, 1)] 

sage: g.min_spanning_tree(algorithm='NetworkX', weight_function=weight) 

[(0, 2, 10), (1, 2, 1)] 

 

If the graph is directed, it is transformed into an undirected graph:: 

 

sage: g = digraphs.Circuit(3) 

sage: g.min_spanning_tree(weight_function=weight) 

[(0, 2, None), (1, 2, None)] 

sage: g.to_undirected().min_spanning_tree(weight_function=weight) 

[(0, 2, None), (1, 2, None)] 

 

If at least an edge weight is not convertible to a float, an error is 

raised:: 

 

sage: g = Graph([(0,1,1), (1,2,'a')], weighted=True) 

sage: g.min_spanning_tree(algorithm="Prim_Boost") 

Traceback (most recent call last): 

... 

ValueError: could not convert string to float: a 

sage: g.min_spanning_tree(algorithm="Prim_fringe") 

Traceback (most recent call last): 

... 

ValueError: could not convert string to float: a 

sage: g.min_spanning_tree(algorithm="Prim_edge") 

Traceback (most recent call last): 

... 

ValueError: could not convert string to float: a 

sage: g.min_spanning_tree(algorithm="Kruskal") 

Traceback (most recent call last): 

... 

ValueError: could not convert string to float: a 

sage: g.min_spanning_tree(algorithm="Kruskal_Boost") 

Traceback (most recent call last): 

... 

ValueError: could not convert string to float: a 

sage: g.min_spanning_tree(algorithm="NetworkX") 

Traceback (most recent call last): 

... 

ValueError: could not convert string to float: a 

 

sage: g = Graph([(0,1,1), (1,2,[1,2,3])], weighted=True) 

 

sage: g.min_spanning_tree(algorithm="Prim_Boost") 

Traceback (most recent call last): 

... 

TypeError: float() argument must be a string or a number 

sage: g.min_spanning_tree(algorithm="Prim_fringe") 

Traceback (most recent call last): 

... 

TypeError: float() argument must be a string or a number 

sage: g.min_spanning_tree(algorithm="Prim_edge") 

Traceback (most recent call last): 

... 

TypeError: float() argument must be a string or a number 

sage: g.min_spanning_tree(algorithm="Kruskal") 

Traceback (most recent call last): 

... 

TypeError: float() argument must be a string or a number 

sage: g.min_spanning_tree(algorithm="Kruskal_Boost") 

Traceback (most recent call last): 

... 

TypeError: float() argument must be a string or a number 

sage: g.min_spanning_tree(algorithm="NetworkX") 

Traceback (most recent call last): 

... 

TypeError: float() argument must be a string or a number 

 

sage: graphs.EmptyGraph().min_spanning_tree() 

[] 

""" 

if self.order() == 0: 

return [] 

 

if weight_function is None: 

if self.weighted(): 

weight_function = lambda e:e[2] 

else: 

weight_function = lambda e:1 

 

wfunction_float = lambda e:float(weight_function(e)) 

 

if algorithm in ["Kruskal", "Kruskal_Boost", "Prim_Boost"]: 

if self.is_directed(): 

g = self.to_undirected() 

else: 

g = self 

 

if algorithm == "Kruskal": 

from .spanning_tree import kruskal 

return kruskal(g, wfunction=wfunction_float, check=check) 

else: 

from sage.graphs.base.boost_graph import min_spanning_tree 

return min_spanning_tree(g, 

weight_function=wfunction_float, 

algorithm=algorithm.split("_")[0]) 

 

if algorithm == "Prim_fringe": 

if starting_vertex is None: 

v = next(self.vertex_iterator()) 

else: 

v = starting_vertex 

tree = set([v]) 

edges = [] 

# Initialize fringe_list with v's neighbors. Fringe_list 

# contains fringe_vertex: (vertex_in_tree, weight) for each 

# fringe vertex. 

fringe_list = dict([e[0] if e[0]!=v else e[1], (wfunction_float(e), v)] for e in self.edges_incident(v)) 

cmp_fun = lambda x: fringe_list[x] 

for i in range(self.order() - 1): 

# find the smallest-weight fringe vertex 

u = min(fringe_list, key=cmp_fun) 

x = fringe_list[u][1] 

edges.append((min(x,u), max(x,u), self.edge_label(x,u))) 

tree.add(u) 

fringe_list.pop(u) 

# update fringe list 

for e in self.edges_incident(u): 

neighbor = e[0] if e[0]!=u else e[1] 

if neighbor in tree: 

continue 

w = wfunction_float(e) 

if neighbor not in fringe_list or fringe_list[neighbor][0] > w: 

fringe_list[neighbor] = (w, u) 

return sorted(edges) 

 

elif algorithm == "Prim_edge": 

if starting_vertex is None: 

v = next(self.vertex_iterator()) 

else: 

v = starting_vertex 

sorted_edges = sorted(self.edges(), key=wfunction_float) 

tree = set([v]) 

edges = [] 

for _ in range(self.order() - 1): 

# Find a minimum-weight edge connecting a vertex in the tree 

# to something outside the tree. Remove the edges between 

# tree vertices for efficiency. 

i = 0 

while True: 

e = sorted_edges[i] 

v0, v1 = e[0], e[1] 

if v0 in tree: 

del sorted_edges[i] 

if v1 in tree: 

continue 

edges.append(e) 

tree.add(v1) 

break 

elif v1 in tree: 

del sorted_edges[i] 

edges.append(e) 

tree.add(v0) 

break 

else: 

i += 1 

return sorted(edges) 

 

elif algorithm == "NetworkX": 

import networkx 

G = networkx.Graph([(u, v, dict(weight=wfunction_float((u, v, l)))) for u, v, l in self.edge_iterator()]) 

return sorted([(u,v,self.edge_label(u,v)) if u<v else (v,u,self.edge_label(u,v)) for (u,v) in networkx.minimum_spanning_tree(G).edges()]) 

 

else: 

raise NotImplementedError("Minimum Spanning Tree algorithm '%s' is not implemented." % algorithm) 

 

def spanning_trees_count(self, root_vertex=None): 

""" 

Returns the number of spanning trees in a graph. 

 

In the case of a digraph, counts the number of spanning out-trees rooted 

in ``root_vertex``. Default is to set first vertex as root. 

 

This computation uses Kirchhoff's Matrix Tree Theorem [1] to calculate 

the number of spanning trees. For complete graphs on `n` vertices the 

result can also be reached using Cayley's formula: the number of 

spanning trees are `n^(n-2)`. 

 

For digraphs, the augmented Kirchhoff Matrix as defined in [2] is 

used for calculations. Here the result is the number of out-trees 

rooted at a specific vertex. 

 

INPUT: 

 

- ``root_vertex`` -- integer (default: the first vertex) This is the vertex 

that will be used as root for all spanning out-trees if the graph 

is a directed graph. This argument is ignored if the graph is not a digraph. 

 

.. SEEALSO:: 

 

:meth:`~sage.graphs.graph.Graph.spanning_trees` -- enumerates all 

spanning trees of a graph. 

 

 

REFERENCES: 

 

- [1] http://mathworld.wolfram.com/MatrixTreeTheorem.html 

 

- [2] Lih-Hsing Hsu, Cheng-Kuan Lin, "Graph Theory and 

Interconnection Networks" 

 

AUTHORS: 

 

- Anders Jonsson (2009-10-10) 

 

EXAMPLES:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.spanning_trees_count() 

2000 

 

:: 

 

sage: n = 11 

sage: G = graphs.CompleteGraph(n) 

sage: ST = G.spanning_trees_count() 

sage: ST == n^(n-2) 

True 

 

:: 

 

sage: M=matrix(3,3,[0,1,0,0,0,1,1,1,0]) 

sage: D=DiGraph(M) 

sage: D.spanning_trees_count() 

1 

sage: D.spanning_trees_count(0) 

1 

sage: D.spanning_trees_count(2) 

2 

 

""" 

 

if self.order() == 0: 

return 0 

 

if not self.is_directed(): 

M = self.kirchhoff_matrix() 

M.subdivide(1,1) 

M2 = M.subdivision(1,1) 

return M2.determinant() 

else: 

if root_vertex is None: 

root_vertex=next(self.vertex_iterator()) 

if root_vertex not in self.vertices(): 

raise ValueError("Vertex (%s) not in the graph."%root_vertex) 

 

M=self.kirchhoff_matrix() 

 

index=self.vertices().index(root_vertex) 

M[index,index]+=1 

return abs(M.determinant()) 

 

def cycle_basis(self, output='vertex'): 

r""" 

Returns a list of cycles which form a basis of the cycle space 

of ``self``. 

 

A basis of cycles of a graph is a minimal collection of cycles 

(considered as sets of edges) such that the edge set of any 

cycle in the graph can be written as a `Z/2Z` sum of the 

cycles in the basis. 

 

INPUT: 

 

- ``output`` (``'vertex'`` (default) or ``'edge'``) -- whether 

every cycle is given as a list of vertices or a list of 

edges. 

 

OUTPUT: 

 

A list of lists, each of them representing the vertices (or 

the edges) of a cycle in a basis. 

 

ALGORITHM: 

 

Uses the NetworkX library for graphs without multiple edges. 

 

Otherwise, by the standard algorithm using a spanning tree. 

 

EXAMPLES: 

 

A cycle basis in Petersen's Graph :: 

 

sage: g = graphs.PetersenGraph() 

sage: g.cycle_basis() 

[[1, 2, 7, 5, 0], [8, 3, 2, 7, 5], [4, 3, 2, 7, 5, 0], [4, 9, 7, 5, 0], [8, 6, 9, 7, 5], [1, 6, 9, 7, 5, 0]] 

 

One can also get the result as a list of lists of edges:: 

 

sage: g.cycle_basis(output='edge') 

[[(1, 2, None), (2, 7, None), (7, 5, None), (5, 0, None), 

(0, 1, None)], [(8, 3, None), (3, 2, None), (2, 7, None), 

(7, 5, None), (5, 8, None)], [(4, 3, None), (3, 2, None), 

(2, 7, None), (7, 5, None), (5, 0, None), (0, 4, None)], 

[(4, 9, None), (9, 7, None), (7, 5, None), (5, 0, None), 

(0, 4, None)], [(8, 6, None), (6, 9, None), (9, 7, None), 

(7, 5, None), (5, 8, None)], [(1, 6, None), (6, 9, None), 

(9, 7, None), (7, 5, None), (5, 0, None), (0, 1, None)]] 

 

Checking the given cycles are algebraically free:: 

 

sage: g = graphs.RandomGNP(30,.4) 

sage: basis = g.cycle_basis() 

 

Building the space of (directed) edges over `Z/2Z`. On the way, 

building a dictionary associating an unique vector to each 

undirected edge:: 

 

sage: m = g.size() 

sage: edge_space = VectorSpace(FiniteField(2),m) 

sage: edge_vector = dict( zip( g.edges(labels = False), edge_space.basis() ) ) 

sage: for (u,v),vec in list(edge_vector.items()): 

....: edge_vector[(v,u)] = vec 

 

Defining a lambda function associating a vector to the 

vertices of a cycle:: 

 

sage: vertices_to_edges = lambda x : zip( x, x[1:] + [x[0]] ) 

sage: cycle_to_vector = lambda x : sum( edge_vector[e] for e in vertices_to_edges(x) ) 

 

Finally checking the cycles are a free set:: 

 

sage: basis_as_vectors = [cycle_to_vector(_) for _ in basis] 

sage: edge_space.span(basis_as_vectors).rank() == len(basis) 

True 

 

For undirected graphs with multiple edges:: 

 

sage: G = Graph([(0,2,'a'),(0,2,'b'),(0,1,'c'),(1,2,'d')], multiedges=True) 

sage: G.cycle_basis() 

[[0, 2], [2, 1, 0]] 

sage: G.cycle_basis(output='edge') 

[[(0, 2, 'a'), (2, 0, 'b')], [(2, 1, 'd'), (1, 0, 'c'), 

(0, 2, 'a')]] 

 

Disconnected graph:: 

 

sage: G.add_cycle(["Hey", "Wuuhuu", "Really ?"]) 

sage: G.cycle_basis() 

[['Really ?', 'Hey', 'Wuuhuu'], [0, 2], [2, 1, 0]] 

sage: G.cycle_basis(output='edge') 

[[('Really ?', 'Hey', None), 

('Hey', 'Wuuhuu', None), 

('Wuuhuu', 'Really ?', None)], 

[(0, 2, 'a'), (2, 0, 'b')], 

[(2, 1, 'd'), (1, 0, 'c'), (0, 2, 'a')]] 

 

Graph that allows multiple edges but does not contain any:: 

 

sage: G = graphs.CycleGraph(3) 

sage: G.allow_multiple_edges(True) 

sage: G.cycle_basis() 

[[2, 1, 0]] 

 

Not yet implemented for directed graphs with multiple edges:: 

 

sage: G = DiGraph([(0,2,'a'),(0,2,'b'),(0,1,'c'),(1,2,'d')], multiedges=True) 

sage: G.cycle_basis() 

Traceback (most recent call last): 

... 

NotImplementedError: not implemented for directed graphs 

with multiple edges 

""" 

if not output in ['vertex', 'edge']: 

raise ValueError('output must be either vertex or edge') 

 

if self.allows_multiple_edges(): 

if self.is_directed(): 

raise NotImplementedError('not implemented for directed ' 

'graphs with multiple edges') 

 

if not self.is_connected(): 

return sum([g.cycle_basis(output=output) 

for g in self.connected_components_subgraphs()], 

[]) 

 

T = self.min_spanning_tree() 

return [self.subgraph(edges=T + [e]).is_forest(certificate=True, 

output=output)[1] 

for e in self.edges() if not e in T] 

 

# second case: there are no multiple edges 

import networkx 

cycle_basis_v = networkx.cycle_basis(self.networkx_graph(copy=False)) 

if output == 'vertex': 

return cycle_basis_v 

 

def vertices_to_edges(x): 

return [(u[0], u[1], self.edge_label(u[0], u[1])) 

for u in zip(x, x[1:] + [x[0]])] 

return [vertices_to_edges(_) for _ in cycle_basis_v] 

 

 

### Planarity 

 

def is_planar(self, on_embedding=None, kuratowski=False, set_embedding=False, set_pos=False): 

r""" 

Test whether the graph is planar. 

 

This wraps the reference implementation provided by John Boyer of the 

linear time planarity algorithm by edge addition due to Boyer 

Myrvold. (See reference code in :mod:`~sage.graphs.planarity`). 

 

.. NOTE:: 

 

The argument on_embedding takes precedence over 

``set_embedding``. This means that only the ``on_embedding`` 

combinatorial embedding will be tested for planarity and no 

``_embedding`` attribute will be set as a result of this function 

call, unless ``on_embedding`` is None. 

 

REFERENCE: 

 

.. [1] John M. Boyer and Wendy J. Myrvold, On the Cutting Edge: 

Simplified `O(n)` Planarity by Edge Addition. Journal of Graph 

Algorithms and Applications, Vol. 8, No. 3, pp. 241-273, 

2004. 

 

.. SEEALSO:: 

 

- "Almost planar graph": :meth:`~Graph.is_apex` 

- "Measuring non-planarity": :meth:`~genus`, :meth:`~crossing_number` 

- :meth:`planar_dual` 

- :meth:`faces` 

- :meth:`~sage.graphs.graph.Graph.is_polyhedral` 

 

INPUT: 

 

- ``kuratowski`` - returns a tuple with boolean as first entry. If the 

graph is nonplanar, will return the Kuratowski subgraph (i.e. an edge 

subdivision of `K_5` or `K_{3,3}`) as the second tuple entry. If the 

graph is planar, returns ``None`` as the second entry. 

 

- ``on_embedding`` - the embedding dictionary to test planarity 

on. (i.e.: will return ``True`` or ``False`` only for the given 

embedding.) 

 

- ``set_embedding`` - whether or not to set the instance field variable 

that contains a combinatorial embedding (clockwise ordering of 

neighbors at each vertex). This value will only be set if a planar 

embedding is found. It is stored as a Python dict: ``v1: [n1,n2,n3]`` 

where ``v1`` is a vertex and ``n1,n2,n3`` are its neighbors. 

 

- ``set_pos`` - whether or not to set the position 

dictionary (for plotting) to reflect the combinatorial embedding. 

Note that this value will default to False if set_emb is set to 

False. Also, the position dictionary will only be updated if a 

planar embedding is found. 

 

EXAMPLES:: 

 

sage: g = graphs.CubeGraph(4) 

sage: g.is_planar() 

False 

 

:: 

 

sage: g = graphs.CircularLadderGraph(4) 

sage: g.is_planar(set_embedding=True) 

True 

sage: g.get_embedding() 

{0: [1, 4, 3], 

1: [2, 5, 0], 

2: [3, 6, 1], 

3: [0, 7, 2], 

4: [0, 5, 7], 

5: [1, 6, 4], 

6: [2, 7, 5], 

7: [4, 6, 3]} 

 

:: 

 

sage: g = graphs.PetersenGraph() 

sage: (g.is_planar(kuratowski=True))[1].adjacency_matrix() 

[0 1 0 0 0 1 0 0 0] 

[1 0 1 0 0 0 1 0 0] 

[0 1 0 1 0 0 0 1 0] 

[0 0 1 0 0 0 0 0 1] 

[0 0 0 0 0 0 1 1 0] 

[1 0 0 0 0 0 0 1 1] 

[0 1 0 0 1 0 0 0 1] 

[0 0 1 0 1 1 0 0 0] 

[0 0 0 1 0 1 1 0 0] 

 

:: 

 

sage: k43 = graphs.CompleteBipartiteGraph(4,3) 

sage: result = k43.is_planar(kuratowski=True); result 

(False, Graph on 6 vertices) 

sage: result[1].is_isomorphic(graphs.CompleteBipartiteGraph(3,3)) 

True 

 

Multi-edged and looped graphs are partially supported:: 

 

sage: G = Graph({0:[1,1]}, multiedges=True) 

sage: G.is_planar() 

True 

sage: G.is_planar(on_embedding={}) 

Traceback (most recent call last): 

... 

NotImplementedError: Cannot compute with embeddings of multiple-edged or looped graphs. 

sage: G.is_planar(set_pos=True) 

Traceback (most recent call last): 

... 

NotImplementedError: Cannot compute with embeddings of multiple-edged or looped graphs. 

sage: G.is_planar(set_embedding=True) 

Traceback (most recent call last): 

... 

NotImplementedError: Cannot compute with embeddings of multiple-edged or looped graphs. 

sage: G.is_planar(kuratowski=True) 

(True, None) 

 

:: 

 

sage: G = graphs.CompleteGraph(5) 

sage: G = Graph(G, multiedges=True) 

sage: G.add_edge(0,1) 

sage: G.is_planar() 

False 

sage: b,k = G.is_planar(kuratowski=True) 

sage: b 

False 

sage: k.vertices() 

[0, 1, 2, 3, 4] 

 

TESTS: 

 

:trac:`18045`:: 

 

sage: g = graphs.CompleteGraph(4) 

sage: g.is_planar(set_embedding=True) 

True 

sage: emb = {0 : [2,3,1], 1: [2,3,0], 2: [1,3,0], 3:[0,1,2]} 

sage: g.is_planar(on_embedding=emb) 

False 

 

:trac:`19193`:: 

 

sage: posets.BooleanLattice(3).cover_relations_graph().is_planar() 

True 

 

Corner cases:: 

 

sage: graphs.EmptyGraph().is_planar() 

True 

sage: Graph(1).is_planar() 

True 

""" 

# Quick check first 

if (on_embedding is None and not kuratowski and not set_embedding and not set_pos 

and not self.allows_loops() and not self.allows_multiple_edges()): 

if self.order() > 4 and self.size() > 3*self.order()-6: 

return False 

 

if self.has_multiple_edges() or self.has_loops(): 

if set_embedding or (on_embedding is not None) or set_pos: 

raise NotImplementedError("Cannot compute with embeddings of multiple-edged or looped graphs.") 

else: 

return self.to_simple().is_planar(kuratowski=kuratowski) 

 

if on_embedding is not None: 

self._check_embedding_validity(on_embedding,boolean=False) 

return (0 == self.genus(minimal=False,set_embedding=False,on_embedding=on_embedding)) 

else: 

from sage.graphs.planarity import is_planar 

G = self.to_undirected() 

if hasattr(G, '_immutable'): 

G = copy(G) 

planar = is_planar(G,kuratowski=kuratowski,set_pos=set_pos,set_embedding=set_embedding) 

if kuratowski: 

bool_result = planar[0] 

else: 

bool_result = planar 

if bool_result: 

if set_pos: 

self._pos = G._pos 

if set_embedding: 

self._embedding = G._embedding 

return planar 

 

def is_circular_planar(self, on_embedding=None, kuratowski=False, 

set_embedding=True, boundary=None, 

ordered=False, set_pos=False): 

""" 

Tests whether the graph is circular planar (outerplanar) 

 

A graph is circular planar if it has a planar embedding in which all 

vertices can be drawn in order on a circle. This method can also be used 

to check the existence of a planar embedding in which the vertices of a 

specific set (the *boundary*) can be drawn on a circle, all other 

vertices being drawn inside of the circle. An order can be defined on 

the vertices of the boundary in order to define how they are to appear 

on the circle. 

 

INPUT: 

 

- ``kuratowski`` (boolean) - if set to True, returns a tuple with 

boolean first entry and the Kuratowski subgraph (i.e. an edge 

subdivision of `K_5` or `K_{3,3}`) as the second entry (see OUTPUT below). 

It is set to ``False`` by default. 

 

- ``on_embedding`` (boolean) - the embedding dictionary to test 

planarity on. (i.e.: will return ``True`` or ``False`` only for the 

given embedding). It is set to ``False`` by default. 

 

- ``set_embedding`` (boolean) - whether or not to set the instance field 

variable that contains a combinatorial embedding (clockwise ordering 

of neighbors at each vertex). This value will only be set if a 

circular planar embedding is found. It is stored as a Python dict: 

``v1: [n1,n2,n3]`` where ``v1`` is a vertex and ``n1,n2,n3`` are its 

neighbors. It is set to ``True`` by default. 

 

- ``boundary`` - a set of vertices that are required to be drawn on the 

circle, all others being drawn inside of it. It is set to ``None`` by 

default, meaning that *all* vertices should be drawn on the boundary. 

 

- ``ordered`` (boolean) - whether or not to consider the order of the 

boundary. It is set to ``False`` by default, and required 

``boundary`` to be defined. 

 

- ``set_pos`` - whether or not to set the position dictionary (for 

plotting) to reflect the combinatorial embedding. Note that this 

value will default to False if set_emb is set to False. Also, the 

position dictionary will only be updated if a circular planar 

embedding is found. 

 

OUTPUT: 

 

The method returns ``True`` if the graph is circular planar, and 

``False`` if it is not. 

 

If ``kuratowski`` is set to ``True``, then this function will return a 

tuple, whose first entry is a boolean and whose second entry is the 

Kuratowski subgraph (i.e. an edge subdivision of `K_5` or `K_{3,3}`) 

isolated by the Boyer-Myrvold algorithm. Note that this graph 

might contain a vertex or edges that were not in the initial graph. 

These would be elements referred to below 

as parts of the wheel and the star, which were added to the graph to 

require that the boundary can be drawn on the boundary of a disc, with 

all other vertices drawn inside (and no edge crossings). 

 

ALGORITHM: 

 

This is a linear time algorithm to test for circular planarity. It 

relies on the edge-addition planarity algorithm due to 

Boyer-Myrvold. We accomplish linear time for circular planarity by 

modifying the graph before running the general planarity 

algorithm. 

 

REFERENCE: 

 

.. [BM04] John M. Boyer and Wendy J. Myrvold, On the Cutting Edge: 

Simplified O(n) Planarity by Edge Addition. Journal of Graph 

Algorithms and Applications, Vol. 8, No. 3, pp. 241-273, 

2004. 

 

EXAMPLES:: 

 

sage: g439 = Graph({1:[5,7], 2:[5,6], 3:[6,7], 4:[5,6,7]}) 

sage: g439.show() 

sage: g439.is_circular_planar(boundary = [1,2,3,4]) 

False 

sage: g439.is_circular_planar(kuratowski=True, boundary = [1,2,3,4]) 

(False, Graph on 8 vertices) 

sage: g439.is_circular_planar(kuratowski=True, boundary = [1,2,3]) 

(True, None) 

sage: g439.get_embedding() 

{1: [7, 5], 

2: [5, 6], 

3: [6, 7], 

4: [7, 6, 5], 

5: [1, 4, 2], 

6: [2, 4, 3], 

7: [3, 4, 1]} 

 

Order matters:: 

 

sage: K23 = graphs.CompleteBipartiteGraph(2,3) 

sage: K23.is_circular_planar(boundary = [0,1,2,3]) 

True 

sage: K23.is_circular_planar(ordered=True, boundary = [0,1,2,3]) 

False 

 

With a different order:: 

 

sage: K23.is_circular_planar(set_embedding=True, boundary = [0,2,1,3]) 

True 

 

TESTS: 

 

Corner cases:: 

 

sage: graphs.EmptyGraph().is_circular_planar() 

True 

sage: Graph(1).is_circular_planar() 

True 

""" 

if ordered and boundary is None: 

raise ValueError("boundary must be set when ordered is True.") 

 

# Quick check first 

if (on_embedding is None and not kuratowski and set_embedding and 

boundary is None and not ordered and not set_pos and 

not self.allows_loops() and not self.allows_multiple_edges()): 

if self.order() > 3 and self.size() > 2*self.order()-3: 

return False 

 

if boundary is None: 

boundary = self 

 

# A local copy of self 

from sage.graphs.graph import Graph 

from sage.graphs.planarity import is_planar 

graph = Graph(self) 

if hasattr(graph, '_embedding'): 

del(graph._embedding) 

 

# Adds a new vertex to the graph 

extra = 0 

while graph.has_vertex(extra): 

extra=extra+1 

graph.add_vertex(extra) 

 

# Adds edges from the new vertex to all vertices of the boundary 

for vertex in boundary: 

graph.add_edge(vertex,extra) 

 

extra_edges = [] 

 

# When ordered is True, we need a way to make sure that the ordering is 

# respected. 

if ordered: 

 

# We add edges between consecutive vertices of the boundary (only 

# len(boundary)-1 are actually sufficient) 

for u, v in zip(boundary[:-1], boundary[1:]): 

if not graph.has_edge(u, v): 

extra_edges.append((u, v)) 

 

graph.add_edges(extra_edges) 

 

result = is_planar(graph,kuratowski=kuratowski,set_embedding=set_embedding,circular=True) 

 

if kuratowski: 

bool_result = result[0] 

else: 

bool_result = result 

 

if bool_result: 

graph.delete_vertex(extra) 

graph.delete_edges(extra_edges) 

 

if hasattr(graph,'_embedding'): 

# strip the embedding to fit original graph 

for u,v in extra_edges: 

graph._embedding[u].pop(graph._embedding[u].index(v)) 

graph._embedding[v].pop(graph._embedding[v].index(u)) 

for w in boundary: 

graph._embedding[w].pop(graph._embedding[w].index(extra)) 

 

if set_embedding: 

self._embedding = graph._embedding.copy() 

 

if (set_pos and set_embedding): 

self.set_planar_positions() 

 

del graph 

return result 

 

# TODO: rename into _layout_planar 

def set_planar_positions(self, test = False, **layout_options): 

""" 

Compute a planar layout for self using Schnyder's algorithm, 

and save it as default layout. 

 

EXAMPLES:: 

 

sage: g = graphs.CycleGraph(7) 

sage: g.set_planar_positions(test=True) 

True 

 

This method is deprecated since Sage-4.4.1.alpha2. Please use instead: 

 

sage: g.layout(layout = "planar", save_pos = True) 

{0: [1, 4], 1: [5, 1], 2: [0, 5], 3: [1, 0], 4: [1, 2], 5: [2, 1], 6: [4, 1]} 

""" 

self.layout(layout = "planar", save_pos = True, test = test, **layout_options) 

if test: # Optional error-checking, ( looking for edge-crossings O(n^2) ). 

return self.is_drawn_free_of_edge_crossings() # returns true if tests pass 

else: 

return 

 

def layout_planar(self, set_embedding=False, on_embedding=None, external_face=None, test=False, circular=False, **options): 

""" 

Uses Schnyder's algorithm to compute a planar layout for self, 

raising an error if self is not planar. 

 

INPUT: 

 

- ``set_embedding`` - if True, sets the combinatorial 

embedding used (see self.get_embedding()) 

 

- ``on_embedding`` - dict: provide a combinatorial 

embedding 

 

- ``external_face`` - ignored 

 

- ``test`` - if True, perform sanity tests along the way 

 

- ``circular`` - ignored 

 

 

EXAMPLES:: 

 

sage: g = graphs.PathGraph(10) 

sage: g.set_planar_positions(test=True) 

True 

sage: g = graphs.BalancedTree(3,4) 

sage: g.set_planar_positions(test=True) 

True 

sage: g = graphs.CycleGraph(7) 

sage: g.set_planar_positions(test=True) 

True 

sage: g = graphs.CompleteGraph(5) 

sage: g.set_planar_positions(test=True,set_embedding=True) 

Traceback (most recent call last): 

... 

ValueError: Complete graph is not a planar graph 

""" 

from sage.graphs.graph import Graph 

from sage.graphs.schnyder import _triangulate, _normal_label, _realizer, _compute_coordinates 

 

G = Graph(self) 

try: 

G._embedding = self._embedding 

except AttributeError: 

pass 

embedding_copy = None 

if set_embedding: 

if not (G.is_planar(set_embedding=True)): 

raise ValueError('%s is not a planar graph'%self) 

embedding_copy = G._embedding 

else: 

if on_embedding is not None: 

G._check_embedding_validity(on_embedding,boolean=False) 

if not (G.is_planar(on_embedding=on_embedding)): 

raise ValueError('provided embedding is not a planar embedding for %s'%self ) 

else: 

if hasattr(G,'_embedding'): 

if G._check_embedding_validity(): 

if not (G.is_planar(on_embedding=G._embedding)): 

raise ValueError('%s has nonplanar _embedding attribute. Try putting set_embedding=True'%self) 

embedding_copy = G._embedding 

else: 

raise ValueError('provided embedding is not a valid embedding for %s. Try putting set_embedding=True'%self) 

else: 

G.is_planar(set_embedding=True) 

# The following is what was breaking the code. It is where we were specifying the external 

# face ahead of time. This is definitely a TODO: 

# 

# Running is_planar(set_embedding=True) has set attribute self._embedding 

#if external_face is None: 

# faces = faces( self, self._embedding ) 

# faces.sort(key=len) 

# external_face = faces[-1] 

 

#n = len(external_face) 

#other_added_edges = [] 

#if n > 3: 

# v1, v2, v3 = external_face[0][0], external_face[int(n/3)][0], external_face[int(2*n/3)][0] 

# if not self.has_edge( (v1,v2) ): 

# self.add_edge( (v1, v2) ) 

# other_added_edges.append( (v1, v2) ) 

# if not self.has_edge( (v2,v3) ): 

# self.add_edge( (v2, v3) ) 

# other_added_edges.append( (v2, v3) ) 

# if not self.has_edge( (v3,v1) ): 

# self.add_edge( (v3, v1) ) 

# other_added_edges.append( (v3, v1) ) 

# if not self.is_planar(set_embedding=True): # get new combinatorial embedding (with added edges) 

# raise ValueError('modified graph %s is not planar. Try specifying an external face'%self) 

 

# Triangulate the graph 

extra_edges = _triangulate( G, G._embedding) 

 

# Optional error-checking 

if test: 

G.is_planar(set_embedding=True) # to get new embedding 

test_faces = G.faces(G._embedding) 

for face in test_faces: 

if len(face) != 3: 

raise RuntimeError('BUG: Triangulation returned face: %s'%face) 

 

G.is_planar(set_embedding=True) 

faces = G.faces(G._embedding) 

# Assign a normal label to the graph 

label = _normal_label( G, G._embedding, faces[0] ) 

 

# Get dictionary of tree nodes from the realizer 

tree_nodes = _realizer( G, label) 

 

# Compute the coordinates and store in position dictionary (attr self._pos) 

_compute_coordinates( G, tree_nodes ) 

 

# Delete all the edges added to the graph 

#G.delete_edges( extra_edges ) 

#self.delete_edges( other_added_edges ) 

 

if embedding_copy is not None: 

self._embedding = embedding_copy 

 

return G._pos 

 

def is_drawn_free_of_edge_crossings(self): 

""" 

Returns True is the position dictionary for this graph is set and 

that position dictionary gives a planar embedding. 

 

This simply checks all pairs of edges that don't share a vertex to 

make sure that they don't intersect. 

 

.. note:: 

 

This function require that _pos attribute is set. (Returns 

False otherwise.) 

 

EXAMPLES:: 

 

sage: D = graphs.DodecahedralGraph() 

sage: D.set_planar_positions() 

sage: D.is_drawn_free_of_edge_crossings() 

True 

""" 

if self._pos is None: 

return False 

 

G = self.to_undirected() 

for edge1 in G.edges(labels = False): 

for edge2 in G.edges(labels = False): 

if edge1[0] == edge2[0] or edge1[0] == edge2[1] or edge1[1] == edge2[0] or edge1[1] == edge2[1]: 

continue 

p1, p2 = self._pos[edge1[0]], self._pos[edge1[1]] 

dy = Rational(p2[1] - p1[1]) 

dx = Rational(p2[0] - p1[0]) 

q1, q2 = self._pos[edge2[0]], self._pos[edge2[1]] 

db = Rational(q2[1] - q1[1]) 

da = Rational(q2[0] - q1[0]) 

if(da * dy == db * dx): 

if dx != 0: 

t1 = Rational(q1[0] - p1[0])/dx 

t2 = Rational(q2[0] - p1[0])/dx 

if (0 <= t1 and t1 <= 1) or (0 <= t2 and t2 <= 1): 

if p1[1] + t1 * dy == q1[1] or p1[1] + t2 * dy == q2[1]: 

return False 

else: 

t1 = Rational(q1[1] - p1[1])/dy 

t2 = Rational(q2[1] - p1[1])/dy 

if (0 <= t1 and t1 <= 1) or (0 <= t2 and t2 <= 1): 

if p1[0] + t1 * dx == q1[0] or p1[0] + t2 * dx == q2[0]: 

return False 

else: 

s = (dx * Rational(q1[1] - p1[1]) + dy * Rational(p1[0] - q1[0])) / (da * dy - db * dx) 

t = (da * Rational(p1[1] - q1[1]) + db * Rational(q1[0] - p1[0])) / (db * dx - da * dy) 

 

if s >= 0 and s <= 1 and t >= 0 and t <= 1: 

print('fail on', p1, p2, ' : ',q1, q2) 

print(edge1, edge2) 

return False 

return True 

 

def genus(self, set_embedding=True, on_embedding=None, minimal=True, maximal=False, circular=None, ordered=True): 

r""" 

Returns the minimal genus of the graph. 

 

The genus of a compact surface is the number of handles it 

has. The genus of a graph is the minimal genus of the surface 

it can be embedded into. It can be seen as a measure of non-planarity; 

a planar graph has genus zero. 

 

Note - This function uses Euler's formula and thus it is necessary 

to consider only connected graphs. 

 

INPUT: 

 

- ``set_embedding (boolean)`` - whether or not to 

store an embedding attribute of the computed (minimal) genus of the 

graph. (Default is True). 

 

- ``on_embedding`` (default: ``None``) - two kinds of input are allowed: 

 

- a dictionary representing a combinatorial embedding on which the 

genus should be computed. Note that this must be a valid embedding 

for the graph. The dictionary structure is given by: ``vertex1: 

[neighbor1, neighbor2, neighbor3], vertex2: [neighbor]`` where 

there is a key for each vertex in the graph and a (clockwise) 

ordered list of each vertex's neighbors as values. The value of 

``on_embedding`` takes precedence over a stored ``_embedding`` 

attribute if minimal is set to ``False``. 

 

- The value ``True``, in order to indicate that the embedding stored 

as ``_embedding`` should be used (see examples). 

 

- ``minimal (boolean)`` - whether or not to compute 

the minimal genus of the graph (i.e., testing all embeddings). If 

minimal is False, then either maximal must be True or on_embedding 

must not be None. If on_embedding is not None, it will take 

priority over minimal. Similarly, if maximal is True, it will take 

priority over minimal. 

 

- ``maximal (boolean)`` - whether or not to compute 

the maximal genus of the graph (i.e., testing all embeddings). If 

maximal is False, then either minimal must be True or on_embedding 

must not be None. If on_embedding is not None, it will take 

priority over maximal. However, maximal takes priority over the 

default minimal. 

 

- ``circular (list)`` - if ``circular`` is a list of vertices, the 

method computes the genus preserving a planar embedding of the this 

list. If circular is defined, ``on_embedding`` is not a valid 

option. It is set to ``None`` by default. 

 

- ``ordered (boolean)`` - if circular is True, then 

whether or not the boundary order may be permuted. (Default is 

True, which means the boundary order is preserved.) 

 

EXAMPLES:: 

 

sage: g = graphs.PetersenGraph() 

sage: g.genus() # tests for minimal genus by default 

1 

sage: g.genus(on_embedding=True, maximal=True) # on_embedding overrides minimal and maximal arguments 

1 

sage: g.genus(maximal=True) # setting maximal to True overrides default minimal=True 

3 

sage: g.genus(on_embedding=g.get_embedding()) # can also send a valid combinatorial embedding dict 

3 

sage: (graphs.CubeGraph(3)).genus() 

0 

sage: K23 = graphs.CompleteBipartiteGraph(2,3) 

sage: K23.genus() 

0 

sage: K33 = graphs.CompleteBipartiteGraph(3,3) 

sage: K33.genus() 

1 

 

Using the circular argument, we can compute the minimal genus 

preserving a planar, ordered boundary:: 

 

sage: cube = graphs.CubeGraph(2) 

sage: cube.genus(circular=['01','10']) 

0 

sage: cube.is_circular_planar() 

True 

sage: cube.genus(circular=['01','10']) 

0 

sage: cube.genus(circular=['01','10'], on_embedding=True) 

0 

sage: cube.genus(circular=['01','10'], maximal=True) 

Traceback (most recent call last): 

... 

NotImplementedError: Cannot compute the maximal genus of a genus respecting a boundary. 

 

Note: not everything works for multigraphs, looped graphs or digraphs. But the 

minimal genus is ultimately computable for every connected graph -- but the 

embedding we obtain for the simple graph can't be easily converted to an 

embedding of a non-simple graph. Also, the maximal genus of a multigraph does 

not trivially correspond to that of its simple graph. :: 

 

sage: G = DiGraph({ 0 : [0,1,1,1], 1 : [2,2,3,3], 2 : [1,3,3], 3:[0,3]}) 

sage: G.genus() 

Traceback (most recent call last): 

... 

NotImplementedError: Can't work with embeddings of non-simple graphs 

sage: G.to_simple().genus() 

0 

sage: G.genus(set_embedding=False) 

0 

sage: G.genus(maximal=True, set_embedding=False) 

Traceback (most recent call last): 

... 

NotImplementedError: Can't compute the maximal genus of a graph with loops or multiple edges 

 

 

We break graphs with cut vertices into their blocks, which greatly speeds up 

computation of minimal genus. This is not implemented for maximal genus. :: 

 

sage: K5 = graphs.CompleteGraph(5) 

sage: G = K5.copy() 

sage: s = 4 

sage: for i in range(1,100): 

....: k = K5.relabel(list(range(s,s+5)),inplace=False) 

....: G.add_edges(k.edges()) 

....: s += 4 

sage: G.genus() 

100 

 

""" 

if not self.is_connected(): 

raise TypeError("Graph must be connected to use Euler's Formula to compute minimal genus.") 

 

G = self.to_simple(immutable=False) 

verts = G.order() 

edges = G.size() 

 

if maximal: 

minimal = False 

 

if circular is not None: 

if not isinstance(circular,list): 

raise ValueError("'circular' is expected to be a list") 

if maximal: 

raise NotImplementedError("Cannot compute the maximal genus of a genus respecting a boundary.") 

boundary = circular 

if hasattr(G, '_embedding'): 

del(G._embedding) 

 

extra = 0 

while G.has_vertex(extra): 

extra=extra+1 

G.add_vertex(extra) 

verts += 1 

 

for vertex in boundary: 

G.add_edge(vertex,extra) 

 

extra_edges = [] 

if ordered: # WHEEL 

for i in range(len(boundary)-1): 

if not G.has_edge(boundary[i],boundary[i+1]): 

G.add_edge(boundary[i],boundary[i+1]) 

extra_edges.append((boundary[i],boundary[i+1])) 

if not G.has_edge(boundary[-1],boundary[0]): 

G.add_edge(boundary[-1],boundary[0]) 

extra_edges.append((boundary[-1],boundary[0])) 

# else STAR (empty list of extra edges) 

 

edges = G.size() 

 

if on_embedding is not None: 

if self.has_loops() or self.is_directed() or self.has_multiple_edges(): 

raise NotImplementedError("Can't work with embeddings of non-simple graphs") 

 

if isinstance(on_embedding,dict): 

faces = len(self.faces(on_embedding)) 

return (2-verts+edges-faces) // 2 

elif on_embedding: 

try: 

faces = len(self.faces(self._embedding)) 

except AttributeError: 

raise AttributeError('graph must have attribute _embedding set to compute current (embedded) genus') 

return (2-verts+edges-faces) // 2 

else: # then compute either maximal or minimal genus of all embeddings 

from . import genus 

 

if set_embedding: 

if self.has_loops() or self.is_directed() or self.has_multiple_edges(): 

raise NotImplementedError("Can't work with embeddings of non-simple graphs") 

if minimal: 

B,C = G.blocks_and_cut_vertices() 

embedding = {} 

g = 0 

for block in B: 

H = G.subgraph(block) 

g += genus.simple_connected_graph_genus(H, set_embedding = True, check = False, minimal = True) 

emb = H.get_embedding() 

for v in emb: 

if v in embedding: 

embedding[v] += emb[v] 

else: 

embedding[v] = emb[v] 

self._embedding = embedding 

else: 

g = genus.simple_connected_graph_genus(G, set_embedding = True, check = False, minimal = minimal) 

self._embedding = G._embedding 

return g 

else: 

if maximal and (self.has_multiple_edges() or self.has_loops()): 

raise NotImplementedError("Can't compute the maximal genus of a graph with loops or multiple edges") 

if minimal: 

B,C = G.blocks_and_cut_vertices() 

g = 0 

for block in B: 

H = G.subgraph(block) 

g += genus.simple_connected_graph_genus(H, set_embedding = False, check = False, minimal = True) 

return g 

else: 

return genus.simple_connected_graph_genus(G, set_embedding = False, check=False, minimal=minimal) 

 

def crossing_number(self): 

""" 

Return the crossing number of the graph. 

 

The crossing number of a graph is the minimum number of 

edge crossings needed to draw the graph on a plane. It can 

be seen as a measure of non-planarity; a planar graph has 

crossing number zero. 

 

See :wikipedia:`Crossing_number` for more information. 

 

EXAMPLES:: 

 

sage: P = graphs.PetersenGraph() 

sage: P.crossing_number() 

2 

 

ALGORITHM: 

 

This is slow brute force implementation: for every `k` pairs of 

edges try adding a new vertex for a crossing point for them. If 

the result is not planar in any of those, try `k+1` pairs. 

 

Computing the crossing number is NP-hard problem. 

 

TESTS: 

 

Empty graph, graph without edges:: 

 

sage: E = graphs.EmptyGraph() 

sage: E.crossing_number() 

0 

 

sage: g = Graph(5) 

sage: g.crossing_number() 

0 

 

Planar graph:: 

 

sage: C4 = graphs.CompleteGraph(4) 

sage: C4.crossing_number() 

0 

 

Non-connected graph:: 

 

sage: C5x2 = graphs.CompleteGraph(5) * 2 

sage: C5x2.crossing_number() 

2 

 

Test the "un-splitting edges" optimization:: 

 

sage: g = graphs.CompleteGraph(4) 

sage: g.subdivide_edges(g.edges(), 1) 

sage: g.add_edge(0, g.add_vertex()) 

sage: g.crossing_number() 

0 

 

sage: g = graphs.CompleteGraph(5) 

sage: g.subdivide_edges(g.edges(), 2) 

sage: g.add_edge(0, g.add_vertex()) 

sage: g.crossing_number() 

1 

""" 

def _crossing_number(G): 

""" 

Return the crossing number of a biconnected non-planar 

graph ``G``. 

""" 

from sage.combinat.subset import Subsets 

 

# Splitting an edge does not increase the 

# crossing number, so reversedly we can shrink those. We must 

# check that un-splitting would not create multiple edges. 

two = [v for v in G if G.degree(v) == 2] 

for v in two: 

u, w = G.neighbors(v) 

if not G.has_edge(u, w): 

G.add_edge(u, w) 

G.delete_vertex(v) 

 

edgepairs = Subsets(G.edges(labels=False, sort=False), 2) 

edgepairs = [x for x in edgepairs if x[0][0] not in [x[1][0], x[1][1]] and 

x[0][1] not in [x[1][0], x[1][1]]] 

 

k = 1 

while True: 

for edges in Subsets(edgepairs, k): 

g = copy(G) 

for pair in edges: 

g.delete_edges(pair) 

for edge in edges: 

v = g.add_vertex() 

g.add_edge(edge[0][0], v) 

g.add_edge(v, edge[0][1]) 

g.add_edge(edge[1][0], v) 

g.add_edge(v, edge[1][1]) 

if g.is_planar(): 

return k 

k += 1 

 

self._scream_if_not_simple(allow_loops=True) 

 

blocks = self.blocks_and_cut_vertices()[0] 

k = 0 

for block in blocks: 

if len(block) > 4: 

g = self.subgraph(block) 

if not g.is_planar(): 

k += _crossing_number(g) 

return k 

 

def faces(self, embedding = None): 

""" 

Return the faces of an embedded graph. 

 

A combinatorial embedding of a graph is a clockwise ordering of the 

neighbors of each vertex. From this information one can define the faces 

of the embedding, which is what this method returns. 

 

INPUT: 

 

- ``embedding`` - a combinatorial embedding dictionary. Format: 

``{v1:[v2,v3], v2:[v1], v3:[v1]}`` (clockwise ordering of neighbors at 

each vertex). If set to ``None`` (default) the method will use the 

embedding stored as ``self._embedding``. If none is stored, the method 

will compute the set of faces from the embedding returned by 

:meth:`is_planar` (if the graph is, of course, planar). 

 

.. NOTE:: 

 

``embedding`` is an ordered list based on the hash order of the 

vertices of graph. To avoid confusion, it might be best to set the 

rot_sys based on a 'nice_copy' of the graph. 

 

.. SEEALSO:: 

 

* :meth:`set_embedding` 

* :meth:`get_embedding` 

* :meth:`is_planar` 

* :meth:`planar_dual` 

 

EXAMPLES: 

 

Providing an embedding:: 

 

sage: T = graphs.TetrahedralGraph() 

sage: T.faces({0: [1, 3, 2], 1: [0, 2, 3], 2: [0, 3, 1], 3: [0, 1, 2]}) 

[[(0, 1), (1, 2), (2, 0)], 

[(3, 2), (2, 1), (1, 3)], 

[(3, 0), (0, 2), (2, 3)], 

[(3, 1), (1, 0), (0, 3)]] 

 

With no embedding provided:: 

 

sage: graphs.TetrahedralGraph().faces() 

[[(0, 1), (1, 2), (2, 0)], 

[(3, 2), (2, 1), (1, 3)], 

[(3, 0), (0, 2), (2, 3)], 

[(3, 1), (1, 0), (0, 3)]] 

 

With no embedding provided (non-planar graph):: 

 

sage: graphs.PetersenGraph().faces() 

Traceback (most recent call last): 

... 

ValueError: No embedding is provided and the graph is not planar. 

 

TESTS: 

 

The empty graph and a graph without edge have no face:: 

 

sage: Graph().faces() 

[] 

sage: Graph(1).faces() 

[] 

 

The Path Graph has a single face:: 

 

sage: graphs.PathGraph(3).faces() 

[[(0, 1), (1, 2), (2, 1), (1, 0)]] 

""" 

if not self.order() or not self.size(): 

return [] 

 

# Which embedding should we use ? 

if embedding is None: 

# Is self._embedding available ? 

if self._check_embedding_validity(): 

embedding = self._embedding 

else: 

if self.is_planar(set_embedding=True): 

embedding = self._embedding 

self._embedding = None 

else: 

raise ValueError("No embedding is provided and the graph is not planar.") 

 

# Establish set of possible edges 

edgeset = set() 

for u,v in self.edge_iterator(labels=0): 

edgeset.add((u, v)) 

edgeset.add((v, u)) 

 

# Storage for face paths 

faces = [] 

path = [edgeset.pop()] 

 

# Trace faces 

while edgeset: 

u,v = path[-1] 

neighbors = embedding[v] 

next_node = neighbors[(neighbors.index(u)+1)%(len(neighbors))] 

e = (v, next_node) 

if e == path[0]: 

faces.append(path) 

path = [edgeset.pop()] 

else: 

path.append(e) 

edgeset.discard(e) 

if path: faces.append(path) 

return faces 

 

def num_faces(self,embedding=None): 

""" 

Returns the number of faces of an embedded graph. 

 

EXAMPLES:: 

 

sage: T = graphs.TetrahedralGraph() 

sage: T.num_faces() 

4 

 

TESTS:: 

 

sage: G = graphs.CompleteMultipartiteGraph([3,3]) 

sage: G.num_faces() 

Traceback (most recent call last): 

... 

ValueError: No embedding is provided and the graph is not planar. 

""" 

 

return len(self.faces(embedding)) 

 

def planar_dual(self, embedding=None): 

""" 

Return the planar dual of an embedded graph. 

 

A combinatorial embedding of a graph is a clockwise ordering of the 

neighbors of each vertex. From this information one can obtain 

the dual of a plane graph, which is what the method returns. The 

vertices of the dual graph correspond to faces of the primal graph. 

 

INPUT: 

 

- ``embedding`` - a combinatorial embedding dictionary. Format: 

``{v1:[v2, v3], v2:[v1], v3:[v1]}`` (clockwise ordering of neighbors at 

each vertex). If set to ``None`` (default) the method will use the 

embedding stored as ``self._embedding``. If none is stored, the method 

will compute the set of faces from the embedding returned by 

:meth:`is_planar` (if the graph is, of course, planar). 

 

EXAMPLES:: 

 

sage: C = graphs.CubeGraph(3) 

sage: C.planar_dual() 

Graph on 6 vertices 

sage: graphs.IcosahedralGraph().planar_dual().is_isomorphic(graphs.DodecahedralGraph()) 

True 

 

The planar dual of the planar dual is isomorphic to the graph itself:: 

 

sage: g = graphs.BuckyBall() 

sage: g.planar_dual().planar_dual().is_isomorphic(g) 

True 

 

.. SEEALSO:: 

 

* :meth:`faces` 

* :meth:`set_embedding` 

* :meth:`get_embedding` 

* :meth:`is_planar` 

 

TESTS:: 

 

sage: G = graphs.CompleteMultipartiteGraph([3, 3]) 

sage: G.planar_dual() 

Traceback (most recent call last): 

... 

ValueError: No embedding is provided and the graph is not planar. 

sage: G = Graph([[1, 2, 3, 4], [[1, 2], [2, 3], [3, 1], [3, 2], [1, 4], [2, 4], [3, 4]]], multiedges=True) 

sage: G.planar_dual() 

Traceback (most recent call last): 

... 

ValueError: This method is not known to work on graphs with multiedges. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow multiedges using allow_multiple_edges(). 

sage: G = graphs.CompleteGraph(3) 

sage: G.planar_dual() 

Traceback (most recent call last): 

... 

NotImplementedError: the graph must be 3-vertex-connected. 

 

.. TODO:: 

 

Implement the method for graphs that are not 3-vertex-connected, 

or at least have a faster 3-vertex-connectivity test (:trac:`24635`). 

 

""" 

self._scream_if_not_simple() 

 

if not self.vertex_connectivity(k=3): 

raise NotImplementedError("the graph must be 3-vertex-connected.") 

 

from sage.graphs.graph import Graph 

return Graph([[tuple(_) for _ in self.faces()], lambda f, g: not set([tuple(reversed(e)) for e in f]).isdisjoint(g)], loops=False) 

 

 

### Connectivity 

 

def is_connected(self): 

""" 

Indicates whether the (di)graph is connected. Note that in a graph, 

path connected is equivalent to connected. 

 

.. SEEALSO:: 

 

- :meth:`~Graph.is_biconnected` 

 

EXAMPLES:: 

 

sage: G = Graph( { 0 : [1, 2], 1 : [2], 3 : [4, 5], 4 : [5] } ) 

sage: G.is_connected() 

False 

sage: G.add_edge(0,3) 

sage: G.is_connected() 

True 

sage: D = DiGraph( { 0 : [1, 2], 1 : [2], 3 : [4, 5], 4 : [5] } ) 

sage: D.is_connected() 

False 

sage: D.add_edge(0,3) 

sage: D.is_connected() 

True 

sage: D = DiGraph({1:[0], 2:[0]}) 

sage: D.is_connected() 

True 

""" 

if self.order() == 0: 

return True 

 

try: 

return self._backend.is_connected() 

except AttributeError: 

v = next(self.vertex_iterator()) 

conn_verts = list(self.depth_first_search(v, ignore_direction=True)) 

return len(conn_verts) == self.num_verts() 

 

def connected_components(self): 

""" 

Returns the list of connected components. 

 

Returns a list of lists of vertices, each list representing a 

connected component. The list is ordered from largest to smallest 

component. 

 

EXAMPLES:: 

 

sage: G = Graph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } ) 

sage: G.connected_components() 

[[0, 1, 2, 3], [4, 5, 6]] 

sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } ) 

sage: D.connected_components() 

[[0, 1, 2, 3], [4, 5, 6]] 

""" 

seen = set() 

components = [] 

for v in self: 

if v not in seen: 

c = self.connected_component_containing_vertex(v) 

seen.update(c) 

components.append(c) 

components.sort(key=lambda comp: -len(comp)) 

return components 

 

def connected_components_number(self): 

""" 

Returns the number of connected components. 

 

EXAMPLES:: 

 

sage: G = Graph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } ) 

sage: G.connected_components_number() 

2 

sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } ) 

sage: D.connected_components_number() 

2 

""" 

return len(self.connected_components()) 

 

def connected_components_subgraphs(self): 

""" 

Returns a list of connected components as graph objects. 

 

EXAMPLES:: 

 

sage: G = Graph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } ) 

sage: L = G.connected_components_subgraphs() 

sage: graphs_list.show_graphs(L) 

sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } ) 

sage: L = D.connected_components_subgraphs() 

sage: graphs_list.show_graphs(L) 

""" 

cc = self.connected_components() 

list = [] 

for c in cc: 

list.append(self.subgraph(c, inplace=False)) 

return list 

 

def connected_component_containing_vertex(self, vertex): 

""" 

Returns a list of the vertices connected to vertex. 

 

EXAMPLES:: 

 

sage: G = Graph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } ) 

sage: G.connected_component_containing_vertex(0) 

[0, 1, 2, 3] 

sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } ) 

sage: D.connected_component_containing_vertex(0) 

[0, 1, 2, 3] 

""" 

try: 

c = list(self._backend.depth_first_search(vertex, ignore_direction=True)) 

except AttributeError: 

c = list(self.depth_first_search(vertex, ignore_direction=True)) 

 

c.sort() 

return c 

 

def connected_components_sizes(self): 

""" 

Return the sizes of the connected components as a list. 

 

The list is sorted from largest to lower values. 

 

EXAMPLES:: 

 

sage: for x in graphs(3): print(x.connected_components_sizes()) 

[1, 1, 1] 

[2, 1] 

[3] 

[3] 

""" 

return sorted((len(cc) for cc in self.connected_components()),reverse=True) 

 

def blocks_and_cut_vertices(self): 

""" 

Computes the blocks and cut vertices of the graph. 

 

In the case of a digraph, this computation is done on the underlying 

graph. 

 

A cut vertex is one whose deletion increases the number of connected 

components. A block is a maximal induced subgraph which itself has no 

cut vertices. Two distinct blocks cannot overlap in more than a single 

cut vertex. 

 

OUTPUT: ``(B, C)``, where ``B`` is a list of blocks - each is a list of 

vertices and the blocks are the corresponding induced subgraphs - and 

``C`` is a list of cut vertices. 

 

ALGORITHM: 

 

We implement the algorithm proposed by Tarjan in [Tarjan72]_. The 

original version is recursive. We emulate the recursion using a stack. 

 

.. SEEALSO:: 

 

- :meth:`blocks_and_cuts_tree` 

- :meth:`~Graph.is_biconnected` 

- :meth:`~Graph.bridges` 

 

EXAMPLES: 

 

We construct a trivial example of a graph with one cut vertex:: 

 

sage: rings = graphs.CycleGraph(10) 

sage: rings.merge_vertices([0, 5]) 

sage: rings.blocks_and_cut_vertices() 

([[0, 1, 2, 3, 4], [0, 6, 7, 8, 9]], [0]) 

 

The Petersen graph is biconnected, hence has no cut vertices:: 

 

sage: graphs.PetersenGraph().blocks_and_cut_vertices() 

([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], []) 

 

Decomposing paths to pairs:: 

 

sage: g = graphs.PathGraph(4) + graphs.PathGraph(5) 

sage: g.blocks_and_cut_vertices() 

([[2, 3], [1, 2], [0, 1], [7, 8], [6, 7], [5, 6], [4, 5]], [1, 2, 5, 6, 7]) 

 

TESTS:: 

 

sage: Graph(0).blocks_and_cut_vertices() 

([], []) 

sage: Graph(1).blocks_and_cut_vertices() 

([[0]], []) 

sage: Graph(2).blocks_and_cut_vertices() 

([[0], [1]], []) 

 

REFERENCE: 

 

.. [Tarjan72] \R.E. Tarjan. Depth-First Search and Linear Graph 

Algorithms. SIAM J. Comput. 1(2): 146-160 (1972). 

""" 

blocks = [] 

cut_vertices = set() 

 

# We iterate over all vertices to ensure that we visit each connected 

# component of the graph 

seen = set() 

for start in self.vertex_iterator(): 

if start in seen: 

continue 

 

# Special case of an isolated vertex 

if not self.degree(start): 

blocks.append([start]) 

seen.add(start) 

continue 

 

# Each vertex is numbered with an integer from 1...|V(G)|, 

# corresponding to the order in which it is discovered during the 

# DFS. 

number = {} 

num = 1 

 

# Associates to each vertex v the smallest number of a vertex that 

# can be reached from v in the orientation of the graph that the 

# algorithm creates. 

low_point = {} 

 

# Associates to each vertex an iterator over its neighbors 

neighbors = {} 

 

stack = [start] 

edge_stack = [] 

start_already_seen = False 

 

while stack: 

v = stack[-1] 

seen.add(v) 

 

# The first time we meet v 

if not v in number: 

# We number the vertices in the order they are reached 

# during DFS 

number[v] = num 

neighbors[v] = self.neighbor_iterator(v) 

low_point[v] = num 

num += 1 

 

try: 

# We consider the next of its neighbors 

w = next(neighbors[v]) 

 

# If we never met w before, we remember the direction of 

# edge vw, and add w to the stack. 

if not w in number: 

edge_stack.append( (v,w) ) 

stack.append(w) 

 

# If w is an ancestor of v in the DFS tree, we remember the 

# direction of edge vw 

elif number[w]<number[v]: 

edge_stack.append( (v,w) ) 

low_point[v] = min(low_point[v], number[w]) 

 

# We went through all of v's neighbors 

except StopIteration: 

# We trackback, so w takes the value of v and we pop the 

# stack 

w = stack.pop() 

 

# Test termination of the algorithm 

if not stack: 

break 

 

v = stack[-1] 

 

# Propagating the information : low_point[v] indicates the 

# smallest vertex (the vertex x with smallest number[x]) 

# that can be reached from v 

low_point[v] = min(low_point[v], low_point[w]) 

 

# The situation in which there is no path from w to an 

# ancestor of v : we have identified a new biconnected 

# component 

if low_point[w] >= number[v]: 

new_block = set() 

nw = number[w] 

u1,u2 = edge_stack.pop() 

while number[u1] >= nw: 

new_block.add(u1) 

u1,u2 = edge_stack.pop() 

new_block.add(u1) 

blocks.append(sorted(list(new_block))) 

 

# We update the set of cut vertices. 

# 

# If v is start, then we add it only if it belongs to 

# several blocks. 

if (not v is start) or start_already_seen: 

cut_vertices.add(v) 

else: 

start_already_seen = True 

 

return blocks,sorted(list(cut_vertices)) 

 

def blocks_and_cuts_tree(self): 

""" 

Returns the blocks-and-cuts tree of ``self``. 

 

This new graph has two different kinds of vertices, some representing 

the blocks (type B) and some other the cut vertices of the graph 

``self`` (type C). 

 

There is an edge between a vertex `u` of type B and a vertex `v` of type 

C if the cut-vertex corresponding to `v` is in the block corresponding 

to `u`. 

 

The resulting graph is a tree, with the additional characteristic 

property that the distance between two leaves is even. When ``self`` is 

not connected, the resulting graph is a forest. 

 

When ``self`` is biconnected, the tree is reduced to a single node of 

type `B`. 

 

.. SEEALSO:: 

 

- :meth:`blocks_and_cut_vertices` 

- :meth:`~Graph.is_biconnected` 

 

EXAMPLES:: 

 

sage: T = graphs.KrackhardtKiteGraph().blocks_and_cuts_tree(); T 

Graph on 5 vertices 

sage: T.is_isomorphic(graphs.PathGraph(5)) 

True 

 

The distance between two leaves is even:: 

 

sage: T = graphs.RandomTree(40).blocks_and_cuts_tree() 

sage: T.is_tree() 

True 

sage: leaves = [v for v in T if T.degree(v) == 1] 

sage: all(T.distance(u,v) % 2 == 0 for u in leaves for v in leaves) 

True 

 

The tree of a biconnected graph has a single vertex, of type `B`:: 

 

sage: T = graphs.PetersenGraph().blocks_and_cuts_tree() 

sage: T.vertices() 

[('B', (0, 1, 2, 3, 4, 5, 6, 7, 8, 9))] 

 

TESTS: 

 

When ``self`` is not connected, the resulting graph is a forest (:trac:`24163`):: 

 

sage: T = Graph(2).blocks_and_cuts_tree() 

sage: T.is_forest() 

True 

 

REFERENCES: 

 

.. [HarPri] \F. Harary and G. Prins. The block-cutpoint-tree of 

a graph. Publ. Math. Debrecen 13 1966 103-107. 

.. [Gallai] \T. Gallai, Elementare Relationen bezueglich der 

Glieder und trennenden Punkte von Graphen, Magyar 

Tud. Akad. Mat. Kutato Int. Kozl. 9 (1964) 235-236 

""" 

from sage.graphs.graph import Graph 

B, C = self.blocks_and_cut_vertices() 

B = map(tuple, B) 

G = Graph() 

for bloc in B: 

G.add_vertex(('B', bloc)) 

for c in bloc: 

if c in C: 

G.add_edge(('B', bloc), ('C', c)) 

return G 

 

def is_cut_edge(self, u, v=None, label=None): 

""" 

Returns True if the input edge is a cut-edge or a bridge. 

 

A cut edge (or bridge) is an edge that when removed increases 

the number of connected components. This function works with 

simple graphs as well as graphs with loops and multiedges. In 

a digraph, a cut edge is an edge that when removed increases 

the number of (weakly) connected components. 

 

INPUT: The following forms are accepted 

 

- G.is_cut_edge( 1, 2 ) 

 

- G.is_cut_edge( (1, 2) ) 

 

- G.is_cut_edge( 1, 2, 'label' ) 

 

- G.is_cut_edge( (1, 2, 'label') ) 

 

OUTPUT: 

 

- Returns True if (u,v) is a cut edge, False otherwise 

 

EXAMPLES:: 

 

sage: G = graphs.CompleteGraph(4) 

sage: G.is_cut_edge(0,2) 

False 

 

sage: G = graphs.CompleteGraph(4) 

sage: G.add_edge((0,5,'silly')) 

sage: G.is_cut_edge((0,5,'silly')) 

True 

 

sage: G = Graph([[0,1],[0,2],[3,4],[4,5],[3,5]]) 

sage: G.is_cut_edge((0,1)) 

True 

 

sage: G = Graph([[0,1],[0,2],[1,1]], loops = True) 

sage: G.is_cut_edge((1,1)) 

False 

 

sage: G = digraphs.Circuit(5) 

sage: G.is_cut_edge((0,1)) 

False 

 

sage: G = graphs.CompleteGraph(6) 

sage: G.is_cut_edge((0,7)) 

Traceback (most recent call last): 

... 

ValueError: edge not in graph 

""" 

if label is None: 

if v is None: 

try: 

u, v, label = u 

except ValueError: 

u, v = u 

label = None 

 

if not self.has_edge(u,v): 

raise ValueError('edge not in graph') 

 

# If edge (u,v) is a pending edge, it is also a cut-edge 

if self.degree(u) == 1 or self.degree(v) == 1: 

return True 

elif self.allows_multiple_edges(): 

# If we have two or more edges between u and v, it is not a cut-edge 

if len([(uu,vv) for uu,vv,ll in self.edges_incident(u) if uu == v or vv == v]) > 1: 

return False 

 

G = self.copy(immutable=False) if self.is_immutable() else self 

G.delete_edge(u,v,label) 

if G.is_directed(): 

# (u,v) is a cut-edge if u is not in the connected 

# component containing v of self-(u,v) 

sol = not u in G.connected_component_containing_vertex(v) 

else: 

# (u,v) is a cut-edge if there is no path from u to v in 

# self-(u,v) 

sol = not G.distance(u,v) < G.order() 

 

G.add_edge(u,v,label) 

return sol 

 

 

def is_cut_vertex(self, u, weak=False): 

r""" 

Returns True if the input vertex is a cut-vertex. 

 

A vertex is a cut-vertex if its removal from the (di)graph increases the 

number of (strongly) connected components. Isolated vertices or leafs 

are not cut-vertices. This function works with simple graphs as well as 

graphs with loops and multiple edges. 

 

INPUT: 

 

- ``u`` -- a vertex 

 

- ``weak`` -- (default: ``False``) boolean set to `True` if the 

connectivity of directed graphs is to be taken in the weak sense, that 

is ignoring edges orientations. 

 

OUTPUT: 

 

Returns True if ``u`` is a cut-vertex, and False otherwise. 

 

EXAMPLES: 

 

Giving a LollipopGraph(4,2), that is a complete graph with 4 vertices with a pending edge:: 

 

sage: G = graphs.LollipopGraph(4,2) 

sage: G.is_cut_vertex(0) 

False 

sage: G.is_cut_vertex(3) 

True 

 

Comparing the weak and strong connectivity of a digraph:: 

 

sage: D = digraphs.Circuit(6) 

sage: D.is_strongly_connected() 

True 

sage: D.is_cut_vertex(2) 

True 

sage: D.is_cut_vertex(2, weak=True) 

False 

 

Giving a vertex that is not in the graph:: 

 

sage: G = graphs.CompleteGraph(6) 

sage: G.is_cut_vertex(7) 

Traceback (most recent call last): 

... 

ValueError: The input vertex is not in the vertex set. 

""" 

if not u in self: 

raise ValueError('The input vertex is not in the vertex set.') 

 

# Initialization 

if not self.is_directed() or weak: 

# Weak connectivity 

 

if self.degree(u) < 2: 

# An isolated or a leaf vertex is not a cut vertex 

return False 

 

neighbors_func = [self.neighbor_iterator] 

start = next(self.neighbor_iterator(u)) 

CC = set(self.vertex_iterator()) 

 

else: 

# Strong connectivity for digraphs 

 

if self.out_degree(u) == 0 or self.in_degree(u) == 0: 

# A vertex without in or out neighbors is not a cut vertex 

return False 

 

# We consider only the strongly connected component containing u 

CC = set(self.strongly_connected_component_containing_vertex(u)) 

 

# We perform two DFS starting from an out neighbor of u and avoiding 

# u. The first DFS follows the edges directions, and the second is 

# in the reverse order. If both allow to reach all neighbors of u, 

# then u is not a cut vertex 

neighbors_func = [self.neighbor_out_iterator, self.neighbor_in_iterator] 

start = next(self.neighbor_out_iterator(u)) 

 

CC.discard(u) 

CC.discard(start) 

for neighbors in neighbors_func: 

 

# We perform a DFS starting from a neighbor of u and avoiding u 

queue = [start] 

seen = set(queue) 

targets = set(self.neighbor_iterator(u))&CC 

targets.discard(start) 

while queue and targets: 

v = queue.pop() 

for w in neighbors(v): 

if not w in seen and w in CC: 

seen.add(w) 

queue.append(w) 

targets.discard(w) 

 

# If some neighbors cannot be reached, u is a cut vertex. 

if targets: 

return True 

 

return False 

 

 

def steiner_tree(self,vertices, weighted = False, solver = None, verbose = 0): 

r""" 

Returns a tree of minimum weight connecting the given 

set of vertices. 

 

Definition : 

 

Computing a minimum spanning tree in a graph can be done in `n 

\log(n)` time (and in linear time if all weights are equal) where 

`n = V + E`. On the other hand, if one is given a large (possibly 

weighted) graph and a subset of its vertices, it is NP-Hard to 

find a tree of minimum weight connecting the given set of 

vertices, which is then called a Steiner Tree. 

 

`Wikipedia article on Steiner Trees 

<http://en.wikipedia.org/wiki/Steiner_tree_problem>`_. 

 

INPUT: 

 

- ``vertices`` -- the vertices to be connected by the Steiner 

Tree. 

 

- ``weighted`` (boolean) -- Whether to consider the graph as 

weighted, and use each edge's label as a weight, considering 

``None`` as a weight of `1`. If ``weighted=False`` (default) 

all edges are considered to have a weight of `1`. 

 

- ``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. 

 

 

.. NOTE:: 

 

* This problem being defined on undirected graphs, the 

orientation is not considered if the current graph is 

actually a digraph. 

 

* The graph is assumed not to have multiple edges. 

 

ALGORITHM: 

 

Solved through Linear Programming. 

 

COMPLEXITY: 

 

NP-Hard. 

 

Note that this algorithm first checks whether the given 

set of vertices induces a connected graph, returning one of its 

spanning trees if ``weighted`` is set to ``False``, and thus 

answering very quickly in some cases 

 

EXAMPLES: 

 

The Steiner Tree of the first 5 vertices in a random graph is, 

of course, always a tree :: 

 

sage: g = graphs.RandomGNP(30,.5) 

sage: st = g.steiner_tree(g.vertices()[:5]) 

sage: st.is_tree() 

True 

 

And all the 5 vertices are contained in this tree :: 

 

sage: all([v in st for v in g.vertices()[:5] ]) 

True 

 

An exception is raised when the problem is impossible, i.e. 

if the given vertices are not all included in the same 

connected component :: 

 

sage: g = 2 * graphs.PetersenGraph() 

sage: st = g.steiner_tree([5,15]) 

Traceback (most recent call last): 

... 

EmptySetError: The given vertices do not all belong to the same connected component. This problem has no solution ! 

""" 

self._scream_if_not_simple(allow_loops=True) 

 

if self.is_directed(): 

from sage.graphs.all import Graph 

g = Graph(self) 

else: 

g = self 

 

# Can the problem be solved ? Are all the vertices in the same 

# connected component ? 

cc = g.connected_component_containing_vertex(vertices[0]) 

if not all([v in cc for v in vertices]): 

from sage.categories.sets_cat import EmptySetError 

raise EmptySetError("The given vertices do not all belong to the same connected component. This problem has no solution !") 

 

# Can it be solved using the min spanning tree algorithm ? 

if not weighted: 

gg = g.subgraph(vertices) 

if gg.is_connected(): 

st = g.subgraph(edges = gg.min_spanning_tree(), immutable=False) 

st.delete_vertices([v for v in g if st.degree(v) == 0]) 

return st 

 

# Then, LP formulation 

from sage.numerical.mip import MixedIntegerLinearProgram 

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

 

# Reorder an edge 

R = lambda x_y: x_y if x_y[0] < x_y[1] else (x_y[1], x_y[0]) 

 

# edges used in the Steiner Tree 

edges = p.new_variable(binary=True) 

 

# relaxed edges to test for acyclicity 

r_edges = p.new_variable(nonnegative=True) 

 

# Whether a vertex is in the Steiner Tree 

vertex = p.new_variable(binary = True) 

for v in g: 

for e in g.edges_incident(v, labels=False): 

p.add_constraint(vertex[v] - edges[R(e)], min = 0) 

 

# We must have the given vertices in our tree 

for v in vertices: 

p.add_constraint(p.sum([edges[R(e)] for e in g.edges_incident(v,labels=False)]), min=1) 

 

# The number of edges is equal to the number of vertices in our tree minus 1 

p.add_constraint(p.sum([vertex[v] for v in g]) - p.sum([edges[R(e)] for e in g.edges(labels=None)]), max = 1, min = 1) 

 

# There are no cycles in our graph 

 

for u,v in g.edges(labels = False): 

p.add_constraint( r_edges[(u,v)]+ r_edges[(v,u)] - edges[R((u,v))] , min = 0 ) 

 

eps = 1/(5*Integer(g.order())) 

for v in g: 

p.add_constraint(p.sum([r_edges[(u,v)] for u in g.neighbors(v)]), max = 1-eps) 

 

 

# Objective 

if weighted: 

w = lambda x_y: g.edge_label(x_y[0], x_y[1]) if g.edge_label(x_y[0], x_y[1]) is not None else 1 

else: 

w = lambda x_y: 1 

 

p.set_objective(p.sum([w(e)*edges[R(e)] for e in g.edges(labels = False)])) 

 

p.solve(log = verbose) 

 

edges = p.get_values(edges) 

 

st = g.subgraph(edges=[e for e in g.edges(labels = False) if edges[R(e)] == 1], 

immutable=False) 

st.delete_vertices([v for v in g if st.degree(v) == 0]) 

return st 

 

def edge_disjoint_spanning_trees(self,k, root=None, solver = None, verbose = 0): 

r""" 

Returns the desired number of edge-disjoint spanning 

trees/arborescences. 

 

INPUT: 

 

- ``k`` (integer) -- the required number of edge-disjoint 

spanning trees/arborescences 

 

- ``root`` (vertex) -- root of the disjoint arborescences 

when the graph is directed. 

If set to ``None``, the first vertex in the graph is picked. 

 

- ``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. 

 

ALGORITHM: 

 

Mixed Integer Linear Program. The formulation can be found 

in [LPForm]_. 

 

There are at least two possible rewritings of this method 

which do not use Linear Programming: 

 

* The algorithm presented in the paper entitled "A short 

proof of the tree-packing theorem", by Thomas Kaiser 

[KaisPacking]_. 

 

* The implementation of a Matroid class and of the Matroid 

Union Theorem (see section 42.3 of [SchrijverCombOpt]_), 

applied to the cycle Matroid (see chapter 51 of 

[SchrijverCombOpt]_). 

 

EXAMPLES: 

 

The Petersen Graph does have a spanning tree (it is connected):: 

 

sage: g = graphs.PetersenGraph() 

sage: [T] = g.edge_disjoint_spanning_trees(1) 

sage: T.is_tree() 

True 

 

Though, it does not have 2 edge-disjoint trees (as it has less 

than `2(|V|-1)` edges):: 

 

sage: g.edge_disjoint_spanning_trees(2) 

Traceback (most recent call last): 

... 

EmptySetError: This graph does not contain the required number of trees/arborescences ! 

 

By Edmond's theorem, a graph which is `k`-connected always has `k` edge-disjoint 

arborescences, regardless of the root we pick:: 

 

sage: g = digraphs.RandomDirectedGNP(28,.3) # reduced from 30 to 28, cf. #9584 

sage: k = Integer(g.edge_connectivity()) 

sage: arborescences = g.edge_disjoint_spanning_trees(k) # long time (up to 15s on sage.math, 2011) 

sage: all([a.is_directed_acyclic() for a in arborescences]) # long time 

True 

sage: all([a.is_connected() for a in arborescences]) # long time 

True 

 

In the undirected case, we can only ensure half of it:: 

 

sage: g = graphs.RandomGNP(30,.3) 

sage: k = floor(Integer(g.edge_connectivity())/2) 

sage: trees = g.edge_disjoint_spanning_trees(k) 

sage: all([t.is_tree() for t in trees]) 

True 

 

REFERENCES: 

 

.. [LPForm] Nathann Cohen, 

Several Graph problems and their Linear Program formulations, 

http://hal.archives-ouvertes.fr/inria-00504914/en 

 

.. [KaisPacking] Thomas Kaiser 

A short proof of the tree-packing theorem 

:arxiv:`0911.2809` 

 

.. [SchrijverCombOpt] Alexander Schrijver 

Combinatorial optimization: polyhedra and efficiency 

2003 

""" 

 

from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException 

 

p = MixedIntegerLinearProgram(solver = solver) 

p.set_objective(None) 

 

# The colors we can use 

colors = list(range(k)) 

 

# edges[j,e] is equal to one if and only if edge e belongs to color j 

edges = p.new_variable(binary=True) 

 

if root is None: 

root = next(self.vertex_iterator()) 

 

# r_edges is a relaxed variable grater than edges. It is used to 

# check the presence of cycles 

r_edges = p.new_variable(nonnegative=True) 

 

epsilon = 1/(3*(Integer(self.order()))) 

 

if self.is_directed(): 

# Does nothing to an edge.. Useful when out of "if self.directed" 

S = lambda x_y: x_y 

 

# An edge belongs to at most arborescence 

for e in self.edges(labels=False): 

p.add_constraint(p.sum([edges[j,e] for j in colors]), max=1) 

 

 

for j in colors: 

# each color class has self.order()-1 edges 

p.add_constraint(p.sum([edges[j,e] for e in self.edges(labels=None)]), min=self.order()-1) 

 

# Each vertex different from the root has indegree equals to one 

for v in self.vertices(): 

if v is not root: 

p.add_constraint(p.sum([edges[j,e] for e in self.incoming_edges(v, labels=None)]), max=1, min=1) 

else: 

p.add_constraint(p.sum([edges[j,e] for e in self.incoming_edges(v, labels=None)]), max=0, min=0) 

 

# r_edges is larger than edges 

for u,v in self.edges(labels=None): 

if self.has_edge(v,u): 

if v<u: 

p.add_constraint(r_edges[j,(u,v)] + r_edges[j,(v, u)] - edges[j,(u,v)] - edges[j,(v,u)], min=0) 

else: 

p.add_constraint(r_edges[j,(u,v)] + r_edges[j,(v, u)] - edges[j,(u,v)], min=0) 

 

from sage.graphs.digraph import DiGraph 

D = DiGraph() 

D.add_vertices(self.vertices()) 

D.set_pos(self.get_pos()) 

classes = [D.copy() for j in colors] 

 

else: 

 

# Sort an edge 

S = lambda x_y: x_y if x_y[0] < x_y[1] else (x_y[1], x_y[0]) 

 

# An edge belongs to at most one arborescence 

for e in self.edges(labels=False): 

p.add_constraint(p.sum([edges[j,S(e)] for j in colors]), max=1) 

 

 

for j in colors: 

# each color class has self.order()-1 edges 

p.add_constraint(p.sum([edges[j,S(e)] for e in self.edges(labels=None)]), min=self.order()-1) 

 

# Each vertex is in the tree 

for v in self.vertices(): 

p.add_constraint(p.sum([edges[j,S(e)] for e in self.edges_incident(v, labels=None)]), min=1) 

 

# r_edges is larger than edges 

for u,v in self.edges(labels=None): 

p.add_constraint(r_edges[j,(u,v)] + r_edges[j,(v, u)] - edges[j,S((u,v))], min=0) 

 

from sage.graphs.graph import Graph 

D = Graph() 

D.add_vertices(self.vertices()) 

D.set_pos(self.get_pos()) 

classes = [D.copy() for j in colors] 

 

# no cycles 

for j in colors: 

for v in self: 

p.add_constraint(p.sum(r_edges[j,(u,v)] for u in self.neighbors(v)), max=1-epsilon) 

try: 

p.solve(log = verbose) 

 

except MIPSolverException: 

from sage.categories.sets_cat import EmptySetError 

raise EmptySetError("This graph does not contain the required number of trees/arborescences !") 

 

edges = p.get_values(edges) 

 

for j,g in enumerate(classes): 

for e in self.edges(labels=False): 

if edges[j,S(e)] == 1: 

g.add_edge(e) 

if len(list(g.breadth_first_search(root))) != self.order(): 

raise RuntimeError("The computation seems to have gone wrong somewhere..."+ 

"This is probably because of the value of epsilon, but"+ 

" in any case please report this bug, with the graph "+ 

"that produced it ! ;-)") 

 

return classes 

 

def edge_cut(self, s, t, value_only=True, use_edge_labels=False, vertices=False, algorithm="FF", solver=None, verbose=0): 

r""" 

Return a minimum edge cut between vertices `s` and `t`. 

 

A minimum edge cut between two vertices `s` and `t` of self 

is a set `A` of edges of minimum weight such that the graph 

obtained by removing `A` from the graph is disconnected. For more 

information, see the 

`Wikipedia article on cuts 

<http://en.wikipedia.org/wiki/Cut_%28graph_theory%29>`_. 

 

INPUT: 

 

- ``s`` -- source vertex 

 

- ``t`` -- sink vertex 

 

- ``value_only`` -- boolean (default: ``True``). When set to 

``True``, only the weight of a minimum cut is returned. 

Otherwise, a list of edges of a minimum cut is also returned. 

 

- ``use_edge_labels`` -- boolean (default: ``False``). When set to 

``True``, computes a weighted minimum cut where each edge has 

a weight defined by its label (if an edge has no label, `1` 

is assumed). Otherwise, each edge has weight `1`. 

 

- ``vertices`` -- boolean (default: ``False``). When set to 

``True``, returns a list of edges in the edge cut and the 

two sets of vertices that are disconnected by the cut. 

 

Note: ``vertices=True`` implies ``value_only=False``. 

 

- ``algorithm`` -- algorithm to use: 

 

* If ``algorithm = "FF"``, a Python implementation of the 

Ford-Fulkerson algorithm is used 

 

* If ``algorithm = "LP"``, the flow problem is solved using 

Linear Programming. 

 

* If ``algorithm = "igraph"``, the igraph implementation of the 

Goldberg-Tarjan algorithm is used (only available when 

igraph is installed) 

 

* If ``algorithm = None`` (default), we use ``LP`` if 

``vertex_bound = True``, otherwise, we use ``igraph`` if 

it is available, ``FF`` if it is not available. 

 

- ``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. 

 

.. NOTE:: 

 

The use of Linear Programming for non-integer problems may 

possibly mean the presence of a (slight) numerical noise. 

 

OUTPUT: 

 

Real number or tuple, depending on the given arguments 

(examples are given below). 

 

EXAMPLES: 

 

A basic application in the Pappus graph:: 

 

sage: g = graphs.PappusGraph() 

sage: g.edge_cut(1, 2, value_only=True) 

3 

 

Or on Petersen's graph, with the corresponding bipartition of 

the vertex set:: 

 

sage: g = graphs.PetersenGraph() 

sage: g.edge_cut(0, 3, vertices=True) 

[3, [(0, 1, None), (0, 4, None), (0, 5, None)], [[0], [1, 2, 3, 4, 5, 6, 7, 8, 9]]] 

 

If the graph is a path with randomly weighted edges:: 

 

sage: g = graphs.PathGraph(15) 

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

....: g.set_edge_label(u,v,random()) 

 

The edge cut between the two ends is the edge of minimum weight:: 

 

sage: minimum = min([l for u,v,l in g.edge_iterator()]) 

sage: minimum == g.edge_cut(0, 14, use_edge_labels=True) 

True 

sage: [value,[e]] = g.edge_cut(0, 14, use_edge_labels=True, value_only=False) 

sage: g.edge_label(e[0],e[1]) == minimum 

True 

 

The two sides of the edge cut are obviously shorter paths:: 

 

sage: value,edges,[set1,set2] = g.edge_cut(0, 14, use_edge_labels=True, vertices=True) 

sage: g.subgraph(set1).is_isomorphic(graphs.PathGraph(len(set1))) 

True 

sage: g.subgraph(set2).is_isomorphic(graphs.PathGraph(len(set2))) 

True 

sage: len(set1) + len(set2) == g.order() 

True 

 

TESTS: 

 

If algorithm is set to an exotic value:: 

 

sage: g = graphs.PetersenGraph() 

sage: g.edge_cut(0, 1, algorithm="Divination") 

Traceback (most recent call last): 

... 

ValueError: The algorithm argument has to be equal to "FF", "LP", "igraph", or None 

 

Same result for all three methods:: 

 

sage: g = graphs.RandomGNP(20,.3) 

sage: for u,v in g.edges(labels=False): 

....: g.set_edge_label(u,v,round(random(),5)) 

sage: g.edge_cut(0, 1, algorithm="FF") == g.edge_cut(0, 1, algorithm="LP") 

True 

sage: g.edge_cut(0, 1, algorithm="FF") == g.edge_cut(0, 1, algorithm="igraph") # optional - python_igraph 

True 

 

Rounded return value when using the LP method:: 

 

sage: g = graphs.PappusGraph() 

sage: g.edge_cut(1, 2, value_only=True, algorithm="LP") 

3 

 

:trac:`12797`:: 

 

sage: G = Graph([(0, 3, 1), (0, 4, 1), (1, 2, 1), (2, 3, 1), (2, 4, 1)]) 

sage: G.edge_cut(0, 1, value_only=False, use_edge_labels=True) 

[1, [(1, 2, 1)]] 

sage: G = DiGraph([(0, 3, 1), (0, 4, 1), (2, 1, 1), (3, 2, 1), (4, 2, 1)]) 

sage: G.edge_cut(0, 1, value_only=False, use_edge_labels=True) 

[1, [(2, 1, 1)]] 

sage: G.edge_cut(0, 1, value_only=False, use_edge_labels=True, algorithm='LP') 

(1.0, [(2, 1)]) 

""" 

self._scream_if_not_simple(allow_loops=True) 

if vertices: 

value_only = False 

 

if use_edge_labels: 

weight = lambda x: x if (x!={} and x is not None) else 1 

else: 

weight = lambda x: 1 

 

if algorithm in ["FF", "igraph", None]: 

if value_only: 

return self.flow(s,t,value_only=value_only,use_edge_labels=use_edge_labels, algorithm=algorithm) 

 

from sage.graphs.digraph import DiGraph 

g = DiGraph(self) 

 

flow_value, flow_graph = self.flow(s,t,value_only=value_only,use_edge_labels=use_edge_labels, algorithm=algorithm) 

 

for u,v,l in flow_graph.edge_iterator(): 

g.add_edge(v,u) 

if (not use_edge_labels or 

(weight(g.edge_label(u,v)) == weight(l))): 

g.delete_edge(u,v) 

 

return_value = [flow_value] 

 

reachable_from_s = list(g.breadth_first_search(s)) 

 

return_value.append(self.edge_boundary(reachable_from_s)) 

 

if vertices: 

return_value.append([reachable_from_s,list(set(self.vertices())-set(reachable_from_s))]) 

 

return return_value 

 

if algorithm != "LP": 

raise ValueError("The algorithm argument has to be equal to \"FF\", " + 

"\"LP\", \"igraph\", or None") 

 

from sage.numerical.mip import MixedIntegerLinearProgram 

g = self 

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

b = p.new_variable(binary = True) 

v = p.new_variable(binary = True) 

 

# Some vertices belong to part 1, others to part 0 

p.add_constraint(v[s], min=0, max=0) 

p.add_constraint(v[t], min=1, max=1) 

 

if g.is_directed(): 

 

# we minimize the number of edges 

p.set_objective(p.sum([weight(w) * b[x,y] for (x,y,w) in g.edges()])) 

 

# Adjacent vertices can belong to different parts only if the 

# edge that connects them is part of the cut 

for (x,y) in g.edges(labels=None): 

p.add_constraint(v[x] + b[x,y] - v[y], min=0) 

 

else: 

# we minimize the number of edges 

p.set_objective(p.sum([weight(w) * b[min(x,y),max(x,y)] for (x,y,w) in g.edges()])) 

# Adjacent vertices can belong to different parts only if the 

# edge that connects them is part of the cut 

for (x,y) in g.edges(labels=None): 

p.add_constraint(v[x] + b[min(x,y),max(x,y)] - v[y], min=0) 

p.add_constraint(v[y] + b[min(x,y),max(x,y)] - v[x], min=0) 

 

if value_only: 

if use_edge_labels: 

return p.solve(objective_only=True, log=verbose) 

else: 

return Integer(round(p.solve(objective_only=True, log=verbose))) 

else: 

obj = p.solve(log=verbose) 

 

if use_edge_labels is False: 

obj = Integer(round(obj)) 

 

b = p.get_values(b) 

answer = [obj] 

if g.is_directed(): 

answer.append([(x,y) for (x,y) in g.edges(labels=None) if b[x,y] == 1]) 

else: 

answer.append([(x,y) for (x,y) in g.edges(labels=None) if b[min(x,y),max(x,y)] == 1]) 

 

if vertices: 

v = p.get_values(v) 

l0 = [] 

l1 = [] 

for x in g.vertex_iterator(): 

if x in v and v[x] == 1: 

l1.append(x) 

else: 

l0.append(x) 

answer.append([l0, l1]) 

return tuple(answer) 

 

def vertex_cut(self, s, t, value_only=True, vertices=False, solver=None, verbose=0): 

r""" 

Returns a minimum vertex cut between non-adjacent vertices `s` and `t` 

represented by a list of vertices. 

 

A vertex cut between two non-adjacent vertices is a set `U` 

of vertices of self such that the graph obtained by removing 

`U` from self is disconnected. For more information, see the 

`Wikipedia article on cuts 

<http://en.wikipedia.org/wiki/Cut_%28graph_theory%29>`_. 

 

INPUT: 

 

- ``value_only`` -- boolean (default: ``True``). When set to 

``True``, only the size of the minimum cut is returned. 

 

- ``vertices`` -- boolean (default: ``False``). When set to 

``True``, also returns the two sets of vertices that 

are disconnected by the cut. Implies ``value_only`` 

set to False. 

 

- ``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: 

 

Real number or tuple, depending on the given arguments 

(examples are given below). 

 

EXAMPLES: 

 

A basic application in the Pappus graph:: 

 

sage: g = graphs.PappusGraph() 

sage: g.vertex_cut(1, 16, value_only=True) 

3 

 

In the bipartite complete graph `K_{2,8}`, a cut between the two 

vertices in the size `2` part consists of the other `8` vertices:: 

 

sage: g = graphs.CompleteBipartiteGraph(2, 8) 

sage: [value, vertices] = g.vertex_cut(0, 1, value_only=False) 

sage: print(value) 

8 

sage: vertices == list(range(2,10)) 

True 

 

Clearly, in this case the two sides of the cut are singletons :: 

 

sage: [value, vertices, [set1, set2]] = g.vertex_cut(0,1, vertices=True) 

sage: len(set1) == 1 

True 

sage: len(set2) == 1 

True 

""" 

from sage.numerical.mip import MixedIntegerLinearProgram 

g = self 

if g.has_edge(s,t): 

from sage.categories.sets_cat import EmptySetError 

raise EmptySetError("There can be no vertex cut between adjacent vertices !") 

if vertices: 

value_only = False 

 

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

b = p.new_variable(binary=True) 

v = p.new_variable(binary=True) 

 

# Some vertices belong to part 1, some others to part 0 

p.add_constraint(v[s] == 0) 

p.add_constraint(v[t] == 1) 

 

# b indicates whether the vertices belong to the cut 

p.add_constraint(b[s] ==0) 

p.add_constraint(b[t] ==0) 

 

if g.is_directed(): 

 

p.set_objective(p.sum([b[x] for x in g.vertices()])) 

 

# adjacent vertices belong to the same part except if one of them 

# belongs to the cut 

for (x,y) in g.edges(labels=None): 

p.add_constraint(v[x] + b[y] - v[y], min=0) 

 

else: 

p.set_objective(p.sum([b[x] for x in g.vertices()])) 

# adjacent vertices belong to the same part except if one of them 

# belongs to the cut 

for (x,y) in g.edges(labels=None): 

p.add_constraint(v[x] + b[y] >= v[y]) 

p.add_constraint(v[y] + b[x] >= v[x]) 

 

if value_only: 

return Integer(round(p.solve(objective_only=True, log=verbose))) 

else: 

obj = Integer(round(p.solve(log=verbose))) 

b = p.get_values(b) 

answer = [obj,[x for x in g if b[x] == 1]] 

if vertices: 

v = p.get_values(v) 

l0 = [] 

l1 = [] 

for x in g.vertex_iterator(): 

# if the vertex is not in the cut 

if not (x in b and b[x] == 1): 

if (x in v and v[x] == 1): 

l1.append(x) 

else: 

l0.append(x) 

answer.append([l0, l1]) 

return tuple(answer) 

 

 

def multiway_cut(self, vertices, value_only = False, use_edge_labels = False, solver = None, verbose = 0): 

r""" 

Returns a minimum edge multiway cut corresponding to the 

given set of vertices 

( cf. http://www.d.kth.se/~viggo/wwwcompendium/node92.html ) 

represented by a list of edges. 

 

A multiway cut for a vertex set `S` in a graph or a digraph 

`G` is a set `C` of edges such that any two vertices `u,v` 

in `S` are disconnected when removing the edges from `C` from `G`. 

 

Such a cut is said to be minimum when its cardinality 

(or weight) is minimum. 

 

INPUT: 

 

- ``vertices`` (iterable)-- the set of vertices 

 

- ``value_only`` (boolean) 

 

- When set to ``True``, only the value of a minimum 

multiway cut is returned. 

 

- When set to ``False`` (default), the list of edges 

is returned 

 

- ``use_edge_labels`` (boolean) 

- When set to ``True``, computes a weighted minimum cut 

where each edge has a weight defined by its label. ( if 

an edge has no label, `1` is assumed ) 

 

- when set to ``False`` (default), each edge has weight `1`. 

 

- ``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. 

 

EXAMPLES: 

 

Of course, a multiway cut between two vertices correspond 

to a minimum edge cut :: 

 

sage: g = graphs.PetersenGraph() 

sage: g.edge_cut(0,3) == g.multiway_cut([0,3], value_only = True) 

True 

 

As Petersen's graph is `3`-regular, a minimum multiway cut 

between three vertices contains at most `2\times 3` edges 

(which could correspond to the neighborhood of 2 

vertices):: 

 

sage: g.multiway_cut([0,3,9], value_only = True) == 2*3 

True 

 

In this case, though, the vertices are an independent set. 

If we pick instead vertices `0,9,` and `7`, we can save `4` 

edges in the multiway cut :: 

 

sage: g.multiway_cut([0,7,9], value_only = True) == 2*3 - 1 

True 

 

This example, though, does not work in the directed case anymore, 

as it is not possible in Petersen's graph to mutualise edges :: 

 

sage: g = DiGraph(g) 

sage: g.multiway_cut([0,7,9], value_only = True) == 3*3 

True 

 

Of course, a multiway cut between the whole vertex set 

contains all the edges of the graph:: 

 

sage: C = g.multiway_cut(g.vertices()) 

sage: set(C) == set(g.edges()) 

True 

""" 

self._scream_if_not_simple(allow_loops=True) 

from sage.numerical.mip import MixedIntegerLinearProgram 

from itertools import combinations, chain 

 

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

 

# height[c,v] represents the height of vertex v for commodity c 

height = p.new_variable(nonnegative=True) 

 

# cut[e] represents whether e is in the cut 

cut = p.new_variable(binary = True) 

 

# Reorder 

R = lambda x,y : (x,y) if x<y else (y,x) 

 

# Weight function 

if use_edge_labels: 

w = lambda l : l if l is not None else 1 

else: 

w = lambda l : 1 

 

if self.is_directed(): 

 

p.set_objective( p.sum([ w(l) * cut[u,v] for u,v,l in self.edge_iterator() ]) ) 

 

for s,t in chain( combinations(vertices,2), [(x_y[1],x_y[0]) for x_y in combinations(vertices,2)]) : 

# For each commodity, the source is at height 0 

# and the destination is at height 1 

p.add_constraint( height[(s,t),s], min = 0, max = 0) 

p.add_constraint( height[(s,t),t], min = 1, max = 1) 

 

# given a commodity (s,t), the height of two adjacent vertices u,v 

# can differ of at most the value of the edge between them 

for u,v in self.edges(labels = False): 

p.add_constraint( height[(s,t),u] - height[(s,t),v] - cut[u,v], max = 0) 

 

else: 

 

p.set_objective( p.sum([ w(l) * cut[R(u,v)] for u,v,l in self.edge_iterator() ]) ) 

 

for s,t in combinations(vertices,2): 

# For each commodity, the source is at height 0 

# and the destination is at height 1 

p.add_constraint( height[(s,t),s], min = 0, max = 0) 

p.add_constraint( height[(s,t),t], min = 1, max = 1) 

 

# given a commodity (s,t), the height of two adjacent vertices u,v 

# can differ of at most the value of the edge between them 

for u,v in self.edges(labels = False): 

p.add_constraint( height[(s,t),u] - height[(s,t),v] - cut[R(u,v)], max = 0) 

p.add_constraint( height[(s,t),v] - height[(s,t),u] - cut[R(u,v)], max = 0) 

 

if value_only: 

if use_edge_labels: 

return p.solve(objective_only = True, log = verbose) 

else: 

return Integer(round(p.solve(objective_only = True, log = verbose))) 

 

p.solve(log = verbose) 

 

cut = p.get_values(cut) 

 

if self.is_directed(): 

return [x for x in self.edge_iterator() if cut[x[0], x[1]] == 1] 

 

return [x for x in self.edge_iterator() if cut[R(x[0], x[1])] == 1] 

 

 

def max_cut(self, value_only=True, use_edge_labels=False, vertices=False, solver=None, verbose=0): 

r""" 

Returns a maximum edge cut of the graph. For more information, see the 

`Wikipedia article on cuts 

<http://en.wikipedia.org/wiki/Cut_%28graph_theory%29>`_. 

 

INPUT: 

 

- ``value_only`` -- boolean (default: ``True``) 

 

- When set to ``True`` (default), only the value is returned. 

 

- When set to ``False``, both the value and a maximum edge cut 

are returned. 

 

- ``use_edge_labels`` -- boolean (default: ``False``) 

 

- When set to ``True``, computes a maximum weighted cut 

where each edge has a weight defined by its label. (If 

an edge has no label, `1` is assumed.) 

 

- When set to ``False``, each edge has weight `1`. 

 

- ``vertices`` -- boolean (default: ``False``) 

 

- When set to ``True``, also returns the two sets of 

vertices that are disconnected by the cut. This implies 

``value_only=False``. 

 

- ``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. 

 

EXAMPLES: 

 

Quite obviously, the max cut of a bipartite graph 

is the number of edges, and the two sets of vertices 

are the two sides :: 

 

sage: g = graphs.CompleteBipartiteGraph(5,6) 

sage: [ value, edges, [ setA, setB ]] = g.max_cut(vertices=True) 

sage: value == 5*6 

True 

sage: bsetA, bsetB = map(list,g.bipartite_sets()) 

sage: (bsetA == setA and bsetB == setB ) or ((bsetA == setB and bsetB == setA )) 

True 

 

The max cut of a Petersen graph:: 

 

sage: g=graphs.PetersenGraph() 

sage: g.max_cut() 

12 

 

TESTS:: 

 

sage: graphs.EmptyGraph().max_cut() 

Traceback (most recent call last): 

... 

ValueError: max cut is not defined for the empty graph 

""" 

self._scream_if_not_simple(allow_loops=True) 

g=self 

 

if self.order() == 0: 

raise ValueError("max cut is not defined for the empty graph") 

 

if vertices: 

value_only=False 

 

if use_edge_labels: 

from sage.rings.real_mpfr import RR 

weight = lambda x: x if x in RR else 1 

else: 

weight = lambda x: 1 

 

if g.is_directed(): 

reorder_edge = lambda x,y : (x,y) 

else: 

reorder_edge = lambda x,y : (x,y) if x<= y else (y,x) 

 

from sage.numerical.mip import MixedIntegerLinearProgram 

 

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

 

in_set = p.new_variable(binary = True) 

in_cut = p.new_variable(binary = True) 

 

# A vertex has to be in some set 

for v in g: 

p.add_constraint(in_set[0,v]+in_set[1,v],max=1,min=1) 

 

# There is no empty set 

p.add_constraint(p.sum([in_set[1,v] for v in g]),min=1) 

p.add_constraint(p.sum([in_set[0,v] for v in g]),min=1) 

 

if g.is_directed(): 

# There is no edge from set 0 to set 1 which 

# is not in the cut 

# Besides, an edge can only be in the cut if its vertices 

# belong to different sets 

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

p.add_constraint(in_set[0,u] + in_set[1,v] - in_cut[(u,v)], max = 1) 

p.add_constraint(in_set[0,u] + in_set[0,v] + in_cut[(u,v)], max = 2) 

p.add_constraint(in_set[1,u] + in_set[1,v] + in_cut[(u,v)], max = 2) 

else: 

 

# Two adjacent vertices are in different sets if and only if 

# the edge between them is in the cut 

 

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

p.add_constraint(in_set[0,u] + in_set[1,v] - in_cut[reorder_edge(u,v)], max=1) 

p.add_constraint(in_set[1,u] + in_set[0,v] - in_cut[reorder_edge(u,v)], max=1) 

p.add_constraint(in_set[0,u] + in_set[0,v] + in_cut[reorder_edge(u,v)], max = 2) 

p.add_constraint(in_set[1,u] + in_set[1,v] + in_cut[reorder_edge(u,v)], max = 2) 

 

p.set_objective(p.sum([weight(l ) * in_cut[reorder_edge(u,v)] for (u,v,l ) in g.edge_iterator()])) 

 

if value_only: 

obj = p.solve(objective_only=True, log=verbose) 

return obj if use_edge_labels else Integer(round(obj)) 

else: 

obj = p.solve(log=verbose) 

 

if use_edge_labels: 

obj = Integer(round(obj)) 

 

val = [obj] 

 

in_cut = p.get_values(in_cut) 

in_set = p.get_values(in_set) 

 

edges = [] 

for (u,v,l) in g.edge_iterator(): 

if in_cut[reorder_edge(u,v)] == 1: 

edges.append((u,v,l)) 

 

val.append(edges) 

 

if vertices: 

a = [] 

b = [] 

for v in g: 

if in_set[0,v] == 1: 

a.append(v) 

else: 

b.append(v) 

val.append([a,b]) 

 

return val 

 

def longest_path(self, s=None, t=None, use_edge_labels=False, algorithm="MILP", solver=None, verbose=0): 

r""" 

Returns a longest path of ``self``. 

 

INPUT: 

 

- ``s`` (vertex) -- forces the source of the path (the method then 

returns the longest path starting at ``s``). The argument is set to 

``None`` by default, which means that no constraint is set upon the 

first vertex in the path. 

 

- ``t`` (vertex) -- forces the destination of the path (the method then 

returns the longest path ending at ``t``). The argument is set to 

``None`` by default, which means that no constraint is set upon the 

last vertex in the path. 

 

- ``use_edge_labels`` (boolean) -- whether the labels on the edges are 

to be considered as weights (a label set to ``None`` or ``{}`` being 

considered as a weight of `1`). Set to ``False`` by default. 

 

- ``algorithm`` -- one of ``"MILP"`` (default) or ``"backtrack"``. Two 

remarks on this respect: 

 

* While the MILP formulation returns an exact answer, the 

backtrack algorithm is a randomized heuristic. 

 

* As the backtrack algorithm does not support edge weighting, 

setting ``use_edge_labels=True`` will force the use of the MILP 

algorithm. 

 

- ``solver`` -- (default: ``None``) Specify the 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. 

 

.. NOTE:: 

 

The length of a path is assumed to be the number of its edges, or 

the sum of their labels. 

 

OUTPUT: 

 

A subgraph of ``self`` corresponding to a (directed if ``self`` is 

directed) longest path. If ``use_edge_labels == True``, a pair ``weight, 

path`` is returned. 

 

ALGORITHM: 

 

Mixed Integer Linear Programming. (This problem is known to be NP-Hard). 

 

EXAMPLES: 

 

Petersen's graph being hypohamiltonian, it has a longest path 

of length `n-2`:: 

 

sage: g = graphs.PetersenGraph() 

sage: lp = g.longest_path() 

sage: lp.order() >= g.order() - 2 

True 

 

The heuristic totally agrees:: 

 

sage: g = graphs.PetersenGraph() 

sage: g.longest_path(algorithm="backtrack").edges() 

[(0, 1, None), (1, 2, None), (2, 3, None), (3, 4, None), (4, 9, None), (5, 7, None), (5, 8, None), (6, 8, None), (6, 9, None)] 

 

.. PLOT:: 

 

g = graphs.PetersenGraph() 

sphinx_plot(g.plot(edge_colors={"red":g.longest_path().edges()})) 

 

Let us compute longest paths on random graphs with random weights. Each 

time, we ensure the resulting graph is indeed a path:: 

 

sage: for i in range(20): 

....: g = graphs.RandomGNP(15, 0.3) 

....: for u, v in g.edges(labels=False): 

....: g.set_edge_label(u, v, random()) 

....: lp = g.longest_path() 

....: if (not lp.is_forest() or 

....: not max(lp.degree()) <= 2 or 

....: not lp.is_connected()): 

....: print("Error!") 

....: break 

 

TESTS: 

 

The argument ``algorithm`` must be either ``'backtrack'`` or 

``'MILP'``:: 

 

sage: graphs.PetersenGraph().longest_path(algorithm="abc") 

Traceback (most recent call last): 

... 

ValueError: algorithm must be either 'backtrack' or 'MILP' 

 

Disconnected graphs not weighted:: 

 

sage: g1 = graphs.PetersenGraph() 

sage: g2 = 2 * g1 

sage: lp1 = g1.longest_path() 

sage: lp2 = g2.longest_path() 

sage: len(lp1) == len(lp2) 

True 

 

Disconnected graphs weighted:: 

 

sage: g1 = graphs.PetersenGraph() 

sage: for u,v in g.edges(labels=False): 

....: g.set_edge_label(u, v, random()) 

sage: g2 = 2 * g1 

sage: lp1 = g1.longest_path(use_edge_labels=True) 

sage: lp2 = g2.longest_path(use_edge_labels=True) 

sage: lp1[0] == lp2[0] 

True 

 

Empty graphs:: 

 

sage: Graph().longest_path() 

Graph on 0 vertices 

sage: Graph().longest_path(use_edge_labels=True) 

[0, Graph on 0 vertices] 

sage: graphs.EmptyGraph().longest_path() 

Graph on 0 vertices 

sage: graphs.EmptyGraph().longest_path(use_edge_labels=True) 

[0, Graph on 0 vertices] 

 

Trivial graphs:: 

 

sage: G = Graph() 

sage: G.add_vertex(0) 

sage: G.longest_path() 

Graph on 0 vertices 

sage: G.longest_path(use_edge_labels=True) 

[0, Graph on 0 vertices] 

sage: graphs.CompleteGraph(1).longest_path() 

Graph on 0 vertices 

sage: graphs.CompleteGraph(1).longest_path(use_edge_labels=True) 

[0, Graph on 0 vertices] 

 

Random test for digraphs:: 

 

sage: for i in range(20): 

....: g = digraphs.RandomDirectedGNP(15, 0.3) 

....: for u, v in g.edges(labels=False): 

....: g.set_edge_label(u, v, random()) 

....: lp = g.longest_path() 

....: if (not lp.is_directed_acyclic() or 

....: not max(lp.out_degree()) <= 1 or 

....: not max(lp.in_degree()) <= 1 or 

....: not lp.is_connected()): 

....: print("Error!") 

....: print(g.edges()) 

....: break 

 

:trac:`13019`:: 

 

sage: g = graphs.CompleteGraph(5).to_directed() 

sage: g.longest_path(s=1,t=2) 

Subgraph of (Complete graph): Digraph on 5 vertices 

 

:trac:`14412`:: 

 

sage: l = [(0, 1), (0, 3), (2, 0), (3, 4)] 

sage: G = DiGraph(l) 

sage: H = {(0, 3), (2, 0), (3, 4)} 

sage: H=={x for x in G.longest_path().edges(labels=False)} 

True 

""" 

self._scream_if_not_simple() 

 

if use_edge_labels: 

algorithm = "MILP" 

if algorithm not in ("backtrack", "MILP"): 

raise ValueError("algorithm must be either 'backtrack' or 'MILP'") 

 

# Quick improvement 

if not self.is_connected(): 

if use_edge_labels: 

return max(g.longest_path(s=s, t=t, 

use_edge_labels=use_edge_labels, 

algorithm=algorithm) 

for g in self.connected_components_subgraphs()) 

else: 

return max((g.longest_path(s=s, t=t, 

use_edge_labels=use_edge_labels, 

algorithm=algorithm) 

for g in self.connected_components_subgraphs()), 

key=lambda x: x.order()) 

 

# Stupid cases 

# - Graph having <= 1 vertex. 

# 

# - The source has outdegree 0 in a directed graph, or 

# degree 0, or is not a vertex of the graph. 

# 

# - The destination has indegree 0 in a directed graph, or 

# degree 0, or is not a vertex of the graph. 

# 

# - Both s and t are specified, but there is no path between 

# the two in a directed graph (the graph is connected). 

if (self.order() <= 1 or 

(s is not None and ( 

(s not in self) or 

(self._directed and self.out_degree(s) == 0) or 

(not self._directed and self.degree(s) == 0))) or 

(t is not None and ( 

(t not in self) or 

(self._directed and self.in_degree(t) == 0) or 

(not self._directed and self.degree(t) == 0))) or 

(self._directed and (s is not None) and (t is not None) and 

len(self.shortest_path(s, t)) == 0)): 

if self._directed: 

from sage.graphs.all import DiGraph 

return [0, DiGraph()] if use_edge_labels else DiGraph() 

from sage.graphs.all import Graph 

return [0, Graph()] if use_edge_labels else Graph() 

 

# Calling the backtrack heuristic if asked 

if algorithm == "backtrack": 

from sage.graphs.generic_graph_pyx import find_hamiltonian as fh 

x = fh(self, find_path=True)[1] 

return self.subgraph(vertices=x, edges=list(zip(x[:-1], x[1:]))) 

 

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

# LP Formulation # 

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

 

# Epsilon... Must be less than 1/(n+1), but we want to avoid 

# numerical problems... 

epsilon = 1/(2*float(self.order())) 

 

# Associating a weight to a label 

if use_edge_labels: 

weight = lambda x: x if (x is not None and x != {}) else 1 

else: 

weight = lambda x: 1 

 

from sage.numerical.mip import MixedIntegerLinearProgram 

p = MixedIntegerLinearProgram(solver=solver) 

 

# edge_used[(u,v)] == 1 if (u,v) is used 

edge_used = p.new_variable(binary=True) 

 

# relaxed version of the previous variable, to prevent the 

# creation of cycles 

r_edge_used = p.new_variable(nonnegative=True) 

 

# vertex_used[v] == 1 if vertex v is used 

vertex_used = p.new_variable(binary=True) 

 

if self._directed: 

 

# if edge uv is used, vu can not be 

for u, v in self.edges(labels=False): 

if self.has_edge(v, u): 

p.add_constraint(edge_used[(u,v)] + edge_used[(v,u)] <= 1) 

 

# A vertex is used if one of its incident edges is 

for u,v in self.edges(labels = False): 

p.add_constraint(vertex_used[v] >= edge_used[(u,v)]) 

p.add_constraint(vertex_used[u] >= edge_used[(u,v)]) 

 

# A path is a tree. If n vertices are used, at most n-1 edges are 

p.add_constraint( 

p.sum(vertex_used[v] for v in self) 

- p.sum(edge_used[e] for e in self.edges(labels=False)) 

== 1) 

 

# A vertex has at most one incoming used edge and at most 

# one outgoing used edge 

for v in self: 

p.add_constraint( 

p.sum(edge_used[(u,v)] for u in self.neighbors_in(v)) <= 1) 

p.add_constraint( 

p.sum(edge_used[(v,u)] for u in self.neighbors_out(v)) <= 1) 

 

# r_edge_used is "more" than edge_used, though it ignores 

# the direction 

for u, v in self.edges(labels=False): 

p.add_constraint(r_edge_used[(u,v)] + r_edge_used[(v,u)] 

>= edge_used[(u,v)]) 

 

# No cycles 

for v in self: 

p.add_constraint( 

p.sum(r_edge_used[(u,v)] for u in self.neighbors(v)) 

<= 1-epsilon) 

 

# Enforcing the source if asked.. If s is set, it has no 

# incoming edge and exactly one son 

if s is not None: 

p.add_constraint( 

p.sum(edge_used[(u,s)] for u in self.neighbors_in(s)), 

max=0, min=0) 

p.add_constraint( 

p.sum(edge_used[(s,u)] for u in self.neighbors_out(s)), 

min=1, max=1) 

 

# Enforcing the destination if asked.. If t is set, it has 

# no outgoing edge and exactly one parent 

if t is not None: 

p.add_constraint( 

p.sum(edge_used[(u,t)] for u in self.neighbors_in(t)), 

min=1, max=1) 

p.add_constraint( 

p.sum(edge_used[(t,u)] for u in self.neighbors_out(t)), 

max=0, min=0) 

 

# Defining the objective 

p.set_objective( 

p.sum(weight(l) * edge_used[(u,v)] for u, v, l in self.edges())) 

else: 

# f_edge_used calls edge_used through reordering u and v 

# to avoid having two different variables 

f_edge_used = lambda u, v: edge_used[ 

(u,v) if hash(u) < hash(v) else (v,u)] 

# A vertex is used if one of its incident edges is 

for v in self: 

for u in self.neighbors(v): 

p.add_constraint(vertex_used[v] - f_edge_used(u,v), min=0) 

# A path is a tree. If n vertices are used, at most n-1 edges are 

p.add_constraint( 

p.sum(vertex_used[v] for v in self) 

- p.sum(f_edge_used(u,v) for u, v in self.edges(labels=False)), 

min=1, max=1) 

# A vertex has at most two incident edges used 

for v in self: 

p.add_constraint( 

p.sum(f_edge_used(u,v) for u in self.neighbors(v)), max=2) 

# r_edge_used is "more" than edge_used 

for u, v in self.edges(labels=False): 

p.add_constraint(r_edge_used[(u,v)] 

+ r_edge_used[(v,u)] 

- f_edge_used(u,v), 

min=0) 

# No cycles 

for v in self: 

p.add_constraint( 

p.sum(r_edge_used[(u,v)] for u in self.neighbors(v)), 

max=1-epsilon) 

# Enforcing the destination if asked.. If s or t are set, 

# they have exactly one incident edge 

if s is not None: 

p.add_constraint( 

p.sum(f_edge_used(s,u) for u in self.neighbors(s)), 

max=1, min=1) 

if t is not None: 

p.add_constraint( 

p.sum(f_edge_used(t,u) for u in self.neighbors(t)), 

max=1, min=1) 

# Defining the objective 

p.set_objective(p.sum(weight(l) * f_edge_used(u,v) 

for u, v, l in self.edges())) 

# Computing the result. No exception has to be raised, as this 

# problem always has a solution (there is at least one edge, 

# and a path from s to t if they are specified). 

p.solve(log=verbose) 

edge_used = p.get_values(edge_used) 

vertex_used = p.get_values(vertex_used) 

if self._directed: 

g = self.subgraph( 

vertices=(v for v in self if vertex_used[v] == 1), 

edges=((u,v,l) for u, v, l in self.edges() 

if edge_used[(u,v)] == 1)) 

else: 

g = self.subgraph( 

vertices=(v for v in self if vertex_used[v] == 1), 

edges=((u,v,l) for u, v, l in self.edges() 

if f_edge_used(u,v) == 1)) 

if use_edge_labels: 

return sum(map(weight, g.edge_labels())), g 

else: 

return g 

 

def hamiltonian_path(self, s=None, t=None, use_edge_labels=False, 

maximize=False, algorithm='MILP', solver=None, verbose=0): 

r""" 

Return a Hamiltonian path of the current graph/digraph. 

 

A path is Hamiltonian if it goes through all the vertices exactly 

once. Computing a Hamiltonian path being NP-Complete, this 

algorithm could run for some time depending on the instance. 

 

When ``use_edge_labels == True``, this method returns either a minimum 

weight hamiltonian path or a maximum weight Hamiltonian path (if 

``maximize == True``). 

 

.. SEEALSO:: 

 

- :meth:`~GenericGraph.longest_path` 

- :meth:`~GenericGraph.hamiltonian_cycle` 

 

INPUT: 

 

- ``s`` -- vertex (optional); if specified, then forces the source 

of the path (the method then returns a Hamiltonian path starting 

at ``s``) 

 

- ``t`` -- vertex (optional); if specified, then forces the 

destination of the path (the method then returns a Hamiltonian 

path ending at ``t``) 

 

- ``use_edge_labels`` -- boolean (default: ``False``); whether the 

labels on the edges are to be considered as weights (a label set 

to ``None`` or ``{}`` being considered as a weight of `1`) 

 

- ``maximize`` -- boolean (default: ``False``); whether to compute a 

minimum (default) of a maximum (when ``maximize == True``) weight 

hamiltonian path. This parameter is considered only if 

``use_edge_labels == True``. 

 

- ``algorithm`` -- one of ``"MILP"`` (default) or ``"backtrack"``; 

two remarks on this respect: 

 

* While the MILP formulation returns an exact answer, the 

backtrack algorithm is a randomized heuristic. 

 

* The backtrack algorithm does not support edge weighting, so setting 

``use_edge_labels=True`` will force the use of the MILP algorithm. 

 

- ``solver`` -- (default: ``None``) specify the 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>` 

 

- ``verbose`` -- integer (default: ``0``); sets the level of 

verbosity with 0 meaning quiet 

 

OUTPUT: 

 

A subgraph of ``self`` corresponding to a (directed if ``self`` is 

directed) hamiltonian path. If no hamiltonian path is found, return 

``None``. If ``use_edge_labels == True``, a pair ``weight, path`` is 

returned. 

 

EXAMPLES: 

 

The `3 \times 3`-grid has an Hamiltonian path, an hamiltonian path 

starting from vertex `(0, 0)` and ending at vertex `(2, 2)`, but no 

Hamiltonian path starting from `(0, 0)` and ending at `(0, 1)`:: 

 

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

sage: g.hamiltonian_path() 

Hamiltonian path from 2D Grid Graph for [3, 3]: Graph on 9 vertices 

sage: g.hamiltonian_path(s=(0,0), t=(2,2)) 

Hamiltonian path from 2D Grid Graph for [3, 3]: Graph on 9 vertices 

sage: g.hamiltonian_path(s=(0,0), t=(2,2), use_edge_labels=True) 

(8, Hamiltonian path from 2D Grid Graph for [3, 3]: Graph on 9 vertices) 

sage: g.hamiltonian_path(s=(0,0), t=(0,1)) is None 

True 

sage: g.hamiltonian_path(s=(0,0), t=(0,1), use_edge_labels=True) 

(0, None) 

 

TESTS: 

 

Empty and one-element graphs:: 

 

sage: g = Graph() 

sage: g.hamiltonian_path() 

Traceback (most recent call last): 

... 

ValueError: the Hamiltonian path problem is not well defined 

for empty and one-element (di)graphs 

sage: g = Graph(1) 

sage: g.hamiltonian_path() 

Traceback (most recent call last): 

... 

ValueError: the Hamiltonian path problem is not well defined 

for empty and one-element (di)graphs 

 

A non-connected (di)graph has no hamiltonian path:: 

 

sage: g = Graph(2) 

sage: g.hamiltonian_path() is None 

True 

sage: g.hamiltonian_path(use_edge_labels=True) 

(0, None) 

sage: g = DiGraph(2) 

sage: g.hamiltonian_path() is None 

True 

 

Asking for a minimum (resp., maximum) weight Hamiltonian path:: 

 

sage: G = Graph([(0, 1, 1), (0, 2, 2), (0, 3, 1), (1, 2, 1), (1, 3, 2), (2, 3, 1)]) 

sage: print(G.hamiltonian_path(s=0, t=1, use_edge_labels=True, maximize=False)[0]) 

3 

sage: print(G.hamiltonian_path(s=0, t=1, use_edge_labels=True, maximize=True)[0]) 

5 

 

Parameter ``algorithm`` must be either ``'backtrack'`` or ``'MILP'``:: 

 

sage: Graph().hamiltonian_path(algorithm='noname') 

Traceback (most recent call last): 

... 

ValueError: algorithm must be either 'backtrack' or 'MILP' 

""" 

if use_edge_labels or algorithm is None: 

# We force the algorithm to 'MILP' 

algorithm = 'MILP' 

elif not algorithm in ['MILP', 'backtrack']: 

raise ValueError("algorithm must be either 'backtrack' or 'MILP'") 

 

if self.order() < 2: 

raise ValueError('the Hamiltonian path problem is not well ' + 

'defined for empty and one-element (di)graphs') 

 

if not self.is_connected(): 

return (0, None) if use_edge_labels else None 

 

# 

# Deal with loops and multiple edges 

# 

if self.has_loops() or self.has_multiple_edges(): 

keep_label = 'max' if (use_edge_labels and maximize) else 'min' 

g = self.to_simple(to_undirected=False, keep_label=keep_label, immutable=False) 

else: 

g = copy(self) 

 

 

new_s, new_t = s, t 

if g.is_directed(): 

# 

# Deal with vertices with no in|out-neighbors 

# 

zeros = [u for u in g if g.in_degree(u)==0] 

if len(zeros) > 1: 

return (0, None) if use_edge_labels else None 

elif len(zeros) == 1: 

if new_s is None: 

new_s = zeros.pop() 

elif not new_s in zeros: 

return (0, None) if use_edge_labels else None 

 

zeros = [u for u in g if g.out_degree(u)==0] 

if len(zeros) > 1: 

return (0, None) if use_edge_labels else None 

elif len(zeros) == 1: 

if new_t is None: 

new_t = zeros.pop() 

elif not new_t in zeros: 

return (0, None) if use_edge_labels else None 

 

else: 

# 

# Deal with vertices of degree one 

# 

ones = [u for u in g if g.degree(u)==1] 

if len(ones) > 2: 

return (0, None) if use_edge_labels else None 

 

elif len(ones) == 2: 

if not new_s is None and not new_s in ones: 

return (0, None) if use_edge_labels else None 

if not new_t is None and not new_t in ones: 

return (0, None) if use_edge_labels else None 

 

# Set new_s and new_t if possible 

if new_s is None and new_t is None: 

new_s,new_t = ones 

elif not new_s is None and new_t is None: 

new_t = ones[1] if new_s == ones[0] else ones[0] 

elif new_s is None and not new_t is None: 

new_s = ones[1] if new_t == ones[0] else ones[0] 

 

elif len(ones) == 1: 

if not new_s is None and not new_t is None and not (new_s in ones or new_t in ones): 

return (0, None) if use_edge_labels else None 

elif new_s is None and (new_t is None or (not new_t is None and not new_t in ones)): 

new_s = ones.pop() 

elif new_t is None and not new_s is None and not new_s in ones: 

new_t = ones.pop() 

 

 

if not use_edge_labels and algorithm == "backtrack": 

path = g.longest_path(s=new_s, t=new_t, algorithm="backtrack") 

return path if path.order() == g.order() else None 

 

# 

# We modify the graph to turn the Hamiltonian Path problem into a 

# Hamiltonian Cycle problem. The modification ensures that the computed 

# Hamiltonian Cycle will visit new_s (if determined) right after new_t 

# (if determined). Hence the extraction of the Hamiltonian Path is easy. 

# 

extra_vertices = [] 

if new_s is None: 

# If the source is not specified or determined, we add a virtual 

# source and an edge from it to each vertex of the (di)graph. 

new_s = g.add_vertex() 

extra_vertices.append(new_s) 

for u in self: # in original set of vertices 

g.add_edge(new_s, u, 0) 

 

if new_t is None: 

# If the source is not specified or determined, we add a virtual 

# destination and an edge from each vertex of the (di)graph to it. 

new_t = g.add_vertex() 

extra_vertices.append(new_t) 

for u in self: 

g.add_edge(u, new_t, 0) 

 

# We force the Hamiltonian cycle to visit new_s right after new_t by 

# inserting an intermediate vertex 

v = g.add_vertex() 

extra_vertices.append(v) 

g.add_edge(new_t, v, 0) 

g.add_edge(v, new_s, 0) 

 

# 

# We now search for an Hamiltonian Cycle in g 

# 

from sage.categories.sets_cat import EmptySetError 

try: 

tsp = g.traveling_salesman_problem(use_edge_labels=use_edge_labels, 

maximize=maximize, 

solver=solver, verbose=verbose) 

except EmptySetError: 

return (0, None) if use_edge_labels else None 

 

tsp.delete_vertices(extra_vertices) 

tsp.name("Hamiltonian path from {}".format(self.name())) 

weight = lambda l: 1 if l is None else l 

return (sum(map(weight,tsp.edge_labels())), tsp) if use_edge_labels else tsp 

 

 

def traveling_salesman_problem(self, use_edge_labels=False, maximize=False, 

solver=None, constraint_generation=None, 

verbose=0, verbose_constraints=False): 

r""" 

Solves the traveling salesman problem (TSP) 

 

Given a graph (resp. a digraph) `G` with weighted edges, the traveling 

salesman problem consists in finding a Hamiltonian cycle (resp. circuit) 

of the graph of minimum cost. 

 

This TSP is one of the most famous NP-Complete problems, this function 

can thus be expected to take some time before returning its result. 

 

INPUT: 

 

- ``use_edge_labels`` (boolean) -- whether to consider the weights of 

the edges. 

 

- If set to ``False`` (default), all edges are assumed to weight 

`1` 

 

- If set to ``True``, the weights are taken into account, and the 

circuit returned is the one minimizing the sum of the weights 

(an edge with no label is assumed to have weight `1`). 

 

- ``maximize`` -- boolean (default: ``False``). When set to ``True`` 

search for a Hamiltonian cycle (res. circuit) of maximum weight 

instead of minimum weight. This parameter is used only when 

``use_edge_labels`` is ``True``. 

 

- ``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>`. 

 

- ``constraint_generation`` (boolean) -- whether to use constraint 

generation when solving the Mixed Integer Linear Program. 

 

When ``constraint_generation = None``, constraint generation is used 

whenever the graph has a density larger than 70%. 

 

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

verbosity. Set to 0 by default, which means quiet. 

 

- ``verbose_constraints`` -- whether to display which constraints are 

being generated. 

 

OUTPUT: 

 

A solution to the TSP, as a ``Graph`` object whose vertex set is `V(G)`, 

and whose edges are only those of the solution. 

 

ALGORITHM: 

 

This optimization problem is solved through the use of Linear 

Programming. 

 

.. NOTE:: 

 

This function is correctly defined for both graph and digraphs. In 

the second case, the returned cycle is a circuit of optimal cost. 

 

EXAMPLES: 

 

The Heawood graph is known to be Hamiltonian:: 

 

sage: g = graphs.HeawoodGraph() 

sage: tsp = g.traveling_salesman_problem() 

sage: tsp 

TSP from Heawood graph: Graph on 14 vertices 

 

The solution to the TSP has to be connected :: 

 

sage: tsp.is_connected() 

True 

 

It must also be a `2`-regular graph:: 

 

sage: tsp.is_regular(k=2) 

True 

 

And obviously it is a subgraph of the Heawood graph:: 

 

sage: tsp.is_subgraph(g, induced=False) 

True 

 

On the other hand, the Petersen Graph is known not to 

be Hamiltonian:: 

 

sage: g = graphs.PetersenGraph() 

sage: tsp = g.traveling_salesman_problem() 

Traceback (most recent call last): 

... 

EmptySetError: The given graph is not Hamiltonian 

 

One easy way to change it is obviously to add to this graph the edges 

corresponding to a Hamiltonian cycle. If we do this by setting the cost 

of these new edges to `2`, while the others are set to `1`, we notice 

that not all the edges we added are used in the optimal solution :: 

 

sage: for u, v in g.edges(labels = None): 

....: g.set_edge_label(u,v,1) 

 

sage: cycle = graphs.CycleGraph(10) 

sage: for u,v in cycle.edges(labels = None): 

....: if not g.has_edge(u,v): 

....: g.add_edge(u,v) 

....: g.set_edge_label(u,v,2) 

 

sage: tsp = g.traveling_salesman_problem(use_edge_labels = True) 

sage: sum( tsp.edge_labels() ) < 2*10 

True 

 

If we pick `1/2` instead of `2` as a cost for these new edges, they 

clearly become the optimal solution:: 

 

sage: for u,v in cycle.edges(labels = None): 

....: g.set_edge_label(u,v,1/2) 

 

sage: tsp = g.traveling_salesman_problem(use_edge_labels = True) 

sage: sum( tsp.edge_labels() ) == (1/2)*10 

True 

 

Search for a minimum and a maximum weight Hamiltonian cycle:: 

 

sage: G = Graph([(0, 1, 1), (0, 2, 2), (0, 3, 1), (1, 2, 1), (1, 3, 2), (2, 3, 1)]) 

sage: tsp = G.traveling_salesman_problem(use_edge_labels=True, maximize=False) 

sage: print(sum(tsp.edge_labels())) 

4 

sage: tsp = G.traveling_salesman_problem(use_edge_labels=True, maximize=True) 

sage: print(sum(tsp.edge_labels())) 

6 

 

TESTS: 

 

Comparing the results returned according to the value of 

``constraint_generation``. First, for graphs:: 

 

sage: from operator import itemgetter 

sage: n = 20 

sage: for i in range(20): 

....: g = Graph() 

....: g.allow_multiple_edges(False) 

....: for u,v in graphs.RandomGNP(n,.2).edges(labels = False): 

....: g.add_edge(u,v,round(random(),5)) 

....: for u,v in graphs.CycleGraph(n).edges(labels = False): 

....: if not g.has_edge(u,v): 

....: g.add_edge(u,v,round(random(),5)) 

....: v1 = g.traveling_salesman_problem(constraint_generation = False, use_edge_labels = True) 

....: v2 = g.traveling_salesman_problem(use_edge_labels = True) 

....: c1 = sum(map(itemgetter(2), v1.edges())) 

....: c2 = sum(map(itemgetter(2), v2.edges())) 

....: if c1 != c2: 

....: print("Error !",c1,c2) 

....: break 

 

Then for digraphs:: 

 

sage: from operator import itemgetter 

sage: set_random_seed(0) 

sage: n = 20 

sage: for i in range(20): 

....: g = DiGraph() 

....: g.allow_multiple_edges(False) 

....: for u,v in digraphs.RandomDirectedGNP(n,.2).edges(labels = False): 

....: g.add_edge(u,v,round(random(),5)) 

....: for u,v in digraphs.Circuit(n).edges(labels = False): 

....: if not g.has_edge(u,v): 

....: g.add_edge(u,v,round(random(),5)) 

....: v2 = g.traveling_salesman_problem(use_edge_labels = True) 

....: v1 = g.traveling_salesman_problem(constraint_generation = False, use_edge_labels = True) 

....: c1 = sum(map(itemgetter(2), v1.edges())) 

....: c2 = sum(map(itemgetter(2), v2.edges())) 

....: if c1 != c2: 

....: print("Error !",c1,c2) 

....: print("With constraint generation :",c2) 

....: print("Without constraint generation :",c1) 

....: break 

 

Simple tests for multiple edges and loops:: 

 

sage: G = DiGraph(multiedges=True, loops=True) 

sage: G.is_hamiltonian() 

False 

sage: G.add_vertex(0) 

sage: G.is_hamiltonian() 

False 

sage: G.add_edge(0,0,1) 

sage: G.add_edge(0,0,2) 

sage: tsp = G.traveling_salesman_problem(use_edge_labels=True) 

Traceback (most recent call last): 

... 

EmptySetError: the given graph is not Hamiltonian 

sage: G.add_vertex(1) 

sage: G.is_hamiltonian() 

False 

sage: G.add_edge(0,1,2) 

sage: G.add_edge(0,1,3) 

sage: G.add_edge(1,1,1) 

sage: G.add_edge(1,0,2) 

sage: G.is_hamiltonian() 

True 

sage: tsp = G.traveling_salesman_problem(use_edge_labels=True) 

sage: sum(tsp.edge_labels()) 

4 

 

Graphs on 2 vertices:: 

 

sage: Graph([(0,1),(0,1)],multiedges=True).is_hamiltonian() 

True 

sage: DiGraph([(0,1),(0,1)],multiedges=True).is_hamiltonian() 

False 

sage: DiGraph([(0,1),(1,0)],multiedges=True).is_hamiltonian() 

True 

sage: G = DiGraph(loops=True) 

sage: G.add_edges([(0,0),(0,1),(1,1),(1,0)]) # i.e. complete digraph with loops 

sage: G.is_hamiltonian() 

True 

sage: G.remove_loops() 

sage: G.is_hamiltonian() 

True 

sage: G.allow_loops(False) 

sage: G.is_hamiltonian() 

True 

 

Check that weight 0 edges are handled correctly (see :trac:`16214`):: 

 

sage: G = Graph([(0,1,1),(0,2,0),(0,3,1),(1,2,1),(1,3,0),(2,3,1)]) 

sage: tsp = G.traveling_salesman_problem(use_edge_labels=True) 

sage: sum(tsp.edge_labels()) 

2 

""" 

from sage.categories.sets_cat import EmptySetError 

 

# Associating a weight to a label 

if use_edge_labels: 

weight = lambda l: 1 if l is None else l 

else: 

weight = lambda l: 1 

 

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

# 0 or 1 vertex graphs # 

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

 

if self.order() < 2: 

raise EmptySetError("the given graph is not Hamiltonian") 

 

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

# 2-vertices graphs # 

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

 

if self.order() == 2: 

u,v = self.vertices() 

if self.is_directed(): 

if self.has_edge(u,v) and self.has_edge(v,u): 

if self.has_multiple_edges(): 

if maximize: 

edges = [(u,v,max(self.edge_label(u,v), key=weight)), 

(v,u,max(self.edge_label(v,u), key=weight))] 

else: 

edges = [(u,v,min(self.edge_label(u,v), key=weight)), 

(v,u,min(self.edge_label(v,u), key=weight))] 

else: 

edges = [(u,v,self.edge_label(u,v)), 

(v,u,self.edge_label(v,u))] 

answer = self.subgraph(edges = edges, immutable = False) 

answer.set_pos(self.get_pos()) 

answer.name("TSP from "+self.name()) 

if self.is_immutable(): 

answer = answer.copy(immutable = True) 

return answer 

else: 

if self.has_multiple_edges() and len(self.edge_label(u,v)) > 1: 

edges = self.edges() 

edges.sort(key=weight) 

answer = self.subgraph(edges = edges[:2], immutable = False) 

answer.set_pos(self.get_pos()) 

answer.name("TSP from "+self.name()) 

if self.is_immutable(): 

answer = answer.copy(immutable = True) 

return answer 

 

raise EmptySetError("the given graph is not Hamiltonian") 

 

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

# Quick checks of connectivity # 

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

 

# TODO : Improve it by checking vertex-connectivity instead of 

# edge-connectivity.... But calling the vertex_connectivity (which 

# builds a LP) is way too slow. These tests only run traversals written 

# in Cython --> hence FAST 

 

if self.is_directed(): 

if not self.is_strongly_connected(): 

raise EmptySetError("the given graph is not Hamiltonian") 

 

else: 

# Checks whether the graph is 2-connected 

if not self.strong_orientation().is_strongly_connected(): 

raise EmptySetError("the given graph is not Hamiltonian") 

 

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

# Deal with loops and multiple edges # 

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

 

if self.has_loops() or self.has_multiple_edges(): 

keep_label = 'max' if (use_edge_labels and maximize) else 'min' 

g = self.to_simple(to_undirected=False, keep_label=keep_label, immutable=False) 

else: 

g = self 

 

 

if constraint_generation is None: 

if g.density() > .7: 

constraint_generation = False 

else: 

constraint_generation = True 

 

from sage.numerical.mip import MixedIntegerLinearProgram 

from sage.numerical.mip import MIPSolverException 

 

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

# Constraint-generation formulation of the problem # 

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

 

if constraint_generation: 

 

p = MixedIntegerLinearProgram(maximization=maximize, 

solver=solver, 

constraint_generation=True) 

 

# Directed Case # 

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

if g.is_directed(): 

 

from sage.graphs.all import DiGraph 

b = p.new_variable(binary = True) 

 

# Objective function 

if use_edge_labels: 

p.set_objective(p.sum([ weight(l)*b[u,v] 

for u,v,l in g.edge_iterator()])) 

 

# All the vertices have in-degree 1 and out-degree 1 

for v in g: 

p.add_constraint(p.sum([b[u,v] for u in g.neighbor_in_iterator(v)]), 

min = 1, 

max = 1) 

p.add_constraint(p.sum([b[v,u] for u in g.neighbor_out_iterator(v)]), 

min = 1, 

max = 1) 

 

# Initial Solve 

try: 

p.solve(log = verbose) 

except MIPSolverException: 

raise EmptySetError("The given graph is not Hamiltonian") 

 

while True: 

# We build the DiGraph representing the current solution 

h = DiGraph() 

for u,v,l in g.edge_iterator(): 

if p.get_values(b[u,v]) == 1: 

h.add_edge(u,v,l) 

 

# If there is only one circuit, we are done ! 

cc = h.connected_components() 

if len(cc) == 1: 

break 

 

# Adding the corresponding constraints 

for c in cc: 

if verbose_constraints: 

print("Adding a constraint on set", c) 

p.add_constraint(p.sum(b[u,v] for u,v in 

g.edge_boundary(c, labels = False)), 

min = 1) 

 

try: 

p.solve(log = verbose) 

except MIPSolverException: 

raise EmptySetError("The given graph is not Hamiltonian") 

 

# Undirected Case # 

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

else: 

 

from sage.graphs.all import Graph 

b = p.new_variable(binary = True) 

B = lambda u,v : b[frozenset((u,v))] 

 

# Objective function 

if use_edge_labels: 

p.set_objective(p.sum( weight(l)*B(u,v) for u,v,l in g.edge_iterator()) ) 

 

# All the vertices have degree 2 

for v in g: 

p.add_constraint(p.sum( B(u,v) for u in g.neighbor_iterator(v)), 

min = 2, 

max = 2) 

 

# Initial Solve 

try: 

p.solve(log = verbose) 

except MIPSolverException: 

raise EmptySetError("The given graph is not Hamiltonian") 

 

while True: 

# We build the DiGraph representing the current solution 

h = Graph() 

for u,v,l in g.edge_iterator(): 

if p.get_values(B(u,v)) == 1: 

h.add_edge(u,v,l) 

 

# If there is only one circuit, we are done ! 

cc = h.connected_components() 

if len(cc) == 1: 

break 

 

# Adding the corresponding constraints 

for c in cc: 

if verbose_constraints: 

print("Adding a constraint on set", c) 

p.add_constraint(p.sum(B(u,v) for u,v in 

g.edge_boundary(c, labels = False)), 

min = 2) 

 

try: 

p.solve(log = verbose) 

except MIPSolverException: 

raise EmptySetError("The given graph is not Hamiltonian") 

 

# We can now return the TSP ! 

answer = self.subgraph(edges = h.edges(), immutable = False) 

answer.set_pos(self.get_pos()) 

answer.name("TSP from "+g.name()) 

if self.is_immutable(): 

answer = answer.copy(immutable = True) 

return answer 

 

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

# ILP formulation without constraint generation # 

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

 

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

 

f = p.new_variable(binary=True) 

r = p.new_variable(nonnegative=True) 

 

eps = 1/(2*Integer(g.order())) 

x = next(g.vertex_iterator()) 

 

 

if g.is_directed(): 

 

# returns the variable corresponding to arc u,v 

E = lambda u,v : f[(u,v)] 

 

# All the vertices have in-degree 1 and out-degree 1 

for v in g: 

p.add_constraint(p.sum(f[(u,v)] for u in g.neighbor_in_iterator(v)), 

min = 1, 

max = 1) 

 

p.add_constraint(p.sum(f[(v,u)] for u in g.neighbor_out_iterator(v)), 

min = 1, 

max = 1) 

 

# r is greater than f 

for u,v in g.edges(labels = None): 

if g.has_edge(v,u): 

if u < v: 

p.add_constraint( r[(u,v)] + r[(v,u)]- f[(u,v)] - f[(v,u)], min = 0) 

 

# no 2-cycles 

p.add_constraint( f[(u,v)] + f[(v,u)], max = 1) 

 

else: 

p.add_constraint( r[(u,v)] + r[(v,u)] - f[(u,v)], min = 0) 

 

# defining the answer when g is directed 

from sage.graphs.all import DiGraph 

tsp = DiGraph() 

else: 

 

# reorders the edge as they can appear in the two different ways 

R = lambda x,y : frozenset((x,y)) 

 

# returns the variable corresponding to arc u,v 

E = lambda u,v : f[R(u,v)] 

 

# All the vertices have degree 2 

for v in g: 

p.add_constraint(p.sum( E(u,v) for u in g.neighbor_iterator(v)), 

min = 2, 

max = 2) 

 

# r is greater than f 

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

p.add_constraint( r[(u,v)] + r[(v,u)] - E(u,v), min = 0) 

 

from sage.graphs.all import Graph 

 

# defining the answer when g is not directed 

tsp = Graph() 

 

# no cycle which does not contain x 

for v in g: 

if v != x: 

p.add_constraint(p.sum( r[(u,v)] for u in g.neighbor_iterator(v)),max = 1-eps) 

 

if use_edge_labels: 

p.set_objective(p.sum( weight(l)*E(u,v) for u,v,l in g.edge_iterator()) ) 

 

try: 

obj = p.solve(log=verbose) 

f = p.get_values(f) 

tsp.add_vertices(g.vertices()) 

tsp.set_pos(g.get_pos()) 

tsp.name("TSP from "+g.name()) 

tsp.add_edges([(u,v,l) for u,v,l in g.edge_iterator() if E(u,v) == 1]) 

 

return tsp 

 

except MIPSolverException: 

raise EmptySetError("The given graph is not Hamiltonian") 

 

 

def hamiltonian_cycle(self, algorithm='tsp' ): 

r""" 

Returns a Hamiltonian cycle/circuit of the current graph/digraph 

 

A graph (resp. digraph) is said to be Hamiltonian 

if it contains as a subgraph a cycle (resp. a circuit) 

going through all the vertices. 

 

Computing a Hamiltonian cycle/circuit being NP-Complete, 

this algorithm could run for some time depending on 

the instance. 

 

ALGORITHM: 

 

See ``Graph.traveling_salesman_problem`` for 'tsp' algorithm and 

``find_hamiltonian`` from ``sage.graphs.generic_graph_pyx`` 

for 'backtrack' algorithm. 

 

INPUT: 

 

- ``algorithm`` - one of 'tsp' or 'backtrack'. 

 

OUTPUT: 

 

If using the 'tsp' algorithm, returns a Hamiltonian cycle/circuit if it 

exists; otherwise, raises a ``EmptySetError`` exception. If using the 

'backtrack' algorithm, returns a pair (B,P). If B is True then P is a 

Hamiltonian cycle and if B is False, P is a longest path found by the 

algorithm. Observe that if B is False, the graph may still be Hamiltonian. 

The 'backtrack' algorithm is only implemented for undirected 

graphs. 

 

.. WARNING:: 

 

The 'backtrack' algorithm may loop endlessly on graphs 

with vertices of degree 1. 

 

NOTE: 

 

This function, as ``is_hamiltonian``, computes a Hamiltonian 

cycle if it exists: the user should *NOT* test for 

Hamiltonicity using ``is_hamiltonian`` before calling this 

function, as it would result in computing it twice. 

 

The backtrack algorithm is only implemented for undirected graphs. 

 

EXAMPLES: 

 

The Heawood Graph is known to be Hamiltonian :: 

 

sage: g = graphs.HeawoodGraph() 

sage: g.hamiltonian_cycle() 

TSP from Heawood graph: Graph on 14 vertices 

 

The Petersen Graph, though, is not :: 

 

sage: g = graphs.PetersenGraph() 

sage: g.hamiltonian_cycle() 

Traceback (most recent call last): 

... 

EmptySetError: The given graph is not Hamiltonian 

 

Now, using the backtrack algorithm in the Heawood graph :: 

 

sage: G=graphs.HeawoodGraph() 

sage: G.hamiltonian_cycle(algorithm='backtrack') 

(True, [11, 10, 1, 2, 3, 4, 9, 8, 7, 6, 5, 0, 13, 12]) 

 

And now in the Petersen graph :: 

 

sage: G=graphs.PetersenGraph() 

sage: G.hamiltonian_cycle(algorithm='backtrack') 

(False, [6, 8, 5, 0, 1, 2, 7, 9, 4, 3]) 

 

Finally, we test the algorithm in a cube graph, which is Hamiltonian :: 

 

sage: G=graphs.CubeGraph(3) 

sage: G.hamiltonian_cycle(algorithm='backtrack') 

(True, ['010', '110', '100', '000', '001', '101', '111', '011']) 

 

""" 

if self.order() < 2: 

raise ValueError("the traveling salesman problem is not defined for empty or one-element graph") 

 

if algorithm=='tsp': 

from sage.numerical.mip import MIPSolverException 

 

try: 

return self.traveling_salesman_problem(use_edge_labels = False) 

except MIPSolverException: 

from sage.categories.sets_cat import EmptySetError 

raise EmptySetError("The given graph is not Hamiltonian") 

elif algorithm=='backtrack': 

from sage.graphs.generic_graph_pyx import find_hamiltonian as fh 

return fh( self ) 

 

else: 

raise ValueError("``algorithm`` (%s) should be 'tsp' or 'backtrack'."%(algorithm)) 

 

def feedback_vertex_set(self, value_only=False, solver=None, verbose=0, constraint_generation=True): 

r""" 

Return the minimum feedback vertex set of a (di)graph. 

 

The minimum feedback vertex set of a (di)graph is a set of vertices that 

intersect all of its cycles. Equivalently, a minimum feedback vertex 

set of a (di)graph is a set `S` of vertices such that the digraph `G-S` 

is acyclic. For more information, see :wikipedia:`Feedback_vertex_set`. 

 

INPUT: 

 

- ``value_only`` -- boolean (default: ``False``) 

 

- When set to ``True``, only the minimum cardinal of a minimum vertex 

set is returned. 

 

- When set to ``False``, the ``Set`` of vertices of a minimal feedback 

vertex set is returned. 

 

- ``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. 

 

- ``constraint_generation`` (boolean) -- whether to use constraint 

generation when solving the Mixed Integer Linear Program (default: 

``True``). 

 

ALGORITHMS: 

 

(Constraints generation) 

 

When the parameter ``constraint_generation`` is enabled (default) the 

following MILP formulation is used to solve the problem: 

 

.. MATH:: 

 

\mbox{Minimize : }&\sum_{v\in G} b_{v}\\ 

\mbox{Such that : }&\\ 

&\forall C\text{ circuits }\subseteq G, \sum_{v\in C}b_{v}\geq 1\\ 

 

As the number of circuits contained in a graph is exponential, this LP 

is solved through constraint generation. This means that the solver is 

sequentially asked to solve the problem, knowing only a portion of the 

circuits contained in `G`, each time adding to the list of its 

constraints the circuit which its last answer had left intact. 

 

(Another formulation based on an ordering of the vertices) 

 

When the graph is directed, a second (and very slow) formulation is 

available, which should only be used to check the result of the first 

implementation in case of doubt. 

 

.. MATH:: 

 

\mbox{Minimize : }&\sum_{v\in G} b_v\\ 

\mbox{Such that : }&\\ 

&\forall (u,v)\in G, d_u-d_v+nb_u+nb_v\geq 0\\ 

&\forall u\in G, 0\leq d_u\leq |G|\\ 

 

A brief explanation: 

 

An acyclic digraph can be seen as a poset, and every poset has a linear 

extension. This means that in any acyclic digraph the vertices can be 

ordered with a total order `<` in such a way that if `(u,v)\in G`, then 

`u<v`. Thus, this linear program is built in order to assign to each 

vertex `v` a number `d_v\in [0,\dots,n-1]` such that if there exists an 

edge `(u,v)\in G` then either `d_v<d_u` or one of `u` or `v` is removed. 

The number of vertices removed is then minimized, which is the 

objective. 

 

EXAMPLES: 

 

The necessary example:: 

 

sage: g = graphs.PetersenGraph() 

sage: fvs = g.feedback_vertex_set() 

sage: len(fvs) 

3 

sage: g.delete_vertices(fvs) 

sage: g.is_forest() 

True 

 

In a digraph built from a graph, any edge is replaced by arcs going in 

the two opposite directions, thus creating a cycle of length two. 

Hence, to remove all the cycles from the graph, each edge must see one 

of its neighbors removed: a feedback vertex set is in this situation a 

vertex cover:: 

 

sage: cycle = graphs.CycleGraph(5) 

sage: dcycle = DiGraph(cycle) 

sage: cycle.vertex_cover(value_only=True) 

3 

sage: feedback = dcycle.feedback_vertex_set() 

sage: len(feedback) 

3 

sage: (u,v,l) = next(cycle.edge_iterator()) 

sage: u in feedback or v in feedback 

True 

 

For a circuit, the minimum feedback arc set is clearly `1`:: 

 

sage: circuit = digraphs.Circuit(5) 

sage: circuit.feedback_vertex_set(value_only=True) == 1 

True 

 

TESTS: 

 

Comparing with/without constraint generation:: 

 

sage: g = digraphs.RandomDirectedGNP(10,.3) 

sage: x = g.feedback_vertex_set(value_only=True) 

sage: y = g.feedback_vertex_set(value_only=True, 

....: constraint_generation=False) 

sage: x == y 

True 

 

Bad algorithm:: 

 

sage: g = graphs.PetersenGraph() 

sage: g.feedback_vertex_set(constraint_generation=False) 

Traceback (most recent call last): 

... 

ValueError: The only implementation available for undirected graphs is with constraint_generation set to True. 

""" 

if not constraint_generation and not self.is_directed(): 

raise ValueError("The only implementation available for " 

"undirected graphs is with constraint_generation " 

"set to True.") 

 

# It would be a pity to start a LP if the graph is already acyclic 

if ((not self.is_directed() and self.is_forest()) or 

( self.is_directed() and self.is_directed_acyclic())): 

if value_only: 

return 0 

return [] 

 

from sage.numerical.mip import MixedIntegerLinearProgram 

 

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

# Constraint Generation Implementation # 

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

if constraint_generation: 

 

p = MixedIntegerLinearProgram(constraint_generation=True, 

maximization=False, solver=solver) 

 

# A variable for each vertex 

b = p.new_variable(binary=True) 

 

# Variables are binary, and their coefficient in the objective is 1 

p.set_objective(p.sum( b[v] for v in self)) 

 

p.solve(log=verbose) 

 

# For as long as we do not break because the digraph is acyclic.... 

while True: 

 

# Building the graph without the vertices removed by the LP 

h = self.subgraph([v for v in self if p.get_values(b[v]) == 0]) 

 

# Is the graph acyclic ? 

if self.is_directed(): 

isok, certificate = h.is_directed_acyclic(certificate=True) 

else: 

isok, certificate = h.is_forest(certificate=True) 

 

# If so, we are done ! 

if isok: 

break 

 

# There is a circuit left. Let's add the corresponding 

# constraint ! 

while not isok: 

 

p.add_constraint(p.sum(b[v] for v in certificate), min=1) 

if verbose: 

print("Adding a constraint on circuit: ", certificate) 

 

# Let's search for a vertex disjoint circuit, if any 

h.delete_vertices(certificate) 

if self.is_directed(): 

isok, certificate = h.is_directed_acyclic(certificate=True) 

else: 

isok, certificate = h.is_forest(certificate=True) 

 

obj = p.solve(log=verbose) 

 

if value_only: 

return obj 

 

else: 

 

# Listing the vertices contained in the MFVS 

return [v for v in self if p.get_values(b[v]) == 1] 

 

else: 

 

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

# Ordering-based MILP Implementation # 

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

 

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

 

b = p.new_variable(binary=True) 

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

n = self.order() 

 

# The removed vertices cover all the back arcs ( third condition ) 

for (u,v) in self.edges(labels = None): 

p.add_constraint(d[u]-d[v]+n*(b[u]+b[v]), min=1) 

 

for u in self: 

p.add_constraint(d[u], max = n) 

 

p.set_objective(p.sum([b[v] for v in self])) 

 

if value_only: 

return Integer(round(p.solve(objective_only=True, log=verbose))) 

else: 

p.solve(log=verbose) 

b_sol = p.get_values(b) 

 

return [v for v in self if b_sol[v] == 1] 

 

def flow(self, x, y, value_only=True, integer=False, use_edge_labels=True, vertex_bound=False, algorithm = None, solver=None, verbose=0): 

r""" 

Returns a maximum flow in the graph from ``x`` to ``y`` 

represented by an optimal valuation of the edges. For more 

information, see the 

`Wikipedia article on maximum flow 

<http://en.wikipedia.org/wiki/Max_flow>`_. 

 

As an optimization problem, is can be expressed this way : 

 

.. MATH:: 

 

\mbox{Maximize : }&\sum_{e\in G.edges()} w_e b_e\\ 

\mbox{Such that : }&\forall v \in G, \sum_{(u,v)\in G.edges()} b_{(u,v)}\leq 1\\ 

&\forall x\in G, b_x\mbox{ is a binary variable} 

 

INPUT: 

 

- ``x`` -- Source vertex 

 

- ``y`` -- Sink vertex 

 

- ``value_only`` -- boolean (default: ``True``) 

 

- When set to ``True``, only the value of a maximal 

flow is returned. 

 

- When set to ``False``, is returned a pair whose first element 

is the value of the maximum flow, and whose second value is 

a flow graph (a copy of the current graph, such that each edge 

has the flow using it as a label, the edges without flow being 

omitted). 

 

- ``integer`` -- boolean (default: ``True``) 

 

- When set to ``True``, computes an optimal solution under the 

constraint that the flow going through an edge has to be an 

integer. 

 

- ``use_edge_labels`` -- boolean (default: ``True``) 

 

- When set to ``True``, computes a maximum flow 

where each edge has a capacity defined by its label. (If 

an edge has no label, `1` is assumed.) 

 

- When set to ``False``, each edge has capacity `1`. 

 

- ``vertex_bound`` -- boolean (default: ``False``) 

 

- When set to ``True``, sets the maximum flow leaving 

a vertex different from `x` to `1` (useful for vertex 

connectivity parameters). 

 

- ``algorithm`` -- There are currently three different 

implementations of this method: 

 

* If ``algorithm = "FF"``, a Python implementation of the 

Ford-Fulkerson algorithm is used (only available when 

``vertex_bound = False``) 

 

* If ``algorithm = "LP"``, the flow problem is solved using 

Linear Programming. 

 

* If ``algorithm = "igraph"``, the igraph implementation of the 

Goldberg-Tarjan algorithm is used (only available when 

igraph is installed and ``vertex_bound = False``) 

 

* If ``algorithm = None`` (default), we use ``LP`` if 

``vertex_bound = True``, otherwise, we use ``igraph`` if 

it is available, ``FF`` if it is not available. 

 

- ``solver`` -- Specify a Linear Program solver to be used. 

If set to ``None``, the default one is used. function of 

``MixedIntegerLinearProgram``. See the documentation of 

``MixedIntegerLinearProgram.solve`` for more information. 

 

Only useful when LP is used to solve the flow problem. 

 

- ``verbose`` (integer) -- sets the level of verbosity. Set to 0 

by default (quiet). 

 

Only useful when LP is used to solve the flow problem. 

 

.. NOTE:: 

 

Even though the three different implementations are meant to 

return the same Flow values, they can not be expected to 

return the same Flow graphs. 

 

Besides, the use of Linear Programming may possibly mean a 

(slight) numerical noise. 

 

EXAMPLES: 

 

Two basic applications of the flow method for the ``PappusGraph`` and the 

``ButterflyGraph`` with parameter `2` :: 

 

sage: g=graphs.PappusGraph() 

sage: int(g.flow(1,2)) 

3 

 

:: 

 

sage: b=digraphs.ButterflyGraph(2) 

sage: int(b.flow(('00',1),('00',2))) 

1 

 

The flow method can be used to compute a matching in a bipartite graph 

by linking a source `s` to all the vertices of the first set and linking 

a sink `t` to all the vertices of the second set, then computing 

a maximum `s-t` flow :: 

 

sage: g = DiGraph() 

sage: g.add_edges([('s',i) for i in range(4)]) 

sage: g.add_edges([(i,4+j) for i in range(4) for j in range(4)]) 

sage: g.add_edges([(4+i,'t') for i in range(4)]) 

sage: [cardinal, flow_graph] = g.flow('s','t',integer=True,value_only=False) 

sage: flow_graph.delete_vertices(['s','t']) 

sage: len(flow_graph.edges()) 

4 

 

The undirected case:: 

 

sage: g = Graph() 

sage: g.add_edges([('s',i) for i in range(4)]) 

sage: g.add_edges([(i,4+j) for i in range(4) for j in range(4)]) 

sage: g.add_edges([(4+i,'t') for i in range(4)]) 

sage: [cardinal, flow_graph] = g.flow('s','t',integer=True,value_only=False) 

sage: flow_graph.delete_vertices(['s','t']) 

sage: len(flow_graph.edges()) 

4 

 

TESTS: 

 

An exception if raised when forcing "FF" or "igraph" with ``vertex_bound = True``:: 

 

sage: g = graphs.PetersenGraph() 

sage: g.flow(0,1,vertex_bound = True, algorithm = "FF") 

Traceback (most recent call last): 

... 

ValueError: This method does not support both vertex_bound=True and algorithm='FF'. 

sage: g.flow(0,1,vertex_bound = True, algorithm = "igraph") 

Traceback (most recent call last): 

... 

ValueError: This method does not support both vertex_bound=True and algorithm='igraph'. 

 

Or if the method is different from the expected values:: 

 

sage: g.flow(0,1, algorithm="Divination") 

Traceback (most recent call last): 

... 

ValueError: The algorithm argument has to be equal to either "FF", "LP", "igraph", or None 

 

The two algorithms are indeed returning the same results (possibly with 

some numerical noise, cf. :trac:`12362`):: 

 

sage: g = graphs.RandomGNP(20,.3) 

sage: for u,v in g.edges(labels=False): 

....: g.set_edge_label(u,v,round(random(),5)) 

sage: flow_ff = g.flow(0,1, algorithm="FF") 

sage: flow_lp = g.flow(0,1, algorithm="LP") 

sage: abs(flow_ff-flow_lp) < 0.01 

True 

sage: flow_igraph = g.flow(0,1, algorithm="igraph") # optional python_igraph 

sage: abs(flow_ff-flow_igraph) < 0.00001 # optional python_igraph 

True 

""" 

from sage.misc.package import is_package_installed, PackageNotFoundError 

self._scream_if_not_simple(allow_loops=True) 

if vertex_bound and algorithm in ["FF", "igraph"]: 

raise ValueError("This method does not support both " + 

"vertex_bound=True and algorithm='" + algorithm + "'.") 

if use_edge_labels: 

from sage.rings.real_mpfr import RR 

if integer: 

from math import floor 

capacity=lambda x: floor(x) if x in RR else 1 

else: 

capacity=lambda x: x if x in RR else 1 

else: 

capacity=lambda x: 1 

 

if algorithm is None: 

if vertex_bound: 

algorithm = "LP" 

elif is_package_installed("python_igraph"): 

algorithm = "igraph" 

else: 

algorithm = "FF" 

 

if (algorithm == "FF"): 

return self._ford_fulkerson(x,y, value_only=value_only, integer=integer, use_edge_labels=use_edge_labels) 

elif (algorithm == 'igraph'): 

 

try: 

import igraph 

except ImportError: 

raise PackageNotFoundError("python_igraph") 

vertices = self.vertices() 

x_int = vertices.index(x) 

y_int = vertices.index(y) 

if use_edge_labels: 

g_igraph = self.igraph_graph(edge_attrs={'capacity':[float(capacity(e[2])) for e in self.edge_iterator()]}) 

maxflow = g_igraph.maxflow(x_int, y_int, 'capacity') 

else: 

g_igraph = self.igraph_graph() 

maxflow = g_igraph.maxflow(x_int, y_int) 

 

if value_only: 

return maxflow.value 

else: 

from sage.graphs.digraph import DiGraph 

igraph_flow = iter(maxflow.flow) 

flow_digraph = DiGraph() 

if self.is_directed(): 

for e in g_igraph.es(): 

f = maxflow.flow[e.index] 

if f != 0: 

flow_digraph.add_edge(e.source, e.target, f) 

else: 

# If the graph is undirected, the output of igraph is a list 

# of weights: a positive weight means that the flow is from 

# the vertex with minimum label to the vertex with maximum 

# label, a negative weight means the converse. 

for e in g_igraph.es(): 

f = maxflow.flow[e.index] 

if (f > 0 and e.source < e.target) or (f < 0 and e.source > e.target): 

flow_digraph.add_edge(e.source, e.target, abs(f)) 

elif f != 0: 

flow_digraph.add_edge(e.target, e.source, abs(f)) 

flow_digraph.relabel({i:vertices[i] for i in flow_digraph}) 

return [maxflow.value, flow_digraph] 

 

if algorithm != "LP": 

raise ValueError("The algorithm argument has to be equal to either " + 

"\"FF\", \"LP\", \"igraph\", or None") 

 

 

from sage.numerical.mip import MixedIntegerLinearProgram 

g=self 

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

flow=p.new_variable(nonnegative=True) 

 

if g.is_directed(): 

# This function return the balance of flow at X 

flow_sum=lambda X: p.sum([flow[(X,v)] for (u,v) in g.outgoing_edges([X],labels=None)])-p.sum([flow[(u,X)] for (u,v) in g.incoming_edges([X],labels=None)]) 

 

# The flow leaving x 

flow_leaving = lambda X : p.sum([flow[(uu,vv)] for (uu,vv) in g.outgoing_edges([X],labels=None)]) 

 

# The flow to be considered when defining the capacity contraints 

capacity_sum = lambda u,v : flow[(u,v)] 

 

else: 

# This function return the balance of flow at X 

flow_sum=lambda X:p.sum([flow[(X,v)]-flow[(v,X)] for v in g[X]]) 

 

# The flow leaving x 

flow_leaving = lambda X : p.sum([flow[(X,vv)] for vv in g[X]]) 

 

# The flow to be considered when defining the capacity contraints 

capacity_sum = lambda u,v : flow[(u,v)] + flow[(v,u)] 

 

# Maximizes the flow leaving x 

p.set_objective(flow_sum(x)) 

 

# Elsewhere, the flow is equal to 0 

for v in g: 

if v!=x and v!=y: 

p.add_constraint(flow_sum(v),min=0,max=0) 

 

# Capacity constraints 

for (u,v,w) in g.edges(): 

p.add_constraint(capacity_sum(u,v),max=capacity(w)) 

 

# No vertex except the sources can send more than 1 

if vertex_bound: 

for v in g: 

if v!=x and v!=y: 

p.add_constraint(flow_leaving(v),max=1) 

 

if integer: 

p.set_integer(flow) 

 

 

if value_only: 

return p.solve(objective_only=True, log = verbose) 

 

obj=p.solve(log = verbose) 

 

if integer or use_edge_labels is False: 

obj = Integer(round(obj)) 

 

flow=p.get_values(flow) 

# Builds a clean flow Draph 

flow_graph = g._build_flow_graph(flow, integer=integer) 

 

# Which could be a Graph 

if not self.is_directed(): 

from sage.graphs.graph import Graph 

flow_graph = Graph(flow_graph) 

 

return [obj,flow_graph] 

 

def nowhere_zero_flow(self, k=None, solver=None, verbose=0): 

r""" 

Return a ``k``-nowhere zero flow of the (di)graph. 

 

A flow on a graph `G = (V, E)` is a pair `(D, f)` such that `D` 

is an orientation of `G` and `f` is a function on `E` satisfying 

 

.. MATH:: 

 

\sum_{u \in N^-_D(v)} f(uv) = \sum_{w \in N^+_D(v)} f(vw), 

\ \forall v \in V. 

 

A ``nowhere zero flow`` on a graph `G = (V, E)` is a flow `(D, f)` 

such that `f(e) \neq 0` for every `e \in E`. For a positive 

integer `k`, a `k`-flow on a graph `G = (V, E)` is a flow `(D, f)` 

such that `f: E \to Z` and `-(k - 1) \leq f(e) \leq k - 1` for 

every `e \in E`. A `k`-flow is positive if `f(e) > 0` for every 

`e \in E`. A `k`-flow which is nowhere zero is called a 

`k`-*nowhere zero flow* (or `k`-NZF). 

 

The following are equivalent. 

 

- `G` admits a positive `k`-flow. 

- `G` admits a `k`-NZF. 

- Every orientation of `G` admits a `k`-NZF. 

 

Furthermore, a (di)graph admits a `k`-NZF if and only if it 

is bridgeless and every bridgeless graph admits a `6`-NZF [Sey1981]_. 

See :wikipedia:`Nowhere-zero_flow` for more details. 

 

ALGORITHM: 

 

If ``self`` is not directed, we search for a `k`-NZF on any 

orientation of ``self`` and then build a positive `k`-NZF by 

reverting edges with negative flow. 

 

INPUT: 

 

- ``k`` -- integer (default: ``6``); when set to a positive 

integer `\geq 2`, search for a `k`-nowhere zero flow 

 

- ``solver`` -- (default: ``None``); Specify a Linear Program 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 of the LP solver, where `0` means quiet. 

 

OUTPUT: 

 

A digraph with flow values stored as edge labels if a `k`-nowhere 

zero flow is found. If ``self`` is undirected, the edges of this 

digraph indicate the selected orientation. If no feasible solution 

is found, an error is raised. 

 

EXAMPLES: 

 

The Petersen graph admits a (positive) 5-nowhere zero flow, but no 

4-nowhere zero flow:: 

 

sage: g = graphs.PetersenGraph() 

sage: h = g.nowhere_zero_flow(k=5) 

sage: sorted( set(h.edge_labels()) ) 

[1, 2, 3, 4] 

sage: h = g.nowhere_zero_flow(k=3) 

Traceback (most recent call last): 

... 

EmptySetError: the problem has no feasible solution 

 

The de Bruijn digraph admits a 2-nowhere zero flow:: 

 

sage: g = digraphs.DeBruijn(2, 3) 

sage: h = g.nowhere_zero_flow(k=2) 

sage: sorted(list( set(h.edge_labels()) )) 

[-1, 1] 

 

TESTS: 

 

Empty graph:: 

 

sage: G = Graph() 

sage: G.nowhere_zero_flow() 

Digraph on 0 vertices 

 

Graph with one vertex:: 

 

sage: G = Graph([[1], []]) 

sage: G 

Graph on 1 vertex 

sage: G.nowhere_zero_flow() 

Digraph on 1 vertex 

 

Loops and multiple edges:: 

 

sage: g = Graph([(0, 0), (0, 0)], loops=True, multiedges=True) 

sage: g.nowhere_zero_flow().edges() 

[(0, 0, 1), (0, 0, 1)] 

sage: g = Graph([(0, 0), (0, 1), (0, 1)], loops=True, multiedges=True) 

sage: g.nowhere_zero_flow(k=2).edges() 

[(0, 0, 1), (0, 1, 1), (1, 0, 1)] 

sage: g = DiGraph([(0, 0), (0, 0)], loops=True, multiedges=True) 

sage: g.nowhere_zero_flow().edges() 

[(0, 0, 1), (0, 0, 1)] 

sage: g = DiGraph([(0, 0), (0, 1), (0, 1)], loops=True, multiedges=True) 

sage: g.nowhere_zero_flow(k=2).edges() 

[(0, 0, 1), (0, 1, -1), (0, 1, 1)] 

 

Multiple connected components:: 

 

sage: g = graphs.CycleGraph(3) * 2 

sage: h = g.nowhere_zero_flow() 

sage: h.connected_components_sizes() 

[3, 3] 

 

(Di)Graphs with bridges:: 

 

sage: g = graphs.PathGraph(2) 

sage: g.nowhere_zero_flow() 

Traceback (most recent call last): 

... 

EmptySetError: (di)graphs with bridges have no feasible solution 

sage: g = digraphs.Path(2) 

sage: g.nowhere_zero_flow() 

Traceback (most recent call last): 

... 

EmptySetError: (di)graphs with bridges have no feasible solution 

 

Too small value of ``k``:: 

 

sage: Graph().nowhere_zero_flow(k=1) 

Traceback (most recent call last): 

... 

ValueError: parameter 'k' must be at least 2 

""" 

if k is None: 

k = 6 # See [Sey1981]_ 

elif k < 2: 

raise ValueError("parameter 'k' must be at least 2") 

 

from sage.graphs.digraph import DiGraph 

from sage.categories.sets_cat import EmptySetError 

 

# If the (di)graph is not connected, we solve the problem on each 

# of its connected components 

if not self.is_connected(): 

solution = DiGraph(loops=self.allows_loops(), 

multiedges=self.allows_multiple_edges()) 

solution.add_vertices(self.vertices()) 

for g in self.connected_components_subgraphs(): 

solution.add_edges(g.nowhere_zero_flow(k=k, solver=solver, 

verbose=verbose).edges()) 

return solution 

 

# If the (di)graph has bridges, the problem is not feasible 

if ( (self.is_directed() and not self.is_strongly_connected() and self.to_undirected().bridges()) 

or (not self.is_directed() and self.bridges()) ): 

raise EmptySetError("(di)graphs with bridges have no feasible solution") 

 

# 

# We deal with loops and multiple edges, if any 

# 

if self.has_loops() or self.has_multiple_edges(): 

G = copy(self) if self.is_directed() else next(self.orientations()) 

 

# We assign flow 1 to loops, if any 

solution = DiGraph([G.vertices(), [(u,v,1) for u,v in G.loops(labels=0)]], 

loops=G.has_loops(), 

multiedges=G.has_multiple_edges()) 

G.allow_loops(False) 

 

# We ensure that multiple edges have distinct labels 

multiedges = {(u,v,i) for i,(u,v) in enumerate(G.multiple_edges(labels=0))} 

G.delete_edges(G.multiple_edges()) 

G.add_edges(multiedges) 

 

else: 

G = self if self.is_directed() else next(self.orientations()) 

solution = DiGraph([G.vertices(), []]) 

 

if G.order() <= 1: 

return solution 

 

# 

# We use a MIP formulation to solve the problem 

# 

from sage.numerical.mip import MixedIntegerLinearProgram,MIPSolverException 

p = MixedIntegerLinearProgram(solver=solver) 

f = p.new_variable(nonnegative=False, integer=True) 

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

 

# flow conservation constraints 

for u in G: 

p.add_constraint( p.sum(f[e] for e in G.incoming_edge_iterator(u)) 

== p.sum(f[e] for e in G.outgoing_edge_iterator(u)) ) 

 

# The flow on edge e has value in {-k+1,..., -1, 1, ..., k-1} 

for e in G.edge_iterator(): 

p.add_constraint(p.sum(b[e,i] for i in range(-k+1, k) if i != 0) == 1) 

p.add_constraint(f[e] == p.sum(i * b[e,i] for i in range(-k+1, k) if i != 0)) 

 

# We solve the MIP. 

try: 

p.solve(log=verbose) 

except MIPSolverException: 

raise EmptySetError("the problem has no feasible solution") 

 

# Extract and return the solution. If the graph is not directed, we 

# reverse edges with a negative flow to obtain a positive k-NZF 

for (u,v,_), val in p.get_values(f).items(): 

if self.is_directed() or val > 0: 

solution.add_edge(u, v, int(val)) 

else: 

solution.add_edge(v, u, int(-val)) 

 

return solution 

 

def _ford_fulkerson(self, s, t, use_edge_labels = False, integer = False, value_only = True): 

r""" 

Python implementation of the Ford-Fulkerson algorithm. 

 

This method is a Python implementation of the Ford-Fulkerson 

max-flow algorithm, which is (slightly) faster than the LP 

implementation. 

 

INPUT: 

 

- ``s`` -- Source vertex 

 

- ``t`` -- Sink vertex 

 

- ``value_only`` -- boolean (default: ``True``) 

 

- When set to ``True``, only the value of a maximal 

flow is returned. 

 

- When set to ``False``, is returned a pair whose first element 

is the value of the maximum flow, and whose second value is 

a flow graph (a copy of the current graph, such that each edge 

has the flow using it as a label, the edges without flow being 

omitted). 

 

- ``integer`` -- boolean (default: ``False``) 

 

- When set to ``True``, computes an optimal solution under the 

constraint that the flow going through an edge has to be an 

integer. 

 

- ``use_edge_labels`` -- boolean (default: ``True``) 

 

- When set to ``True``, computes a maximum flow 

where each edge has a capacity defined by its label. (If 

an edge has no label, `1` is assumed.) 

 

- When set to ``False``, each edge has capacity `1`. 

 

EXAMPLES: 

 

Two basic applications of the flow method for the ``PappusGraph`` and the 

``ButterflyGraph`` with parameter `2` :: 

 

sage: g=graphs.PappusGraph() 

sage: g._ford_fulkerson(1,2) 

3 

 

:: 

 

sage: b=digraphs.ButterflyGraph(2) 

sage: b._ford_fulkerson(('00',1),('00',2)) 

1 

 

The flow method can be used to compute a matching in a bipartite graph 

by linking a source `s` to all the vertices of the first set and linking 

a sink `t` to all the vertices of the second set, then computing 

a maximum `s-t` flow :: 

 

sage: g = DiGraph() 

sage: g.add_edges([('s',i) for i in range(4)]) 

sage: g.add_edges([(i,4+j) for i in range(4) for j in range(4)]) 

sage: g.add_edges([(4+i,'t') for i in range(4)]) 

sage: [cardinal, flow_graph] = g._ford_fulkerson('s','t',integer=True,value_only=False) 

sage: flow_graph.delete_vertices(['s','t']) 

sage: len(flow_graph.edges(labels=None)) 

4 

""" 

from sage.graphs.digraph import DiGraph 

from sage.functions.other import floor 

 

# Whether we should consider the edges labeled 

if use_edge_labels: 

l_capacity=lambda x: 1 if (x is None or x == {}) else (floor(x) if integer else x) 

else: 

l_capacity=lambda x: 1 

 

directed = self.is_directed() 

 

# Associated to each edge (u,v) of the flow graph its capacity 

capacity = {} 

# Associates to each edge (u,v) of the graph the (directed) 

# flow going through it 

flow = {} 

 

# Residual graph. Only contains edge on which some flow can be 

# sent. This can happen both when the flow going through the 

# current edge is strictly less than its capacity, or when 

# there exists a back arc with non-null flow 

residual = DiGraph() 

 

# Initializing the variables 

if directed: 

for u,v,l in self.edge_iterator(): 

if l_capacity(l) > 0: 

capacity[(u,v)] = l_capacity(l) + capacity.get((u,v),0) 

capacity[(v,u)] = capacity.get((v,u),0) 

residual.add_edge(u,v) 

flow[(u,v)] = 0 

flow[(v,u)] = 0 

else: 

for u,v,l in self.edge_iterator(): 

if l_capacity(l) > 0: 

capacity[(u,v)] = l_capacity(l) + capacity.get((u,v),0) 

capacity[(v,u)] = l_capacity(l) + capacity.get((v,u),0) 

residual.add_edge(u,v) 

residual.add_edge(v,u) 

flow[(u,v)] = 0 

flow[(v,u)] = 0 

 

# Reqrites a path as a list of edges : 

# ex : [0,1,2,3,4,5] becomes [(0,1), (1,2), (2,3), (3,4), (4,5)] 

path_to_edges = lambda P : zip(P[:-1], P[1:]) 

 

# Rewrites a path as a list of edges labeled with their 

# available capacity 

path_to_labelled_edges = lambda P : [(x_y[0], x_y[1], capacity[(x_y[0], x_y[1])] - flow[(x_y[0], x_y[1])] + flow[(x_y[1], x_y[0])]) for x_y in path_to_edges(P)] 

 

# Total flow going from s to t 

flow_intensity = 0 

 

while True: 

 

# If there is a shortest path from s to t 

path = residual.shortest_path(s,t) 

if not path: 

break 

 

# We are rewriting the shortest path as a sequence of 

# edges whose labels are their available capacities 

edges = path_to_labelled_edges(path) 

 

# minimum capacity available on the whole path 

epsilon = min((x[2] for x in edges)) 

 

flow_intensity = flow_intensity + epsilon 

 

# Updating variables 

for uu,vv,ll in edges: 

 

# The flow on the back arc 

other = flow[(vv,uu)] 

flow[(uu,vv)] = flow[(uu,vv)] + max(0,epsilon-other) 

flow[(vv,uu)] = other - min(other, epsilon) 

 

# If the current edge is fully used, we do not need it 

# anymore in the residual graph 

if capacity[(uu,vv)] - flow[(uu,vv)] + flow[(vv,uu)] == 0: 

residual.delete_edge(uu,vv) 

 

# If the back arc does not exist, it now does as the 

# edge (uu,vv) has a flow of at least epsilon>0 

if not residual.has_edge(vv,uu): 

residual.add_edge(vv,uu,epsilon) 

 

if value_only: 

return flow_intensity 

 

# Building and returning the flow graph 

g = DiGraph() 

g.add_edges([(x,y,l) for ((x,y),l) in iteritems(flow) if l > 0]) 

g.set_pos(self.get_pos()) 

 

return flow_intensity, g 

 

def multicommodity_flow(self, terminals, integer=True, use_edge_labels=False,vertex_bound=False, solver=None, verbose=0): 

r""" 

Solves a multicommodity flow problem. 

 

In the multicommodity flow problem, we are given a set of pairs 

`(s_i, t_i)`, called terminals meaning that `s_i` is willing 

some flow to `t_i`. 

 

Even though it is a natural generalisation of the flow problem 

this version of it is NP-Complete to solve when the flows 

are required to be integer. 

 

For more information, see the 

:wikipedia:`Wikipedia page on multicommodity flows 

<Multi-commodity_flow_problem>`. 

 

INPUT: 

 

- ``terminals`` -- a list of pairs `(s_i, t_i)` or triples 

`(s_i, t_i, w_i)` representing a flow from `s_i` to `t_i` 

of intensity `w_i`. When the pairs are of size `2`, a intensity 

of `1` is assumed. 

 

- ``integer`` (boolean) -- whether to require an integer multicommodity 

flow 

 

- ``use_edge_labels`` (boolean) -- whether to consider the label of edges 

as numerical values representing a capacity. If set to ``False``, a capacity 

of `1` is assumed 

 

- ``vertex_bound`` (boolean) -- whether to require that a vertex can stand at most 

`1` commodity of flow through it of intensity `1`. Terminals can obviously 

still send or receive several units of flow even though vertex_bound is set 

to ``True``, as this parameter is meant to represent topological properties. 

 

- ``solver`` -- Specify a Linear Program solver to be used. 

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

function of ``MixedIntegerLinearProgram``. See the documentation of ``MixedIntegerLinearProgram.solve`` 

for more informations. 

 

- ``verbose`` (integer) -- sets the level of verbosity. Set to 0 

by default (quiet). 

 

ALGORITHM: 

 

(Mixed Integer) Linear Program, depending on the value of ``integer``. 

 

EXAMPLES: 

 

An easy way to obtain a satisfiable multiflow is to compute 

a matching in a graph, and to consider the paired vertices 

as terminals :: 

 

sage: g = graphs.PetersenGraph() 

sage: matching = [(u,v) for u,v,_ in g.matching()] 

sage: h = g.multicommodity_flow(matching) 

sage: len(h) 

5 

 

We could also have considered ``g`` as symmetric and computed 

the multiflow in this version instead. In this case, however 

edges can be used in both directions at the same time:: 

 

sage: h = DiGraph(g).multicommodity_flow(matching) 

sage: len(h) 

5 

 

An exception is raised when the problem has no solution :: 

 

sage: h = g.multicommodity_flow([(u,v,3) for u,v in matching]) 

Traceback (most recent call last): 

... 

EmptySetError: The multiflow problem has no solution 

""" 

self._scream_if_not_simple(allow_loops=True) 

from sage.numerical.mip import MixedIntegerLinearProgram 

g=self 

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

 

# Adding the intensity if not present 

terminals = [(x if len(x) == 3 else (x[0],x[1],1)) for x in terminals] 

 

# defining the set of terminals 

set_terminals = set([]) 

for s,t,_ in terminals: 

set_terminals.add(s) 

set_terminals.add(t) 

 

# flow[i,(u,v)] is the flow of commodity i going from u to v 

flow=p.new_variable(nonnegative=True) 

 

# Whether to use edge labels 

if use_edge_labels: 

from sage.rings.real_mpfr import RR 

capacity=lambda x: x if x in RR else 1 

else: 

capacity=lambda x: 1 

 

if g.is_directed(): 

# This function return the balance of flow at X 

flow_sum=lambda i,X: p.sum([flow[i,(X,v)] for (u,v) in g.outgoing_edges([X],labels=None)])-p.sum([flow[i,(u,X)] for (u,v) in g.incoming_edges([X],labels=None)]) 

 

# The flow leaving x 

flow_leaving = lambda i,X : p.sum([flow[i,(uu,vv)] for (uu,vv) in g.outgoing_edges([X],labels=None)]) 

 

# the flow to consider when defining the capacity contraints 

capacity_sum = lambda i,u,v : flow[i,(u,v)] 

 

else: 

# This function return the balance of flow at X 

flow_sum=lambda i,X:p.sum([flow[i,(X,v)]-flow[i,(v,X)] for v in g[X]]) 

 

# The flow leaving x 

flow_leaving = lambda i, X : p.sum([flow[i,(X,vv)] for vv in g[X]]) 

 

# the flow to consider when defining the capacity contraints 

capacity_sum = lambda i,u,v : flow[i,(u,v)] + flow[i,(v,u)] 

 

 

# Flow constraints 

for i,(s,t,l) in enumerate(terminals): 

for v in g: 

if v == s: 

p.add_constraint(flow_sum(i,v),min=l,max=l) 

elif v == t: 

p.add_constraint(flow_sum(i,v),min=-l,max=-l) 

else: 

p.add_constraint(flow_sum(i,v),min=0,max=0) 

 

# Capacity constraints 

for (u,v,w) in g.edges(): 

p.add_constraint(p.sum([capacity_sum(i,u,v) for i in range(len(terminals))]),max=capacity(w)) 

 

if vertex_bound: 

 

# Any vertex 

for v in g.vertices(): 

 

# which is an endpoint 

if v in set_terminals: 

for i,(s,t,_) in enumerate(terminals): 

 

# only tolerates the commodities of which it is an endpoint 

if not (v==s or v==t): 

p.add_constraint(flow_leaving(i,v), max = 0) 

 

# which is not an endpoint 

else: 

# can stand at most 1 unit of flow through itself 

p.add_constraint(p.sum([flow_leaving(i,v) for i in range(len(terminals))]), max = 1) 

 

p.set_objective(None) 

 

if integer: 

p.set_integer(flow) 

 

from sage.numerical.mip import MIPSolverException 

 

try: 

obj=p.solve(log = verbose) 

except MIPSolverException: 

from sage.categories.sets_cat import EmptySetError 

raise EmptySetError("The multiflow problem has no solution") 

 

flow=p.get_values(flow) 

 

# building clean flow digraphs 

flow_graphs = [g._build_flow_graph({e:f for (ii,e),f in iteritems(flow) if ii == i}, integer=integer) 

for i in range(len(terminals))] 

 

# which could be .. graphs ! 

if not self.is_directed(): 

from sage.graphs.graph import Graph 

flow_graphs = [Graph(_) for _ in flow_graphs] 

 

return flow_graphs 

 

def _build_flow_graph(self, flow, integer): 

r""" 

Builds a "clean" flow graph 

 

It build it, then looks for circuits and removes them 

 

INPUT: 

 

- ``flow`` -- a dictionary associating positive numerical values 

to edges 

 

- ``integer`` (boolean) -- whether the values from ``flow`` are the solution 

of an integer flow. In this case, a value of less than .5 is assumed to be 0 

 

 

EXAMPLES: 

 

This method is tested in ``flow`` and ``multicommodity_flow``:: 

 

sage: g = Graph() 

sage: g.add_edge(0,1) 

sage: f = g._build_flow_graph({(0,1):1}, True) 

 

The method removes zero-cost flow cycles and updates the values accordingly:: 

 

sage: g = digraphs.DeBruijn(2,3) 

sage: flow = {('001','010'):1,('010','100'):1,('010','101'):1,('101','010'):1} 

sage: flow_graph = g._build_flow_graph(flow,True) 

sage: flow_graph.edges() 

[('001', '010', 1), ('010', '100', 1)] 

sage: flow = {('001','010'):2,('010','101'):3,('101','011'):2,('101','010'):1} 

sage: flow_graph = g._build_flow_graph(flow,True) 

sage: flow_graph.edges() 

[('001', '010', 2), ('010', '101', 2), ('101', '011', 2)] 

 

Isolated zero-cost flow cycles are also removed:: 

 

sage: g = digraphs.DeBruijn(2,3) 

sage: flow = {('000','001'):1,('010','101'):1,('101','010'):1} 

sage: flow_graph = g._build_flow_graph(flow,True) 

sage: flow_graph.edges() 

[('000', '001', 1)] 

""" 

 

from sage.graphs.digraph import DiGraph 

g = DiGraph() 

 

# add significant edges 

for (u,v),l in iteritems(flow): 

if l > 0 and not (integer and l<.5): 

g.add_edge(u,v,l) 

 

# stupid way to find Cycles. Will be fixed by #8932 

# for any vertex, for any of its in-neighbors, tried to find a cycle 

for v in g: 

for u in g.neighbor_in_iterator(v): 

 

# the edge from u to v could have been removed in a previous iteration 

if not g.has_edge(u,v): 

break 

sp = g.shortest_path(v,u) 

if sp != []: 

 

#find the minimm value along the cycle. 

m = g.edge_label(u,v) 

for i in range(len(sp)-1): 

m = min(m,g.edge_label(sp[i],sp[i+1])) 

 

# removes it from all the edges of the cycle 

sp.append(v) 

for i in range(len(sp)-1): 

l = g.edge_label(sp[i],sp[i+1]) - m 

 

# an edge with flow 0 is removed 

if l == 0: 

g.delete_edge(sp[i],sp[i+1]) 

else: 

g.set_edge_label(sp[i],sp[i+1],l) 

 

# if integer is set, round values and deletes zeroes 

if integer: 

for (u,v,l) in g.edges(): 

if l<.5: 

g.delete_edge(u,v) 

else: 

g.set_edge_label(u,v, int(round(l))) 

 

# returning a graph with the same embedding, the corresponding name, etc ... 

h = self.subgraph(edges=[], immutable=False) 

h.delete_vertices([v for v in self if (v not in g) or g.degree(v)==0]) 

h.add_edges(g.edges()) 

 

return h 

 

def disjoint_routed_paths(self,pairs, solver=None, verbose=0): 

r""" 

Returns a set of disjoint routed paths. 

 

Given a set of pairs `(s_i,t_i)`, a set 

of disjoint routed paths is a set of 

`s_i-t_i` paths which can intersect at their endpoints 

and are vertex-disjoint otherwise. 

 

INPUT: 

 

- ``pairs`` -- list of pairs of vertices 

 

- ``solver`` -- Specify a Linear Program solver to be used. 

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

function of ``MixedIntegerLinearProgram``. See the documentation of ``MixedIntegerLinearProgram.solve`` 

for more informations. 

 

- ``verbose`` (integer) -- sets the level of verbosity. Set to `0` 

by default (quiet). 

 

EXAMPLES: 

 

Given a grid, finding two vertex-disjoint 

paths, the first one from the top-left corner 

to the bottom-left corner, and the second from 

the top-right corner to the bottom-right corner 

is easy :: 

 

sage: g = graphs.GridGraph([5,5]) 

sage: p1,p2 = g.disjoint_routed_paths( [((0,0), (0,4)), ((4,4), (4,0))]) 

 

Though there is obviously no solution to the problem 

in which each corner is sending information to the opposite 

one:: 

 

sage: g = graphs.GridGraph([5,5]) 

sage: p1,p2 = g.disjoint_routed_paths( [((0,0), (4,4)), ((0,4), (4,0))]) 

Traceback (most recent call last): 

... 

EmptySetError: The disjoint routed paths do not exist. 

""" 

from sage.categories.sets_cat import EmptySetError 

try: 

return self.multicommodity_flow(pairs, vertex_bound = True, solver=solver, verbose=verbose) 

except EmptySetError: 

raise EmptySetError("The disjoint routed paths do not exist.") 

 

def edge_disjoint_paths(self, s, t, algorithm = "FF"): 

r""" 

Returns a list of edge-disjoint paths between two 

vertices as given by Menger's theorem. 

 

The edge version of Menger's theorem asserts that the size 

of the minimum edge cut between two vertices `s` and`t` 

(the minimum number of edges whose removal disconnects `s` 

and `t`) is equal to the maximum number of pairwise 

edge-independent paths from `s` to `t`. 

 

This function returns a list of such paths. 

 

INPUT: 

 

- ``algorithm`` -- There are currently two different 

implementations of this method : 

 

* If ``algorithm = "FF"`` (default), a Python implementation of the 

Ford-Fulkerson algorithm is used. 

 

* If ``algorithm = "LP"``, the flow problem is solved using 

Linear Programming. 

 

.. NOTE:: 

 

This function is topological: it does not take the eventual 

weights of the edges into account. 

 

EXAMPLES: 

 

In a complete bipartite graph :: 

 

sage: g = graphs.CompleteBipartiteGraph(2,3) 

sage: g.edge_disjoint_paths(0,1) 

[[0, 2, 1], [0, 3, 1], [0, 4, 1]] 

""" 

 

[obj, flow_graph] = self.flow(s,t,value_only=False, integer=True, use_edge_labels=False, algorithm=algorithm) 

 

paths = [] 

 

while True: 

path = flow_graph.shortest_path(s,t) 

if not path: 

break 

v = s 

edges = [] 

for w in path: 

edges.append((v,w)) 

v=w 

flow_graph.delete_edges(edges) 

paths.append(path) 

 

return paths 

 

def vertex_disjoint_paths(self, s, t): 

r""" 

Return a list of vertex-disjoint paths between two vertices as given by 

Menger's theorem. 

 

The vertex version of Menger's theorem asserts that the size of the 

minimum vertex cut between two vertices `s` and `t` (the minimum number 

of vertices whose removal disconnects `s` and `t`) is equal to the 

maximum number of pairwise vertex-independent paths from `s` to `t`. 

 

This function returns a list of such paths. 

 

EXAMPLES: 

 

In a complete bipartite graph :: 

 

sage: g = graphs.CompleteBipartiteGraph(2,3) 

sage: g.vertex_disjoint_paths(0,1) 

[[0, 2, 1], [0, 3, 1], [0, 4, 1]] 

 

TESTS: 

 

Fix issues reported in :trac:`22990`:: 

 

sage: g = digraphs.Path(2) 

sage: g.vertex_disjoint_paths(0, 1) 

[[0, 1]] 

sage: g.vertex_disjoint_paths(1,0) 

[] 

""" 

obj, flow_graph = self.flow(s, t, value_only=False, integer=True, use_edge_labels=False, vertex_bound=True) 

 

paths = [] 

if not obj: 

return paths 

if flow_graph.has_edge(s, t): 

flow_graph.delete_edge(s, t) 

paths.append([s, t]) 

 

while True: 

path = flow_graph.shortest_path(s,t) 

if not path: 

break 

flow_graph.delete_vertices(path[1:-1]) 

paths.append(path) 

 

return paths 

 

def dominating_set(self, independent=False, total=False, value_only=False, solver=None, verbose=0): 

r""" 

Returns a minimum dominating set of the graph 

represented by the list of its vertices. For more information, see the 

`Wikipedia article on dominating sets 

<http://en.wikipedia.org/wiki/Dominating_set>`_. 

 

A minimum dominating set `S` of a graph `G` is 

a set of its vertices of minimal cardinality such 

that any vertex of `G` is in `S` or has one of its neighbors 

in `S`. 

 

As an optimization problem, it can be expressed as: 

 

.. MATH:: 

 

\mbox{Minimize : }&\sum_{v\in G} b_v\\ 

\mbox{Such that : }&\forall v \in G, b_v+\sum_{(u,v)\in G.edges()} b_u\geq 1\\ 

&\forall x\in G, b_x\mbox{ is a binary variable} 

 

INPUT: 

 

- ``independent`` -- boolean (default: ``False``). If 

``independent=True``, computes a minimum independent dominating set. 

 

- ``total`` -- boolean (default: ``False``). If 

``total=True``, computes a total dominating set. 

 

- ``value_only`` -- boolean (default: ``False``) 

 

- If ``True``, only the cardinality of a minimum 

dominating set is returned. 

 

- If ``False`` (default), a minimum dominating set 

is returned as the list of its vertices. 

 

- ``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. 

 

EXAMPLES: 

 

A basic illustration on a ``PappusGraph``:: 

 

sage: g=graphs.PappusGraph() 

sage: g.dominating_set(value_only=True) 

5 

 

If we build a graph from two disjoint stars, then link their centers 

we will find a difference between the cardinality of an independent set 

and a stable independent set:: 

 

sage: g = 2 * graphs.StarGraph(5) 

sage: g.add_edge(0,6) 

sage: len(g.dominating_set()) 

2 

sage: len(g.dominating_set(independent=True)) 

6 

 

The total dominating set of the Petersen graph has cardinality 4:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.dominating_set(total=True,value_only=True) 

4 

 

The dominating set is calculated for both the directed and undirected 

graphs (modification introduced in :trac:`17905`):: 

 

sage: g=digraphs.Path(3) 

sage: g.dominating_set(value_only=True) 

2 

sage: g=graphs.PathGraph(3) 

sage: g.dominating_set(value_only=True) 

1 

 

""" 

self._scream_if_not_simple(allow_multiple_edges=True, allow_loops=not total) 

 

from sage.numerical.mip import MixedIntegerLinearProgram 

g=self 

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

b=p.new_variable(binary=True) 

 

# For any vertex v, one of its neighbors or v itself is in 

# the minimum dominating set. If g is directed, we use the 

# in neighbors of v instead. 

 

neighbors_iter=g.neighbor_in_iterator if g.is_directed() else g.neighbor_iterator 

 

for v in g.vertices(): 

p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in neighbors_iter(v)]),min=1) 

 

if independent: 

# no two adjacent vertices are in the set 

for (u,v) in g.edges(labels=None): 

p.add_constraint(b[u]+b[v],max=1) 

 

# Minimizes the number of vertices used 

p.set_objective(p.sum([b[v] for v in g.vertices()])) 

 

if value_only: 

return Integer(round(p.solve(objective_only=True, log=verbose))) 

else: 

p.solve(log=verbose) 

b=p.get_values(b) 

return [v for v in g.vertices() if b[v]==1] 

 

def edge_connectivity(self, 

value_only = True, 

implementation = None, 

use_edge_labels = False, 

vertices = False, 

solver = None, 

verbose = 0): 

r""" 

Returns the edge connectivity of the graph. 

 

For more information, see the 

`Wikipedia article on connectivity 

<http://en.wikipedia.org/wiki/Connectivity_(graph_theory)>`_. 

 

.. NOTE:: 

 

When the graph is a directed graph, this method actually computes 

the *strong* connectivity, (i.e. a directed graph is strongly 

`k`-connected if there are `k` disjoint paths between any two 

vertices `u, v`). If you do not want to consider strong 

connectivity, the best is probably to convert your ``DiGraph`` 

object to a ``Graph`` object, and compute the connectivity of this 

other graph. 

 

INPUT: 

 

- ``value_only`` -- boolean (default: ``True``) 

 

- When set to ``True`` (default), only the value is returned. 

 

- When set to ``False``, both the value and a minimum edge cut 

are returned. 

 

- ``implementation`` -- selects an implementation: 

 

- When set to ``None`` (default): selects the best implementation 

available. 

 

- When set to ``"boost"``, we use the Boost graph library (which is 

much more efficient). It is not available when ``edge_labels=True``, 

and it is unreliable for directed graphs (see :trac:`18753`). 

 

- When set to ``"Sage"``, we use Sage's implementation. 

 

- ``use_edge_labels`` -- boolean (default: ``False``) 

 

- When set to ``True``, computes a weighted minimum cut 

where each edge has a weight defined by its label. (If 

an edge has no label, `1` is assumed.). Implies 

``boost`` = ``False``. 

 

- When set to ``False``, each edge has weight `1`. 

 

- ``vertices`` -- boolean (default: ``False``) 

 

- When set to ``True``, also returns the two sets of 

vertices that are disconnected by the cut. Implies 

``value_only=False``. 

 

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

solver to be used (ignored if ``implementation='boost'``). 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. 

 

EXAMPLES: 

 

A basic application on the PappusGraph:: 

 

sage: g = graphs.PappusGraph() 

sage: g.edge_connectivity() 

3 

 

The edge connectivity of a complete graph ( and of a random graph ) 

is its minimum degree, and one of the two parts of the bipartition 

is reduced to only one vertex. The cutedges isomorphic to a 

Star graph:: 

 

sage: g = graphs.CompleteGraph(5) 

sage: [ value, edges, [ setA, setB ]] = g.edge_connectivity(vertices=True) 

sage: value 

4 

sage: len(setA) == 1 or len(setB) == 1 

True 

sage: cut = Graph() 

sage: cut.add_edges(edges) 

sage: cut.is_isomorphic(graphs.StarGraph(4)) 

True 

 

Even if obviously in any graph we know that the edge connectivity 

is less than the minimum degree of the graph:: 

 

sage: g = graphs.RandomGNP(10,.3) 

sage: min(g.degree()) >= g.edge_connectivity() 

True 

 

If we build a tree then assign to its edges a random value, the 

minimum cut will be the edge with minimum value:: 

 

sage: g = graphs.RandomGNP(15,.5) 

sage: tree = Graph() 

sage: tree.add_edges(g.min_spanning_tree()) 

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

....: tree.set_edge_label(u,v,random()) 

sage: minimum = min([l for u,v,l in tree.edge_iterator()]) 

sage: [value, [(u,v,l)]] = tree.edge_connectivity(value_only=False, use_edge_labels=True) 

sage: l == minimum 

True 

 

When ``value_only = True`` and ``implementation="sage"``, this function is 

optimized for small connectivity values and does not need to build a 

linear program. 

 

It is the case for graphs which are not connected :: 

 

sage: g = 2 * graphs.PetersenGraph() 

sage: g.edge_connectivity(implementation="sage") 

0.0 

 

For directed graphs, the strong connectivity is tested 

through the dedicated function :: 

 

sage: g = digraphs.ButterflyGraph(3) 

sage: g.edge_connectivity(implementation="sage") 

0.0 

 

We check that the result with Boost is the same as the result without 

Boost :: 

 

sage: g = graphs.RandomGNP(15,.3) 

sage: g.edge_connectivity() == g.edge_connectivity(implementation="sage") 

True 

 

Boost interface also works with directed graphs :: 

 

sage: digraphs.Circuit(10).edge_connectivity(implementation = "boost", vertices = True) 

[1, [(0, 1)], [{0}, {1, 2, 3, 4, 5, 6, 7, 8, 9}]] 

 

However, the Boost algorithm is not reliable if the input is directed 

(see :trac:`18753`):: 

 

sage: g = digraphs.Path(3) 

sage: g.edge_connectivity() 

0.0 

sage: g.edge_connectivity(implementation="boost") 

1 

sage: g.add_edge(1,0) 

sage: g.edge_connectivity() 

0.0 

sage: g.edge_connectivity(implementation="boost") 

0 

 

TESTS: 

 

Checking that the two implementations agree:: 

 

sage: for i in range(10): 

....: g = graphs.RandomGNP(30,0.3) 

....: e1 = g.edge_connectivity(implementation="boost") 

....: e2 = g.edge_connectivity(implementation="sage") 

....: assert (e1 == e2) 

 

Disconnected graphs and ``vertices=True``:: 

 

sage: g = graphs.PetersenGraph() 

sage: (2*g).edge_connectivity(vertices=True) 

[0, [], [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]]] 

""" 

self._scream_if_not_simple(allow_loops=True) 

g=self 

 

if vertices: 

value_only=False 

 

if implementation is None: 

if use_edge_labels or g.is_directed(): 

implementation = "sage" 

else: 

implementation = "boost" 

 

implementation = implementation.lower() 

if implementation not in ["boost", "sage"]: 

raise ValueError("'implementation' must be set to 'boost', 'sage' or None.") 

elif implementation=="boost" and use_edge_labels: 

raise ValueError("The Boost implementation is currently not able to handle edge labels") 

 

# Otherwise, an error is created 

if g.num_edges() == 0 or g.num_verts() == 0: 

if value_only: 

return 0 

elif vertices: 

return [0,[],[[],[]]] 

else: 

return [0,[]] 

 

if implementation == "boost": 

from sage.graphs.base.boost_graph import edge_connectivity 

 

[obj, edges] = edge_connectivity(g) 

 

if value_only: 

return obj 

 

val = [obj, edges] 

 

if vertices and not obj == 0: 

H = copy(self) 

H.delete_edges(edges) 

 

if H.is_directed(): 

a = set(H.breadth_first_search([x for x,y in edges])) 

b = set(H).difference(a) 

val.append([a,b]) 

else: 

val.append(H.connected_components()) 

elif vertices: 

val.append(self.connected_components()) 

 

return val 

 

if use_edge_labels: 

from sage.rings.real_mpfr import RR 

weight=lambda x: x if x in RR else 1 

else: 

weight=lambda x: 1 

 

 

# Better methods for small connectivity tests, 

# when one is not interested in cuts... 

if value_only and not use_edge_labels: 

 

if self.is_directed(): 

if not self.is_strongly_connected(): 

return 0.0 

 

else: 

if not self.is_connected(): 

return 0.0 

 

h = self.strong_orientation() 

if not h.is_strongly_connected(): 

return 1.0 

 

 

if g.is_directed(): 

reorder_edge = lambda x,y : (x,y) 

else: 

reorder_edge = lambda x,y : (x,y) if x<= y else (y,x) 

 

from sage.numerical.mip import MixedIntegerLinearProgram 

 

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

 

in_set = p.new_variable(binary = True) 

in_cut = p.new_variable(binary = True) 

 

# A vertex has to be in some set 

for v in g: 

p.add_constraint(in_set[0,v]+in_set[1,v],max=1,min=1) 

 

# There is no empty set 

p.add_constraint(p.sum([in_set[1,v] for v in g]),min=1) 

p.add_constraint(p.sum([in_set[0,v] for v in g]),min=1) 

 

if g.is_directed(): 

# There is no edge from set 0 to set 1 which 

# is not in the cut 

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

p.add_constraint(in_set[0,u] + in_set[1,v] - in_cut[(u,v)], max = 1) 

else: 

 

# Two adjacent vertices are in different sets if and only if 

# the edge between them is in the cut 

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

p.add_constraint(in_set[0,u]+in_set[1,v]-in_cut[reorder_edge(u,v)],max=1) 

p.add_constraint(in_set[1,u]+in_set[0,v]-in_cut[reorder_edge(u,v)],max=1) 

 

p.set_objective(p.sum([weight(l ) * in_cut[reorder_edge(u,v)] for (u,v,l) in g.edge_iterator()])) 

 

obj = p.solve(objective_only=value_only, log=verbose) 

 

if use_edge_labels is False: 

obj = Integer(round(obj)) 

 

if value_only: 

return obj 

 

else: 

val = [obj] 

 

in_cut = p.get_values(in_cut) 

in_set = p.get_values(in_set) 

 

edges = [] 

for (u,v,l) in g.edge_iterator(): 

if in_cut[reorder_edge(u,v)] == 1: 

edges.append((u,v,l)) 

 

val.append(edges) 

 

if vertices: 

a = [] 

b = [] 

for v in g: 

if in_set[0,v] == 1: 

a.append(v) 

else: 

b.append(v) 

val.append([a,b]) 

 

return val 

 

def vertex_connectivity(self, value_only=True, sets=False, k=None, solver=None, verbose=0): 

r""" 

Return the vertex connectivity of the graph. 

 

For more information, see the :wikipedia:`Connectivity_(graph_theory)`. 

 

.. NOTE:: 

 

* When the graph is directed, this method actually computes the 

*strong* connectivity, (i.e. a directed graph is strongly 

`k`-connected if there are `k` vertex disjoint paths between any 

two vertices `u, v`). If you do not want to consider strong 

connectivity, the best is probably to convert your ``DiGraph`` 

object to a ``Graph`` object, and compute the connectivity of this 

other graph. 

 

* By convention, a complete graph on `n` vertices is `n-1` 

connected. In this case, no certificate can be given as there is 

no pair of vertices split by a cut of order `k-1`. For this 

reason, the certificates returned in this situation are empty. 

 

INPUT: 

 

- ``value_only`` -- boolean (default: ``True``) 

 

- When set to ``True`` (default), only the value is returned. 

 

- When set to ``False`` , both the value and a minimum vertex cut are 

returned. 

 

- ``sets`` -- boolean (default: ``False``) 

 

- When set to ``True``, also returns the two sets of vertices that 

are disconnected by the cut. Implies ``value_only=False`` 

 

- ``k`` -- integer (default: ``None``) When specified, check if the 

vertex connectivity of the (di)graph is larger or equal to `k`. The 

method thus outputs a boolean only. 

 

- ``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, see the method :meth:`solve 

<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class 

:class:`MixedIntegerLinearProgram 

<sage.numerical.mip.MixedIntegerLinearProgram>`. Use method 

:meth:`sage.numerical.backends.generic_backend.default_mip_solver` to 

know which default solver is used or to set the default solver. 

 

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

verbosity. Set to 0 by default, which means quiet. 

 

EXAMPLES: 

 

A basic application on a ``PappusGraph``:: 

 

sage: g=graphs.PappusGraph() 

sage: g.vertex_connectivity() 

3 

 

In a grid, the vertex connectivity is equal to the minimum degree, in 

which case one of the two sets is of cardinality `1`:: 

 

sage: g = graphs.GridGraph([ 3,3 ]) 

sage: [value, cut, [ setA, setB ]] = g.vertex_connectivity(sets=True) 

sage: len(setA) == 1 or len(setB) == 1 

True 

 

A vertex cut in a tree is any internal vertex:: 

 

sage: tree = graphs.RandomTree(15) 

sage: val, [cut_vertex] = tree.vertex_connectivity(value_only=False) 

sage: tree.degree(cut_vertex) > 1 

True 

 

When ``value_only = True``, this function is optimized for small 

connectivity values and does not need to build a linear program. 

 

It is the case for connected graphs which are not connected:: 

 

sage: g = 2 * graphs.PetersenGraph() 

sage: g.vertex_connectivity() 

0 

 

Or if they are just 1-connected:: 

 

sage: g = graphs.PathGraph(10) 

sage: g.vertex_connectivity() 

1 

 

For directed graphs, the strong connectivity is tested 

through the dedicated function:: 

 

sage: g = digraphs.ButterflyGraph(3) 

sage: g.vertex_connectivity() 

0 

 

A complete graph on `10` vertices is `9`-connected:: 

 

sage: g = graphs.CompleteGraph(10) 

sage: g.vertex_connectivity() 

9 

 

A complete digraph on `10` vertices is `9`-connected:: 

 

sage: g = DiGraph(graphs.CompleteGraph(10)) 

sage: g.vertex_connectivity() 

9 

 

When parameter ``k`` is set, we only check for the existence of a 

vertex cut of order at least ``k``:: 

 

sage: g = graphs.PappusGraph() 

sage: g.vertex_connectivity(k=3) 

True 

sage: g.vertex_connectivity(k=4) 

False 

 

TESTS: 

 

Giving negative value to parameter ``k``:: 

 

sage: g = graphs.PappusGraph() 

sage: g.vertex_connectivity(k=-1) 

Traceback (most recent call last): 

... 

ValueError: parameter k must be strictly positive 

 

The empty graph has vertex connectivity 0, is considered connected but 

not biconnected. The empty digraph is considered strongly connected:: 

 

sage: empty = Graph() 

sage: empty.vertex_connectivity() 

0 

sage: empty.vertex_connectivity(k=1) == empty.is_connected() 

True 

sage: Graph().vertex_connectivity(k=2) == empty.is_biconnected() 

True 

sage: DiGraph().vertex_connectivity(k=1) == DiGraph().is_strongly_connected() 

True 

""" 

g = self 

 

if k is not None: 

if k < 1: 

raise ValueError("parameter k must be strictly positive") 

if g.order() == 0: 

# We follow the convention of is_connected, is_biconnected and 

# is_strongly_connected 

return k == 1 

if (g.is_directed() and k > min(min(g.in_degree()), min(g.out_degree()))) \ 

or (not g.is_directed() and (k > min(g.degree()))): 

return False 

value_only = True 

sets = False 

 

elif sets: 

value_only = False 

 

# When the graph is complete, the MILP below is infeasible. 

if g.is_clique(directed_clique=g.is_directed()): 

if k is not None: 

return g.order() > k 

if value_only: 

return max(g.order()-1, 0) 

elif not sets: 

return max(g.order()-1, 0), [] 

else: 

return max(g.order()-1, 0), [], [[], []] 

 

if value_only: 

if self.is_directed(): 

if not self.is_strongly_connected(): 

return 0 if k is None else False 

 

else: 

if not self.is_connected(): 

return 0 if k is None else False 

 

if len(self.blocks_and_cut_vertices()[0]) > 1: 

return 1 if k is None else (k == 1) 

 

if k == 1: 

# We know that the (di)graph is (strongly) connected 

return True 

 

from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException 

 

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

 

# Sets 0 and 2 are "real" sets while set 1 represents the cut 

in_set = p.new_variable(binary=True) 

 

# A vertex has to be in some set 

for v in g: 

p.add_constraint(in_set[0, v] + in_set[1, v] + in_set[2, v], max=1, min=1) 

 

# There is no empty set 

p.add_constraint(p.sum(in_set[0, v] for v in g), min=1) 

p.add_constraint(p.sum(in_set[2, v] for v in g), min=1) 

 

if g.is_directed(): 

# There is no edge from set 0 to set 1 which is not in the cut 

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

p.add_constraint(in_set[0, u] + in_set[2, v], max=1) 

else: 

# Two adjacent vertices are in different sets if and only if 

# the edge between them is in the cut 

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

p.add_constraint(in_set[0, u] + in_set[2, v], max=1) 

p.add_constraint(in_set[2, u] + in_set[0, v], max=1) 

 

if k is not None: 

# To check if the vertex connectivity is at least k, we check if 

# there exists a cut of order at most k-1. If the ILP is infeasible, 

# the vertex connectivity is >= k. 

p.add_constraint(p.sum(in_set[1, v] for v in g) <= k-1) 

try: 

p.solve(objective_only=True, log=verbose) 

return False 

except MIPSolverException: 

return True 

 

else: 

p.set_objective(p.sum(in_set[1, v] for v in g)) 

 

if value_only: 

return Integer(round(p.solve(objective_only=True, log=verbose))) 

 

val = Integer(round(p.solve(log=verbose))) 

 

in_set = p.get_values(in_set) 

 

cut = [] 

a = [] 

b = [] 

 

for v in g: 

if in_set[0, v]: 

a.append(v) 

elif in_set[1, v]: 

cut.append(v) 

else: 

b.append(v) 

 

if sets: 

return val, cut, [a, b] 

 

return val, cut 

 

### Vertex handlers 

 

def add_vertex(self, name=None): 

""" 

Creates an isolated vertex. If the vertex already exists, then 

nothing is done. 

 

INPUT: 

 

- ``name`` - Name of the new vertex. If no name is 

specified, then the vertex will be represented by the least integer 

not already representing a vertex. Name must be an immutable 

object, and cannot be None. 

 

As it is implemented now, if a graph `G` has a large number 

of vertices with numeric labels, then G.add_vertex() could 

potentially be slow, if name is None. 

 

OUTPUT: 

 

If ``name``=``None``, the new vertex name is returned. ``None`` otherwise. 

 

EXAMPLES:: 

 

sage: G = Graph(); G.add_vertex(); G 

0 

Graph on 1 vertex 

 

:: 

 

sage: D = DiGraph(); D.add_vertex(); D 

0 

Digraph on 1 vertex 

 

""" 

return self._backend.add_vertex(name) 

 

def add_vertices(self, vertices): 

""" 

Add vertices to the (di)graph from an iterable container of 

vertices. Vertices that already exist in the graph will not be 

added again. 

 

INPUT: 

 

- ``vertices``: iterator of vertex labels. A new label is created, used and returned in 

the output list for all ``None`` values in ``vertices``. 

 

OUTPUT: 

 

Generated names of new vertices if there is at least one ``None`` value 

present in ``vertices``. ``None`` otherwise. 

 

EXAMPLES:: 

 

sage: d = {0: [1,4,5], 1: [2,6], 2: [3,7], 3: [4,8], 4: [9], 5: [7,8], 6: [8,9], 7: [9]} 

sage: G = Graph(d) 

sage: G.add_vertices([10,11,12]) 

sage: G.vertices() 

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] 

sage: G.add_vertices(graphs.CycleGraph(25).vertices()) 

sage: G.vertices() 

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24] 

 

:: 

 

sage: G = Graph() 

sage: G.add_vertices([1,2,3]) 

sage: G.add_vertices([4,None,None,5]) 

[0, 6] 

 

""" 

return self._backend.add_vertices(vertices) 

 

def delete_vertex(self, vertex, in_order=False): 

""" 

Deletes vertex, removing all incident edges. Deleting a 

non-existent vertex will raise an exception. 

 

INPUT: 

 

 

- ``in_order`` - (default False) If True, this 

deletes the ith vertex in the sorted list of vertices, i.e. 

G.vertices()[i] 

 

 

EXAMPLES:: 

 

sage: G = Graph(graphs.WheelGraph(9)) 

sage: G.delete_vertex(0); G.show() 

 

:: 

 

sage: D = DiGraph({0:[1,2,3,4,5],1:[2],2:[3],3:[4],4:[5],5:[1]}) 

sage: D.delete_vertex(0); D 

Digraph on 5 vertices 

sage: D.vertices() 

[1, 2, 3, 4, 5] 

sage: D.delete_vertex(0) 

Traceback (most recent call last): 

... 

RuntimeError: Vertex (0) not in the graph. 

 

:: 

 

sage: G = graphs.CompleteGraph(4).line_graph(labels=False) 

sage: G.vertices() 

[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] 

sage: G.delete_vertex(0, in_order=True) 

sage: G.vertices() 

[(0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] 

sage: G = graphs.PathGraph(5) 

sage: G.set_vertices({0: 'no delete', 1: 'delete'}) 

sage: G.delete_vertex(1) 

sage: G.get_vertices() 

{0: 'no delete', 2: None, 3: None, 4: None} 

sage: G.get_pos() 

{0: (0, 0), 2: (2, 0), 3: (3, 0), 4: (4, 0)} 

""" 

if in_order: 

vertex = self.vertices()[vertex] 

if vertex not in self: 

raise RuntimeError("Vertex (%s) not in the graph."%str(vertex)) 

 

self._backend.del_vertex(vertex) 

attributes_to_update = ('_pos', '_assoc', '_embedding') 

for attr in attributes_to_update: 

if hasattr(self, attr) and getattr(self, attr) is not None: 

getattr(self, attr).pop(vertex, None) 

 

def delete_vertices(self, vertices): 

""" 

Remove vertices from the (di)graph taken from an iterable container 

of vertices. Deleting a non-existent vertex will raise an 

exception. 

 

EXAMPLES:: 

 

sage: D = DiGraph({0:[1,2,3,4,5],1:[2],2:[3],3:[4],4:[5],5:[1]}) 

sage: D.delete_vertices([1,2,3,4,5]); D 

Digraph on 1 vertex 

sage: D.vertices() 

[0] 

sage: D.delete_vertices([1]) 

Traceback (most recent call last): 

... 

RuntimeError: Vertex (1) not in the graph. 

 

""" 

for vertex in vertices: 

if vertex not in self: 

raise RuntimeError("Vertex (%s) not in the graph."%str(vertex)) 

 

self._backend.del_vertices(vertices) 

attributes_to_update = ('_pos', '_assoc', '_embedding') 

for attr in attributes_to_update: 

if hasattr(self, attr) and getattr(self, attr) is not None: 

attr_dict = getattr(self, attr) 

for vertex in vertices: 

attr_dict.pop(vertex, None) 

 

def has_vertex(self, vertex): 

""" 

Return True if vertex is one of the vertices of this graph. 

 

INPUT: 

 

 

- ``vertex`` - an integer 

 

 

OUTPUT: 

 

 

- ``bool`` - True or False 

 

 

EXAMPLES:: 

 

sage: g = Graph({0:[1,2,3], 2:[4]}); g 

Graph on 5 vertices 

sage: 2 in g 

True 

sage: 10 in g 

False 

sage: graphs.PetersenGraph().has_vertex(99) 

False 

""" 

try: 

hash(vertex) 

except Exception: 

return False 

return self._backend.has_vertex(vertex) 

 

__contains__ = has_vertex 

 

def random_vertex(self, **kwds): 

r""" 

Returns a random vertex of self. 

 

INPUT: 

 

- ``**kwds`` - arguments to be passed down to the 

``vertex_iterator`` method. 

 

EXAMPLES: 

 

The returned value is a vertex of self:: 

 

sage: g = graphs.PetersenGraph() 

sage: v = g.random_vertex() 

sage: v in g 

True 

 

TESTS:: 

 

sage: graphs.EmptyGraph().random_vertex() 

Traceback (most recent call last): 

... 

ValueError: can't get a random vertex from the empty graph 

""" 

if self.order() == 0: 

raise ValueError("can't get a random vertex from the empty graph") 

from sage.misc.prandom import randint 

it = self.vertex_iterator(**kwds) 

for i in range(0, randint(0, self.order() - 1)): 

next(it) 

return next(it) 

 

def random_vertex_iterator(self, *args, **kwds): 

r""" 

Return an iterator over random vertices of self. 

 

The returned iterator enables to amortize the cost of accessing random 

vertices, as can be done with multiple calls to method 

:meth:`random_vertex`. 

 

INPUT: 

 

- ``*args`` and ``**kwds`` - arguments to be passed down to the 

:meth:`vertex_iterator` method. 

 

EXAMPLES: 

 

The returned value is an iterator over the vertices of self:: 

 

sage: g = graphs.PetersenGraph() 

sage: it = g.random_vertex_iterator() 

sage: [next(it) in g for _ in range(5)] 

[True, True, True, True, True] 

 

TESTS: 

 

Empty Graph:: 

 

sage: empty_graph = Graph() 

sage: list(empty_graph.random_vertex_iterator()) 

[] 

sage: it = empty_graph.random_vertex_iterator() 

sage: next(it) 

Traceback (most recent call last): 

... 

StopIteration 

""" 

from sage.misc.prandom import choice 

if self.order(): 

V = list(self.vertex_iterator(*args, **kwds)) 

while True: 

yield choice(V) 

 

def random_edge(self,**kwds): 

r""" 

Returns a random edge of self. 

 

INPUT: 

 

- ``**kwds`` - arguments to be passed down to the 

``edge_iterator`` method. 

 

EXAMPLES: 

 

The returned value is an edge of self:: 

 

sage: g = graphs.PetersenGraph() 

sage: u,v = g.random_edge(labels=False) 

sage: g.has_edge(u,v) 

True 

 

As the ``edges()`` method would, this function returns 

by default a triple ``(u,v,l)`` of values, in which 

``l`` is the label of edge `(u,v)`:: 

 

sage: g.random_edge() 

(3, 4, None) 

 

TESTS:: 

 

sage: graphs.EmptyGraph().random_edge() 

Traceback (most recent call last): 

... 

ValueError: can't get a random edge from a graph without edges 

""" 

if self.size() == 0: 

raise ValueError("can't get a random edge from a graph without edges") 

 

from sage.misc.prandom import randint 

it = self.edge_iterator(**kwds) 

for i in range(0, randint(0, self.size() - 1)): 

next(it) 

return next(it) 

 

def random_edge_iterator(self, *args, **kwds): 

r""" 

Return an iterator over random edges of self. 

 

The returned iterator enables to amortize the cost of accessing random 

edges, as can be done with multiple calls to method :meth:`random_edge`. 

 

INPUT: 

 

- ``*args`` and ``**kwds`` - arguments to be passed down to the 

:meth:`edge_iterator` method. 

 

EXAMPLES: 

 

The returned value is an iterator over the edges of self:: 

 

sage: g = graphs.PetersenGraph() 

sage: it = g.random_edge_iterator() 

sage: [g.has_edge(next(it)) for _ in range(5)] 

[True, True, True, True, True] 

 

As the :meth:`edges` method would, this function returns by default a 

triple ``(u,v,l)`` of values, in which ``l`` is the label of edge 

``(u,v)``:: 

 

sage: print(next(g.random_edge_iterator())) # random 

(0, 5, None) 

sage: print(next(g.random_edge_iterator(labels=False))) # random 

(5, 7) 

 

TESTS: 

 

Graph without edge:: 

 

sage: empty_graph = Graph() 

sage: list(empty_graph.random_edge_iterator()) 

[] 

sage: it = empty_graph.random_edge_iterator() 

sage: next(it) 

Traceback (most recent call last): 

... 

StopIteration 

""" 

from sage.misc.prandom import choice 

if self.size(): 

E = list(self.edge_iterator(*args, **kwds)) 

while True: 

yield choice(E) 

 

def vertex_boundary(self, vertices1, vertices2=None): 

""" 

Returns a list of all vertices in the external boundary of 

vertices1, intersected with vertices2. If vertices2 is None, then 

vertices2 is the complement of vertices1. This is much faster if 

vertices1 is smaller than vertices2. 

 

The external boundary of a set of vertices is the union of the 

neighborhoods of each vertex in the set. Note that in this 

implementation, since vertices2 defaults to the complement of 

vertices1, if a vertex `v` has a loop, then 

vertex_boundary(v) will not contain `v`. 

 

In a digraph, the external boundary of a vertex v are those 

vertices u with an arc (v, u). 

 

EXAMPLES:: 

 

sage: G = graphs.CubeGraph(4) 

sage: l = ['0111', '0000', '0001', '0011', '0010', '0101', '0100', '1111', '1101', '1011', '1001'] 

sage: G.vertex_boundary(['0000', '1111'], l) 

['0111', '0001', '0010', '0100', '1101', '1011'] 

 

:: 

 

sage: D = DiGraph({0:[1,2], 3:[0]}) 

sage: D.vertex_boundary([0]) 

[1, 2] 

 

 

TESTS: 

 

When vertices2 is None, then vertices2 is the complement of vertices 1. 

Corrected in ticket :trac:`20479`:: 

 

sage: P = graphs.PathGraph(3) 

sage: P.vertex_boundary([0,1]) 

[2] 

sage: P.vertex_boundary([0,1], set(P.vertices()).difference([0,1])) 

[2] 

sage: Q = DiGraph(P) 

sage: Q.vertex_boundary([0,1]) 

[2] 

sage: Q.vertex_boundary([0,1], set(Q.vertices()).difference([0,1])) 

[2] 

""" 

vertices1 = [v for v in vertices1 if v in self] 

output = set() 

if self._directed: 

for v in vertices1: 

output.update(self.neighbor_out_iterator(v)) 

else: 

for v in vertices1: 

output.update(self.neighbor_iterator(v)) 

if vertices2 is None: 

output.difference_update(vertices1) 

else: 

output.intersection_update(vertices2) 

return list(output) 

 

def set_vertices(self, vertex_dict): 

""" 

Associate arbitrary objects with each vertex, via an association 

dictionary. 

 

INPUT: 

 

 

- ``vertex_dict`` - the association dictionary 

 

 

EXAMPLES:: 

 

sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), 2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() } 

sage: d[2] 

Moebius-Kantor Graph: Graph on 16 vertices 

sage: T = graphs.TetrahedralGraph() 

sage: T.vertices() 

[0, 1, 2, 3] 

sage: T.set_vertices(d) 

sage: T.get_vertex(1) 

Flower Snark: Graph on 20 vertices 

""" 

if hasattr(self, '_assoc') is False: 

self._assoc = {} 

 

self._assoc.update(vertex_dict) 

 

def set_vertex(self, vertex, object): 

""" 

Associate an arbitrary object with a vertex. 

 

INPUT: 

 

 

- ``vertex`` - which vertex 

 

- ``object`` - object to associate to vertex 

 

 

EXAMPLES:: 

 

sage: T = graphs.TetrahedralGraph() 

sage: T.vertices() 

[0, 1, 2, 3] 

sage: T.set_vertex(1, graphs.FlowerSnark()) 

sage: T.get_vertex(1) 

Flower Snark: Graph on 20 vertices 

""" 

if hasattr(self, '_assoc') is False: 

self._assoc = {} 

 

self._assoc[vertex] = object 

 

def get_vertex(self, vertex): 

""" 

Retrieve the object associated with a given vertex. 

 

INPUT: 

 

 

- ``vertex`` - the given vertex 

 

 

EXAMPLES:: 

 

sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), 2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() } 

sage: d[2] 

Moebius-Kantor Graph: Graph on 16 vertices 

sage: T = graphs.TetrahedralGraph() 

sage: T.vertices() 

[0, 1, 2, 3] 

sage: T.set_vertices(d) 

sage: T.get_vertex(1) 

Flower Snark: Graph on 20 vertices 

""" 

if hasattr(self, '_assoc') is False: 

return None 

 

return self._assoc.get(vertex, None) 

 

def get_vertices(self, verts=None): 

""" 

Return a dictionary of the objects associated to each vertex. 

 

INPUT: 

 

 

- ``verts`` - iterable container of vertices 

 

 

EXAMPLES:: 

 

sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), 2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() } 

sage: T = graphs.TetrahedralGraph() 

sage: T.set_vertices(d) 

sage: T.get_vertices([1,2]) 

{1: Flower Snark: Graph on 20 vertices, 

2: Moebius-Kantor Graph: Graph on 16 vertices} 

""" 

if verts is None: 

verts = self.vertices() 

 

if hasattr(self, '_assoc') is False: 

return dict.fromkeys(verts, None) 

 

output = {} 

 

for v in verts: 

output[v] = self._assoc.get(v, None) 

 

return output 

 

def vertex_iterator(self, vertices=None): 

""" 

Returns an iterator over the given vertices. 

 

Returns False if not given a vertex, sequence, iterator or None. None is 

equivalent to a list of every vertex. Note that ``for v in G`` syntax is 

allowed. 

 

INPUT: 

 

- ``vertices`` - iterated vertices are these 

intersected with the vertices of the (di)graph 

 

EXAMPLES:: 

 

sage: P = graphs.PetersenGraph() 

sage: for v in P.vertex_iterator(): 

....: print(v) 

0 

1 

2 

... 

8 

9 

 

:: 

 

sage: G = graphs.TetrahedralGraph() 

sage: for i in G: 

....: print(i) 

0 

1 

2 

3 

 

Note that since the intersection option is available, the 

vertex_iterator() function is sub-optimal, speed-wise, but note the 

following optimization:: 

 

sage: timeit V = P.vertices() # not tested 

100000 loops, best of 3: 8.85 [micro]s per loop 

sage: timeit V = list(P.vertex_iterator()) # not tested 

100000 loops, best of 3: 5.74 [micro]s per loop 

sage: timeit V = list(P._nxg.adj) # not tested 

100000 loops, best of 3: 3.45 [micro]s per loop 

 

In other words, if you want a fast vertex iterator, call the 

dictionary directly. 

""" 

return self._backend.iterator_verts(vertices) 

 

__iter__ = vertex_iterator 

 

def neighbor_iterator(self, vertex): 

""" 

Return an iterator over neighbors of vertex. 

 

EXAMPLES:: 

 

sage: G = graphs.CubeGraph(3) 

sage: for i in G.neighbor_iterator('010'): 

....: print(i) 

011 

000 

110 

sage: D = G.to_directed() 

sage: for i in D.neighbor_iterator('010'): 

....: print(i) 

011 

000 

110 

 

:: 

 

sage: D = DiGraph({0:[1,2], 3:[0]}) 

sage: list(D.neighbor_iterator(0)) 

[1, 2, 3] 

""" 

return self._backend.iterator_nbrs(vertex) 

 

def vertices(self, key=None): 

r""" 

Return a list of the vertices. 

 

INPUT: 

 

- ``key`` - default: ``None`` - a function that takes 

a vertex as its one argument and returns a value that 

can be used for comparisons in the sorting algorithm. 

 

OUTPUT: 

 

The vertices of the list. 

 

.. warning:: 

 

There is always an attempt to sort the list before 

returning the result. However, since any object may 

be a vertex, there is no guarantee that any two 

vertices will be comparable. With default objects 

for vertices (all integers), or when all the vertices 

are of the same simple type, then there should not be 

a problem with how the vertices will be sorted. However, 

if you need to guarantee a total order for the sort, 

use the ``key`` argument, as illustrated in the 

examples below. 

 

EXAMPLES:: 

 

sage: P = graphs.PetersenGraph() 

sage: P.vertices() 

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

 

If you do not care about sorted output and you are 

concerned about the time taken to sort, consider the 

following alternatives. The moral is: if you want a 

fast vertex iterator, call the dictionary directly. :: 

 

sage: timeit V = P.vertices() # not tested 

100000 loops, best of 3: 8.85 [micro]s per loop 

sage: timeit V = list(P.vertex_iterator()) # not tested 

100000 loops, best of 3: 5.74 [micro]s per loop 

sage: timeit V = list(P._nxg.adj) # not tested 

100000 loops, best of 3: 3.45 [micro]s per loop 

 

We illustrate various ways to use a ``key`` to sort the list:: 

 

sage: H=graphs.HanoiTowerGraph(3,3,labels=False) 

sage: H.vertices() 

[0, 1, 2, 3, 4, ... 22, 23, 24, 25, 26] 

sage: H.vertices(key=lambda x: -x) 

[26, 25, 24, 23, 22, ... 4, 3, 2, 1, 0] 

 

:: 

 

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

sage: G.vertices() 

[(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 1, 0), ... (2, 2, 1), (2, 2, 2)] 

sage: G.vertices(key = lambda x: (x[1], x[2], x[0])) 

[(0, 0, 0), (1, 0, 0), (2, 0, 0), (0, 0, 1), ... (1, 2, 2), (2, 2, 2)] 

 

The discriminant of a polynomial is a function that returns an integer. 

We build a graph whose vertices are polynomials, and use the discriminant 

function to provide an ordering. Note that since functions are first-class 

objects in Python, we can specify precisely the function from the Sage library 

that we wish to use as the key. :: 

 

sage: t = polygen(QQ, 't') 

sage: K = Graph({5*t:[t^2], t^2:[t^2+2], t^2+2:[4*t^2-6], 4*t^2-6:[5*t]}) 

sage: dsc = sage.rings.polynomial.polynomial_rational_flint.Polynomial_rational_flint.discriminant 

sage: verts = K.vertices(key=dsc) 

sage: verts 

[t^2 + 2, t^2, 5*t, 4*t^2 - 6] 

sage: [x.discriminant() for x in verts] 

[-8, 0, 1, 96] 

""" 

return sorted(list(self.vertex_iterator()), key=key) 

 

def neighbors(self, vertex): 

""" 

Return a list of neighbors (in and out if directed) of vertex. 

 

G[vertex] also works. 

 

EXAMPLES:: 

 

sage: P = graphs.PetersenGraph() 

sage: sorted(P.neighbors(3)) 

[2, 4, 8] 

sage: sorted(P[4]) 

[0, 3, 9] 

""" 

return list(self.neighbor_iterator(vertex)) 

 

__getitem__ = neighbors 

 

 

def merge_vertices(self,vertices): 

r""" 

Merge vertices. 

 

This function replaces a set `S` of vertices by a single vertex 

`v_{new}`, such that the edge `uv_{new}` exists if and only if 

`\exists v'\in S: (u,v')\in G`. 

 

The new vertex is named after the first vertex in the list 

given in argument. If this first name is `None`, a new vertex 

is created. 

 

In the case of multigraphs, the multiplicity is preserved. 

 

INPUT: 

 

- ``vertices`` -- the list of vertices to be merged. 

 

.. NOTE:: 

 

If ``u`` and ``v`` are distinct vertices in ``vertices``, 

any edges between ``u`` and ``v`` will be lost. 

 

EXAMPLES:: 

 

sage: g=graphs.CycleGraph(3) 

sage: g.merge_vertices([0,1]) 

sage: g.edges() 

[(0, 2, None)] 

 

sage: # With a Multigraph : 

sage: g=graphs.CycleGraph(3) 

sage: g.allow_multiple_edges(True) 

sage: g.merge_vertices([0,1]) 

sage: g.edges(labels=False) 

[(0, 2), (0, 2)] 

 

sage: P=graphs.PetersenGraph() 

sage: P.merge_vertices([5,7]) 

sage: P.vertices() 

[0, 1, 2, 3, 4, 5, 6, 8, 9] 

 

sage: g=graphs.CycleGraph(5) 

sage: g.vertices() 

[0, 1, 2, 3, 4] 

sage: g.merge_vertices([None, 1, 3]) 

sage: g.edges(labels=False) 

[(0, 4), (0, 5), (2, 5), (4, 5)] 

 

TESTS: 

 

Check that :trac:`23290` was fixed:: 

 

sage: edgelist = [(0,0,'a'),(0,1,'b'),(1,1,'c')] 

sage: G = Graph(edgelist, loops=True, multiedges=True) 

sage: G.merge_vertices([0,1]); G.edges() 

[(0, 0, 'a'), (0, 0, 'c')] 

sage: D = DiGraph(edgelist, loops=True, multiedges=True) 

sage: D.merge_vertices([0,1]); D.edges() 

[(0, 0, 'a'), (0, 0, 'c')] 

 

Without multiedges:: 

 

sage: edgelist = [(0,0,'a'),(0,1,'b'),(1,1,'c')] 

sage: G = Graph(edgelist, loops=True, multiedges=False) 

sage: G.merge_vertices([0,1]); G.edges() 

[(0, 0, 'c')] 

sage: edgelist = [(0,0,'a'),(0,1,'b'),(1,1,'c'), (1, 2, 'd'), (2, 2, 'e')] 

sage: G = Graph(edgelist, loops=True, multiedges=False) 

sage: G.merge_vertices([0,1,2]); G.edges() 

[(0, 0, 'e')] 

sage: G = Graph(edgelist, loops=True, multiedges=False) 

sage: G.merge_vertices([0,2,1]); G.edges() 

[(0, 0, 'c')] 

 

 

""" 

if len(vertices) <= 1: 

return None 

if vertices[0] is None: 

vertices[0] = self.add_vertex() 

 

u = vertices[0] 

if self.allows_loops(): 

for v in vertices[1:]: 

if self.has_edge(v, v): 

if self.allows_multiple_edges(): 

for l in self.edge_label(v, v): 

self.add_edge(u, u, l) 

else: 

self.add_edge(u, u, self.edge_label(v, v)) 

 

if self.is_directed(): 

out_edges=self.edge_boundary(vertices) 

in_edges=self.edge_boundary([v for v in self if not v in vertices]) 

self.delete_vertices(vertices[1:]) 

self.add_edges([(u, v0, l) for (u0, v0, l) in out_edges if u0 != u]) 

self.add_edges([(v0, u, l) for (v0, u0, l) in in_edges if u0 != u]) 

else: 

edges=self.edge_boundary(vertices) 

self.delete_vertices(vertices[1:]) 

add_edges=[] 

for (u0, v0, l) in edges: 

if v0 in vertices and v0 != u: 

add_edges.append((u, u0, l)) 

if u0 in vertices and u0 != u: 

add_edges.append((u, v0, l)) 

self.add_edges(add_edges) 

 

### Edge handlers 

 

def add_edge(self, u, v=None, label=None): 

""" 

Adds an edge from u and v. 

 

INPUT: The following forms are all accepted: 

 

- G.add_edge( 1, 2 ) 

- G.add_edge( (1, 2) ) 

- G.add_edges( [ (1, 2) ]) 

- G.add_edge( 1, 2, 'label' ) 

- G.add_edge( (1, 2, 'label') ) 

- G.add_edges( [ (1, 2, 'label') ] ) 

 

WARNING: The following intuitive input results in nonintuitive 

output:: 

 

sage: G = Graph() 

sage: G.add_edge((1,2), 'label') 

sage: G.networkx_graph().adj # random output order 

{'label': {(1, 2): None}, (1, 2): {'label': None}} 

 

Use one of these instead:: 

 

sage: G = Graph() 

sage: G.add_edge((1,2), label="label") 

sage: G.networkx_graph().adj # random output order 

{1: {2: 'label'}, 2: {1: 'label'}} 

 

:: 

 

sage: G = Graph() 

sage: G.add_edge(1,2,'label') 

sage: G.networkx_graph().adj # random output order 

{1: {2: 'label'}, 2: {1: 'label'}} 

 

The following syntax is supported, but note that you must use 

the ``label`` keyword:: 

 

sage: G = Graph() 

sage: G.add_edge((1,2), label='label') 

sage: G.edges() 

[(1, 2, 'label')] 

sage: G = Graph() 

sage: G.add_edge((1,2), 'label') 

sage: G.edges() 

[('label', (1, 2), None)] 

 

Vertex name cannot be None, so:: 

 

sage: G = Graph() 

sage: G.add_edge(None, 4) 

sage: G.vertices() 

[0, 4] 

""" 

if label is None: 

if v is None: 

try: 

u, v, label = u 

except Exception: 

try: 

u, v = u 

except Exception: 

pass 

else: 

if v is None: 

try: 

u, v = u 

except Exception: 

pass 

 

self._backend.add_edge(u, v, label, self._directed) 

 

def add_edges(self, edges, loops=True): 

""" 

Add edges from an iterable container. 

 

INPUT: 

 

- ``edges`` -- an iterable of edges, given either as ``(u, v)`` 

or ``(u, v, label)``. 

 

- ``loops`` -- (default: ``True``) if ``False``, remove all 

loops ``(v, v)`` from the input iterator. If ``None``, remove 

loops unless the graph allows loops. 

 

EXAMPLES:: 

 

sage: G = graphs.DodecahedralGraph() 

sage: H = Graph() 

sage: H.add_edges( G.edge_iterator() ); H 

Graph on 20 vertices 

sage: G = graphs.DodecahedralGraph().to_directed() 

sage: H = DiGraph() 

sage: H.add_edges( G.edge_iterator() ); H 

Digraph on 20 vertices 

sage: H.add_edges(iter([])) 

 

sage: H = Graph() 

sage: H.add_edges([(0, 1), (0, 2, "label")]) 

sage: H.edges() 

[(0, 1, None), (0, 2, 'label')] 

 

We demonstrate the ``loops`` argument:: 

 

sage: H = Graph() 

sage: H.add_edges([(0,0)], loops=False); H.edges() 

[] 

sage: H.add_edges([(0,0)], loops=None); H.edges() 

[] 

sage: H.add_edges([(0,0)]); H.edges() 

Traceback (most recent call last): 

... 

ValueError: cannot add edge from 0 to 0 in graph without loops 

sage: H = Graph(loops=True) 

sage: H.add_edges([(0,0)], loops=False); H.edges() 

[] 

sage: H.add_edges([(0,0)], loops=None); H.edges() 

[(0, 0, None)] 

sage: H.add_edges([(0,0)]); H.edges() 

[(0, 0, None)] 

 

TESTS:: 

 

sage: H.add_edges([(0,1,2,3)]) 

Traceback (most recent call last): 

... 

TypeError: cannot interpret (0, 1, 2, 3) as graph edge 

sage: H.add_edges([1234]) 

Traceback (most recent call last): 

... 

TypeError: cannot interpret 1234 as graph edge 

""" 

if loops is None: 

loops = self.allows_loops() 

 

for t in edges: 

try: 

if len(t) == 3: 

u, v, label = t 

else: 

u, v = t 

label = None 

except Exception: 

raise TypeError("cannot interpret {!r} as graph edge".format(t)) 

if loops or u != v: 

self._backend.add_edge(u, v, label, self._directed) 

 

def subdivide_edge(self, *args): 

""" 

Subdivides an edge `k` times. 

 

INPUT: 

 

The following forms are all accepted to subdivide `8` times 

the edge between vertices `1` and `2` labeled with 

``"my_label"``. 

 

- ``G.subdivide_edge( 1, 2, 8 )`` 

- ``G.subdivide_edge( (1, 2), 8 )`` 

- ``G.subdivide_edge( (1, 2, "my_label"), 8 )`` 

 

.. NOTE:: 

 

* If the given edge is labelled with `l`, all the edges 

created by the subdivision will have the same label. 

 

* If no label is given, the label used will be the one 

returned by the method :meth:`edge_label` on the pair 

``u,v`` 

 

EXAMPLES: 

 

Subdividing `5` times an edge in a path of length 

`3` makes it a path of length `8`:: 

 

sage: g = graphs.PathGraph(3) 

sage: edge = g.edges()[0] 

sage: g.subdivide_edge(edge, 5) 

sage: g.is_isomorphic(graphs.PathGraph(8)) 

True 

 

Subdividing a labelled edge in two ways :: 

 

sage: g = Graph() 

sage: g.add_edge(0,1,"label1") 

sage: g.add_edge(1,2,"label2") 

sage: print(sorted(g.edges())) 

[(0, 1, 'label1'), (1, 2, 'label2')] 

 

Specifying the label:: 

 

sage: g.subdivide_edge(0,1,"label1", 3) 

sage: print(sorted(g.edges())) 

[(0, 3, 'label1'), (1, 2, 'label2'), (1, 5, 'label1'), (3, 4, 'label1'), (4, 5, 'label1')] 

 

The lazy way:: 

 

sage: g.subdivide_edge(1,2,"label2", 5) 

sage: print(sorted(g.edges())) 

[(0, 3, 'label1'), (1, 5, 'label1'), (1, 6, 'label2'), (2, 10, 'label2'), (3, 4, 'label1'), (4, 5, 'label1'), (6, 7, 'label2'), (7, 8, 'label2'), (8, 9, 'label2'), (9, 10, 'label2')] 

 

If too many arguments are given, an exception is raised :: 

 

sage: g.subdivide_edge(0,1,1,1,1,1,1,1,1,1,1) 

Traceback (most recent call last): 

... 

ValueError: This method takes at most 4 arguments ! 

 

The same goes when the given edge does not exist:: 

 

sage: g.subdivide_edge(0,1,"fake_label",5) 

Traceback (most recent call last): 

... 

ValueError: The given edge does not exist. 

 

.. SEEALSO:: 

 

- :meth:`subdivide_edges` -- subdivides multiples edges at a time 

 

TESTS: 

 

:trac:`15895` is fixed:: 

 

sage: F = graphs.PathGraph(3) 

sage: S = 'S'; F.add_vertex(S) 

sage: F.add_edge(S,0) 

sage: F2 = Graph(F) 

sage: F2.subdivide_edges(list(F2.edges(labels=False)),2) 

sage: 0 in F2.degree() 

False 

""" 

if len(args) == 2: 

edge, k = args 

 

if len(edge) == 2: 

u, v = edge 

l = self.edge_label(u, v) 

elif len(edge) == 3: 

u, v, l = edge 

 

elif len(args) == 3: 

u, v, k = args 

l = self.edge_label(u, v) 

 

elif len(args) == 4: 

u, v, l, k = args 

 

else: 

raise ValueError("This method takes at most 4 arguments !") 

 

if not self.has_edge(u, v, l): 

raise ValueError("The given edge does not exist.") 

 

new_verts = [self.add_vertex() for i in range(k)] 

 

self.delete_edge(u, v, l) 

 

edges = [] 

for uu in new_verts + [v]: 

edges.append((u, uu, l)) 

u = uu 

 

self.add_edges(edges) 

 

def subdivide_edges(self, edges, k): 

""" 

Subdivides k times edges from an iterable container. 

 

For more information on the behaviour of this method, please 

refer to the documentation of :meth:`subdivide_edge`. 

 

INPUT: 

 

- ``edges`` -- a list of edges 

 

- ``k`` (integer) -- common length of the subdivisions 

 

 

.. NOTE:: 

 

If a given edge is labelled with `l`, all the edges 

created by its subdivision will have the same label. 

 

EXAMPLES: 

 

If we are given the disjoint union of several paths:: 

 

sage: paths = [2,5,9] 

sage: paths = map(graphs.PathGraph, paths) 

sage: g = Graph() 

sage: for P in paths: 

....: g = g + P 

 

... subdividing edges in each of them will only change their 

lengths:: 

 

sage: edges = [P.edges()[0] for P in g.connected_components_subgraphs()] 

sage: k = 6 

sage: g.subdivide_edges(edges, k) 

 

Let us check this by creating the graph we expect to have built 

through subdivision:: 

 

sage: paths2 = [2+k, 5+k, 9+k] 

sage: paths2 = map(graphs.PathGraph, paths2) 

sage: g2 = Graph() 

sage: for P in paths2: 

....: g2 = g2 + P 

sage: g.is_isomorphic(g2) 

True 

 

.. SEEALSO:: 

 

- :meth:`subdivide_edge` -- subdivides one edge 

""" 

for e in edges: 

self.subdivide_edge(e, k) 

 

def delete_edge(self, u, v=None, label=None): 

r""" 

Delete the edge from u to v, returning silently if vertices or edge 

does not exist. 

 

INPUT: The following forms are all accepted: 

 

- G.delete_edge( 1, 2 ) 

- G.delete_edge( (1, 2) ) 

- G.delete_edges( [ (1, 2) ] ) 

- G.delete_edge( 1, 2, 'label' ) 

- G.delete_edge( (1, 2, 'label') ) 

- G.delete_edges( [ (1, 2, 'label') ] ) 

 

EXAMPLES:: 

 

sage: G = graphs.CompleteGraph(19).copy(implementation='c_graph') 

sage: G.size() 

171 

sage: G.delete_edge( 1, 2 ) 

sage: G.delete_edge( (3, 4) ) 

sage: G.delete_edges( [ (5, 6), (7, 8) ] ) 

sage: G.size() 

167 

 

:: 

 

sage: G.delete_edge( 9, 10, 'label' ) 

sage: G.delete_edge( (11, 12, 'label') ) 

sage: G.delete_edges( [ (13, 14, 'label') ] ) 

sage: G.size() 

167 

 

:: 

 

sage: C = graphs.CompleteGraph(19).to_directed(sparse=True) 

sage: C.size() 

342 

sage: C.delete_edge( 1, 2 ) 

sage: C.delete_edge( (3, 4) ) 

sage: C.delete_edges( [ (5, 6), (7, 8) ] ) 

 

:: 

 

sage: C.delete_edge( 9, 10, 'label' ) 

sage: C.delete_edge( (11, 12, 'label') ) 

sage: C.delete_edges( [ (13, 14, 'label') ] ) 

sage: C.size() # correct! 

338 

sage: C.has_edge( (11, 12) ) # correct! 

True 

 

""" 

if label is None: 

if v is None: 

try: 

u, v, label = u 

except Exception: 

u, v = u 

label = None 

self._backend.del_edge(u, v, label, self._directed) 

 

def delete_edges(self, edges): 

""" 

Delete edges from an iterable container. 

 

EXAMPLES:: 

 

sage: K12 = graphs.CompleteGraph(12) 

sage: K4 = graphs.CompleteGraph(4) 

sage: K12.size() 

66 

sage: K12.delete_edges(K4.edge_iterator()) 

sage: K12.size() 

60 

 

:: 

 

sage: K12 = graphs.CompleteGraph(12).to_directed() 

sage: K4 = graphs.CompleteGraph(4).to_directed() 

sage: K12.size() 

132 

sage: K12.delete_edges(K4.edge_iterator()) 

sage: K12.size() 

120 

""" 

for e in edges: 

self.delete_edge(e) 

 

def contract_edge(self, u, v=None, label=None): 

""" 

Contract an edge from `u` to `v`. 

 

This method returns silently if the edge does not exist. 

 

INPUT: The following forms are all accepted: 

 

- G.contract_edge( 1, 2 ) 

- G.contract_edge( (1, 2) ) 

- G.contract_edge( [ (1, 2) ] ) 

- G.contract_edge( 1, 2, 'label' ) 

- G.contract_edge( (1, 2, 'label') ) 

- G.contract_edge( [ (1, 2, 'label') ] ) 

 

EXAMPLES:: 

 

sage: G = graphs.CompleteGraph(4) 

sage: G.contract_edge((0,1)); G.edges() 

[(0, 2, None), (0, 3, None), (2, 3, None)] 

sage: G = graphs.CompleteGraph(4) 

sage: G.allow_loops(True); G.allow_multiple_edges(True) 

sage: G.contract_edge((0,1)); G.edges() 

[(0, 2, None), (0, 2, None), (0, 3, None), (0, 3, None), (2, 3, None)] 

sage: G.contract_edge((0,2)); G.edges() 

[(0, 0, None), (0, 3, None), (0, 3, None), (0, 3, None)] 

 

:: 

 

sage: G = graphs.CompleteGraph(4).to_directed() 

sage: G.allow_loops(True) 

sage: G.contract_edge(0,1); G.edges() 

[(0, 0, None), 

(0, 2, None), 

(0, 3, None), 

(2, 0, None), 

(2, 3, None), 

(3, 0, None), 

(3, 2, None)] 

 

TESTS: 

 

Make sure loops don't get lost:: 

 

sage: edgelist = [(0,0,'a'), (0,1,'b'), (1,1,'c')] 

sage: G = Graph(edgelist, loops=True, multiedges=True) 

sage: G.contract_edge(0,1,'b'); G.edges() 

[(0, 0, 'a'), (0, 0, 'c')] 

sage: D = DiGraph(edgelist, loops=True, multiedges=True) 

sage: D.contract_edge(0,1,'b'); D.edges() 

[(0, 0, 'a'), (0, 0, 'c')] 

 

With labeled edges:: 

 

sage: G = graphs.CompleteGraph(4) 

sage: G.allow_loops(True); G.allow_multiple_edges(True) 

sage: for e in G.edges(): 

....: G.set_edge_label(e[0], e[1], (e[0] + e[1])) 

....: 

sage: G.contract_edge(0,1); G.edges() 

[(0, 2, 2), (0, 2, 3), (0, 3, 3), (0, 3, 4), (2, 3, 5)] 

sage: G.contract_edge(0,2,4); G.edges() 

[(0, 2, 2), (0, 2, 3), (0, 3, 3), (0, 3, 4), (2, 3, 5)] 

""" 

# standard code to allow 3 arguments or a single tuple: 

if label is None: 

if v is None: 

try: 

u, v, label = u 

except Exception: 

u, v = u 

label = None 

# unlike delete_edge(), we must be careful about contracting non-edges 

if not self.has_edge(u, v, label): 

return 

self.delete_edge(u ,v ,label) 

# if the edge was a loop, stop 

# this could potentially leave isolated vertices 

if u == v: 

return 

 

if (self.allows_loops() and (self.allows_multiple_edges() or 

not self.has_edge(u, u))): 

# add loops 

for (x, y, l) in self.edges_incident(v): 

if set([x, y]) == set([u, v]): 

self.add_edge(u, u, l) 

 

self.merge_vertices([u,v]) 

 

def contract_edges(self, edges): 

""" 

Contract edges from an iterable container. 

 

If `e` is an edge that is not contracted but the vertices of `e` are 

merged by contraction of other edges, then `e` will become a loop. 

 

INPUT: 

 

- ``edges`` -- a list containing 2-tuples or 3-tuples that represent 

edges 

 

EXAMPLES:: 

 

sage: G = graphs.CompleteGraph(4) 

sage: G.allow_loops(True); G.allow_multiple_edges(True) 

sage: G.contract_edges([(0,1),(1,2),(0,2)]); G.edges() 

[(0, 3, None), (0, 3, None), (0, 3, None)] 

sage: G.contract_edges([(1,3),(2,3)]); G.edges() 

[(0, 3, None), (0, 3, None), (0, 3, None)] 

sage: G = graphs.CompleteGraph(4) 

sage: G.allow_loops(True); G.allow_multiple_edges(True) 

sage: G.contract_edges([(0,1),(1,2),(0,2),(1,3),(2,3)]); G.edges() 

[(0, 0, None)] 

 

:: 

 

sage: D = graphs.CompleteGraph(4).to_directed() 

sage: D.allow_loops(True); D.allow_multiple_edges(True) 

sage: D.contract_edges([(0,1),(1,0),(0,2)]); D.edges() 

[(0, 0, None), 

(0, 0, None), 

(0, 0, None), 

(0, 3, None), 

(0, 3, None), 

(0, 3, None), 

(3, 0, None), 

(3, 0, None), 

(3, 0, None)] 

 

TESTS: 

 

With non-edges in the input:: 

 

sage: G = graphs.BullGraph(); G.add_edge(3,4); G.edges() 

[(0, 1, None), 

(0, 2, None), 

(1, 2, None), 

(1, 3, None), 

(2, 4, None), 

(3, 4, None)] 

sage: G.contract_edges([(1,3),(1,4)]); G.edges() 

[(0, 1, None), (0, 2, None), (1, 2, None), (1, 4, None), (2, 4, None)] 

 

With loops in a digraph:: 

 

sage: D = DiGraph([(0,0), (0,1), (1,1)], loops=True, multiedges=True) 

sage: D.contract_edges([(1,0)]); D.edges() 

[(0, 0, None), (0, 1, None), (1, 1, None)] 

sage: D.contract_edges([(0,1)]); D.edges() 

[(0, 0, None), (0, 0, None)] 

 

:: 

 

sage: edgelist = [(0,1,0), (0,1,1), (0,1,2)] 

sage: G = Graph(edgelist, loops=True, multiedges=True) 

sage: G.contract_edges([(0,1), (0,1,2)]); G.edges() 

Traceback (most recent call last): 

... 

ValueError: edge tuples in input should have the same length 

 

:: 

 

sage: G = graphs.CompleteGraph(4) 

sage: G.allow_loops(True); G.allow_multiple_edges(True) 

sage: for e in G.edges(): 

....: G.set_edge_label(e[0], e[1], (e[0] + e[1])) 

sage: H = G.copy() 

sage: G.contract_edges([(0,1), (0,2)]); G.edges() 

[(0, 0, 3), (0, 3, 3), (0, 3, 4), (0, 3, 5)] 

sage: H.contract_edges([(0,1,1), (0,2,3)]); H.edges() 

[(0, 2, 2), (0, 2, 3), (0, 3, 3), (0, 3, 4), (2, 3, 5)] 

""" 

if len(set([len(e) for e in edges])) > 1: 

raise ValueError("edge tuples in input should have the same length") 

edge_list = [] 

vertices = set() 

for e in edges: 

# try to get the vertices and label of e as distinct variables 

try: 

u, v, label = e 

except Exception: 

u, v = e 

label = None 

if self.has_edge(u, v, label): 

edge_list.append((u, v, label)) 

vertices.add(u) 

vertices.add(v) 

if not edge_list: 

return 

 

# implementation of union_find using DisjointSet 

from sage.sets.disjoint_set import DisjointSet 

DS = DisjointSet(self.vertices()) 

 

for (u, v, label) in edge_list: 

DS.union(u, v) 

 

self.delete_edges(edge_list) 

edges_incident = [] 

vertices = [v for v in vertices if v!= DS.find(v)] 

if self.is_directed(): 

for v in vertices: 

out_edges = self.edge_boundary([v]) 

edges_incident.extend(out_edges) 

edges_incident.extend(self.incoming_edges(v)) 

self.delete_vertex(v) 

else: 

for v in vertices: 

edges_incident.extend(self.edges_incident(v)) 

self.delete_vertex(v) 

 

for (u, v, label) in edges_incident: 

root_u = DS.find(u) 

root_v = DS.find(v) 

if root_v != root_u or self.allows_loops(): 

self.add_edge(root_u, root_v, label) 

 

def delete_multiedge(self, u, v): 

""" 

Deletes all edges from u and v. 

 

EXAMPLES:: 

 

sage: G = Graph(multiedges=True,sparse=True) 

sage: G.add_edges([(0,1), (0,1), (0,1), (1,2), (2,3)]) 

sage: G.edges() 

[(0, 1, None), (0, 1, None), (0, 1, None), (1, 2, None), (2, 3, None)] 

sage: G.delete_multiedge( 0, 1 ) 

sage: G.edges() 

[(1, 2, None), (2, 3, None)] 

 

:: 

 

sage: D = DiGraph(multiedges=True,sparse=True) 

sage: D.add_edges([(0,1,1), (0,1,2), (0,1,3), (1,0,None), (1,2,None), (2,3,None)]) 

sage: D.edges() 

[(0, 1, 1), (0, 1, 2), (0, 1, 3), (1, 0, None), (1, 2, None), (2, 3, None)] 

sage: D.delete_multiedge( 0, 1 ) 

sage: D.edges() 

[(1, 0, None), (1, 2, None), (2, 3, None)] 

""" 

if self.allows_multiple_edges(): 

for l in self.edge_label(u, v): 

self.delete_edge(u, v, l) 

else: 

self.delete_edge(u, v) 

 

def set_edge_label(self, u, v, l): 

""" 

Set the edge label of a given edge. 

 

.. note:: 

 

There can be only one edge from u to v for this to make 

sense. Otherwise, an error is raised. 

 

INPUT: 

 

 

- ``u, v`` - the vertices (and direction if digraph) 

of the edge 

 

- ``l`` - the new label 

 

 

EXAMPLES:: 

 

sage: SD = DiGraph( { 1:[18,2], 2:[5,3], 3:[4,6], 4:[7,2], 5:[4], 6:[13,12], 7:[18,8,10], 8:[6,9,10], 9:[6], 10:[11,13], 11:[12], 12:[13], 13:[17,14], 14:[16,15], 15:[2], 16:[13], 17:[15,13], 18:[13] }, sparse=True) 

sage: SD.set_edge_label(1, 18, 'discrete') 

sage: SD.set_edge_label(4, 7, 'discrete') 

sage: SD.set_edge_label(2, 5, 'h = 0') 

sage: SD.set_edge_label(7, 18, 'h = 0') 

sage: SD.set_edge_label(7, 10, 'aut') 

sage: SD.set_edge_label(8, 10, 'aut') 

sage: SD.set_edge_label(8, 9, 'label') 

sage: SD.set_edge_label(8, 6, 'no label') 

sage: SD.set_edge_label(13, 17, 'k > h') 

sage: SD.set_edge_label(13, 14, 'k = h') 

sage: SD.set_edge_label(17, 15, 'v_k finite') 

sage: SD.set_edge_label(14, 15, 'v_k m.c.r.') 

sage: posn = {1:[ 3,-3], 2:[0,2], 3:[0, 13], 4:[3,9], 5:[3,3], 6:[16, 13], 7:[6,1], 8:[6,6], 9:[6,11], 10:[9,1], 11:[10,6], 12:[13,6], 13:[16,2], 14:[10,-6], 15:[0,-10], 16:[14,-6], 17:[16,-10], 18:[6,-4]} 

sage: SD.plot(pos=posn, vertex_size=400, vertex_colors={'#FFFFFF':list(range(1,19))}, edge_labels=True).show() # long time 

 

:: 

 

sage: G = graphs.HeawoodGraph() 

sage: for u,v,l in G.edges(): 

....: G.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')') 

sage: G.edges() 

[(0, 1, '(0,1)'), 

(0, 5, '(0,5)'), 

(0, 13, '(0,13)'), 

... 

(11, 12, '(11,12)'), 

(12, 13, '(12,13)')] 

 

:: 

 

sage: g = Graph({0: [0,1,1,2]}, loops=True, multiedges=True, sparse=True) 

sage: g.set_edge_label(0,0,'test') 

sage: g.edges() 

[(0, 0, 'test'), (0, 1, None), (0, 1, None), (0, 2, None)] 

sage: g.add_edge(0,0,'test2') 

sage: g.set_edge_label(0,0,'test3') 

Traceback (most recent call last): 

... 

RuntimeError: Cannot set edge label, since there are multiple edges from 0 to 0. 

 

:: 

 

sage: dg = DiGraph({0 : [1], 1 : [0]}, sparse=True) 

sage: dg.set_edge_label(0,1,5) 

sage: dg.set_edge_label(1,0,9) 

sage: dg.outgoing_edges(1) 

[(1, 0, 9)] 

sage: dg.incoming_edges(1) 

[(0, 1, 5)] 

sage: dg.outgoing_edges(0) 

[(0, 1, 5)] 

sage: dg.incoming_edges(0) 

[(1, 0, 9)] 

 

:: 

 

sage: G = Graph({0:{1:1}}, sparse=True) 

sage: G.num_edges() 

1 

sage: G.set_edge_label(0,1,1) 

sage: G.num_edges() 

1 

""" 

if self.allows_multiple_edges(): 

if len(self.edge_label(u, v)) > 1: 

raise RuntimeError("Cannot set edge label, since there are multiple edges from %s to %s."%(u,v)) 

self._backend.set_edge_label(u, v, l, self._directed) 

 

def has_edge(self, u, v=None, label=None): 

r""" 

Returns True if (u, v) is an edge, False otherwise. 

 

INPUT: The following forms are accepted by NetworkX: 

 

- G.has_edge( 1, 2 ) 

- G.has_edge( (1, 2) ) 

- G.has_edge( 1, 2, 'label' ) 

- G.has_edge( (1, 2, 'label') ) 

 

EXAMPLES:: 

 

sage: graphs.EmptyGraph().has_edge(9,2) 

False 

sage: DiGraph().has_edge(9,2) 

False 

sage: G = Graph(sparse=True) 

sage: G.add_edge(0,1,"label") 

sage: G.has_edge(0,1,"different label") 

False 

sage: G.has_edge(0,1,"label") 

True 

""" 

if label is None: 

if v is None: 

try: 

u, v, label = u 

except Exception: 

u, v = u 

label = None 

return self._backend.has_edge(u, v, label) 

 

def edges(self, labels=True, sort=True, key=None): 

r""" 

Return a list of edges. 

 

Each edge is a triple (u,v,l) where u and v are vertices and l is a 

label. If the parameter ``labels`` is False then a list of couple (u,v) 

is returned where u and v are vertices. 

 

INPUT: 

 

- ``labels`` - default: ``True`` - if ``False``, each 

edge is simply a pair (u,v) of vertices. 

 

- ``sort`` - default: ``True`` - if ``True``, edges are 

sorted according to the default ordering. 

 

- ``key`` - default: ``None`` - a function takes an edge 

(a pair or a triple, according to the ``labels`` keyword) 

as its one argument and returns a value that can be used 

for comparisons in the sorting algorithm. 

 

OUTPUT: A list of tuples. It is safe to change the returned list. 

 

.. warning:: 

 

Since any object may be a vertex, there is no guarantee 

that any two vertices will be comparable, and thus no 

guarantee how two edges may compare. With default 

objects for vertices (all integers), or when all the 

vertices are of the same simple type, then there should 

not be a problem with how the vertices will be sorted. 

However, if you need to guarantee a total order for 

the sorting of the edges, use the ``key`` argument, 

as illustrated in the examples below. 

 

EXAMPLES:: 

 

sage: graphs.DodecahedralGraph().edges() 

[(0, 1, None), (0, 10, None), (0, 19, None), (1, 2, None), (1, 8, None), (2, 3, None), (2, 6, None), (3, 4, None), (3, 19, None), (4, 5, None), (4, 17, None), (5, 6, None), (5, 15, None), (6, 7, None), (7, 8, None), (7, 14, None), (8, 9, None), (9, 10, None), (9, 13, None), (10, 11, None), (11, 12, None), (11, 18, None), (12, 13, None), (12, 16, None), (13, 14, None), (14, 15, None), (15, 16, None), (16, 17, None), (17, 18, None), (18, 19, None)] 

 

:: 

 

sage: graphs.DodecahedralGraph().edges(labels=False) 

[(0, 1), (0, 10), (0, 19), (1, 2), (1, 8), (2, 3), (2, 6), (3, 4), (3, 19), (4, 5), (4, 17), (5, 6), (5, 15), (6, 7), (7, 8), (7, 14), (8, 9), (9, 10), (9, 13), (10, 11), (11, 12), (11, 18), (12, 13), (12, 16), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19)] 

 

:: 

 

sage: D = graphs.DodecahedralGraph().to_directed() 

sage: D.edges() 

[(0, 1, None), (0, 10, None), (0, 19, None), (1, 0, None), (1, 2, None), (1, 8, None), (2, 1, None), (2, 3, None), (2, 6, None), (3, 2, None), (3, 4, None), (3, 19, None), (4, 3, None), (4, 5, None), (4, 17, None), (5, 4, None), (5, 6, None), (5, 15, None), (6, 2, None), (6, 5, None), (6, 7, None), (7, 6, None), (7, 8, None), (7, 14, None), (8, 1, None), (8, 7, None), (8, 9, None), (9, 8, None), (9, 10, None), (9, 13, None), (10, 0, None), (10, 9, None), (10, 11, None), (11, 10, None), (11, 12, None), (11, 18, None), (12, 11, None), (12, 13, None), (12, 16, None), (13, 9, None), (13, 12, None), (13, 14, None), (14, 7, None), (14, 13, None), (14, 15, None), (15, 5, None), (15, 14, None), (15, 16, None), (16, 12, None), (16, 15, None), (16, 17, None), (17, 4, None), (17, 16, None), (17, 18, None), (18, 11, None), (18, 17, None), (18, 19, None), (19, 0, None), (19, 3, None), (19, 18, None)] 

sage: D.edges(labels = False) 

[(0, 1), (0, 10), (0, 19), (1, 0), (1, 2), (1, 8), (2, 1), (2, 3), (2, 6), (3, 2), (3, 4), (3, 19), (4, 3), (4, 5), (4, 17), (5, 4), (5, 6), (5, 15), (6, 2), (6, 5), (6, 7), (7, 6), (7, 8), (7, 14), (8, 1), (8, 7), (8, 9), (9, 8), (9, 10), (9, 13), (10, 0), (10, 9), (10, 11), (11, 10), (11, 12), (11, 18), (12, 11), (12, 13), (12, 16), (13, 9), (13, 12), (13, 14), (14, 7), (14, 13), (14, 15), (15, 5), (15, 14), (15, 16), (16, 12), (16, 15), (16, 17), (17, 4), (17, 16), (17, 18), (18, 11), (18, 17), (18, 19), (19, 0), (19, 3), (19, 18)] 

 

The default is to sort the returned list in the default fashion, as in the above examples. 

this can be overridden by specifying a key function. This first example just ignores 

the labels in the third component of the triple. :: 

 

sage: G=graphs.CycleGraph(5) 

sage: G.edges(key = lambda x: (x[1],-x[0])) 

[(0, 1, None), (1, 2, None), (2, 3, None), (3, 4, None), (0, 4, None)] 

 

We set the labels to characters and then perform a default sort 

followed by a sort according to the labels. :: 

 

sage: G=graphs.CycleGraph(5) 

sage: for e in G.edges(): 

....: G.set_edge_label(e[0], e[1], chr(ord('A')+e[0]+5*e[1])) 

sage: G.edges(sort=True) 

[(0, 1, 'F'), (0, 4, 'U'), (1, 2, 'L'), (2, 3, 'R'), (3, 4, 'X')] 

sage: G.edges(key=lambda x: x[2]) 

[(0, 1, 'F'), (1, 2, 'L'), (2, 3, 'R'), (0, 4, 'U'), (3, 4, 'X')] 

 

TESTS: 

 

It is an error to turn off sorting while providing a key function for sorting. :: 

 

sage: P=graphs.PetersenGraph() 

sage: P.edges(sort=False, key=lambda x: x) 

Traceback (most recent call last): 

... 

ValueError: sort keyword is False, yet a key function is given 

 

sage: G = Graph() 

sage: G.add_edge(0, 1, [7]) 

sage: G.add_edge(0, 2, [7]) 

sage: G.edge_label(0,1)[0] += 1 

sage: G.edges() 

[(0, 1, [8]), (0, 2, [7])] 

""" 

if not(sort) and key: 

raise ValueError('sort keyword is False, yet a key function is given') 

L = list(self.edge_iterator(labels=labels)) 

if sort: 

L.sort(key=key) 

return L 

 

def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=True): 

""" 

Returns a list of edges `(u,v,l)` with `u` in ``vertices1`` 

and `v` in ``vertices2``. If ``vertices2`` is ``None``, then 

it is set to the complement of ``vertices1``. 

 

In a digraph, the external boundary of a vertex `v` are those 

vertices `u` with an arc `(v, u)`. 

 

INPUT: 

 

 

- ``labels`` - if ``False``, each edge is a tuple `(u,v)` of 

vertices. 

 

 

EXAMPLES:: 

 

sage: K = graphs.CompleteBipartiteGraph(9,3) 

sage: len(K.edge_boundary( [0,1,2,3,4,5,6,7,8], [9,10,11] )) 

27 

sage: K.size() 

27 

 

Note that the edge boundary preserves direction:: 

 

sage: K = graphs.CompleteBipartiteGraph(9,3).to_directed() 

sage: len(K.edge_boundary( [0,1,2,3,4,5,6,7,8], [9,10,11] )) 

27 

sage: K.size() 

54 

 

:: 

 

sage: D = DiGraph({0:[1,2], 3:[0]}) 

sage: D.edge_boundary([0]) 

[(0, 1, None), (0, 2, None)] 

sage: D.edge_boundary([0], labels=False) 

[(0, 1), (0, 2)] 

 

TESTS:: 

 

sage: G = graphs.DiamondGraph() 

sage: G.edge_boundary([0,1]) 

[(0, 2, None), (1, 2, None), (1, 3, None)] 

sage: G.edge_boundary([0], [0]) 

[] 

sage: G.edge_boundary([2], [0]) 

[(0, 2, None)] 

""" 

vertices1 = set([v for v in vertices1 if v in self]) 

if self._directed: 

if vertices2 is not None: 

vertices2 = set([v for v in vertices2 if v in self]) 

output = [e for e in self.outgoing_edge_iterator(vertices1,labels=labels) 

if e[1] in vertices2] 

else: 

output = [e for e in self.outgoing_edge_iterator(vertices1,labels=labels) 

if e[1] not in vertices1] 

else: 

if vertices2 is not None: 

vertices2 = set([v for v in vertices2 if v in self]) 

output = [e for e in self.edge_iterator(vertices1,labels=labels) 

if (e[0] in vertices1 and e[1] in vertices2) or 

(e[1] in vertices1 and e[0] in vertices2)] 

else: 

output = [e for e in self.edge_iterator(vertices1,labels=labels) 

if e[1] not in vertices1 or e[0] not in vertices1] 

if sort: 

output.sort() 

return output 

 

def edge_iterator(self, vertices=None, labels=True, ignore_direction=False): 

""" 

Returns an iterator over edges. 

 

The iterator returned is over the edges incident with any vertex given 

in the parameter ``vertices``. If the graph is directed, iterates over 

edges going out only. If vertices is None, then returns an iterator over 

all edges. If self is directed, returns outgoing edges only. 

 

INPUT: 

 

- ``vertices`` - (default: None) a vertex, a list of vertices or None 

 

- ``labels`` - if False, each edge is a tuple (u,v) of 

vertices. 

 

- ``ignore_direction`` - bool (default: False) - only applies 

to directed graphs. If True, searches across edges in either 

direction. 

 

 

EXAMPLES:: 

 

sage: for i in graphs.PetersenGraph().edge_iterator([0]): 

....: print(i) 

(0, 1, None) 

(0, 4, None) 

(0, 5, None) 

sage: D = DiGraph( { 0 : [1,2], 1: [0] } ) 

sage: for i in D.edge_iterator([0]): 

....: print(i) 

(0, 1, None) 

(0, 2, None) 

 

:: 

 

sage: G = graphs.TetrahedralGraph() 

sage: list(G.edge_iterator(labels=False)) 

[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] 

 

:: 

 

sage: D = DiGraph({1:[0], 2:[0]}) 

sage: list(D.edge_iterator(0)) 

[] 

sage: list(D.edge_iterator(0, ignore_direction=True)) 

[(1, 0, None), (2, 0, None)] 

""" 

if vertices is None: 

vertices = self 

elif vertices in self: 

vertices = [vertices] 

else: 

vertices = [v for v in vertices if v in self] 

 

if ignore_direction and self._directed: 

from itertools import chain 

return chain(self._backend.iterator_out_edges(vertices, labels), 

self._backend.iterator_in_edges(vertices, labels)) 

elif self._directed: 

return self._backend.iterator_out_edges(vertices, labels) 

else: 

return self._backend.iterator_edges(vertices, labels) 

 

def edges_incident(self, vertices=None, labels=True, sort=True): 

""" 

Returns incident edges to some vertices. 

 

If ``vertices` is a vertex, then it returns the list of edges incident to 

that vertex. If ``vertices`` is a list of vertices then it returns the 

list of all edges adjacent to those vertices. If ``vertices`` 

is None, returns a list of all edges in graph. For digraphs, only 

lists outward edges. 

 

INPUT: 

 

- ``vertices`` - object (default: None) - a vertex, a list of vertices 

or None. 

 

- ``labels`` - bool (default: True) - if False, each edge is a tuple 

(u,v) of vertices. 

 

- ``sort`` - bool (default: True) - if True the returned list is sorted. 

 

 

EXAMPLES:: 

 

sage: graphs.PetersenGraph().edges_incident([0,9], labels=False) 

[(0, 1), (0, 4), (0, 5), (4, 9), (6, 9), (7, 9)] 

sage: D = DiGraph({0:[1]}) 

sage: D.edges_incident([0]) 

[(0, 1, None)] 

sage: D.edges_incident([1]) 

[] 

 

TESTS:: 

 

sage: G = Graph({0:[0]}, loops=True) # ticket 9581 

sage: G.edges_incident(0) 

[(0, 0, None)] 

""" 

if vertices in self: 

vertices = [vertices] 

 

if sort: 

return sorted(self.edge_iterator(vertices=vertices,labels=labels)) 

return list(self.edge_iterator(vertices=vertices,labels=labels)) 

 

def edge_label(self, u, v): 

""" 

Return the label of an edge. 

 

If the graph allows multiple edges, then the list of labels 

on the edges is returned. 

 

.. SEEALSO:: 

 

- :meth:`set_edge_label` 

 

EXAMPLES:: 

 

sage: G = Graph({0 : {1 : 'edgelabel'}}) 

sage: G.edge_label(0, 1) 

'edgelabel' 

sage: D = DiGraph({1 : {2: 'up'}, 2: {1: 'down'}}) 

sage: D.edge_label(2, 1) 

'down' 

 

:: 

 

sage: G = Graph(multiedges=True) 

sage: [G.add_edge(0, 1, i) for i in range(1, 6)] 

[None, None, None, None, None] 

sage: sorted(G.edge_label(0, 1)) 

[1, 2, 3, 4, 5] 

 

TESTS:: 

 

sage: g = graphs.CycleGraph(5) 

sage: g.edge_label(2, 3) is None 

True 

""" 

return self._backend.get_edge_label(u,v) 

 

def edge_labels(self): 

""" 

Return a list of edge labels. 

 

EXAMPLES:: 

 

sage: G = Graph({0:{1:'x',2:'z',3:'a'}, 2:{5:'out'}}, sparse=True) 

sage: G.edge_labels() 

['x', 'z', 'a', 'out'] 

sage: G = DiGraph({0:{1:'x',2:'z',3:'a'}, 2:{5:'out'}}, sparse=True) 

sage: G.edge_labels() 

['x', 'z', 'a', 'out'] 

""" 

return [l for _, _, l in self.edge_iterator()] 

 

def remove_multiple_edges(self): 

""" 

Removes all multiple edges, retaining one edge for each. 

 

EXAMPLES:: 

 

sage: G = Graph(multiedges=True, sparse=True) 

sage: G.add_edges( [ (0,1), (0,1), (0,1), (0,1), (1,2) ] ) 

sage: G.edges(labels=False) 

[(0, 1), (0, 1), (0, 1), (0, 1), (1, 2)] 

 

:: 

 

sage: G.remove_multiple_edges() 

sage: G.edges(labels=False) 

[(0, 1), (1, 2)] 

 

:: 

 

sage: D = DiGraph(multiedges=True, sparse=True) 

sage: D.add_edges( [ (0,1,1), (0,1,2), (0,1,3), (0,1,4), (1,2,None) ] ) 

sage: D.edges(labels=False) 

[(0, 1), (0, 1), (0, 1), (0, 1), (1, 2)] 

sage: D.remove_multiple_edges() 

sage: D.edges(labels=False) 

[(0, 1), (1, 2)] 

""" 

if self.allows_multiple_edges(): 

if self._directed: 

for v in self: 

for u in self.neighbor_in_iterator(v): 

edges = self.edge_boundary([u], [v]) 

if len(edges) > 1: 

self.delete_edges(edges[1:]) 

else: 

for v in self: 

for u in self.neighbor_iterator(v): 

edges = self.edge_boundary([v], [u]) 

if len(edges) > 1: 

self.delete_edges(edges[1:]) 

 

def remove_loops(self, vertices=None): 

""" 

Removes loops on vertices in vertices. If vertices is None, removes 

all loops. 

 

EXAMPLE 

 

:: 

 

sage: G = Graph(4, loops=True) 

sage: G.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] ) 

sage: G.edges(labels=False) 

[(0, 0), (1, 1), (2, 2), (2, 3), (3, 3)] 

sage: G.remove_loops() 

sage: G.edges(labels=False) 

[(2, 3)] 

sage: G.allows_loops() 

True 

sage: G.has_loops() 

False 

 

sage: D = DiGraph(4, loops=True) 

sage: D.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] ) 

sage: D.edges(labels=False) 

[(0, 0), (1, 1), (2, 2), (2, 3), (3, 3)] 

sage: D.remove_loops() 

sage: D.edges(labels=False) 

[(2, 3)] 

sage: D.allows_loops() 

True 

sage: D.has_loops() 

False 

""" 

if vertices is None: 

vertices = self 

for v in vertices: 

if self.has_edge(v,v): 

self.delete_multiedge(v,v) 

 

### Modifications 

 

def clear(self): 

""" 

Empties the graph of vertices and edges and removes name, associated 

objects, and position information. 

 

EXAMPLES:: 

 

sage: G=graphs.CycleGraph(4); G.set_vertices({0:'vertex0'}) 

sage: G.order(); G.size() 

4 

4 

sage: len(G._pos) 

4 

sage: G.name() 

'Cycle graph' 

sage: G.get_vertex(0) 

'vertex0' 

sage: H = G.copy(implementation='c_graph', sparse=True) 

sage: H.clear() 

sage: H.order(); H.size() 

0 

0 

sage: len(H._pos) 

0 

sage: H.name() 

'' 

sage: H.get_vertex(0) 

sage: H = G.copy(implementation='c_graph', sparse=False) 

sage: H.clear() 

sage: H.order(); H.size() 

0 

0 

sage: len(H._pos) 

0 

sage: H.name() 

'' 

sage: H.get_vertex(0) 

""" 

self.name('') 

self.delete_vertices(self.vertices()) 

 

### Degree functions 

 

def degree(self, vertices=None, labels=False): 

""" 

Gives the degree (in + out for digraphs) of a vertex or of 

vertices. 

 

INPUT: 

 

 

- ``vertices`` - If vertices is a single vertex, 

returns the number of neighbors of vertex. If vertices is an 

iterable container of vertices, returns a list of degrees. If 

vertices is None, same as listing all vertices. 

 

- ``labels`` - see OUTPUT 

 

 

OUTPUT: Single vertex- an integer. Multiple vertices- a list of 

integers. If labels is True, then returns a dictionary mapping each 

vertex to its degree. 

 

EXAMPLES:: 

 

sage: P = graphs.PetersenGraph() 

sage: P.degree(5) 

3 

 

:: 

 

sage: K = graphs.CompleteGraph(9) 

sage: K.degree() 

[8, 8, 8, 8, 8, 8, 8, 8, 8] 

 

:: 

 

sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } ) 

sage: D.degree(vertices = [0,1,2], labels=True) 

{0: 5, 1: 4, 2: 3} 

sage: D.degree() 

[5, 4, 3, 3, 3, 2] 

""" 

if labels: 

return dict(self.degree_iterator(vertices,labels)) 

elif vertices in self and not labels: 

return next(self.degree_iterator(vertices,labels)) 

else: 

return list(self.degree_iterator(vertices,labels)) 

 

def average_degree(self): 

r""" 

Returns the average degree of the graph. 

 

The average degree of a graph `G=(V,E)` is equal to 

``\frac {2|E|}{|V|}``. 

 

EXAMPLES: 

 

The average degree of a regular graph is equal to the 

degree of any vertex:: 

 

sage: g = graphs.CompleteGraph(5) 

sage: g.average_degree() == 4 

True 

 

The average degree of a tree is always strictly less than 

`2`:: 

 

sage: g = graphs.RandomGNP(20,.5) 

sage: tree = Graph() 

sage: tree.add_edges(g.min_spanning_tree()) 

sage: tree.average_degree() < 2 

True 

 

For any graph, it is equal to ``\frac {2|E|}{|V|}``:: 

 

sage: g = graphs.RandomGNP(50,.8) 

sage: g.average_degree() == 2*g.size()/g.order() 

True 

""" 

return 2*Integer(self.size())/Integer(self.order()) 

 

def degree_histogram(self): 

""" 

Return a list, whose ith entry is the frequency of degree i. 

 

EXAMPLES:: 

 

sage: G = graphs.Grid2dGraph(9,12) 

sage: G.degree_histogram() 

[0, 0, 4, 34, 70] 

 

:: 

 

sage: G = graphs.Grid2dGraph(9,12).to_directed() 

sage: G.degree_histogram() 

[0, 0, 0, 0, 4, 0, 34, 0, 70] 

 

TESTS:: 

 

sage: Graph().degree_histogram() 

[] 

""" 

if self.order() == 0: 

return [] 

degree_sequence = self.degree() 

dmax = max(degree_sequence) + 1 

frequency = [0]*dmax 

for d in degree_sequence: 

frequency[d] += 1 

return frequency 

 

def degree_iterator(self, vertices=None, labels=False): 

""" 

Returns an iterator over the degrees of the (di)graph. 

 

In the case of a digraph, the degree is defined as the sum of the 

in-degree and the out-degree, i.e. the total number of edges incident to 

a given vertex. 

 

INPUT: 

 

- ``labels`` (boolean) -- if set to ``False`` (default) the method 

returns an iterator over degrees. Otherwise it returns an iterator 

over tuples (vertex, degree). 

 

- ``vertices`` - if specified, restrict to this 

subset. 

 

 

EXAMPLES:: 

 

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

sage: for i in G.degree_iterator(): 

....: print(i) 

3 

4 

2 

... 

2 

4 

sage: for i in G.degree_iterator(labels=True): 

....: print(i) 

((0, 1), 3) 

((1, 2), 4) 

((0, 0), 2) 

... 

((0, 3), 2) 

((1, 1), 4) 

 

:: 

 

sage: D = graphs.Grid2dGraph(2,4).to_directed() 

sage: for i in D.degree_iterator(): 

....: print(i) 

6 

6 

... 

4 

6 

sage: for i in D.degree_iterator(labels=True): 

....: print(i) 

((0, 1), 6) 

((1, 2), 6) 

... 

((1, 0), 4) 

((0, 2), 6) 

""" 

if vertices is None: 

vertices = self 

elif vertices in self: 

vertices = [vertices] 

else: 

vertices = [v for v in vertices if v in self] 

if labels: 

filter_ = lambda v, self: (v, self._backend.degree(v, self._directed)) 

else: 

filter_ = lambda v, self: self._backend.degree(v, self._directed) 

for v in vertices: 

yield filter_(v, self) 

 

def degree_sequence(self): 

r""" 

Return the degree sequence of this (di)graph. 

 

EXAMPLES: 

 

The degree sequence of an undirected graph:: 

 

sage: g = Graph({1: [2, 5], 2: [1, 5, 3, 4], 3: [2, 5], 4: [3], 5: [2, 3]}) 

sage: g.degree_sequence() 

[4, 3, 3, 2, 2] 

 

The degree sequence of a digraph:: 

 

sage: g = DiGraph({1: [2, 5, 6], 2: [3, 6], 3: [4, 6], 4: [6], 5: [4, 6]}) 

sage: g.degree_sequence() 

[5, 3, 3, 3, 3, 3] 

 

Degree sequences of some common graphs:: 

 

sage: graphs.PetersenGraph().degree_sequence() 

[3, 3, 3, 3, 3, 3, 3, 3, 3, 3] 

sage: graphs.HouseGraph().degree_sequence() 

[3, 3, 2, 2, 2] 

sage: graphs.FlowerSnark().degree_sequence() 

[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3] 

""" 

return sorted(self.degree_iterator(), reverse=True) 

 

def is_regular(self, k = None): 

""" 

Return ``True`` if this graph is (`k`-)regular. 

 

INPUT: 

 

- ``k`` (default: ``None``) - the degree of regularity to 

check for 

 

EXAMPLES:: 

 

sage: G = graphs.HoffmanSingletonGraph() 

sage: G.is_regular() 

True 

sage: G.is_regular(9) 

False 

 

So the Hoffman-Singleton graph is regular, but not 

9-regular. In fact, we can now find the degree easily as 

follows:: 

 

sage: next(G.degree_iterator()) 

7 

 

The house graph is not regular:: 

 

sage: graphs.HouseGraph().is_regular() 

False 

 

A graph without vertices is `k`-regular for every `k`:: 

 

sage: Graph().is_regular() 

True 

""" 

if self.order() == 0: 

return True 

 

deg_it = self.degree_iterator() 

if k is None: 

k = next(deg_it) 

 

for d in deg_it: 

if d != k: 

return False 

 

return True 

 

### Substructures 

 

def subgraph(self, vertices=None, edges=None, inplace=False, 

vertex_property=None, edge_property=None, algorithm=None, 

immutable=None): 

""" 

Returns the subgraph containing the given vertices and edges. 

 

If either vertices or edges are not specified, they are assumed to be 

all vertices or edges. If edges are not specified, returns the subgraph 

induced by the vertices. 

 

INPUT: 

 

 

- ``inplace`` - Using inplace is True will simply 

delete the extra vertices and edges from the current graph. This 

will modify the graph. 

 

- ``vertices`` - Vertices can be a single vertex or an 

iterable container of vertices, e.g. a list, set, graph, file or 

numeric array. If not passed, defaults to the entire graph. 

 

- ``edges`` - As with vertices, edges can be a single 

edge or an iterable container of edges (e.g., a list, set, file, 

numeric array, etc.). If not edges are not specified, then all 

edges are assumed and the returned graph is an induced subgraph. In 

the case of multiple edges, specifying an edge as (u,v) means to 

keep all edges (u,v), regardless of the label. 

 

- ``vertex_property`` - If specified, this is 

expected to be a function on vertices, which is intersected with 

the vertices specified, if any are. 

 

- ``edge_property`` - If specified, this is expected 

to be a function on edges, which is intersected with the edges 

specified, if any are. 

 

- ``algorithm`` - If ``algorithm=delete`` or ``inplace=True``, 

then the graph is constructed by deleting edges and 

vertices. If ``add``, then the graph is constructed by 

building a new graph from the appropriate vertices and 

edges. If not specified, then the algorithm is chosen based 

on the number of vertices in the subgraph. 

 

- ``immutable`` (boolean) -- whether to create a mutable/immutable 

subgraph. ``immutable=None`` (default) means that the graph and its 

subgraph will behave the same way. 

 

 

EXAMPLES:: 

 

sage: G = graphs.CompleteGraph(9) 

sage: H = G.subgraph([0,1,2]); H 

Subgraph of (Complete graph): Graph on 3 vertices 

sage: G 

Complete graph: Graph on 9 vertices 

sage: J = G.subgraph(edges=[(0,1)]) 

sage: J.edges(labels=False) 

[(0, 1)] 

sage: J.vertices()==G.vertices() 

True 

sage: G.subgraph([0,1,2], inplace=True); G 

Subgraph of (Complete graph): Graph on 3 vertices 

sage: G.subgraph()==G 

True 

 

:: 

 

sage: D = graphs.CompleteGraph(9).to_directed() 

sage: H = D.subgraph([0,1,2]); H 

Subgraph of (Complete graph): Digraph on 3 vertices 

sage: H = D.subgraph(edges=[(0,1), (0,2)]) 

sage: H.edges(labels=False) 

[(0, 1), (0, 2)] 

sage: H.vertices()==D.vertices() 

True 

sage: D 

Complete graph: Digraph on 9 vertices 

sage: D.subgraph([0,1,2], inplace=True); D 

Subgraph of (Complete graph): Digraph on 3 vertices 

sage: D.subgraph()==D 

True 

 

A more complicated example involving multiple edges and labels. 

 

:: 

 

sage: G = Graph(multiedges=True, sparse=True) 

sage: G.add_edges([(0,1,'a'), (0,1,'b'), (1,0,'c'), (0,2,'d'), (0,2,'e'), (2,0,'f'), (1,2,'g')]) 

sage: G.subgraph(edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges() 

[(0, 1, 'a'), (0, 1, 'b'), (0, 1, 'c'), (0, 2, 'd')] 

sage: J = G.subgraph(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')]) 

sage: J.edges() 

[(0, 1, 'a')] 

sage: J.vertices() 

[0, 1] 

sage: G.subgraph(vertices=G.vertices())==G 

True 

 

:: 

 

sage: D = DiGraph(multiedges=True, sparse=True) 

sage: D.add_edges([(0,1,'a'), (0,1,'b'), (1,0,'c'), (0,2,'d'), (0,2,'e'), (2,0,'f'), (1,2,'g')]) 

sage: D.subgraph(edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges() 

[(0, 1, 'a'), (0, 1, 'b'), (0, 2, 'd')] 

sage: H = D.subgraph(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')]) 

sage: H.edges() 

[(0, 1, 'a')] 

sage: H.vertices() 

[0, 1] 

 

Using the property arguments:: 

 

sage: P = graphs.PetersenGraph() 

sage: S = P.subgraph(vertex_property = lambda v : v%2 == 0) 

sage: S.vertices() 

[0, 2, 4, 6, 8] 

 

:: 

 

sage: C = graphs.CubeGraph(2) 

sage: S = C.subgraph(edge_property=(lambda e: e[0][0] == e[1][0])) 

sage: C.edges() 

[('00', '01', None), ('00', '10', None), ('01', '11', None), ('10', '11', None)] 

sage: S.edges() 

[('00', '01', None), ('10', '11', None)] 

 

 

The algorithm is not specified, then a reasonable choice is made for speed. 

 

:: 

 

sage: g=graphs.PathGraph(1000) 

sage: g.subgraph(list(range(10))) # uses the 'add' algorithm 

Subgraph of (Path graph): Graph on 10 vertices 

 

 

 

TESTS: 

 

The appropriate properties are preserved. 

 

:: 

 

sage: g = graphs.PathGraph(10) 

sage: g.is_planar(set_embedding=True) 

True 

sage: g.set_vertices(dict((v, 'v%d'%v) for v in g.vertices())) 

sage: h = g.subgraph([3..5]) 

sage: h.get_pos().keys() 

[3, 4, 5] 

sage: h.get_vertices() 

{3: 'v3', 4: 'v4', 5: 'v5'} 

""" 

if vertices is None: 

vertices=self.vertices() 

elif vertices in self: 

vertices=[vertices] 

else: 

vertices=list(vertices) 

 

if vertex_property is not None: 

vertices = [v for v in vertices if vertex_property(v)] 

 

if algorithm is not None and algorithm not in ("delete", "add"): 

raise ValueError('algorithm should be None, "delete", or "add"') 

 

if inplace or len(vertices)>0.05*self.order() or algorithm=="delete": 

return self._subgraph_by_deleting(vertices=vertices, edges=edges, 

inplace=inplace, 

edge_property=edge_property, 

immutable=immutable) 

else: 

return self._subgraph_by_adding(vertices=vertices, edges=edges, 

edge_property=edge_property, 

immutable=immutable) 

 

def _subgraph_by_adding(self, vertices=None, edges=None, edge_property=None, immutable=None): 

""" 

Returns the subgraph containing the given vertices and edges. 

The edges also satisfy the edge_property, if it is not None. 

The subgraph is created by creating a new empty graph and 

adding the necessary vertices, edges, and other properties. 

 

INPUT: 

 

- ``vertices`` - Vertices is a list of vertices 

 

- ``edges`` - Edges can be a single edge or an iterable 

container of edges (e.g., a list, set, file, numeric array, 

etc.). If not edges are not specified, then all edges are 

assumed and the returned graph is an induced subgraph. In 

the case of multiple edges, specifying an edge as (u,v) 

means to keep all edges (u,v), regardless of the label. 

 

- ``edge_property`` - If specified, this is expected 

to be a function on edges, which is intersected with the edges 

specified, if any are. 

 

- ``immutable`` (boolean) -- whether to create a mutable/immutable 

subgraph. ``immutable=None`` (default) means that the graph and its 

subgraph will behave the same way. 

 

 

EXAMPLES:: 

 

sage: G = graphs.CompleteGraph(9) 

sage: H = G._subgraph_by_adding([0,1,2]); H 

Subgraph of (Complete graph): Graph on 3 vertices 

sage: G 

Complete graph: Graph on 9 vertices 

sage: J = G._subgraph_by_adding(vertices=G.vertices(), edges=[(0,1)]) 

sage: J.edges(labels=False) 

[(0, 1)] 

sage: J.vertices()==G.vertices() 

True 

sage: G._subgraph_by_adding(vertices=G.vertices())==G 

True 

 

:: 

 

sage: D = graphs.CompleteGraph(9).to_directed() 

sage: H = D._subgraph_by_adding([0,1,2]); H 

Subgraph of (Complete graph): Digraph on 3 vertices 

sage: H = D._subgraph_by_adding(vertices=D.vertices(), edges=[(0,1), (0,2)]) 

sage: H.edges(labels=False) 

[(0, 1), (0, 2)] 

sage: H.vertices()==D.vertices() 

True 

sage: D 

Complete graph: Digraph on 9 vertices 

sage: D._subgraph_by_adding(D.vertices())==D 

True 

 

A more complicated example involving multiple edges and labels. 

 

:: 

 

sage: G = Graph(multiedges=True, sparse=True) 

sage: G.add_edges([(0,1,'a'), (0,1,'b'), (1,0,'c'), (0,2,'d'), (0,2,'e'), (2,0,'f'), (1,2,'g')]) 

sage: G._subgraph_by_adding(G.vertices(), edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges() 

[(0, 1, 'a'), (0, 1, 'b'), (0, 1, 'c'), (0, 2, 'd')] 

sage: J = G._subgraph_by_adding(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')]) 

sage: J.edges() 

[(0, 1, 'a')] 

sage: J.vertices() 

[0, 1] 

sage: G._subgraph_by_adding(vertices=G.vertices())==G 

True 

 

:: 

 

sage: D = DiGraph(multiedges=True, sparse=True) 

sage: D.add_edges([(0,1,'a'), (0,1,'b'), (1,0,'c'), (0,2,'d'), (0,2,'e'), (2,0,'f'), (1,2,'g')]) 

sage: D._subgraph_by_adding(vertices=D.vertices(), edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges() 

[(0, 1, 'a'), (0, 1, 'b'), (0, 2, 'd')] 

sage: H = D._subgraph_by_adding(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')]) 

sage: H.edges() 

[(0, 1, 'a')] 

sage: H.vertices() 

[0, 1] 

 

Using the property arguments:: 

 

sage: C = graphs.CubeGraph(2) 

sage: S = C._subgraph_by_adding(vertices=C.vertices(), edge_property=(lambda e: e[0][0] == e[1][0])) 

sage: C.edges() 

[('00', '01', None), ('00', '10', None), ('01', '11', None), ('10', '11', None)] 

sage: S.edges() 

[('00', '01', None), ('10', '11', None)] 

 

TESTS: 

 

Properties of the graph are preserved. 

 

:: 

 

sage: g = graphs.PathGraph(10) 

sage: g.is_planar(set_embedding=True) 

True 

sage: g.set_vertices(dict((v, 'v%d'%v) for v in g.vertices())) 

sage: h = g._subgraph_by_adding([3..5]) 

sage: h.get_pos().keys() 

[3, 4, 5] 

sage: h.get_vertices() 

{3: 'v3', 4: 'v4', 5: 'v5'} 

""" 

G = self.__class__(weighted=self._weighted, loops=self.allows_loops(), 

multiedges= self.allows_multiple_edges()) 

G.name("Subgraph of (%s)"%self.name()) 

G.add_vertices(vertices) 

if edges is not None: 

if G._directed: 

edges_graph = (e for e in self.edge_iterator(vertices) if e[1] in vertices) 

edges_to_keep_labeled = [e for e in edges if len(e)==3] 

edges_to_keep_unlabeled = [e for e in edges if len(e)==2] 

else: 

edges_graph = (sorted(e[0:2])+[e[2]] for e in self.edge_iterator(vertices) if e[0] in vertices and e[1] in vertices) 

edges_to_keep_labeled = [sorted(e[0:2])+[e[2]] for e in edges if len(e)==3] 

edges_to_keep_unlabeled = [sorted(e) for e in edges if len(e)==2] 

edges_to_keep = [tuple(e) for e in edges_graph if e in edges_to_keep_labeled 

or e[0:2] in edges_to_keep_unlabeled] 

else: 

edges_to_keep=[e for e in self.edge_iterator(vertices) if e[0] in vertices and e[1] in vertices] 

 

if edge_property is not None: 

edges_to_keep = [e for e in edges_to_keep if edge_property(e)] 

G.add_edges(edges_to_keep) 

 

attributes_to_update = ('_pos', '_assoc') 

for attr in attributes_to_update: 

if hasattr(self, attr) and getattr(self, attr) is not None: 

value = dict([(v, getattr(self, attr).get(v, None)) for v in G]) 

setattr(G, attr,value) 

 

if immutable is None: 

immutable = self.is_immutable() 

if immutable: 

G = G.copy(immutable=True) 

 

return G 

 

def _subgraph_by_deleting(self, vertices=None, edges=None, inplace=False, 

edge_property=None, immutable=None): 

""" 

Returns the subgraph containing the given vertices and edges. 

The edges also satisfy the edge_property, if it is not None. 

The subgraph is created by creating deleting things that are 

not needed. 

 

INPUT: 

 

- ``vertices`` - Vertices is a list of vertices 

 

- ``edges`` - Edges can be a single edge or an iterable 

container of edges (e.g., a list, set, file, numeric array, 

etc.). If not edges are not specified, then all edges are 

assumed and the returned graph is an induced subgraph. In 

the case of multiple edges, specifying an edge as (u,v) 

means to keep all edges (u,v), regardless of the label. 

 

- ``edge_property`` - If specified, this is expected 

to be a function on edges, which is intersected with the edges 

specified, if any are. 

 

- ``inplace`` - Using inplace is True will simply 

delete the extra vertices and edges from the current graph. This 

will modify the graph. 

 

- ``immutable`` (boolean) -- whether to create a mutable/immutable 

subgraph. ``immutable=None`` (default) means that the graph and its 

subgraph will behave the same way. 

 

 

EXAMPLES:: 

 

sage: G = graphs.CompleteGraph(9) 

sage: H = G._subgraph_by_deleting([0,1,2]); H 

Subgraph of (Complete graph): Graph on 3 vertices 

sage: G 

Complete graph: Graph on 9 vertices 

sage: J = G._subgraph_by_deleting(vertices=G.vertices(), edges=[(0,1)]) 

sage: J.edges(labels=False) 

[(0, 1)] 

sage: J.vertices()==G.vertices() 

True 

sage: G._subgraph_by_deleting([0,1,2], inplace=True); G 

Subgraph of (Complete graph): Graph on 3 vertices 

sage: G._subgraph_by_deleting(vertices=G.vertices())==G 

True 

 

:: 

 

sage: D = graphs.CompleteGraph(9).to_directed() 

sage: H = D._subgraph_by_deleting([0,1,2]); H 

Subgraph of (Complete graph): Digraph on 3 vertices 

sage: H = D._subgraph_by_deleting(vertices=D.vertices(), edges=[(0,1), (0,2)]) 

sage: H.edges(labels=False) 

[(0, 1), (0, 2)] 

sage: H.vertices()==D.vertices() 

True 

sage: D 

Complete graph: Digraph on 9 vertices 

sage: D._subgraph_by_deleting([0,1,2], inplace=True); D 

Subgraph of (Complete graph): Digraph on 3 vertices 

sage: D._subgraph_by_deleting(D.vertices())==D 

True 

 

A more complicated example involving multiple edges and labels. 

 

:: 

 

sage: G = Graph(multiedges=True, sparse=True) 

sage: G.add_edges([(0,1,'a'), (0,1,'b'), (1,0,'c'), (0,2,'d'), (0,2,'e'), (2,0,'f'), (1,2,'g')]) 

sage: G._subgraph_by_deleting(G.vertices(), edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges() 

[(0, 1, 'a'), (0, 1, 'b'), (0, 1, 'c'), (0, 2, 'd')] 

sage: J = G._subgraph_by_deleting(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')]) 

sage: J.edges() 

[(0, 1, 'a')] 

sage: J.vertices() 

[0, 1] 

sage: G._subgraph_by_deleting(vertices=G.vertices())==G 

True 

 

:: 

 

sage: D = DiGraph(multiedges=True, sparse=True) 

sage: D.add_edges([(0,1,'a'), (0,1,'b'), (1,0,'c'), (0,2,'d'), (0,2,'e'), (2,0,'f'), (1,2,'g')]) 

sage: D._subgraph_by_deleting(vertices=D.vertices(), edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges() 

[(0, 1, 'a'), (0, 1, 'b'), (0, 2, 'd')] 

sage: H = D._subgraph_by_deleting(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')]) 

sage: H.edges() 

[(0, 1, 'a')] 

sage: H.vertices() 

[0, 1] 

 

Using the property arguments:: 

 

sage: C = graphs.CubeGraph(2) 

sage: S = C._subgraph_by_deleting(vertices=C.vertices(), edge_property=(lambda e: e[0][0] == e[1][0])) 

sage: C.edges() 

[('00', '01', None), ('00', '10', None), ('01', '11', None), ('10', '11', None)] 

sage: S.edges() 

[('00', '01', None), ('10', '11', None)] 

 

TESTS: 

 

Properties of the graph are preserved. 

 

:: 

 

sage: g = graphs.PathGraph(10) 

sage: g.is_planar(set_embedding=True) 

True 

sage: g.set_vertices(dict((v, 'v%d'%v) for v in g.vertices())) 

sage: h = g._subgraph_by_deleting([3..5]) 

sage: h.get_pos().keys() 

[3, 4, 5] 

sage: h.get_vertices() 

{3: 'v3', 4: 'v4', 5: 'v5'} 

 

:trac:`17683`:: 

 

sage: graphs.PetersenGraph().copy(immutable=True).subgraph([1,2]) 

Subgraph of (Petersen graph): Graph on 2 vertices 

""" 

if inplace: 

G = self 

else: 

G = copy(self) 

G.name("Subgraph of (%s)"%self.name()) 

 

G.delete_vertices([v for v in G if v not in vertices]) 

 

edges_to_delete=[] 

if edges is not None: 

if G._directed: 

edges_graph = G.edge_iterator() 

edges_to_keep_labeled = [e for e in edges if len(e)==3] 

edges_to_keep_unlabeled = [e for e in edges if len(e)==2] 

else: 

edges_graph = [sorted(e[0:2])+[e[2]] for e in G.edge_iterator()] 

edges_to_keep_labeled = [sorted(e[0:2])+[e[2]] for e in edges if len(e)==3] 

edges_to_keep_unlabeled = [sorted(e) for e in edges if len(e)==2] 

for e in edges_graph: 

if e not in edges_to_keep_labeled and e[0:2] not in edges_to_keep_unlabeled: 

edges_to_delete.append(tuple(e)) 

if edge_property is not None: 

for e in G.edge_iterator(): 

if not edge_property(e): 

# We might get duplicate edges, but this does 

# handle the case of multiple edges. 

edges_to_delete.append(e) 

 

G.delete_edges(edges_to_delete) 

if not inplace: 

if immutable is None: 

immutable = self.is_immutable() 

if immutable: 

G = G.copy(immutable=True) 

return G 

 

def subgraph_search(self, G, induced=False): 

r""" 

Returns a copy of ``G`` in ``self``. 

 

INPUT: 

 

- ``G`` -- the (di)graph whose copy we are looking for in ``self``. 

 

- ``induced`` -- boolean (default: ``False``). Whether or not to 

search for an induced copy of ``G`` in ``self``. 

 

OUTPUT: 

 

- If ``induced=False``, return a copy of ``G`` in this graph. 

Otherwise, return an induced copy of ``G`` in ``self``. If ``G`` 

is the empty graph, return the empty graph since it is a subgraph 

of every graph. Now suppose ``G`` is not the empty graph. If there 

is no copy (induced or otherwise) of ``G`` in ``self``, we return 

``None``. 

 

.. NOTE:: 

 

This method does not take vertex/edge labels into account. 

 

.. SEEALSO:: 

 

- :meth:`~GenericGraph.subgraph_search_count` -- Counts the number 

of copies of `H` inside of `G` 

 

- :meth:`~GenericGraph.subgraph_search_iterator` -- Iterate on the 

copies of `H` inside of `G` 

 

ALGORITHM: 

 

See the documentation of 

:class:`~sage.graphs.generic_graph_pyx.SubgraphSearch`. 

 

EXAMPLES: 

 

The Petersen graph contains the path graph `P_5`:: 

 

sage: g = graphs.PetersenGraph() 

sage: h1 = g.subgraph_search(graphs.PathGraph(5)); h1 

Subgraph of (Petersen graph): Graph on 5 vertices 

sage: h1.vertices(); h1.edges(labels=False) 

[0, 1, 2, 3, 4] 

[(0, 1), (1, 2), (2, 3), (3, 4)] 

sage: I1 = g.subgraph_search(graphs.PathGraph(5), induced=True); I1 

Subgraph of (Petersen graph): Graph on 5 vertices 

sage: I1.vertices(); I1.edges(labels=False) 

[0, 1, 2, 3, 8] 

[(0, 1), (1, 2), (2, 3), (3, 8)] 

 

It also contains the claw `K_{1,3}`:: 

 

sage: h2 = g.subgraph_search(graphs.ClawGraph()); h2 

Subgraph of (Petersen graph): Graph on 4 vertices 

sage: h2.vertices(); h2.edges(labels=False) 

[0, 1, 4, 5] 

[(0, 1), (0, 4), (0, 5)] 

sage: I2 = g.subgraph_search(graphs.ClawGraph(), induced=True); I2 

Subgraph of (Petersen graph): Graph on 4 vertices 

sage: I2.vertices(); I2.edges(labels=False) 

[0, 1, 4, 5] 

[(0, 1), (0, 4), (0, 5)] 

 

Of course the induced copies are isomorphic to the graphs we were 

looking for:: 

 

sage: I1.is_isomorphic(graphs.PathGraph(5)) 

True 

sage: I2.is_isomorphic(graphs.ClawGraph()) 

True 

 

However, the Petersen graph does not contain a subgraph isomorphic to 

`K_3`:: 

 

sage: g.subgraph_search(graphs.CompleteGraph(3)) is None 

True 

 

Nor does it contain a nonempty induced subgraph isomorphic to `P_6`:: 

 

sage: g.subgraph_search(graphs.PathGraph(6), induced=True) is None 

True 

 

The empty graph is a subgraph of every graph:: 

 

sage: g.subgraph_search(graphs.EmptyGraph()) 

Graph on 0 vertices 

sage: g.subgraph_search(graphs.EmptyGraph(), induced=True) 

Graph on 0 vertices 

 

The subgraph may just have edges missing:: 

 

sage: k3=graphs.CompleteGraph(3); p3=graphs.PathGraph(3) 

sage: k3.relabel(list('abc')) 

sage: s=k3.subgraph_search(p3) 

sage: s.edges(labels=False) 

[('a', 'b'), ('b', 'c')] 

 

Of course, `P_3` is not an induced subgraph of `K_3`, though:: 

 

sage: k3=graphs.CompleteGraph(3); p3=graphs.PathGraph(3) 

sage: k3.relabel(list('abc')) 

sage: k3.subgraph_search(p3, induced=True) is None 

True 

 

TESTS: 

 

Inside of a small graph (:trac:`13906`):: 

 

sage: Graph(5).subgraph_search(Graph(1)) 

Graph on 1 vertex 

""" 

from sage.graphs.generic_graph_pyx import SubgraphSearch 

from sage.graphs.graph_generators import GraphGenerators 

 

if G.order() == 0: 

return GraphGenerators().EmptyGraph() 

 

# SubgraphSearch assumes the graph we are searching for has order at least 2. 

if G.order() == 1: 

if self.order() >= 1: 

from . import graph 

return graph.Graph({ self.vertices()[0]:[]}) 

else: 

return None 

 

S = SubgraphSearch(self, G, induced = induced) 

 

for g in S: 

if induced: 

return self.subgraph(g) 

else: 

Gcopy = copy(G) 

Gcopy.relabel(g) 

return self.subgraph(vertices=Gcopy.vertices(), edges=Gcopy.edges()) 

 

return None 

 

def subgraph_search_count(self, G, induced=False): 

r""" 

Return the number of labelled occurrences of ``G`` in ``self``. 

 

INPUT: 

 

- ``G`` -- the (di)graph whose copies we are looking for in 

``self``. 

 

- ``induced`` -- boolean (default: ``False``). Whether or not 

to count induced copies of ``G`` in ``self``. 

 

.. NOTE:: 

 

This method does not take vertex/edge labels into account. 

 

ALGORITHM: 

 

See the documentation of 

:class:`~sage.graphs.generic_graph_pyx.SubgraphSearch`. 

 

.. SEEALSO:: 

 

- :meth:`~GenericGraph.subgraph_search` -- finds an subgraph 

isomorphic to `H` inside of a graph `G` 

 

- :meth:`~GenericGraph.subgraph_search_iterator` -- Iterate on the 

copies of a graph `H` inside of a graph `G` 

 

EXAMPLES: 

 

Counting the number of paths `P_5` in a PetersenGraph:: 

 

sage: g = graphs.PetersenGraph() 

sage: g.subgraph_search_count(graphs.PathGraph(5)) 

240 

 

Requiring these subgraphs be induced:: 

 

sage: g.subgraph_search_count(graphs.PathGraph(5), induced = True) 

120 

 

If we define the graph `T_k` (the transitive tournament on `k` 

vertices) as the graph on `\{0, ..., k-1\}` such that `ij \in 

T_k` iif `i<j`, how many directed triangles can be found in 

`T_5` ? The answer is of course `0` :: 

 

sage: T5 = DiGraph() 

sage: T5.add_edges([(i,j) for i in range(5) for j in range(i+1, 5)]) 

sage: T5.subgraph_search_count(digraphs.Circuit(3)) 

0 

 

If we count instead the number of `T_3` in `T_5`, we expect 

the answer to be `\binom{5}{3}`:: 

 

sage: T3 = T5.subgraph([0,1,2]) 

sage: T5.subgraph_search_count(T3) 

10 

sage: binomial(5,3) 

10 

 

The empty graph is a subgraph of every graph:: 

 

sage: g.subgraph_search_count(graphs.EmptyGraph()) 

1 

 

TESTS: 

 

Inside of a small graph (:trac:`13906`):: 

 

sage: Graph(5).subgraph_search_count(Graph(1)) 

5 

""" 

from sage.graphs.generic_graph_pyx import SubgraphSearch 

 

if G.order() == 0: 

return 1 

 

if self.order() == 0: 

return 0 

 

if G.order() == 1: 

return self.order() 

 

S = SubgraphSearch(self, G, induced = induced) 

 

return S.cardinality() 

 

def subgraph_search_iterator(self, G, induced=False): 

r""" 

Returns an iterator over the labelled copies of ``G`` in ``self``. 

 

INPUT: 

 

- ``G`` -- the graph whose copies we are looking for in 

``self``. 

 

- ``induced`` -- boolean (default: ``False``). Whether or not 

to iterate over the induced copies of ``G`` in ``self``. 

 

.. NOTE:: 

 

This method does not take vertex/edge labels into account. 

 

ALGORITHM: 

 

See the documentation of 

:class:`~sage.graphs.generic_graph_pyx.SubgraphSearch`. 

 

OUTPUT: 

 

Iterator over the labelled copies of ``G`` in ``self``, as 

*lists*. For each value `(v_1, v_2, ..., v_k)` returned, 

the first vertex of `G` is associated with `v_1`, the 

second with `v_2`, etc ... 

 

.. NOTE:: 

 

This method also works on digraphs. 

 

.. SEEALSO:: 

 

- :meth:`~GenericGraph.subgraph_search` -- finds an subgraph 

isomorphic to `H` inside of `G` 

 

- :meth:`~GenericGraph.subgraph_search_count` -- Counts the number 

of copies of `H` inside of `G` 

 

EXAMPLES: 

 

Iterating through all the labelled `P_3` of `P_5`:: 

 

sage: g = graphs.PathGraph(5) 

sage: for p in g.subgraph_search_iterator(graphs.PathGraph(3)): 

....: print(p) 

[0, 1, 2] 

[1, 2, 3] 

[2, 1, 0] 

[2, 3, 4] 

[3, 2, 1] 

[4, 3, 2] 

 

TESTS: 

 

Inside of a small graph (:trac:`13906`):: 

 

sage: list(Graph(5).subgraph_search_iterator(Graph(1))) 

[Graph on 1 vertex, Graph on 1 vertex, Graph on 1 vertex, Graph on 1 vertex, Graph on 1 vertex] 

""" 

 

if G.order() == 0: 

from sage.graphs.graph_generators import GraphGenerators 

return [GraphGenerators().EmptyGraph()] 

 

elif self.order() == 0: 

return [] 

 

elif G.order() == 1: 

from . import graph 

return iter([graph.Graph({v:[]}) for v in self.vertices()]) 

else: 

from sage.graphs.generic_graph_pyx import SubgraphSearch 

return SubgraphSearch(self, G, induced = induced) 

 

def random_subgraph(self, p, inplace=False): 

""" 

Return a random subgraph that contains each vertex with prob. p. 

 

EXAMPLES:: 

 

sage: P = graphs.PetersenGraph() 

sage: P.random_subgraph(.25) 

Subgraph of (Petersen graph): Graph on 4 vertices 

""" 

vertices = [] 

p = float(p) 

for v in self: 

if random() < p: 

vertices.append(v) 

return self.subgraph(vertices=vertices, inplace=inplace) 

 

def is_chordal(self, certificate=False, algorithm="B"): 

r""" 

Tests whether the given graph is chordal. 

 

A Graph `G` is said to be chordal if it contains no induced hole (a 

cycle of length at least 4). 

 

Alternatively, chordality can be defined using a Perfect Elimination 

Order : 

 

A Perfect Elimination Order of a graph `G` is an ordering `v_1,...,v_n` 

of its vertex set such that for all `i`, the neighbors of `v_i` whose 

index is greater that `i` induce a complete subgraph in `G`. Hence, the 

graph `G` can be totally erased by successively removing vertices whose 

neighborhood is a clique (also called *simplicial* vertices) 

[Fulkerson65]_. 

 

(It can be seen that if `G` contains an induced hole, then it can not 

have a perfect elimination order. Indeed, if we write `h_1,...,h_k` the 

`k` vertices of such a hole, then the first of those vertices to be 

removed would have two non-adjacent neighbors in the graph.) 

 

A Graph is then chordal if and only if it has a Perfect Elimination 

Order. 

 

INPUT: 

 

- ``certificate`` (boolean) -- Whether to return a certificate. 

 

* If ``certificate = False`` (default), returns ``True`` or 

``False`` accordingly. 

 

* If ``certificate = True``, returns : 

 

* ``(True, peo)`` when the graph is chordal, where ``peo`` is a 

perfect elimination order of its vertices. 

 

* ``(False, Hole)`` when the graph is not chordal, where 

``Hole`` (a ``Graph`` object) is an induced subgraph of 

``self`` isomorphic to a hole. 

 

- ``algorithm`` -- Two algorithms are available for this method (see 

next section), which can be selected by setting ``algorithm = "A"`` or 

``algorithm = "B"`` (default). While they will agree on whether the 

given graph is chordal, they can not be expected to return the same 

certificates. 

 

ALGORITHM: 

 

This algorithm works through computing a Lex BFS on the graph, then 

checking whether the order is a Perfect Elimination Order by computing 

for each vertex `v` the subgraph induces by its non-deleted neighbors, 

then testing whether this graph is complete. 

 

This problem can be solved in `O(m)` [Rose75]_ ( where `m` is the number 

of edges in the graph ) but this implementation is not linear because of 

the complexity of Lex BFS. 

 

.. NOTE:: 

 

Because of a past bug (:trac:`11735`, :trac:`11961`), the 

first implementation (algorithm A) of this method 

sometimes returned as certificates subgraphs which were 

**not** holes. Since then, this bug has been fixed and the 

values are now double-checked before being returned, so 

that the algorithm only returns correct values or raises 

an exception. In the case where an exception is raised, 

the user is advised to switch to the other algorithm. And 

to **please** report the bug :-) 

 

EXAMPLES: 

 

The lexicographic product of a Path and a Complete Graph 

is chordal :: 

 

sage: g = graphs.PathGraph(5).lexicographic_product(graphs.CompleteGraph(3)) 

sage: g.is_chordal() 

True 

 

The same goes with the product of a random lobster 

( which is a tree ) and a Complete Graph :: 

 

sage: g = graphs.RandomLobster(10,.5,.5).lexicographic_product(graphs.CompleteGraph(3)) 

sage: g.is_chordal() 

True 

 

The disjoint union of chordal graphs is still chordal:: 

 

sage: (2*g).is_chordal() 

True 

 

Let us check the certificate given by Sage is indeed a perfect elimination order:: 

 

sage: (_, peo) = g.is_chordal(certificate = True) 

sage: for v in peo: 

....: if not g.subgraph(g.neighbors(v)).is_clique(): 

....: print("This should never happen !") 

....: g.delete_vertex(v) 

 

Of course, the Petersen Graph is not chordal as it has girth 5 :: 

 

sage: g = graphs.PetersenGraph() 

sage: g.girth() 

5 

sage: g.is_chordal() 

False 

 

We can even obtain such a cycle as a certificate :: 

 

sage: (_, hole) = g.is_chordal(certificate = True) 

sage: hole 

Subgraph of (Petersen graph): Graph on 5 vertices 

sage: hole.is_isomorphic(graphs.CycleGraph(5)) 

True 

 

TESTS: 

 

This shouldn't raise exceptions (:trac:`10899`):: 

 

sage: Graph(1).is_chordal() 

True 

sage: for g in graphs(5): 

....: _ = g.is_chordal() 

 

:trac:`11735`:: 

 

sage: g = Graph({3:[2,1,4],2:[1],4:[1],5:[2,1,4]}) 

sage: _, g1 = g.is_chordal(certificate=True); g1.is_chordal() 

False 

sage: g1.is_isomorphic(graphs.CycleGraph(g1.order())) 

True 

 

REFERENCES: 

 

.. [Rose75] Rose, D.J. and Tarjan, R.E., 

Algorithmic aspects of vertex elimination, 

Proceedings of seventh annual ACM symposium on Theory of computing 

Page 254, ACM 1975 

 

.. [Fulkerson65] Fulkerson, D.R. and Gross, OA 

Incidence matrices and interval graphs 

Pacific J. Math 1965 

Vol. 15, number 3, pages 835--855 

""" 

if algorithm not in ['A', 'B']: 

raise ValueError('unknown algorithm "{}"'.format(algorithm)) 

 

self._scream_if_not_simple() 

 

# If the graph is not connected, we are computing the result on each component 

if not self.is_connected(): 

 

# If the user wants a certificate, we had no choice but to 

# collect the perfect elimination orders... But we return 

# a hole immediately if we find any ! 

if certificate: 

peo = [] 

for gg in self.connected_components_subgraphs(): 

 

b, certif = gg.is_chordal(certificate = True) 

if not b: 

return False, certif 

else: 

peo.extend(certif) 

 

return True, peo 

 

# One line if no certificate is requested 

else: 

return all( gg.is_chordal() for gg in self.connected_components_subgraphs() ) 

 

hole = None 

g = copy(self) 

 

# Algorithms 

# 

# They find the perfect elimination ordering or produce a hole 

 

if algorithm == "A": 

 

peo,t_peo = self.lex_BFS(tree=True) 

peo.reverse() 

 

# Iteratively removing vertices and checking everything is fine. 

for v in peo: 

 

if t_peo.out_degree(v) == 0: 

g.delete_vertex(v) 

continue 

 

x = next(t_peo.neighbor_out_iterator(v)) 

S = self.neighbors(x)+[x] 

 

if not frozenset(g.neighbors(v)).issubset(S): 

 

# Do we need to return a hole ? 

if certificate: 

 

# In this case, let us take two nonadjacent neighbors of v 

# In this case, let us take two nonadjacent neighbors of 

# v. In order to do so, we pick a vertex y which is a 

# neighbor of v but is not adjacent to x, which we know 

# exists by the test written two lines above. 

 

for y in g.neighbors(v): 

if y not in S: 

break 

 

g.delete_vertices([vv for vv in g.neighbors(v) if vv != y and vv != x]) 

g.delete_vertex(v) 

 

# Our hole is v + (a shortest path between x and y not 

# containing v or any of its neighbors). 

 

hole = self.subgraph([v] + g.shortest_path(x,y)) 

 

# End of the algorithm 

break 

else: 

return False 

 

g.delete_vertex(v) 

 

elif algorithm == "B": 

 

peo,t_peo = self.lex_BFS(reverse=True, tree=True) 

 

# Remembering the (closed) neighborhoods of each vertex 

neighbors_subsets = dict([(v,self.neighbors(v)+[v]) for v in g]) 

pos_in_peo = dict(zip(peo, range(self.order()))) 

 

# Iteratively removing vertices and checking everything is fine. 

for v in reversed(peo): 

 

if (t_peo.out_degree(v)>0 and 

not frozenset([v1 for v1 in g.neighbors(v) if pos_in_peo[v1] > pos_in_peo[v]]).issubset( 

neighbors_subsets[next(t_peo.neighbor_out_iterator(v))])): 

 

# Do we need to return a hole ? 

if certificate: 

 

# In this case, let us take two nonadjacent neighbors of 

# v. In order to do so, we pick a vertex y which is a 

# neighbor of v but is not adjacent to x, which we know 

# exists by the test written two lines above. 

max_tup = (-1, 0) 

nb1 = [u for u in g.neighbors(v) if pos_in_peo[u] > pos_in_peo[v]] 

for xi in nb1: 

for yi in nb1: 

if not yi in neighbors_subsets[xi]: 

new_tup = (pos_in_peo[xi], pos_in_peo[yi]) 

if max_tup < new_tup: 

max_tup = new_tup 

x, y = xi, yi 

 

# Our hole is v + (a shortest path between x and y not 

# containing v or any of its neighbors). 

 

#g.delete_vertices([vv for vv in g.vertices() if pos_in_peo[vv] < pos_in_peo[v]]) 

 

g.delete_vertices([vv for vv in g.neighbors(v) if vv != y and vv != x]) 

g.delete_vertex(v) 

 

hole = self.subgraph([v] + g.shortest_path(x,y)) 

 

# End of the algorithm 

break 

else: 

return False 

 

 

# Returning values 

# ---------------- 

 

# 1- The graph is not chordal 

 

if not hole is None: 

# There was a bug there once, so it's better to check the 

# answer is valid, especially when it is so cheap ;-) 

 

if hole.order() <= 3 or not hole.is_regular(k=2): 

raise RuntimeError("the graph is not chordal, and something went wrong in the computation of the certificate. Please report this bug, providing the graph if possible!") 

 

return (False, hole) 

 

 

# 2- The graph is chordal 

if certificate: 

return True, peo 

 

else: 

return True 

 

def is_circulant(self, certificate = False): 

r""" 

Tests whether the graph is circulant. 

 

For more information on circulant graphs, see the 

:wikipedia:`Wikipedia page on circulant graphs 

<Circulant_graph>`. 

 

INPUT: 

 

- ``certificate`` (boolean) -- whether to return a certificate for 

yes-answers. See OUTPUT section. Set to ``False`` by default. 

 

OUTPUT: 

 

When ``certificate`` is set to ``False`` (default) this method only 

returns ``True`` or ``False`` answers. When ``certificate`` is set to 

``True``, the method either returns ``(False, None)`` or ``(True, 

lists_of_parameters)`` each element of ``lists_of_parameters`` can be 

used to define the graph as a circulant graph. 

 

See the documentation of 

:func:`~sage.graphs.graph_generators.GraphGenerators.CirculantGraph` and 

:meth:`~sage.graphs.digraph_generators.DiGraphGenerators.Circulant` for 

more information, and the examples below. 

 

.. SEEALSO:: 

 

:meth:`~sage.graphs.graph_generators.GraphGenerators.CirculantGraph` 

-- a constructor for circulant graphs. 

 

EXAMPLES: 

 

The Petersen graph is not a circulant graph:: 

 

sage: g = graphs.PetersenGraph() 

sage: g.is_circulant() 

False 

 

A cycle is obviously a circulant graph, but several sets of parameters 

can be used to define it:: 

 

sage: g = graphs.CycleGraph(5) 

sage: g.is_circulant(certificate = True) 

(True, [(5, [1, 4]), (5, [2, 3])]) 

 

The same goes for directed graphs:: 

 

sage: g = digraphs.Circuit(5) 

sage: g.is_circulant(certificate = True) 

(True, [(5, [1]), (5, [3]), (5, [2]), (5, [4])]) 

 

With this information, it is very easy to create (and plot) all possible 

drawings of a circulant graph:: 

 

sage: g = graphs.CirculantGraph(13, [2, 3, 10, 11]) 

sage: for param in g.is_circulant(certificate = True)[1]: 

....: graphs.CirculantGraph(*param) 

Circulant graph ([2, 3, 10, 11]): Graph on 13 vertices 

Circulant graph ([1, 5, 8, 12]): Graph on 13 vertices 

Circulant graph ([4, 6, 7, 9]): Graph on 13 vertices 

 

TESTS:: 

 

sage: digraphs.DeBruijn(3,1).is_circulant(certificate = True) 

(True, [(3, [0, 1, 2])]) 

sage: Graph(1).is_circulant(certificate = True) 

(True, (1, [])) 

sage: Graph(0).is_circulant(certificate = True) 

(True, (0, [])) 

sage: Graph({0:[0]}).is_circulant(certificate = True) 

(True, (1, [0])) 

""" 

self._scream_if_not_simple(allow_loops=True) 

# Stupid cases 

if self.order() <= 1: 

if certificate: 

return (True,(self.order(),[0] if self.size() else [])) 

else: 

return True 

 

certif_list = [] 

 

# The automorphism group, the translation between the vertices of self 

# and 1..n, and the orbits. 

ag, orbits = self.automorphism_group([self.vertices()], 

order=False, 

return_group=True, 

orbits=True) 

 

# Not transitive ? Not a circulant graph ! 

if len(orbits) != 1: 

return (False, None) if certificate else False 

 

# We go through all conjugacy classes of the automorphism 

# group, and only keep the cycles of length n 

for e in ag.conjugacy_classes_representatives(): 

cycles = e.cycle_tuples() 

 

# If the automorphism is not the identity and has exactly one 

# cycle that contains all vertices. 

if ((not cycles) or 

len(cycles[0]) != self.order()): 

continue 

 

# From now on the graph is a circulant graph ! 

 

if not certificate: 

return True 

 

# We build the list of integers defining the circulant graph, and 

# add it to the list. 

parameters = [] 

cycle = cycles[0] 

u = cycle[0] 

integers = [i for i,v in enumerate(cycle) if self.has_edge(u,v)] 

certif_list.append((self.order(),integers)) 

 

if not certificate: 

return False 

else: 

if certif_list: 

return (True, certif_list) 

else: 

return (False, None) 

 

def is_interval(self, certificate=False): 

r""" 

Check whether the graph is an interval graph. 

 

An *interval graph* is one where every vertex can be seen as 

an interval on the real line so that there is an edge in the graph 

iff the corresponding intervals intersects. 

 

See :wikipedia:`Interval_graph` for more information. 

 

INPUT: 

 

- ``certificate`` (boolean) -- The function returns ``True`` 

or ``False`` according to the graph when ``certificate=False`` 

(default). When ``certificate=True`` it returns either 

``(False, None)`` or ``(True, d)`` where ``d`` is a dictionary 

whose keys are the vertices and values are pairs of integers. 

They correspond to an embedding of the interval graph, each 

vertex being represented by an interval going from the first 

of the two values to the second. 

 

ALGORITHM: 

 

Through the use of PQ-Trees. 

 

AUTHOR: 

 

Nathann Cohen (implementation) 

 

EXAMPLES:: 

 

sage: g = Graph({1: [2, 3, 4], 4: [2, 3]}) 

sage: g.is_interval() 

True 

sage: g.is_interval(certificate=True) 

(True, {1: (0, 5), 2: (4, 6), 3: (1, 3), 4: (2, 7)}) 

 

The Petersen Graph is not chordal, so it can't be an interval 

graph:: 

 

sage: g = graphs.PetersenGraph() 

sage: g.is_interval() 

False 

 

A chordal but still not an interval graph:: 

 

sage: g = Graph({1: [4, 2, 3], 2: [3, 5], 3: [6]}) 

sage: g.is_interval() 

False 

 

.. SEEALSO:: 

 

- :mod:`Interval Graph Recognition <sage.graphs.pq_trees>`. 

 

- :meth:`PQ <sage.graphs.pq_trees.PQ>` 

-- Implementation of PQ-Trees. 

- :meth:`is_chordal` 

- :meth:`~sage.graphs.graph_generators.GraphGenerators.IntervalGraph` 

- :meth:`~sage.graphs.graph_generators.GraphGenerators.RandomIntervalGraph` 

 

TESTS:: 

 

sage: E = Graph() 

sage: E.is_interval() 

True 

sage: E.is_interval(certificate=True) 

(True, {}) 

 

sage: graphs.CycleGraph(4).is_interval(certificate=True) 

(False, None) 

 

Enumerate all small interval graphs (see :oeis:`A005975`):: 

 

sage: [sum(1 for g in graphs(i) if g.is_interval()) for i in range(8)] # long time 

[1, 1, 2, 4, 10, 27, 92, 369] 

 

Test certicate on a larger graph by re-doing isomorphic graph:: 

 

sage: g = Graph(':S__@_@A_@AB_@AC_@ACD_@ACDE_ACDEF_ACDEFG_ACDEGH_ACDEGHI_ACDEGHIJ_ACDEGIJK_ACDEGIJKL_ACDEGIJKLMaCEGIJKNaCEGIJKNaCGIJKNPaCIP', loops=False, multiedges=False) 

sage: d = g.is_interval(certificate=True)[1] 

sage: g2 = graphs.IntervalGraph(d.values()) 

sage: g2.is_isomorphic(g) 

True 

""" 

self._scream_if_not_simple() 

 

# An interval graph first is a chordal graph. Without this, 

# there is no telling how we should find its maximal cliques, 

# by the way :-) 

 

if not self.is_chordal(): 

return (False, None) if certificate else False 

 

# First, we need to gather the list of maximal cliques, which 

# is easy as the graph is chordal 

 

cliques = [] 

 

# As we will be deleting vertices ... 

g = copy(self) 

 

for cc in g.connected_components_subgraphs(): 

 

# We pick a perfect elimination order for every connected 

# component. We will then iteratively take the last vertex 

# in the order (a simplicial vertex) and consider the 

# clique it forms with its neighbors. If we do not have an 

# inclusion-wise larger clique in our list, we add it ! 

 

peo = cc.lex_BFS() 

 

while peo: 

v = peo.pop() 

clique = frozenset([v] + cc.neighbors(v)) 

cc.delete_vertex(v) 

 

if not any(clique.issubset(c) for c in cliques): 

cliques.append(clique) 

 

from sage.graphs.pq_trees import reorder_sets 

 

try: 

ordered_sets = reorder_sets(cliques) 

if not certificate: 

return True 

 

except ValueError: 

return (False, None) if certificate else False 

 

# We are now listing the maximal cliques in the given order, 

# and keeping track of the vertices appearing/disappearing 

 

current = set() 

beg = {} 

end = {} 

 

i = 0 

 

ordered_sets.append([]) 

for S in map(set,ordered_sets): 

for v in current-S: 

end[v] = i 

i = i + 1 

 

for v in S-current: 

beg[v] = i 

i = i + 1 

 

current = S 

 

return (True, {v: (beg[v], end[v]) for v in self}) 

 

def is_gallai_tree(self): 

r""" 

Returns whether the current graph is a Gallai tree. 

 

A graph is a Gallai tree if and only if it is 

connected and its `2`-connected components are all 

isomorphic to complete graphs or odd cycles. 

 

A connected graph is not degree-choosable if and 

only if it is a Gallai tree [erdos1978choos]_. 

 

REFERENCES: 

 

.. [erdos1978choos] Erdos, P. and Rubin, A.L. and Taylor, H. 

Proc. West Coast Conf. on Combinatorics 

Graph Theory and Computing, Congressus Numerantium 

vol 26, pages 125--157, 1979 

 

EXAMPLES: 

 

A complete graph is, or course, a Gallai Tree:: 

 

sage: g = graphs.CompleteGraph(15) 

sage: g.is_gallai_tree() 

True 

 

The Petersen Graph is not:: 

 

sage: g = graphs.PetersenGraph() 

sage: g.is_gallai_tree() 

False 

 

A Graph built from vertex-disjoint complete graphs 

linked by one edge to a special vertex `-1` is a 

''star-shaped'' Gallai tree :: 

 

sage: g = 8 * graphs.CompleteGraph(6) 

sage: g.add_edges([(-1,c[0]) for c in g.connected_components()]) 

sage: g.is_gallai_tree() 

True 

""" 

self._scream_if_not_simple() 

if not self.is_connected(): 

return False 

 

for c in self.blocks_and_cut_vertices()[0]: 

gg = self.subgraph(c) 

# is it an odd cycle ? a complete graph ? 

if not ( (len(c)%2 == 1 and gg.size() == len(c)+1) or gg.is_clique() ): 

return False 

 

return True 

 

def is_clique(self, vertices=None, directed_clique=False): 

""" 

Tests whether a set of vertices is a clique 

 

A clique is a set of vertices such that there is an edge between any two 

vertices. 

 

INPUT: 

 

- ``vertices`` - Vertices can be a single vertex or an 

iterable container of vertices, e.g. a list, set, graph, file or 

numeric array. If not passed, defaults to the entire graph. 

 

- ``directed_clique`` - (default False) If set to 

False, only consider the underlying undirected graph. If set to 

True and the graph is directed, only return True if all possible 

edges in _both_ directions exist. 

 

EXAMPLES:: 

 

sage: g = graphs.CompleteGraph(4) 

sage: g.is_clique([1,2,3]) 

True 

sage: g.is_clique() 

True 

sage: h = graphs.CycleGraph(4) 

sage: h.is_clique([1,2]) 

True 

sage: h.is_clique([1,2,3]) 

False 

sage: h.is_clique() 

False 

sage: i = graphs.CompleteGraph(4).to_directed() 

sage: i.delete_edge([0,1]) 

sage: i.is_clique() 

True 

sage: i.is_clique(directed_clique=True) 

False 

""" 

if directed_clique and self._directed: 

subgraph=self.subgraph(vertices, immutable = False) 

subgraph.allow_loops(False) 

subgraph.allow_multiple_edges(False) 

n=subgraph.order() 

return subgraph.size()==n*(n-1) 

else: 

if vertices is None: 

subgraph = self 

else: 

subgraph=self.subgraph(vertices) 

 

if self._directed: 

subgraph = subgraph.to_simple() 

 

n=subgraph.order() 

return subgraph.size()==n*(n-1)/2 

 

def is_cycle(self, directed_cycle=True): 

r""" 

Test whether ``self`` is a (directed) cycle graph. 

 

We follow the definition provided in [BM2008]_ for undirected graphs. A 

cycle on three or more vertices is a simple graph whose vertices can be 

arranged in a cyclic order so that two vertices are adjacent if they are 

consecutive in the order, and not adjacent otherwise. A cycle on a 

vertex consists of a single vertex provided with a loop and a cycle with 

two vertices consists of two vertices connected by a pair of parallel 

edges. In other words, an undirected graph is a cycle if it is 2-regular 

and connected. The empty graph is not a cycle. 

 

For directed graphs, a directed cycle, or circuit, on two or more 

vertices is a strongly connected directed graph without loops nor 

multiple edges with has many arcs as vertices. A circuit on a vertex 

consists of a single vertex provided with a loop. 

 

INPUT: 

 

- ``directed_cycle`` -- (default ``True``) If set to ``True`` and the 

graph is directed, only return ``True`` if ``self`` is a directed 

cycle graph (i.e., a circuit). If set to ``False``, we ignore the 

direction of edges and so opposite arcs become multiple (parallel) 

edges. This parameter is ignored for undirected graphs. 

 

EXAMPLES:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.is_cycle() 

False 

sage: graphs.CycleGraph(5).is_cycle() 

True 

sage: Graph([(0,1)]).is_cycle() 

False 

sage: Graph([(0,1), (0,1)], multiedges=True).is_cycle() 

True 

sage: Graph([(0,1), (0,1), (0,1)], multiedges=True).is_cycle() 

False 

sage: Graph().is_cycle() 

False 

sage: G = Graph(); G.allow_loops(True); G.add_edge(0,0) 

sage: G.is_cycle() 

True 

sage: digraphs.Circuit(3).is_cycle() 

True 

sage: digraphs.Circuit(2).is_cycle() 

True 

sage: digraphs.Circuit(2).is_cycle(directed_cycle = False) 

True 

sage: D = DiGraph( graphs.CycleGraph(3) ) 

sage: D.is_cycle() 

False 

sage: D.is_cycle(directed_cycle = False) 

False 

sage: D.edges(labels = False) 

[(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)] 

""" 

if not self.order(): 

# The empty graph is not a cycle 

return False 

elif self.order() == 1: 

# A (di)graph of order one is a cycle if it has a single loop 

return self.size() == 1 

 

g = self 

if g._directed: 

if directed_cycle: 

return g.order() == g.size() and g.is_strongly_connected() 

else: 

# We make a copy of self ignoring the direction of edges 

from sage.graphs.graph import Graph 

g = Graph(multiedges = True) 

g.allow_loops(True) 

g.add_edges(self.edges(labels=False)) 

 

return g.is_regular(k=2) and g.is_connected() 

 

def is_independent_set(self, vertices=None): 

""" 

Returns True if the set ``vertices`` is an independent 

set, False if not. An independent set is a set of vertices such 

that there is no edge between any two vertices. 

 

INPUT: 

 

- ``vertices`` - Vertices can be a single vertex or an 

iterable container of vertices, e.g. a list, set, graph, file or 

numeric array. If not passed, defaults to the entire graph. 

 

EXAMPLES:: 

 

sage: graphs.CycleGraph(4).is_independent_set([1,3]) 

True 

sage: graphs.CycleGraph(4).is_independent_set([1,2,3]) 

False 

""" 

return self.subgraph(vertices).size()==0 

 

def is_subgraph(self, other, induced=True): 

""" 

Return ``True`` if the graph is a subgraph of ``other``, and ``False`` otherwise. 

 

.. WARNING:: 

 

Please note that this method does not check whether ``self`` 

contains a subgraph *isomorphic* to ``other``, but only if it 

directly contains it as a subgraph ! 

 

By default ``induced`` is ``True`` for backwards compatibility. 

 

INPUT: 

 

- ``induced`` - boolean (default: ``True``) If set to ``True`` tests 

whether the graph is an *induced* subgraph of ``other`` that is if 

the vertices of the graph are also vertices of ``other``, and the 

edges of the graph are equal to the edges of ``other`` between the 

vertices contained in the graph. 

 

If set to ``False`` tests whether the graph is a subgraph of 

``other`` that is if all vertices of the graph are also in 

``other`` and all edges of the graph are also in ``other``. 

 

OUTPUT: 

 

boolean -- ``True`` iff the graph is a (possibly induced) 

subgraph of ``other``. 

 

.. SEEALSO:: 

 

If you are interested in the (possibly induced) subgraphs 

isomorphic to the graph in ``other``, you are looking for 

the following methods: 

 

- :meth:`~GenericGraph.subgraph_search` -- Find a subgraph 

isomorphic to ``other`` inside of the graph. 

 

- :meth:`~GenericGraph.subgraph_search_count` -- Count the number 

of such copies. 

 

- :meth:`~GenericGraph.subgraph_search_iterator` -- 

Iterate over all the copies of ``other`` contained in the graph. 

 

EXAMPLES:: 

 

sage: P = graphs.PetersenGraph() 

sage: G = P.subgraph(list(range(6))) 

sage: G.is_subgraph(P) 

True 

 

sage: H = graphs.CycleGraph(5) 

sage: G = graphs.PathGraph(5) 

sage: G.is_subgraph(H) 

False 

sage: G.is_subgraph(H, induced=False) 

True 

sage: H.is_subgraph(G, induced=False) 

False 

 

TESTS: 

 

Raise an error when self and other are of different types:: 

 

sage: Graph([(0,1)]).is_subgraph( DiGraph([(0,1)]) ) 

Traceback (most recent call last): 

... 

ValueError: The input parameter must be a Graph. 

sage: DiGraph([(0,1)]).is_subgraph( Graph([(0,1)]) ) 

Traceback (most recent call last): 

... 

ValueError: The input parameter must be a DiGraph. 

 

""" 

from sage.graphs.graph import Graph 

from sage.graphs.digraph import DiGraph 

if isinstance(self,Graph) and not isinstance(other,Graph): 

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

 

if isinstance(self,DiGraph) and not isinstance(other,DiGraph): 

raise ValueError('The input parameter must be a DiGraph.') 

 

if self.num_verts() > other.num_verts(): 

return False 

 

if any(v not in other for v in self.vertex_iterator()): 

return False 

 

if induced: 

return other.subgraph(self.vertices()) == self 

else: 

self._scream_if_not_simple(allow_loops=True) 

return all(other.has_edge(e) for e in self.edge_iterator()) 

 

### Cluster 

 

def cluster_triangles(self, nbunch=None, with_labels=False): 

r""" 

Returns the number of triangles for the set `nbunch` of vertices 

as a dictionary keyed by vertex. 

 

See also section "Clustering" in chapter "Algorithms" of [HSSNX]_. 

 

INPUT: 

 

- ``nbunch`` - The vertices to inspect. If ``nbunch=None``, 

returns data for all vertices in the graph. 

 

REFERENCE: 

 

.. [HSSNX] Aric Hagberg, Dan Schult and Pieter Swart. NetworkX 

documentation. [Online] Available: 

http://networkx.github.io/documentation/latest/reference/index.html 

 

EXAMPLES:: 

 

sage: list((graphs.FruchtGraph()).cluster_triangles().values()) 

[1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0] 

sage: (graphs.FruchtGraph()).cluster_triangles() 

{0: 1, 1: 1, 2: 0, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 0, 9: 1, 10: 1, 11: 0} 

sage: (graphs.FruchtGraph()).cluster_triangles(nbunch=[0,1,2]) 

{0: 1, 1: 1, 2: 0} 

""" 

import networkx 

return networkx.triangles(self.networkx_graph(copy=False), nbunch) 

 

def clustering_average(self, implementation=None): 

r""" 

Returns the average clustering coefficient. 

 

The clustering coefficient of a node `i` is the fraction 

of existing triangles containing node `i` and all 

possible triangles containing `i`: `c_i = T(i) / \binom {k_i} 2` 

where `T(i)` is the number of existing triangles through `i`, and 

`k_i` is the degree of vertex `i`. 

 

A coefficient for the whole graph is the average of the `c_i`. 

 

See also section "Clustering" in chapter "Algorithms" of [HSSNX]_. 

 

INPUT: 

 

- ``implementation`` - one of ``'boost'``, ``'sparse_copy'``, 

``'dense_copy'``, ``'networkx'`` or ``None`` (default). In the latter 

case, the best algorithm available is used. Note that only 

``'networkx'`` supports directed graphs. 

 

EXAMPLES:: 

 

sage: (graphs.FruchtGraph()).clustering_average() 

1/4 

sage: (graphs.FruchtGraph()).clustering_average(implementation='networkx') 

0.25 

 

TESTS: 

 

Boost does not work with DiGraph:: 

 

sage: digraphs.Circuit(10).clustering_average(implementation='boost') 

Traceback (most recent call last): 

... 

ValueError: This value of 'implementation' is invalid for directed graphs 

 

The result is the same with all implementations:: 

 

sage: G = graphs.RandomGNM(10,20) 

sage: coeffs = [G.clustering_average(implementation=impl) 

....: for impl in ['boost','sparse_copy','dense_copy','networkx']] 

sage: max(coeffs)-min(coeffs) # tol abs 1e-12 

0 

 

""" 

if implementation is None: 

from sage.graphs.base.dense_graph import DenseGraphBackend 

if self.is_directed(): 

implementation = 'networkx' 

elif isinstance(self._backend, DenseGraphBackend): 

implementation = 'dense_copy' 

else: 

implementation = 'sparse_copy' 

 

if not implementation in ['networkx','boost', 'dense_copy', 'sparse_copy']: 

raise ValueError("The implementation can only be 'networkx', " + 

"'boost', 'sparse_copy', 'dense_copy' or None.") 

 

if self.is_directed() and implementation != 'networkx': 

raise ValueError("This value of 'implementation' is invalid for directed graphs") 

 

if implementation == 'boost': 

from sage.graphs.base.boost_graph import clustering_coeff 

return clustering_coeff(self)[0] 

elif implementation == 'networkx': 

import networkx 

return networkx.average_clustering(self.networkx_graph(copy=False)) 

else: 

from sage.stats.basic_stats import mean 

return mean(self.clustering_coeff(implementation=implementation).values()) 

 

def clustering_coeff(self, 

nodes=None, 

weight=False, 

implementation=None): 

r""" 

Returns the clustering coefficient for each vertex in ``nodes`` as 

a dictionary keyed by vertex. 

 

For an unweighted graph, the clustering coefficient of a node `i` 

is the fraction of existing triangles containing node `i` and all 

possible triangles containing `i`: `c_i = T(i) / \binom {k_i} 2` 

where `T(i)` is the number of existing triangles through `i`, and 

`k_i` is the degree of vertex `i`. 

 

For weighted graphs the clustering is defined as the geometric 

average of the subgraph edge weights, normalized by the 

maximum weight in the network. 

 

The value of `c_i` is assigned `0` if `k_i < 2`. 

 

See also section "Clustering" in chapter "Algorithms" of [HSSNX]_. 

 

INPUT: 

 

- ``nodes`` - the vertices to inspect (default ``None``, returns data 

on all vertices in graph) 

 

- ``weight`` - string or boolean (default is ``False``). If it is 

a string it used the indicated edge property as weight. 

``weight = True`` is equivalent to ``weight = 'weight'`` 

 

- ``implementation`` - one of ``'boost'``, ``'sparse_copy'``, 

``'dense_copy'``, ``'networkx'`` or ``None`` (default). In the latter 

case, the best algorithm available is used. Note that only 

``'networkx'`` supports directed or weighted graphs, and that 

``'sparse_copy'`` and ``'dense_copy'`` do not support ``node`` 

different from ``None`` 

 

EXAMPLES:: 

 

sage: graphs.FruchtGraph().clustering_coeff() 

{0: 1/3, 1: 1/3, 2: 0, 3: 1/3, 4: 1/3, 5: 1/3, 

6: 1/3, 7: 1/3, 8: 0, 9: 1/3, 10: 1/3, 11: 0} 

 

sage: (graphs.FruchtGraph()).clustering_coeff(weight=True) 

{0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.0, 

3: 0.3333333333333333, 4: 0.3333333333333333, 

5: 0.3333333333333333, 6: 0.3333333333333333, 

7: 0.3333333333333333, 8: 0.0, 9: 0.3333333333333333, 

10: 0.3333333333333333, 11: 0.0} 

 

sage: (graphs.FruchtGraph()).clustering_coeff(nodes=[0,1,2]) 

{0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.0} 

 

sage: (graphs.FruchtGraph()).clustering_coeff(nodes=[0,1,2], 

....: weight=True) 

{0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.0} 

 

sage: (graphs.GridGraph([5,5])).clustering_coeff(nodes=[(0,0),(0,1),(2,2)]) 

{(0, 0): 0.0, (0, 1): 0.0, (2, 2): 0.0} 

 

TESTS: 

 

Boost does not work with weights:: 

 

sage: graphs.FruchtGraph().clustering_coeff(implementation='boost', weight=True) 

Traceback (most recent call last): 

... 

ValueError: This value of 'implementation' is invalid for directed/weighted graphs 

 

Boost does not work with DiGraph:: 

 

sage: digraphs.Circuit(10).clustering_coeff(implementation='boost') 

Traceback (most recent call last): 

... 

ValueError: This value of 'implementation' is invalid for directed/weighted graphs 

 

Check that the result is the same with all implementations:: 

 

sage: G = graphs.RandomGNM(10,20) 

sage: G.relabel(list("abcdefghik")) 

sage: coeffs = [G.clustering_coeff(implementation=impl) 

....: for impl in ['boost','sparse_copy','dense_copy','networkx']] 

sage: for v in G: 

....: coeffs_v = [c[v] for c in coeffs] 

....: if max(coeffs_v) - min(coeffs_v) > 1E-12: 

....: print("Error for v=",v) 

....: print("min=",min(coeffs_v),"max=",max(coeffs_v)) 

 

TESTS:: 

 

sage: graphs.EmptyGraph().clustering_coeff() 

{} 

""" 

from sage.rings.integer import Integer 

 

if implementation is None: 

from sage.graphs.base.dense_graph import DenseGraphBackend 

if self.is_directed() or weight: 

implementation = 'networkx' 

elif nodes is not None: 

implementation = 'boost' 

elif isinstance(self._backend, DenseGraphBackend): 

implementation = 'dense_copy' 

else: 

implementation = 'sparse_copy' 

 

if not implementation in ['networkx','boost', 'dense_copy', 'sparse_copy']: 

raise ValueError("The implementation can only be 'networkx', " + 

"'boost', 'sparse_copy', 'dense_copy' or None.") 

 

if ((self.is_directed() or weight) and 

implementation != 'networkx'): 

raise ValueError("This value of 'implementation' is invalid for directed/weighted graphs") 

 

if (implementation in ['sparse_copy','dense_copy'] and nodes is not None): 

raise ValueError("'sparse_copy','dense_copy' do not support 'nodes' different from 'None'") 

 

if self.order() == 0: 

return {} 

 

def coeff_from_triangle_count(v,count): 

dv = self.degree(v) 

if dv < 2: 

return 0 

return 2*count/Integer(dv*(dv-1)) 

 

if implementation == 'boost': 

from sage.graphs.base.boost_graph import clustering_coeff 

return clustering_coeff(self, nodes)[1] 

elif implementation == 'networkx': 

import networkx 

return networkx.clustering(self.networkx_graph(copy=False), nodes, weight=weight) 

elif implementation == 'sparse_copy': 

from sage.graphs.base.static_sparse_graph import triangles_count 

return {v:coeff_from_triangle_count(v,count) 

for v,count in iteritems(triangles_count(self))} 

elif implementation =="dense_copy": 

from sage.graphs.base.static_dense_graph import triangles_count 

return {v:coeff_from_triangle_count(v,count) 

for v,count in iteritems(triangles_count(self))} 

 

def cluster_transitivity(self): 

r""" 

Returns the transitivity (fraction of transitive triangles) of the 

graph. 

 

Transitivity is the fraction of all existing triangles and all 

connected triples (triads), `T = 3\times\text{triangles} 

/ \text{triads}`. 

 

See also section "Clustering" in chapter "Algorithms" of [HSSNX]_. 

 

EXAMPLES:: 

 

sage: (graphs.FruchtGraph()).cluster_transitivity() 

0.25 

""" 

import networkx 

return networkx.transitivity(self.networkx_graph(copy=False)) 

 

### Distance 

 

def distance(self, u, v, by_weight=False): 

""" 

Returns the (directed) distance from u to v in the (di)graph, i.e. 

the length of the shortest path from u to v. 

 

This method simply calls 

:meth:`~GenericGraph.shortest_path_length`, 

with default arguments. For more information, and for more option, we 

refer to that method. 

 

INPUT: 

 

- ``by_weight`` - if ``False``, the graph is considered unweighted, and 

the distance is the number of edges in a shortest path. If ``True``, 

the distance is the sum of edge labels (which are assumed to be 

numbers). 

 

EXAMPLES:: 

 

sage: G = graphs.CycleGraph(9) 

sage: G.distance(0,1) 

1 

sage: G.distance(0,4) 

4 

sage: G.distance(0,5) 

4 

sage: G = Graph({0:[], 1:[]}) 

sage: G.distance(0,1) 

+Infinity 

sage: G = Graph({ 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2}}, sparse = True) 

sage: G.plot(edge_labels=True).show() # long time 

sage: G.distance(0, 3) 

2 

sage: G.distance(0, 3, by_weight=True) 

3 

""" 

return self.shortest_path_length(u, v, by_weight = by_weight) 

 

def distance_all_pairs(self, by_weight=False, algorithm=None, 

weight_function=None, check_weight=True): 

r""" 

Returns the distances between all pairs of vertices. 

 

INPUT: 

 

- ``by_weight`` (boolean) - if ``True``, the edges in the graph are 

weighted; if ``False``, all edges have weight 1. 

 

- ``algorithm`` (string) - one of the following algorithms: 

 

- ``'BFS'`` - the computation is done through a BFS centered on each 

vertex successively. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Cython'`` - the Cython implementation of 

the Floyd-Warshall algorithm. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Python'`` - the Python implementation of 

the Floyd-Warshall algorithm. Works also with weighted graphs, even 

with negative weights (but no negative cycle is allowed). 

 

- ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in 

NetworkX. It works with weighted graphs, but no negative weight is 

allowed. 

 

- ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost 

(works only with positive weights). 

 

- ``'Johnson_Boost'``: the Johnson algorithm, implemented in 

Boost (works also with negative weights, if there is no negative 

cycle). 

 

- ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` if 

``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are 

positive, ``'Floyd-Warshall-Cython'`` otherwise. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

- ``check_weight`` (boolean) - if ``True``, we check that the 

weight_function outputs a number for each edge. 

 

OUTPUT: 

 

A doubly indexed dictionary 

 

.. NOTE:: 

 

There is a Cython version of this method that is usually 

much faster for large graphs, as most of the time is 

actually spent building the final double 

dictionary. Everything on the subject is to be found in the 

:mod:`~sage.graphs.distances_all_pairs` module. 

 

.. NOTE:: 

 

This algorithm simply calls 

:meth:`GenericGraph.shortest_path_all_pairs`, and we suggest to look 

at that method for more information and examples. 

 

EXAMPLES: 

 

The Petersen Graph:: 

 

sage: g = graphs.PetersenGraph() 

sage: print(g.distance_all_pairs()) 

{0: {0: 0, 1: 1, 2: 2, 3: 2, 4: 1, 5: 1, 6: 2, 7: 2, 8: 2, 9: 2}, 1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 2, 5: 2, 6: 1, 7: 2, 8: 2, 9: 2}, 2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 2, 5: 2, 6: 2, 7: 1, 8: 2, 9: 2}, 3: {0: 2, 1: 2, 2: 1, 3: 0, 4: 1, 5: 2, 6: 2, 7: 2, 8: 1, 9: 2}, 4: {0: 1, 1: 2, 2: 2, 3: 1, 4: 0, 5: 2, 6: 2, 7: 2, 8: 2, 9: 1}, 5: {0: 1, 1: 2, 2: 2, 3: 2, 4: 2, 5: 0, 6: 2, 7: 1, 8: 1, 9: 2}, 6: {0: 2, 1: 1, 2: 2, 3: 2, 4: 2, 5: 2, 6: 0, 7: 2, 8: 1, 9: 1}, 7: {0: 2, 1: 2, 2: 1, 3: 2, 4: 2, 5: 1, 6: 2, 7: 0, 8: 2, 9: 1}, 8: {0: 2, 1: 2, 2: 2, 3: 1, 4: 2, 5: 1, 6: 1, 7: 2, 8: 0, 9: 2}, 9: {0: 2, 1: 2, 2: 2, 3: 2, 4: 1, 5: 2, 6: 1, 7: 1, 8: 2, 9: 0}} 

 

Testing on Random Graphs:: 

 

sage: g = graphs.RandomGNP(20,.3) 

sage: distances = g.distance_all_pairs() 

sage: all([g.distance(0,v) == distances[0][v] for v in g]) 

True 

 

.. SEEALSO:: 

 

* :meth:`~sage.graphs.generic_graph.GenericGraph.distance_matrix` 

* :meth:`~sage.graphs.generic_graph.GenericGraph.shortest_path_all_pairs` 

""" 

return self.shortest_path_all_pairs(by_weight=by_weight, 

algorithm=algorithm, 

weight_function=weight_function, 

check_weight=check_weight)[0] 

 

def eccentricity(self, v=None, by_weight=False, algorithm=None, 

weight_function=None, check_weight=True, dist_dict=None, 

with_labels=False): 

""" 

Return the eccentricity of vertex (or vertices) v. 

 

The eccentricity of a vertex is the maximum distance to any other 

vertex. 

 

For more information and examples on how to use input variables, see 

:meth:`~GenericGraph.shortest_paths` 

 

INPUT: 

 

- ``v`` - either a single vertex or a list of vertices. If it is not 

specified, then it is taken to be all vertices. 

 

- ``by_weight`` - if ``True``, edge weights are taken into account; if 

False, all edges have weight 1. 

 

- ``algorithm`` (string) - one of the following algorithms: 

 

- ``'BFS'`` - the computation is done through a BFS centered on each 

vertex successively. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Cython'`` - a Cython implementation of 

the Floyd-Warshall algorithm. Works only if ``by_weight==False`` 

and ``v is None``. 

 

- ``'Floyd-Warshall-Python'`` - a Python implementation of 

the Floyd-Warshall algorithm. Works also with weighted graphs, even 

with negative weights (but no negative cycle is allowed). However, 

``v`` must be ``None``. 

 

- ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in 

NetworkX. It works with weighted graphs, but no negative weight is 

allowed. 

 

- ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost 

(works only with positive weights). 

 

- ``'Johnson_Boost'``: the Johnson algorithm, implemented in 

Boost (works also with negative weights, if there is no negative 

cycle). 

 

- ``'From_Dictionary'``: uses the (already computed) distances, that 

are provided by input variable ``dist_dict``. 

 

- ``None`` (default): Sage chooses the best algorithm: 

``'From_Dictionary'`` if ``dist_dict`` is not None, ``'BFS'`` for 

unweighted graphs, ``'Dijkstra_Boost'`` if all weights are 

positive, ``'Johnson_Boost'`` otherwise. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

- ``check_weight`` (boolean) - if ``True``, we check that the 

weight_function outputs a number for each edge. 

 

- ``dist_dict`` - used only if ``algorithm=='From_Dictionary'`` - a dict 

of dicts of distances. 

 

- ``with_labels`` - Whether to return a list or a dict. 

 

EXAMPLES:: 

 

sage: G = graphs.KrackhardtKiteGraph() 

sage: G.eccentricity() 

[4, 4, 4, 4, 4, 3, 3, 2, 3, 4] 

sage: G.vertices() 

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

sage: G.eccentricity(7) 

2 

sage: G.eccentricity([7,8,9]) 

[3, 4, 2] 

sage: G.eccentricity([7,8,9], with_labels=True) == {8: 3, 9: 4, 7: 2} 

True 

sage: G = Graph( { 0 : [], 1 : [], 2 : [1] } ) 

sage: G.eccentricity() 

[+Infinity, +Infinity, +Infinity] 

sage: G = Graph({0:[]}) 

sage: G.eccentricity(with_labels=True) 

{0: 0} 

sage: G = Graph({0:[], 1:[]}) 

sage: G.eccentricity(with_labels=True) 

{0: +Infinity, 1: +Infinity} 

sage: G = Graph([(0,1,1), (1,2,1), (0,2,3)]) 

sage: G.eccentricity(algorithm = 'BFS') 

[1, 1, 1] 

sage: G.eccentricity(algorithm = 'Floyd-Warshall-Cython') 

[1, 1, 1] 

sage: G.eccentricity(by_weight = True, algorithm = 'Dijkstra_NetworkX') 

[2, 1, 2] 

sage: G.eccentricity(by_weight = True, algorithm = 'Dijkstra_Boost') 

[2, 1, 2] 

sage: G.eccentricity(by_weight = True, algorithm = 'Johnson_Boost') 

[2, 1, 2] 

sage: G.eccentricity(by_weight = True, algorithm = 'Floyd-Warshall-Python') 

[2, 1, 2] 

sage: G.eccentricity(dist_dict = G.shortest_path_all_pairs(by_weight = True)[0]) 

[2, 1, 2] 

 

TESTS: 

 

A non-implemented algorithm:: 

 

sage: G.eccentricity(algorithm = 'boh') 

Traceback (most recent call last): 

... 

ValueError: unknown algorithm "boh" 

 

An algorithm that does not work with edge weights:: 

 

sage: G.eccentricity(by_weight = True, algorithm = 'BFS') 

Traceback (most recent call last): 

... 

ValueError: Algorithm 'BFS' does not work with weights. 

sage: G.eccentricity(by_weight = True, algorithm = 'Floyd-Warshall-Cython') 

Traceback (most recent call last): 

... 

ValueError: Algorithm 'Floyd-Warshall-Cython' does not work with weights. 

 

An algorithm that computes the all-pair-shortest-paths when not all 

vertices are needed:: 

 

sage: G.eccentricity(0, algorithm = 'Floyd-Warshall-Cython') 

Traceback (most recent call last): 

... 

ValueError: Algorithm 'Floyd-Warshall-Cython' works only if all eccentricities are needed. 

sage: G.eccentricity(0, algorithm = 'Floyd-Warshall-Python') 

Traceback (most recent call last): 

... 

ValueError: Algorithm 'Floyd-Warshall-Python' works only if all eccentricities are needed. 

sage: G.eccentricity(0, algorithm = 'Johnson_Boost') 

Traceback (most recent call last): 

... 

ValueError: Algorithm 'Johnson_Boost' works only if all eccentricities are needed. 

""" 

if weight_function is not None: 

by_weight = True 

elif by_weight: 

weight_function = lambda e:e[2] 

 

if algorithm is None: 

if dist_dict is not None: 

algorithm='From_Dictionary' 

elif not by_weight: 

algorithm='BFS' 

else: 

for e in self.edge_iterator(): 

try: 

if float(weight_function(e)) < 0: 

algorithm='Johnson_Boost' 

break 

except (ValueError, TypeError): 

raise ValueError("The weight function cannot find the" + 

" weight of " + str(e) + ".") 

if algorithm is None: 

algorithm='Dijkstra_Boost' 

 

if v is None: 

# If we want to use BFS, we use the Cython routine 

if algorithm=='BFS': 

if by_weight: 

raise ValueError("Algorithm 'BFS' does not work with weights.") 

from sage.graphs.distances_all_pairs import eccentricity 

 

if with_labels: 

return dict(zip(self.vertices(), eccentricity(self))) 

else: 

return eccentricity(self, algorithm='standard' if self.is_directed() else 'bounds') 

if algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost']: 

dist_dict = self.shortest_path_all_pairs(by_weight, algorithm, 

weight_function, 

check_weight)[0] 

algorithm='From_Dictionary' 

v = self.vertices() 

elif algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost']: 

raise ValueError("Algorithm '" + algorithm + "' works only if all" + 

" eccentricities are needed.") 

 

if not isinstance(v, list): 

v = [v] 

e = {} 

 

from sage.rings.infinity import Infinity 

 

for u in v: 

if algorithm=='From_Dictionary': 

length = dist_dict[u] 

else: 

# If algorithm is wrong, the error is raised by the 

# shortest_path_lengths function 

length = self.shortest_path_lengths(u, by_weight=by_weight, 

algorithm=algorithm, weight_function=weight_function, 

check_weight=check_weight) 

 

if len(length) != self.num_verts(): 

e[u] = Infinity 

else: 

e[u] = max(length.values()) 

 

if with_labels: 

return e 

else: 

if len(e) == 1: 

v, = e.values() 

return v # return single value 

return e.values() 

 

def radius(self, by_weight=False, algorithm=None, weight_function=None, 

check_weight=True): 

r""" 

Returns the radius of the (di)graph. 

 

The radius is defined to be the minimum eccentricity of any vertex, 

where the eccentricity is the maximum distance to any other 

vertex. For more information and examples on how to use input variables, 

see :meth:`~GenericGraph.shortest_paths` and 

:meth:`~GenericGraph.eccentricity` 

 

INPUT: 

 

- ``by_weight`` - if ``True``, edge weights are taken into account; if 

False, all edges have weight 1. 

 

- ``algorithm`` (string) - one of the following algorithms: 

 

- ``'BFS'`` - the computation is done through a BFS centered on each 

vertex successively. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Cython'`` - a Cython implementation of 

the Floyd-Warshall algorithm. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Python'`` - a Python implementation of 

the Floyd-Warshall algorithm. Works also with weighted graphs, even 

with negative weights (but no negative cycle is allowed). 

 

- ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in 

NetworkX. It works with weighted graphs, but no negative weight is 

allowed. 

 

- ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost 

(works only with positive weights). 

 

- ``'Johnson_Boost'``: the Johnson algorithm, implemented in 

Boost (works also with negative weights, if there is no negative 

cycle). 

 

- ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` for 

unweighted graphs, ``'Dijkstra_Boost'`` if all weights are 

positive, ``'Johnson_Boost'``, otherwise. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

- ``check_weight`` (boolean) - if ``True``, we check that the 

weight_function outputs a number for each edge. 

 

EXAMPLES: The more symmetric a graph is, the smaller (diameter - 

radius) is. 

 

:: 

 

sage: G = graphs.BarbellGraph(9, 3) 

sage: G.radius() 

3 

sage: G.diameter() 

6 

 

:: 

 

sage: G = graphs.OctahedralGraph() 

sage: G.radius() 

2 

sage: G.diameter() 

2 

 

TESTS:: 

 

sage: g = Graph() 

sage: g.radius() 

Traceback (most recent call last): 

... 

ValueError: radius is not defined for the empty graph 

""" 

if self.order() == 0: 

raise ValueError("radius is not defined for the empty graph") 

 

return min(self.eccentricity(by_weight=by_weight, 

weight_function=weight_function, 

check_weight=check_weight, 

algorithm=algorithm)) 

 

def diameter(self, by_weight=False, algorithm = None, weight_function=None, 

check_weight=True): 

r""" 

Returns the diameter of the (di)graph. 

 

The diameter is defined to be the maximum distance between two vertices. 

It is infinite if the (di)graph is not (strongly) connected. 

 

For more information and examples on how to use input variables, 

see :meth:`~GenericGraph.shortest_paths` and 

:meth:`~GenericGraph.eccentricity` 

 

INPUT: 

 

- ``by_weight`` - if ``True``, edge weights are taken into account; if 

False, all edges have weight 1. 

 

- ``algorithm`` (string) - one of the following algorithms: 

 

- ``'BFS'`` - the computation is done through a BFS centered on each 

vertex successively. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Cython'`` - a Cython implementation of 

the Floyd-Warshall algorithm. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Python'`` - a Python implementation of 

the Floyd-Warshall algorithm. Works also with weighted graphs, even 

with negative weights (but no negative cycle is allowed). 

 

- ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in 

NetworkX. It works with weighted graphs, but no negative weight is 

allowed. 

 

- ``'standard'``, ``'2sweep'``, ``'multi-sweep'``, ``'iFUB'``: these 

algorithms are implemented in 

:func:`sage.graphs.distances_all_pairs.diameter` 

They work only if ``by_weight==False``. See the function 

documentation for more information. 

 

- ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost 

(works only with positive weights). 

 

- ``'Johnson_Boost'``: the Johnson algorithm, implemented in 

Boost (works also with negative weights, if there is no negative 

cycle). 

 

- ``None`` (default): Sage chooses the best algorithm: ``'iFUB'`` if 

``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are 

positive, ``'Johnson_Boost'``, otherwise. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

- ``check_weight`` (boolean) - if ``True``, we check that the 

weight_function outputs a number for each edge. 

 

EXAMPLES: The more symmetric a graph is, the smaller (diameter - 

radius) is. 

 

:: 

 

sage: G = graphs.BarbellGraph(9, 3) 

sage: G.radius() 

3 

sage: G.diameter() 

6 

 

:: 

 

sage: G = graphs.OctahedralGraph() 

sage: G.radius() 

2 

sage: G.diameter() 

2 

 

TESTS:: 

 

sage: g = Graph() 

sage: g.diameter() 

Traceback (most recent call last): 

... 

ValueError: diameter is not defined for the empty graph 

sage: g = Graph([(1,2,{'weight':1})]) 

sage: g.diameter(algorithm='iFUB', weight_function=lambda e:e[2]['weight']) 

Traceback (most recent call last): 

... 

ValueError: Algorithm 'iFUB' does not work on weighted graphs. 

""" 

if self.order() == 0: 

raise ValueError("diameter is not defined for the empty graph") 

 

if weight_function is not None: 

by_weight = True 

 

if algorithm is None and not by_weight: 

algorithm = 'iFUB' 

elif algorithm=='BFS': 

algorithm = 'standard' 

 

if algorithm in ['standard', '2sweep', 'multi-sweep', 'iFUB']: 

if by_weight: 

raise ValueError("Algorithm '" + algorithm + "' does not work" + 

" on weighted graphs.") 

from .distances_all_pairs import diameter 

return diameter(self, algorithm=algorithm) 

 

return max(self.eccentricity(by_weight=by_weight, 

weight_function=weight_function, 

check_weight=check_weight, 

algorithm=algorithm)) 

 

def center(self, by_weight=False, algorithm=None, weight_function=None, 

check_weight=True): 

r""" 

Returns the set of vertices in the center, i.e. whose eccentricity 

is equal to the radius of the (di)graph. 

 

In other words, the center is the set of vertices achieving the 

minimum eccentricity. 

 

For more information and examples on how to use input variables, 

see :meth:`~GenericGraph.shortest_paths` and 

:meth:`~GenericGraph.eccentricity` 

 

INPUT: 

 

- ``by_weight`` - if ``True``, edge weights are taken into account; if 

False, all edges have weight 1. 

 

- ``algorithm`` (string) - one of the following algorithms: 

 

- ``'BFS'`` - the computation is done through a BFS centered on each 

vertex successively. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Cython'`` - a Cython implementation of 

the Floyd-Warshall algorithm. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Python'`` - a Python implementation of 

the Floyd-Warshall algorithm. Works also with weighted graphs, even 

with negative weights (but no negative cycle is allowed). 

 

- ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in 

NetworkX. It works with weighted graphs, but no negative weight is 

allowed. 

 

- ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost 

(works only with positive weights). 

 

- ``'Johnson_Boost'``: the Johnson algorithm, implemented in 

Boost (works also with negative weights, if there is no negative 

cycle). 

 

- ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` for 

unweighted graphs, ``'Dijkstra_Boost'`` if all weights are 

positive, ``'Johnson_Boost'``, otherwise. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

- ``check_weight`` (boolean) - if ``True``, we check that the 

weight_function outputs a number for each edge. 

 

EXAMPLES: 

 

Is Central African Republic in the center of Africa in graph theoretic 

sense? Yes:: 

 

sage: A = graphs.AfricaMap(continental=True) 

sage: sorted(A.center()) 

['Cameroon', 'Central Africa'] 

 

Some other graphs. Center can be the whole graph:: 

 

sage: G = graphs.DiamondGraph() 

sage: G.center() 

[1, 2] 

sage: P = graphs.PetersenGraph() 

sage: P.subgraph(P.center()) == P 

True 

sage: S = graphs.StarGraph(19) 

sage: S.center() 

[0] 

 

TESTS:: 

 

sage: G = Graph() 

sage: G.center() 

[] 

sage: G.add_vertex() 

0 

sage: G.center() 

[0] 

""" 

e = self.eccentricity(by_weight=by_weight, 

weight_function=weight_function, 

algorithm=algorithm, 

check_weight=check_weight, 

with_labels=True) 

try: 

r = min(e.values()) 

except Exception: 

return [] 

return [v for v in e if e[v]==r] 

 

 

def distance_graph(self, dist): 

r""" 

Returns the graph on the same vertex set as 

the original graph but vertices are adjacent 

in the returned graph if and only if they are 

at specified distances in the original graph. 

 

INPUT: 

 

- ``dist`` is a nonnegative integer or 

a list of nonnegative integers. 

``Infinity`` may be used here to describe 

vertex pairs in separate components. 

 

OUTPUT: 

 

The returned value is an undirected graph. The 

vertex set is identical to the calling graph, but edges 

of the returned graph join vertices whose distance in 

the calling graph are present in the input ``dist``. 

Loops will only be present if distance 0 is included. If 

the original graph has a position dictionary specifying 

locations of vertices for plotting, then this information 

is copied over to the distance graph. In some instances 

this layout may not be the best, and might even be confusing 

when edges run on top of each other due to symmetries 

chosen for the layout. 

 

EXAMPLES:: 

 

sage: G = graphs.CompleteGraph(3) 

sage: H = G.cartesian_product(graphs.CompleteGraph(2)) 

sage: K = H.distance_graph(2) 

sage: K.am() 

[0 0 0 1 0 1] 

[0 0 1 0 1 0] 

[0 1 0 0 0 1] 

[1 0 0 0 1 0] 

[0 1 0 1 0 0] 

[1 0 1 0 0 0] 

 

To obtain the graph where vertices are adjacent if their 

distance apart is ``d`` or less use a ``range()`` command 

to create the input, using ``d+1`` as the input to ``range``. 

Notice that this will include distance 0 and hence place a loop 

at each vertex. To avoid this, use ``range(1,d+1)``. :: 

 

sage: G = graphs.OddGraph(4) 

sage: d = G.diameter() 

sage: n = G.num_verts() 

sage: H = G.distance_graph(list(range(d+1))) 

sage: H.is_isomorphic(graphs.CompleteGraph(n)) 

False 

sage: H = G.distance_graph(list(range(1,d+1))) 

sage: H.is_isomorphic(graphs.CompleteGraph(n)) 

True 

 

A complete collection of distance graphs will have 

adjacency matrices that sum to the matrix of all ones. :: 

 

sage: P = graphs.PathGraph(20) 

sage: all_ones = sum([P.distance_graph(i).am() for i in range(20)]) 

sage: all_ones == matrix(ZZ, 20, 20, [1]*400) 

True 

 

Four-bit strings differing in one bit is the same as 

four-bit strings differing in three bits. :: 

 

sage: G = graphs.CubeGraph(4) 

sage: H = G.distance_graph(3) 

sage: G.is_isomorphic(H) 

True 

 

The graph of eight-bit strings, adjacent if different 

in an odd number of bits. :: 

 

sage: G = graphs.CubeGraph(8) # long time 

sage: H = G.distance_graph([1,3,5,7]) # long time 

sage: degrees = [0]*sum([binomial(8,j) for j in [1,3,5,7]]) # long time 

sage: degrees.append(2^8) # long time 

sage: degrees == H.degree_histogram() # long time 

True 

 

An example of using ``Infinity`` as the distance in 

a graph that is not connected. :: 

 

sage: G = graphs.CompleteGraph(3) 

sage: H = G.disjoint_union(graphs.CompleteGraph(2)) 

sage: L = H.distance_graph(Infinity) 

sage: L.am() 

[0 0 0 1 1] 

[0 0 0 1 1] 

[0 0 0 1 1] 

[1 1 1 0 0] 

[1 1 1 0 0] 

 

TESTS: 

 

Empty input, or unachievable distances silently yield empty graphs. :: 

 

sage: G = graphs.CompleteGraph(5) 

sage: G.distance_graph([]).num_edges() 

0 

sage: G = graphs.CompleteGraph(5) 

sage: G.distance_graph(23).num_edges() 

0 

 

It is an error to provide a distance that is not an integer type. :: 

 

sage: G = graphs.CompleteGraph(5) 

sage: G.distance_graph('junk') 

Traceback (most recent call last): 

... 

TypeError: unable to convert 'junk' to an integer 

 

It is an error to provide a negative distance. :: 

 

sage: G = graphs.CompleteGraph(5) 

sage: G.distance_graph(-3) 

Traceback (most recent call last): 

... 

ValueError: Distance graph for a negative distance (d=-3) is not defined 

 

AUTHOR: 

 

Rob Beezer, 2009-11-25 

""" 

from sage.rings.infinity import Infinity 

# If input is not a list, make a list with this single object 

if not isinstance(dist, list): 

dist = [dist] 

# Create a list of positive integer (or infinite) distances 

distances = [] 

for d in dist: 

if d == Infinity: 

distances.append(d) 

else: 

dint = ZZ(d) 

if dint < 0: 

raise ValueError('Distance graph for a negative distance (d=%d) is not defined' % dint) 

distances.append(dint) 

# Build a graph on the same vertex set, with loops for distance 0 

vertices = {} 

for v in self.vertex_iterator(): 

vertices[v] = {} 

positions = copy(self.get_pos()) 

if ZZ(0) in distances: 

looped = True 

else: 

looped = False 

from sage.graphs.all import Graph 

D = Graph(vertices, pos=positions, multiedges=False, loops=looped) 

if len(distances) == 1: 

dstring = "distance " + str(distances[0]) 

else: 

dstring = "distances " + str(sorted(distances)) 

D.name("Distance graph for %s in " % dstring + self.name()) 

 

# Create the appropriate edges 

d = self.distance_all_pairs() 

for u in self.vertex_iterator(): 

for v in self.vertex_iterator(): 

if d[u].get(v, Infinity) in distances: 

D.add_edge(u,v) 

return D 

 

def girth(self): 

""" 

Computes the girth of the graph. For directed graphs, computes the 

girth of the undirected graph. 

 

The girth is the length of the shortest cycle in the graph. Graphs 

without cycles have infinite girth. 

 

EXAMPLES:: 

 

sage: graphs.TetrahedralGraph().girth() 

3 

sage: graphs.CubeGraph(3).girth() 

4 

sage: graphs.PetersenGraph().girth() 

5 

sage: graphs.HeawoodGraph().girth() 

6 

sage: next(graphs.trees(9)).girth() 

+Infinity 

 

 

.. SEEALSO:: 

 

* :meth:`~sage.graphs.graph.Graph.odd_girth` -- computes 

the odd girth of a graph. 

 

TESTS: 

 

Prior to :trac:`12243`, the girth computation assumed 

vertices were integers (and failed). The example below 

tests the computation for graphs with vertices that are 

not integers. In this example the vertices are sets. :: 

 

sage: G = graphs.OddGraph(3) 

sage: type(G.vertices()[0]) 

<class 'sage.sets.set.Set_object_enumerated_with_category'> 

sage: G.girth() 

5 

 

Ticket :trac:`12355`:: 

 

sage: H=Graph([(0, 1), (0, 3), (0, 4), (0, 5), (1, 2), (1, 3), (1, 4), (1, 6), (2, 5), (3, 4), (5, 6)]) 

sage: H.girth() 

3 

 

Girth < 3 (see :trac:`12355`):: 

 

sage: g = graphs.PetersenGraph() 

sage: g.allow_multiple_edges(True) 

sage: g.allow_loops(True) 

sage: g.girth() 

5 

sage: g.add_edge(0,0) 

sage: g.girth() 

1 

sage: g.delete_edge(0,0) 

sage: g.add_edge(0,1) 

sage: g.girth() 

2 

sage: g.delete_edge(0,1) 

sage: g.girth() 

5 

sage: g = DiGraph(g) 

sage: g.girth() 

2 

""" 

 

# Cases where girth <= 2 

if self.has_loops(): 

return 1 

if self.is_directed(): 

if any(self.has_edge(v,u) for u,v in self.edges(labels = False)): 

return 2 

else: 

if self.has_multiple_edges(): 

return 2 

 

n = self.num_verts() 

best = n+1 

seen = {} 

for w in self.vertex_iterator(): 

seen[w] = None 

span = set([w]) 

depth = 1 

thisList = set([w]) 

while 2*depth <= best and 3 < best: 

nextList = set() 

for v in thisList: 

for u in self.neighbors(v): 

if u in seen: continue 

if not u in span: 

span.add(u) 

nextList.add(u) 

else: 

if u in thisList: 

best = depth*2-1 

break 

if u in nextList: 

best = depth*2 

if best == 2*depth-1: 

break 

thisList = nextList 

depth += 1 

if best == n+1: 

from sage.rings.infinity import Infinity 

return Infinity 

return best 

 

 

def periphery(self, by_weight=False, algorithm=None, weight_function=None, 

check_weight=True): 

r""" 

Returns the set of vertices in the periphery, i.e. whose 

eccentricity is equal to the diameter of the (di)graph. 

 

In other words, the periphery is the set of vertices achieving the 

maximum eccentricity. 

 

For more information and examples on how to use input variables, 

see :meth:`~GenericGraph.shortest_paths` and 

:meth:`~GenericGraph.eccentricity` 

 

INPUT: 

 

- ``by_weight`` - if ``True``, edge weights are taken into account; if 

False, all edges have weight 1. 

 

- ``algorithm`` (string) - one of the following algorithms: 

 

- ``'BFS'`` - the computation is done through a BFS centered on each 

vertex successively. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Cython'`` - a Cython implementation of 

the Floyd-Warshall algorithm. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Python'`` - a Python implementation of 

the Floyd-Warshall algorithm. Works also with weighted graphs, even 

with negative weights (but no negative cycle is allowed). 

 

- ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in 

NetworkX. It works with weighted graphs, but no negative weight is 

allowed. 

 

- ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost 

(works only with positive weights). 

 

- ``'Johnson_Boost'``: the Johnson algorithm, implemented in 

Boost (works also with negative weights, if there is no negative 

cycle). 

 

- ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` for 

unweighted graphs, ``'Dijkstra_Boost'`` if all weights are 

positive, ``'Johnson_Boost'``, otherwise. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

- ``check_weight`` (boolean) - if ``True``, we check that the 

weight_function outputs a number for each edge. 

 

EXAMPLES:: 

 

sage: G = graphs.DiamondGraph() 

sage: G.periphery() 

[0, 3] 

sage: P = graphs.PetersenGraph() 

sage: P.subgraph(P.periphery()) == P 

True 

sage: S = graphs.StarGraph(19) 

sage: S.periphery() 

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 

sage: G = Graph() 

sage: G.periphery() 

[] 

sage: G.add_vertex() 

0 

sage: G.periphery() 

[0] 

""" 

e = self.eccentricity(by_weight=by_weight, 

weight_function=weight_function, 

algorithm=algorithm, 

check_weight=check_weight, 

with_labels=True) 

try: 

r = max(e.values()) 

except Exception: 

return [] 

return [v for v in e if e[v]==r] 

 

### Centrality 

 

def centrality_betweenness(self, k=None, normalized=True, weight=None, 

endpoints=False, seed=None, exact=False, 

algorithm=None): 

r""" 

Returns the betweenness centrality (fraction of number of 

shortest paths that go through each vertex) as a dictionary 

keyed by vertices. The betweenness is normalized by default to 

be in range (0,1). 

 

Measures of the centrality of a vertex within a graph determine 

the relative importance of that vertex to its graph. Vertices 

that occur on more shortest paths between other vertices have 

higher betweenness than vertices that occur on less. 

 

INPUT: 

 

- ``normalized`` - boolean (default True) - if set to False, 

result is not normalized. 

 

- ``k`` - integer or None (default None) - if set to an integer, use 

``k`` node samples to estimate betweenness. Higher values give better 

approximations. Not available when ``algorithm="Sage"``. 

 

- ``weight`` - None or string. If set to a string, use that attribute of 

the nodes as weight. ``weight = True`` is equivalent to ``weight = 

"weight"``. Not available when ``algorithm="Sage"``. 

 

- ``endpoints`` - Boolean. If set to True it includes the endpoints in 

the shortest paths count. Not available when ``algorithm="Sage"``. 

 

- ``exact`` (boolean, default: ``False``) -- whether to compute over 

rationals or on ``double`` C variables. Not available when 

``algorithm="NetworkX"``. 

 

- ``algorithm`` (default: ``None``) -- can be either ``"Sage"`` (see 

:mod:`~sage.graphs.centrality`), ``"NetworkX"`` or ``"None"``. In the 

latter case, Sage's algorithm will be used whenever possible. 

 

.. SEEALSO:: 

 

- :meth:`~sage.graphs.graph.Graph.centrality_degree` 

- :meth:`~centrality_closeness` 

 

EXAMPLES:: 

 

sage: g = graphs.ChvatalGraph() 

sage: g.centrality_betweenness() # abs tol 1e-10 

{0: 0.06969696969696969, 1: 0.06969696969696969, 

2: 0.0606060606060606, 3: 0.0606060606060606, 

4: 0.06969696969696969, 5: 0.06969696969696969, 

6: 0.0606060606060606, 7: 0.0606060606060606, 

8: 0.0606060606060606, 9: 0.0606060606060606, 

10: 0.0606060606060606, 11: 0.0606060606060606} 

sage: g.centrality_betweenness(normalized=False) # abs tol 1e-10 

{0: 3.833333333333333, 1: 3.833333333333333, 2: 3.333333333333333, 

3: 3.333333333333333, 4: 3.833333333333333, 5: 3.833333333333333, 

6: 3.333333333333333, 7: 3.333333333333333, 8: 3.333333333333333, 

9: 3.333333333333333, 10: 3.333333333333333, 

11: 3.333333333333333} 

sage: D = DiGraph({0:[1,2,3], 1:[2], 3:[0,1]}) 

sage: D.show(figsize=[2,2]) 

sage: D = D.to_undirected() 

sage: D.show(figsize=[2,2]) 

sage: D.centrality_betweenness() # abs tol abs 1e-10 

{0: 0.16666666666666666, 1: 0.16666666666666666, 2: 0.0, 3: 0.0} 

 

TESTS:: 

 

sage: tests = ([graphs.RandomGNP(30,.1) for i in range(10)]+ 

....: [digraphs.RandomDirectedGNP(30,.1) for i in range(10)]) 

sage: for g in tests: 

....: r1 = g.centrality_betweenness(algorithm="Sage",exact=0) 

....: r2 = g.centrality_betweenness(algorithm="Sage",exact=1) 

....: r3 = g.centrality_betweenness(algorithm="NetworkX") 

....: for x in g: 

....: if max([r1[x],r2[x],r3[x]])-min([r1[x],r2[x],r3[x]]) > 0.01: 

....: print("Error",x,[r1[x],r2[x],r3[x]]) 

""" 

if algorithm == "NetworkX" and exact: 

raise ValueError("'exact' is not available with the NetworkX implementation") 

if (algorithm is None and 

seed is None and 

weight is None and 

endpoints is False and 

k is None): 

algorithm = "Sage" 

elif algorithm is None: 

algorithm = "NetworkX" 

 

if algorithm == "Sage": 

from .centrality import centrality_betweenness 

return centrality_betweenness(self, normalize = normalized,exact=exact) 

elif algorithm == "NetworkX": 

import networkx 

return networkx.betweenness_centrality(self.networkx_graph(copy=False), 

k=k, 

normalized=normalized, 

weight=weight, 

endpoints=endpoints, 

seed=seed) 

else: 

raise ValueError("'algorithm' can be \"NetworkX\", \"Sage\" or None") 

 

 

def centrality_closeness(self, vert=None, by_weight=False, algorithm=None, 

weight_function=None, check_weight=True): 

r""" 

Returns the closeness centrality of all vertices in variable ``vert``. 

 

In a (strongly) connected graph, the closeness centrality of a vertex 

`v` is equal 

to the inverse of the average distance between `v` and other vertices. 

If the graph is disconnected, the closeness centrality of `v` is 

multiplied by the fraction of reachable vertices in the graph: 

this way, central vertices should also reach several other vertices 

in the graph [OLJ14]_. In formulas, 

 

.. MATH:: 

 

c(v)=\frac{r(v)-1}{\sum_{w \in R(v)} d(v,w)}\frac{r(v)-1}{n-1} 

 

where `R(v)` is the set of vertices reachable from `v`, and 

`r(v)` is the cardinality of `R(v)`. 

 

'Closeness 

centrality may be defined as the total graph-theoretic distance of 

a given vertex from all other vertices... Closeness is an inverse 

measure of centrality in that a larger value indicates a less 

central actor while a smaller value indicates a more central 

actor,' [Borgatti95]_. 

 

For more information, see the :wikipedia:`Centrality`. 

 

INPUT: 

 

- ``vert`` - the vertex or the list of vertices we want to analyze. If 

``None`` (default), all vertices are considered. 

 

- ``by_weight`` (boolean) - if ``True``, the edges in the graph are 

weighted; if ``False``, all edges have weight 1. 

 

- ``algorithm`` (string) - one of the following algorithms: 

 

- ``'BFS'``: performs a BFS from each vertex that has to be analyzed. 

Does not work with edge weights. 

 

- ``'NetworkX'``: the NetworkX algorithm (works only with positive 

weights). 

 

- ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost 

(works only with positive weights). 

 

- ``'Floyd-Warshall-Cython'`` - the Cython implementation of 

the Floyd-Warshall algorithm. Works only if ``by_weight==False`` and 

all centralities are needed. 

 

- ``'Floyd-Warshall-Python'`` - the Python implementation of 

the Floyd-Warshall algorithm. Works only if all centralities are 

needed, but it can deal with weighted graphs, even 

with negative weights (but no negative cycle is allowed). 

 

- ``'Johnson_Boost'``: the Johnson algorithm, implemented in 

Boost (works also with negative weights, if there is no negative 

cycle). 

 

- ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` if 

``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are 

positive, ``'Johnson_Boost'`` otherwise. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

- ``check_weight`` (boolean) - if ``True``, we check that the 

weight_function outputs a number for each edge. 

 

OUTPUT: 

 

If ``vert`` is a vertex, the closeness centrality of that vertex. 

Otherwise, a dictionary associating to each vertex in ``vert`` its 

closeness centrality. If a vertex has (out)degree 0, its closeness 

centrality is not defined, and the vertex is not included in the output. 

 

.. SEEALSO:: 

 

- :func:`~sage.graphs.centrality.centrality_closeness_top_k` 

- :meth:`~sage.graphs.graph.Graph.centrality_degree` 

- :meth:`~centrality_betweenness` 

 

REFERENCES: 

 

.. [Borgatti95] Stephen P. Borgatti. (1995). Centrality and AIDS. 

[Online] Available: 

http://www.analytictech.com/networks/centaids.htm 

 

.. [OLJ14] Paul W. Olsen, Alan G. Labouseur, Jeong-Hyon Hwang. 

Efficient Top-k Closeness Centrality Search 

Proceedings of the IEEE 30th International Conference on Data 

Engineering (ICDE), 2014 

 

EXAMPLES: 

 

Standard examples:: 

 

sage: (graphs.ChvatalGraph()).centrality_closeness() 

{0: 0.61111111111111..., 1: 0.61111111111111..., 2: 0.61111111111111..., 3: 0.61111111111111..., 4: 0.61111111111111..., 5: 0.61111111111111..., 6: 0.61111111111111..., 7: 0.61111111111111..., 8: 0.61111111111111..., 9: 0.61111111111111..., 10: 0.61111111111111..., 11: 0.61111111111111...} 

sage: D = DiGraph({0:[1,2,3], 1:[2], 3:[0,1]}) 

sage: D.show(figsize=[2,2]) 

sage: D.centrality_closeness(vert=[0,1]) 

{0: 1.0, 1: 0.3333333333333333} 

sage: D = D.to_undirected() 

sage: D.show(figsize=[2,2]) 

sage: D.centrality_closeness() 

{0: 1.0, 1: 1.0, 2: 0.75, 3: 0.75} 

 

In a (strongly) connected (di)graph, the closeness centrality of `v` 

is inverse of the average distance between `v` and all other vertices:: 

 

sage: g = graphs.PathGraph(5) 

sage: g.centrality_closeness(0) 

0.4 

sage: dist = g.shortest_path_lengths(0).values() 

sage: float(len(dist)-1) / sum(dist) 

0.4 

sage: d = g.to_directed() 

sage: d.centrality_closeness(0) 

0.4 

sage: dist = d.shortest_path_lengths(0).values() 

sage: float(len(dist)-1) / sum(dist) 

0.4 

 

If a vertex has (out)degree 0, its closeness centrality is not defined:: 

 

sage: g = Graph(5) 

sage: g.centrality_closeness() 

{} 

sage: print(g.centrality_closeness(0)) 

None 

 

Weighted graphs:: 

 

sage: D = graphs.GridGraph([2,2]) 

sage: weight_function = lambda e:10 

sage: D.centrality_closeness([(0,0),(0,1)]) # tol abs 1e-12 

{(0, 0): 0.75, (0, 1): 0.75} 

sage: D.centrality_closeness((0,0), weight_function=weight_function) # tol abs 1e-12 

0.075 

 

TESTS: 

 

The result does not depend on the algorithm:: 

 

sage: import random 

sage: import itertools 

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

....: n = random.randint(2,20) 

....: m = random.randint(0, n*(n-1)/2) 

....: g = graphs.RandomGNM(n,m) 

....: c1 = g.centrality_closeness(algorithm='BFS') 

....: c2 = g.centrality_closeness(algorithm='NetworkX') 

....: c3 = g.centrality_closeness(algorithm='Dijkstra_Boost') 

....: c4 = g.centrality_closeness(algorithm='Floyd-Warshall-Cython') 

....: c5 = g.centrality_closeness(algorithm='Floyd-Warshall-Python') 

....: c6 = g.centrality_closeness(algorithm='Johnson_Boost') 

....: assert(len(c1)==len(c2)==len(c3)==len(c4)==len(c5)==len(c6)) 

....: c = [c1,c2,c3,c4,c5,c6] 

....: for (ci,cj) in itertools.combinations(c, 2): 

....: assert(sum([abs(ci[v] - cj[v]) for v in g.vertices() if g.degree(v) != 0]) < 1e-12) 

 

Directed graphs:: 

 

sage: import random 

sage: import itertools 

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

....: n = random.randint(2,20) 

....: m = random.randint(0, n*(n-1)/2) 

....: g = digraphs.RandomDirectedGNM(n,m) 

....: c1 = g.centrality_closeness(algorithm='BFS') 

....: c2 = g.centrality_closeness(algorithm='NetworkX') 

....: c3 = g.centrality_closeness(algorithm='Dijkstra_Boost') 

....: c4 = g.centrality_closeness(algorithm='Floyd-Warshall-Cython') 

....: c5 = g.centrality_closeness(algorithm='Floyd-Warshall-Python') 

....: c6 = g.centrality_closeness(algorithm='Johnson_Boost') 

....: assert(len(c1)==len(c2)==len(c3)==len(c4)==len(c5)==len(c6)) 

....: c = [c1,c2,c3,c4,c5,c6] 

....: for (ci,cj) in itertools.combinations(c, 2): 

....: assert(sum([abs(ci[v] - cj[v]) for v in g.vertices() if g.out_degree(v) != 0]) < 1e-12) 

 

Weighted graphs:: 

 

sage: import random 

sage: import itertools 

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

....: n = random.randint(2,20) 

....: m = random.randint(0, n*(n-1)/2) 

....: g = graphs.RandomGNM(n,m) 

....: for v,w in g.edges(labels=False): 

....: g.set_edge_label(v,w,float(random.uniform(1,100))) 

....: c1 = g.centrality_closeness(by_weight=True, algorithm='NetworkX') 

....: c2 = g.centrality_closeness(by_weight=True, algorithm='Dijkstra_Boost') 

....: c3 = g.centrality_closeness(by_weight=True, algorithm='Floyd-Warshall-Python') 

....: c4 = g.centrality_closeness(by_weight=True, algorithm='Johnson_Boost') 

....: assert(len(c1)==len(c2)==len(c3)==len(c4)) 

....: c = [c1,c2,c3,c4] 

....: for (ci,cj) in itertools.combinations(c, 2): 

....: assert(sum([abs(ci[v] - cj[v]) for v in g.vertices() if g.degree(v) != 0]) < 1e-12) 

""" 

if weight_function is not None: 

by_weight=True 

elif by_weight: 

weight_function = lambda e:e[2] 

 

onlyone = False 

if vert in self.vertices(): 

v_iter = iter([vert]) 

onlyone = True 

elif vert is None: 

v_iter = self.vertex_iterator() 

else: 

v_iter = iter(vert) 

 

if algorithm is None: 

if not by_weight: 

algorithm='BFS' 

else: 

for e in self.edge_iterator(): 

try: 

if float(weight_function(e)) < 0: 

algorithm='Johnson_Boost' 

break 

except (ValueError, TypeError): 

raise ValueError("The weight function cannot find the" + 

" weight of " + str(e) + ".") 

if algorithm is None: 

algorithm='Dijkstra_Boost' 

 

if algorithm == 'NetworkX': 

if by_weight and check_weight: 

self._check_weight_function(weight_function) 

import networkx 

if by_weight: 

if self.is_directed(): 

G = networkx.DiGraph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) 

else: 

G = networkx.Graph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) 

else: 

G = self.networkx_graph(copy=False) 

G.add_nodes_from(self.vertices()) 

 

if vert is None: 

closeness = networkx.closeness_centrality(G,vert, 

distance = 'weight' 

if by_weight 

else None) 

return {v:c for v,c in iteritems(closeness) if c != 0} 

closeness = {} 

degree = self.out_degree if self.is_directed else self.degree 

for x in v_iter: 

if degree(x) != 0: 

closeness[x] = networkx.closeness_centrality(G, x, 

distance = 'weight' 

if by_weight 

else None) 

if onlyone: 

return closeness.get(vert, None) 

else: 

return closeness 

elif algorithm=="Johnson_Boost": 

from sage.graphs.base.boost_graph import johnson_closeness_centrality 

self.weighted(by_weight) 

closeness = johnson_closeness_centrality(self, weight_function) 

if onlyone: 

return closeness.get(vert, None) 

else: 

return {v: closeness[v] for v in v_iter if v in closeness} 

else: 

closeness = dict() 

distances = None 

if algorithm in ["Floyd-Warshall-Cython", 

"Floyd-Warshall-Python"]: 

distances = self.shortest_path_all_pairs(by_weight,algorithm, 

weight_function, 

check_weight)[0] 

 

for v in v_iter: 

if distances is None: 

distv = self.shortest_path_lengths(v, by_weight, algorithm, 

weight_function, 

check_weight) 

else: 

distv = distances[v] 

try: 

closeness[v] = float(len(distv) - 1) * (len(distv) - 1) / (float(sum(distv.values())) * (self.num_verts() - 1)) 

except ZeroDivisionError: 

pass 

if onlyone: 

return closeness.get(vert, None) 

else: 

return closeness 

 

### Paths 

 

def all_paths(self, start, end): 

""" 

Return the list of all paths between a pair of vertices. 

 

If ``start`` is the same vertex as ``end``, then ``[[start]]`` is 

returned -- a list containing the 1-vertex, 0-edge path "``start``". 

 

INPUT: 

 

- ``start``, a vertex of a graph -- where to start 

- ``end``, a vertex of a graph -- where to end 

 

EXAMPLES:: 

 

sage: eg1 = Graph({0:[1,2], 1:[4], 2:[3,4], 4:[5], 5:[6]}) 

sage: eg1.all_paths(0,6) 

[[0, 1, 4, 5, 6], [0, 2, 4, 5, 6]] 

sage: eg2 = graphs.PetersenGraph() 

sage: sorted(eg2.all_paths(1,4)) 

[[1, 0, 4], 

[1, 0, 5, 7, 2, 3, 4], 

[1, 0, 5, 7, 2, 3, 8, 6, 9, 4], 

[1, 0, 5, 7, 9, 4], 

[1, 0, 5, 7, 9, 6, 8, 3, 4], 

[1, 0, 5, 8, 3, 2, 7, 9, 4], 

[1, 0, 5, 8, 3, 4], 

[1, 0, 5, 8, 6, 9, 4], 

[1, 0, 5, 8, 6, 9, 7, 2, 3, 4], 

[1, 2, 3, 4], 

[1, 2, 3, 8, 5, 0, 4], 

[1, 2, 3, 8, 5, 7, 9, 4], 

[1, 2, 3, 8, 6, 9, 4], 

[1, 2, 3, 8, 6, 9, 7, 5, 0, 4], 

[1, 2, 7, 5, 0, 4], 

[1, 2, 7, 5, 8, 3, 4], 

[1, 2, 7, 5, 8, 6, 9, 4], 

[1, 2, 7, 9, 4], 

[1, 2, 7, 9, 6, 8, 3, 4], 

[1, 2, 7, 9, 6, 8, 5, 0, 4], 

[1, 6, 8, 3, 2, 7, 5, 0, 4], 

[1, 6, 8, 3, 2, 7, 9, 4], 

[1, 6, 8, 3, 4], 

[1, 6, 8, 5, 0, 4], 

[1, 6, 8, 5, 7, 2, 3, 4], 

[1, 6, 8, 5, 7, 9, 4], 

[1, 6, 9, 4], 

[1, 6, 9, 7, 2, 3, 4], 

[1, 6, 9, 7, 2, 3, 8, 5, 0, 4], 

[1, 6, 9, 7, 5, 0, 4], 

[1, 6, 9, 7, 5, 8, 3, 4]] 

sage: dg = DiGraph({0:[1,3], 1:[3], 2:[0,3]}) 

sage: sorted(dg.all_paths(0,3)) 

[[0, 1, 3], [0, 3]] 

sage: ug = dg.to_undirected() 

sage: sorted(ug.all_paths(0,3)) 

[[0, 1, 3], [0, 2, 3], [0, 3]] 

 

TESTS: 

 

Starting and ending at the same vertex (see :trac:`13006`):: 

 

sage: graphs.CompleteGraph(4).all_paths(2,2) 

[[2]] 

 

Non-existing vertex as end vertex (see :trac:`24495`):: 

 

sage: g = graphs.PathGraph(5) 

sage: g.all_paths(1, 'junk') 

Traceback (most recent call last): 

... 

LookupError: end vertex (junk) is not a vertex of the graph 

""" 

if start not in self: 

raise LookupError("start vertex ({0}) is not a vertex of the graph".format(start)) 

if end not in self: 

raise LookupError("end vertex ({0}) is not a vertex of the graph".format(end)) 

 

if self.is_directed(): 

iterator=self.neighbor_out_iterator 

else: 

iterator=self.neighbor_iterator 

 

if start == end: 

return [[start]] 

 

all_paths = [] # list of 

act_path = [] # the current path 

act_path_iter = [] # the neighbor/successor-iterators of the current path 

done = False 

s=start 

while not done: 

if s==end: # if path completes, add to list 

all_paths.append(act_path+[s]) 

else: 

if s not in act_path: # we want vertices just once in a path 

act_path.append(s) # extend current path 

act_path_iter.append(iterator(s)) # save the state of the neighbor/successor-iterator of the current vertex 

s=None 

while (s is None) and not done: 

try: 

s=next(act_path_iter[-1]) # try to get the next neighbor/successor, ... 

except (StopIteration): # ... if there is none ... 

act_path.pop() # ... go one step back 

act_path_iter.pop() 

if len(act_path)==0: # there is no other vertex ... 

done = True # ... so we are done 

return all_paths 

 

 

def triangles_count(self, algorithm=None): 

r""" 

Returns the number of triangles in the (di)graph. 

 

For digraphs, we count the number of directed circuit of length 3. 

 

INPUT: 

 

- ``algorithm`` -- (default: ``None``) specifies the algorithm to use 

(note that only ``'iter'`` is available for directed graphs): 

 

- ``'sparse_copy'`` -- counts the triangles in a sparse copy of the 

graph (see :mod:`sage.graphs.base.static_sparse_graph`). Calls 

:func:`static_sparse_graph.triangles_count 

<sage.graphs.base.static_sparse_graph.triangles_count>` 

 

- ``'dense_copy'`` -- counts the triangles in a dense copy of the 

graph (see :mod:`sage.graphs.base.static_dense_graph`). Calls 

:func:`static_dense_graph.triangles_count 

<sage.graphs.base.static_dense_graph.triangles_count>` 

 

- ``'matrix'`` uses the trace of the cube of the adjacency matrix. 

 

- ``'iter'`` iterates over the pairs of neighbors of each vertex. No 

copy of the graph is performed 

 

- ``None`` -- for undirected graphs, uses ``"sparse_copy"`` or 

``"dense_copy"`` depending on whether the graph is stored as dense 

or sparse. For directed graphs, uses ``'iter'``. 

 

EXAMPLES: 

 

The Petersen graph is triangle free and thus:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.triangles_count() 

0 

 

Any triple of vertices in the complete graph induces a triangle so we have:: 

 

sage: G = graphs.CompleteGraph(150) 

sage: G.triangles_count() == binomial(150,3) 

True 

 

The 2-dimensional DeBruijn graph of 2 symbols has 2 directed C3:: 

 

sage: G = digraphs.DeBruijn(2,2) 

sage: G.triangles_count() 

2 

 

The directed n-cycle is trivially triangle free for n > 3:: 

 

sage: G = digraphs.Circuit(10) 

sage: G.triangles_count() 

0 

 

TESTS: 

 

Comparison of algorithms:: 

 

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

....: G = graphs.RandomBarabasiAlbert(50,2) 

....: results = [] 

....: results.append(G.triangles_count(algorithm='matrix')) 

....: results.append(G.triangles_count(algorithm='iter')) 

....: results.append(G.triangles_count(algorithm='sparse_copy')) 

....: results.append(G.triangles_count(algorithm='dense_copy')) 

....: if any(x != results[0] for x in results): 

....: print(results) 

....: print("That's not good!") 

 

Asking for an unknown algorithm:: 

 

sage: G = Graph() 

sage: G.triangles_count(algorithm='tip top') 

Traceback (most recent call last): 

... 

ValueError: unknown algorithm "tip top" 

sage: digraphs.Path(5).triangles_count(algorithm="sparse_copy") 

Traceback (most recent call last): 

... 

ValueError: The value of algorithm(=sparse_copy) must be 'iter' or None for direcetd graphs 

""" 

if self.is_directed(): 

if algorithm is not None and algorithm != "iter": 

raise ValueError("The value of algorithm(={}) must be 'iter' " 

"or None for direcetd graphs".format(algorithm)) 

 

self._scream_if_not_simple(allow_loops=True) 

from sage.graphs.digraph_generators import digraphs 

return self.subgraph_search_count(digraphs.Circuit(3)) // 3 

else: 

self._scream_if_not_simple() 

if algorithm is None: 

from sage.graphs.base.dense_graph import DenseGraphBackend 

algorithm = ('dense_copy' if isinstance(self._backend, DenseGraphBackend) else 

'sparse_copy') 

 

if algorithm=='iter': 

tr = 0 

for u in self: 

Nu = set(self.neighbors(u)) 

for v in Nu: 

tr += len(Nu.intersection(self.neighbors(v))) 

return Integer(tr//6) 

elif algorithm=="sparse_copy": 

from sage.graphs.base.static_sparse_graph import triangles_count 

return sum(itervalues(triangles_count(self)))/3 

elif algorithm=="dense_copy": 

from sage.graphs.base.static_dense_graph import triangles_count 

return sum(itervalues(triangles_count(self)))/3 

elif algorithm=='matrix': 

return (self.adjacency_matrix()**3).trace() // 6 

else: 

raise ValueError('unknown algorithm "{}"'.format(algorithm)) 

 

def shortest_path(self, u, v, by_weight=False, algorithm=None, 

weight_function=None, check_weight=True, 

bidirectional=None): 

r""" 

Returns a list of vertices representing some shortest path from u 

to v: if there is no path from u to v, the list is empty. 

 

For more information and more examples, see 

:meth:`~GenericGraph.shortest_paths` (the inputs are very similar). 

 

INPUT: 

 

- ``u``, ``v`` (vertices) - the start and the end vertices of the paths. 

 

- ``by_weight`` (boolean) - if ``True``, the edges in the graph are 

weighted; if ``False``, all edges have weight 1. 

 

- ``algorithm`` (string) - one of the following algorithms: 

 

- ``'BFS'``: performs a BFS from ``u``. Does not work with edge 

weights. 

 

- ``'BFS_Bid``: performs a BFS from ``u`` and from ``v``. Does not 

work with edge weights. 

 

- ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in 

NetworkX. Works only with positive weights. 

 

- ``'Dijkstra_Bid_NetworkX'``: performs a Dijkstra visit from ``u`` 

and from ``v`` (NetworkX implementation). Works only with positive 

weights. 

 

- ``'Dijkstra_Bid'``: a Cython implementation that performs 

a Dijkstra visit from ``u`` and from ``v``. Works only with positive 

weights. 

 

- ``'Bellman-Ford_Boost'``: the Bellman-Ford algorithm, implemented in 

Boost. Works also with negative weights, if there is no negative 

cycle. 

 

- ``None`` (default): Sage chooses the best algorithm: ``'BFS_Bid'`` 

if ``by_weight`` is ``False``, ``'Dijkstra_Bid'`` otherwise. 

 

.. NOTE:: 

 

If there are negative weights and algorithm is ``None``, the 

result is not reliable. This occurs because, for performance 

reasons, we cannot check whether there are edges with negative 

weights before running the algorithm. If there are, the user 

should explicitly input ``algorithm='Bellman-Ford_Boost'``. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

- ``check_weight`` (boolean) - if ``True``, we check that the 

weight_function outputs a number for each edge. 

 

- ``bidirectional`` - Deprecated and replaced by Algorithm: now it has 

no effect. Before, if it was True, the algorithm would expand vertices 

from ``u`` and ``v`` at the same time, making two spheres of half the 

usual radius. 

 

EXAMPLES:: 

 

sage: D = graphs.DodecahedralGraph() 

sage: D.shortest_path(4, 9) 

[4, 17, 16, 12, 13, 9] 

sage: D.shortest_path(4, 9, algorithm='BFS') 

[4, 3, 2, 1, 8, 9] 

sage: D.shortest_path(4, 8, algorithm='Dijkstra_NetworkX') 

[4, 3, 2, 1, 8] 

sage: D.shortest_path(4, 8, algorithm='Dijkstra_Bid_NetworkX') 

[4, 3, 2, 1, 8] 

sage: D.shortest_path(4, 9, algorithm='Dijkstra_Bid') 

[4, 3, 19, 0, 10, 9] 

sage: D.shortest_path(5, 5) 

[5] 

sage: D.delete_edges(D.edges_incident(13)) 

sage: D.shortest_path(13, 4) 

[] 

sage: G = Graph({0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2}}, sparse = True) 

sage: G.plot(edge_labels=True).show() # long time 

sage: G.shortest_path(0, 3) 

[0, 4, 3] 

sage: G.shortest_path(0, 3, by_weight=True) 

[0, 1, 2, 3] 

sage: G.shortest_path(0, 3, by_weight=True, algorithm='Dijkstra_NetworkX') 

[0, 1, 2, 3] 

sage: G.shortest_path(0, 3, by_weight=True, algorithm='Dijkstra_Bid_NetworkX') 

[0, 1, 2, 3] 

 

TESTS: 

 

If the algorithm is not implemented:: 

 

sage: G.shortest_path(0, 3, by_weight=True, algorithm='tip top') 

Traceback (most recent call last): 

... 

ValueError: unknown algorithm "tip top" 

 

BFS on weighted graphs:: 

 

sage: G.shortest_path(0, 3, by_weight=True, algorithm='BFS') 

Traceback (most recent call last): 

... 

ValueError: The 'BFS' algorithm does not work on weighted graphs. 

sage: G.shortest_path(0, 3, by_weight=True, algorithm='BFS_Bid') 

Traceback (most recent call last): 

... 

ValueError: The 'BFS_Bid' algorithm does not work on weighted graphs. 

 

If vertex is not in the graph:: 

 

sage: G.shortest_path(0, 5) 

Traceback (most recent call last): 

... 

ValueError: vertex '5' is not in the (di)graph 

sage: G.shortest_path(6, 5) 

Traceback (most recent call last): 

... 

ValueError: vertex '6' is not in the (di)graph 

""" # TODO- multiple edges?? 

if not self.has_vertex(u): 

raise ValueError("vertex '{}' is not in the (di)graph".format(u)) 

if not self.has_vertex(v): 

raise ValueError("vertex '{}' is not in the (di)graph".format(v)) 

 

if weight_function is not None: 

by_weight = True 

 

if algorithm is None: 

algorithm = 'Dijkstra_Bid' if by_weight else 'BFS_Bid' 

 

if algorithm in ['BFS', 'Dijkstra_NetworkX', 'Bellman-Ford_Boost']: 

return self.shortest_paths(u, by_weight, algorithm, weight_function, check_weight)[v] 

 

if weight_function is None and by_weight: 

weight_function = lambda e:e[2] 

 

if bidirectional is not None: 

deprecation(18938, "Variable 'bidirectional' is deprecated and " + 

"replaced by 'algorithm'.") 

 

if u == v: # to avoid a NetworkX bug 

return [u] 

 

 

if by_weight: 

if algorithm == 'BFS_Bid': 

raise ValueError("The 'BFS_Bid' algorithm does not " + 

"work on weighted graphs.") 

if check_weight: 

self._check_weight_function(weight_function) 

else: 

weight_function = lambda e:1 

 

if algorithm=="Dijkstra_Bid": 

return self._backend.bidirectional_dijkstra(u, v, weight_function) 

elif algorithm=="Dijkstra_Bid_NetworkX": 

import networkx 

if self.is_directed(): 

G = networkx.DiGraph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) 

else: 

G = networkx.Graph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) 

G.add_nodes_from(self.vertices()) 

return networkx.bidirectional_dijkstra(G, u, v)[1] 

elif algorithm=="BFS_Bid": 

return self._backend.shortest_path(u,v) 

else: 

raise ValueError('unknown algorithm "{}"'.format(algorithm)) 

 

def shortest_path_length(self, u, v, by_weight=False, algorithm=None, 

weight_function=None, check_weight=True, 

bidirectional=None, weight_sum=None): 

r""" 

Returns the minimal length of a path from u to v. 

 

If there is no path from u to v, returns Infinity. 

 

For more information and more examples, we refer to 

:meth:`~GenericGraph.shortest_path` and 

:meth:`~GenericGraph.shortest_paths`, which have very similar inputs. 

 

INPUT: 

 

- ``u``, ``v`` (vertices) - the start and the end vertices of the paths. 

 

- ``by_weight`` (boolean) - if ``True``, the edges in the graph are 

weighted; if ``False``, all edges have weight 1. 

 

- ``algorithm`` (string) - one of the following algorithms: 

 

- ``'BFS'``: performs a BFS from ``u``. Does not work with edge 

weights. 

 

- ``'BFS_Bid``: performs a BFS from ``u`` and from ``v``. Does not 

work with edge weights. 

 

- ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in 

NetworkX. 

 

- ``'Dijkstra_Bid_NetworkX'``: performs a Dijkstra visit from ``u`` 

and from ``v`` (NetworkX implementation). 

 

- ``'Dijkstra_Bid'``: a Cython implementation that performs 

a Dijkstra visit from ``u`` and from ``v``. 

 

- ``'Bellman-Ford_Boost'``: the Bellman-Ford algorithm, implemented in 

Boost. Works also with negative weights, if there is no negative 

cycle. 

 

- ``None`` (default): Sage chooses the best algorithm: ``'BFS_Bid'`` 

if ``by_weight`` is ``False``, ``'Dijkstra_Bid'`` otherwise. 

 

.. NOTE:: 

 

If there are negative weights and algorithm is ``None``, the 

result is not reliable. This occurs because, for performance 

reasons, we cannot check whether there are edges with negative 

weights before running the algorithm. If there are, the user 

should explicitly input ``algorithm='Bellman-Ford_Boost'``. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

- ``check_weight`` (boolean) - if ``True``, we check that the 

weight_function outputs a number for each edge. 

 

- ``bidirectional`` - Deprecated and replaced by Algorithm: now it has 

no effect. Before, if it was True, the algorithm would expand vertices 

from ``u`` and ``v`` at the same time, making two spheres of half the 

usual radius. 

 

- ``weight_sum`` - Deprecated: now it has no effect. Before, it was used 

to decide if the algorithm should return the number of edges in the 

shortest path or the length of the (weighted) path. Now it has the 

same value as ``by_weight``. 

 

EXAMPLES: 

 

Standard examples:: 

 

sage: D = graphs.DodecahedralGraph() 

sage: D.shortest_path_length(4, 9) 

5 

sage: D.shortest_path_length(4, 9, algorithm='BFS') 

5 

sage: D.shortest_path_length(4, 9, algorithm='Dijkstra_NetworkX') 

5 

sage: D.shortest_path_length(4, 9, algorithm='Dijkstra_Bid_NetworkX') 

5 

sage: D.shortest_path_length(4, 9, algorithm='Dijkstra_Bid') 

5 

sage: D.shortest_path_length(4, 9, algorithm='Bellman-Ford_Boost') 

5 

sage: D.shortest_path_length(5, 5) 

0 

sage: D.delete_edges(D.edges_incident(13)) 

sage: D.shortest_path_length(13, 4) 

+Infinity 

sage: G = Graph({0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2}}, sparse = True) 

sage: G.plot(edge_labels=True).show() # long time 

sage: G.shortest_path_length(0, 3) 

2 

sage: G.shortest_path_length(0, 3, by_weight=True) 

3 

sage: G.shortest_path_length(0, 3, by_weight=True, algorithm='Dijkstra_NetworkX') 

3 

sage: G.shortest_path_length(0, 3, by_weight=True, algorithm='Dijkstra_Bid_NetworkX') 

3 

 

If Dijkstra is used with negative weights, usually it raises an error:: 

 

sage: G = DiGraph({0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: -2}}, sparse = True) 

sage: G.shortest_path_length(4, 1, by_weight=True, algorithm=None) 

Traceback (most recent call last): 

... 

ValueError: The graph contains an edge with negative weight! 

sage: G.shortest_path_length(4, 1, by_weight=True, algorithm='Bellman-Ford_Boost') 

-1 

 

However, sometimes the result may be wrong, and no error is raised:: 

 

sage: G = DiGraph([(0,1,1),(1,2,1),(0,3,1000),(3,4,-3000), (4,2,1000)]) 

sage: G.shortest_path_length(0, 2, by_weight=True, algorithm='Bellman-Ford_Boost') 

-1000 

sage: G.shortest_path_length(0, 2, by_weight=True) 

2 

 

TESTS: 

 

If vertex is not in the graph:: 

 

sage: G.shortest_path(0, 5) 

Traceback (most recent call last): 

... 

ValueError: vertex '5' is not in the (di)graph 

sage: G.shortest_path(6, 5) 

Traceback (most recent call last): 

... 

ValueError: vertex '6' is not in the (di)graph 

""" 

if not self.has_vertex(u): 

raise ValueError("vertex '{}' is not in the (di)graph".format(u)) 

if not self.has_vertex(v): 

raise ValueError("vertex '{}' is not in the (di)graph".format(v)) 

 

if weight_sum is not None: 

deprecation(18938, "Now weight_sum is replaced by by_weight.") 

 

if u == v: # to avoid a NetworkX bug 

return 0 

 

if weight_function is not None: 

by_weight = True 

 

if algorithm is None: 

algorithm = 'Dijkstra_Bid' if by_weight else 'BFS_Bid' 

 

if weight_function is None and by_weight: 

weight_function = lambda e:e[2] 

 

if algorithm in ['BFS', 'Dijkstra_NetworkX', 'Bellman-Ford_Boost']: 

return self.shortest_path_lengths(u, by_weight, algorithm, weight_function, check_weight)[v] 

 

if bidirectional is not None: 

deprecation(18938, "Variable 'bidirectional' is deprecated and " + 

"replaced by 'algorithm'.") 

 

if by_weight: 

if algorithm == 'BFS_Bid': 

raise ValueError("the 'BFS_Bid' algorithm does not " + 

"work on weighted graphs") 

if check_weight: 

self._check_weight_function(weight_function) 

else: 

weight_function = lambda e:1 

 

if algorithm == "Dijkstra_Bid": 

return self._backend.bidirectional_dijkstra(u, v, weight_function, distance_flag=True) 

elif algorithm == "Dijkstra_Bid_NetworkX": 

import networkx 

if self.is_directed(): 

G = networkx.DiGraph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) 

else: 

G = networkx.Graph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) 

G.add_nodes_from(self.vertices()) 

return networkx.bidirectional_dijkstra(G, u, v)[0] 

elif algorithm == "BFS_Bid": 

return self._backend.shortest_path(u, v, distance_flag=True) 

else: 

raise ValueError('unknown algorithm "{}"'.format(algorithm)) 

 

def _check_weight_function(self, weight_function=None): 

r""" 

Check that an edge weight function outputs only numbers. 

 

The weight function inputs a labelled edge ``(u, v, l)`` and 

outputs its weight. Here, we check that the output is always a number 

(otherwise, several functions might have unexpected behavior). If the 

function fails the test, an exception is raised. 

 

INPUT: 

 

- ``weight_function`` - the weight function to be tested 

 

EXAMPLES: 

 

The standard weight function outputs labels:: 

 

sage: G = Graph([(0,1,1), (1,2,3), (2,3,2)]) 

sage: weight_function=lambda e:e[2] 

sage: G._check_weight_function(weight_function) 

sage: [weight_function(e) for e in G.edges()] 

[1, 3, 2] 

 

However, it might be more complicated:: 

 

sage: G = Graph([(0,1,{'name':'a', 'weight':1}), (1,2,{'name':'b', 'weight':3}), (2,3,{'name':'c', 'weight':2})]) 

sage: weight_function=lambda e:e[2]['weight'] 

sage: G._check_weight_function(weight_function) 

sage: [weight_function(e) for e in G.edges()] 

[1, 3, 2] 

 

A weight function that does not match labels:: 

 

sage: G.add_edge((0,3,{'name':'d', 'weight':'d'})) 

sage: G._check_weight_function(weight_function) 

Traceback (most recent call last): 

... 

ValueError: The weight function cannot find the weight of (0, 3, {'name': 'd', 'weight': 'd'}). 

""" 

for e in self.edge_iterator(): 

try: 

float(weight_function(e)) 

except Exception: 

raise ValueError("The weight function cannot find the " + 

"weight of " + str(e) + ".") 

 

 

def shortest_paths(self, u, by_weight=False, algorithm=None, 

weight_function=None, check_weight=True, cutoff=None): 

r""" 

Returns a dictionary associating to each vertex v a shortest path from u 

to v, if it exists. 

 

If u and v are not connected, vertex v is not present in the dictionary. 

 

INPUT: 

 

- ``u`` (vertex) - the starting vertex. 

 

- ``by_weight`` (boolean) - if ``True``, the edges in the graph are 

weighted; if ``False``, all edges have weight 1. 

 

- ``algorithm`` (string) - one of the following algorithms: 

 

- ``'BFS'``: performs a BFS from ``u``. Does not work with edge 

weights. 

 

- ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in 

NetworkX (works only with positive weights). 

 

- ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost 

(works only with positive weights). 

 

- ``'Bellman-Ford_Boost'``: the Bellman-Ford algorithm, implemented in 

Boost (works also with negative weights, if there is no negative 

cycle). 

 

- ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` if 

``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are 

positive, ``'Bellman-Ford_Boost'`` otherwise. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

- ``check_weight`` (boolean) - if ``True``, we check that the 

weight_function outputs a number for each edge. 

 

- ``cutoff`` (integer) - integer depth to stop search (used only if 

``algorithm=='BFS'``). 

 

EXAMPLES: 

 

Standard example:: 

 

sage: D = graphs.DodecahedralGraph() 

sage: D.shortest_paths(0) 

{0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 19, 3], 4: [0, 19, 3, 4], 5: [0, 1, 2, 6, 5], 6: [0, 1, 2, 6], 7: [0, 1, 8, 7], 8: [0, 1, 8], 9: [0, 10, 9], 10: [0, 10], 11: [0, 10, 11], 12: [0, 10, 11, 12], 13: [0, 10, 9, 13], 14: [0, 1, 8, 7, 14], 15: [0, 19, 18, 17, 16, 15], 16: [0, 19, 18, 17, 16], 17: [0, 19, 18, 17], 18: [0, 19, 18], 19: [0, 19]} 

 

All these paths are obviously induced graphs:: 

 

sage: all([D.subgraph(p).is_isomorphic(graphs.PathGraph(len(p)) )for p in D.shortest_paths(0).values()]) 

True 

 

:: 

 

sage: D.shortest_paths(0, cutoff=2) 

{0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 19, 3], 8: [0, 1, 8], 9: [0, 10, 9], 10: [0, 10], 11: [0, 10, 11], 18: [0, 19, 18], 19: [0, 19]} 

sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True) 

sage: G.plot(edge_labels=True).show() # long time 

sage: G.shortest_paths(0, by_weight=True) 

{0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 1, 2, 3], 4: [0, 4]} 

 

Weighted shortest paths:: 

 

sage: D = DiGraph([(0,1,1),(1,2,3),(0,2,5)]) 

sage: D.shortest_paths(0) 

{0: [0], 1: [0, 1], 2: [0, 2]} 

sage: D.shortest_paths(0, by_weight=True) 

{0: [0], 1: [0, 1], 2: [0, 1, 2]} 

 

Using a weight function (this way, ``by_weight`` is set to ``True``):: 

 

sage: D = DiGraph([(0,1,{'weight':1}),(1,2,{'weight':3}),(0,2,{'weight':5})]) 

sage: weight_function = lambda e:e[2]['weight'] 

sage: D.shortest_paths(0, weight_function=weight_function) 

{0: [0], 1: [0, 1], 2: [0, 1, 2]} 

 

If the weight function does not match the label:: 

 

sage: D.shortest_paths(0, weight_function=lambda e:e[2]) 

Traceback (most recent call last): 

... 

ValueError: The weight function cannot find the weight of (0, 1, {'weight': 1}). 

 

However, if ``check_weight`` is set to False, unexpected behavior may 

occur:: 

 

sage: D.shortest_paths(0, algorithm='Dijkstra_NetworkX', weight_function=lambda e:e[2], check_weight=False) 

Traceback (most recent call last): 

... 

TypeError: unsupported operand type(s) for +: 'int' and 'dict' 

 

Negative weights:: 

 

sage: D = DiGraph([(0,1,1),(1,2,-2),(0,2,4)]) 

sage: D.shortest_paths(0, by_weight=True) 

{0: [0], 1: [0, 1], 2: [0, 1, 2]} 

 

Negative cycles:: 

 

sage: D.add_edge(2,0,0) 

sage: D.shortest_paths(0, by_weight=True) 

Traceback (most recent call last): 

... 

ValueError: the graph contains a negative cycle 

 

TESTS: 

 

If we ask for an unknown algorithm:: 

 

sage: D = DiGraph([(0,1,1),(1,2,2),(0,2,4)]) 

sage: D.shortest_paths(0, algorithm='tip top') 

Traceback (most recent call last): 

... 

ValueError: unknown algorithm "tip top" 

 

If we ask for BFS in a weighted graph:: 

 

sage: D.shortest_paths(0, algorithm='BFS', by_weight=True) 

Traceback (most recent call last): 

... 

ValueError: The 'BFS' algorithm does not work on weighted graphs. 

 

If we run Dijkstra with negative weights:: 

 

sage: D = DiGraph([(0,1,2),(1,2,-2),(0,2,1)]) 

sage: D.shortest_paths(0, algorithm='Dijkstra_Boost', by_weight=True) 

Traceback (most recent call last): 

... 

RuntimeError: Dijkstra algorithm does not work with negative weights. Use Bellman-Ford instead 

sage: D.shortest_paths(0, algorithm='Dijkstra_NetworkX', by_weight=True) 

Traceback (most recent call last): 

... 

ValueError: ('Contradictory paths found:', 'negative weights?') 

""" 

if weight_function is not None: 

by_weight = True 

elif by_weight: 

weight_function = lambda e:e[2] 

else: 

weight_function = lambda e:1 

 

if algorithm is None and not by_weight: 

algorithm = 'BFS' 

 

if by_weight and check_weight: 

self._check_weight_function(weight_function) 

 

if algorithm=='BFS': 

if by_weight: 

raise ValueError("The 'BFS' algorithm does not work on " + 

"weighted graphs.") 

return self._backend.shortest_path_all_vertices(u, cutoff) 

 

elif algorithm=='Dijkstra_NetworkX': 

import networkx 

# If this is not present, an error might be raised by NetworkX 

if self.order() == 1 and self.has_vertex(u): 

return {u:[u]} 

if by_weight: 

if self.is_directed(): 

G = networkx.DiGraph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) 

else: 

G = networkx.Graph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) 

else: 

# Needed to remove labels. 

if self.is_directed(): 

G = networkx.DiGraph(self.edges(labels=False)) 

else: 

G = networkx.Graph(self.edges(labels=False)) 

G.add_nodes_from(self.vertices()) 

return networkx.single_source_dijkstra_path(G, u) 

 

elif algorithm in ['Dijkstra_Boost','Bellman-Ford_Boost',None]: 

from sage.graphs.base.boost_graph import shortest_paths 

_,pred = shortest_paths(self, u, weight_function, algorithm) 

paths = {} 

for v in pred.keys(): 

w = v 

path = [w] 

while w != u: 

w = pred[w] 

path.append(w) 

path.reverse() 

paths[v] = path 

return paths 

 

else: 

raise ValueError('unknown algorithm "{}"'.format(algorithm)) 

 

def _path_length(self, path, by_weight=False, weight_function=None): 

r""" 

Computes the (weighted) length of the path provided. 

 

If the path is empty, returns Infinity. 

 

.. WARNING:: 

 

if the graph is unweighted, the algorithm does not check that 

the path exists. 

 

INPUT: 

 

- ``by_weight`` (boolean) - if ``True``, the edges in the graph are 

weighted; if ``False``, all edges have weight 1. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

EXAMPLES: 

 

The unweighted case:: 

 

sage: G = graphs.CycleGraph(3) 

sage: G._path_length([0,1,2,0,1,2]) 

5 

 

The weighted case:: 

 

sage: G = Graph([(0,1,{'name':'a', 'weight':1}), (1,2,{'name':'b', 'weight':3}), (2,3,{'name':'c', 'weight':2})]) 

sage: G._path_length([0,1,2,3]) 

3 

sage: G._path_length([0,1,2,3], by_weight=True, weight_function=lambda e:e[2]['weight']) 

6 

 

If the path is empty:: 

 

sage: G._path_length([0,1,2,3], by_weight=True, weight_function=lambda e:e[2]['weight']) 

6 

 

If we ask for a path that does not exist:: 

 

sage: G._path_length([0,3], by_weight=False) 

1 

sage: G._path_length([0,3], by_weight=True, weight_function=lambda e:e[2]['weight']) 

Traceback (most recent call last): 

... 

LookupError: (0, 3) is not an edge of the graph. 

""" 

if len(path) == 0: 

from sage.rings.infinity import Infinity 

return Infinity 

 

if by_weight or weight_function is not None: 

if weight_function is None: 

weight_function = lambda e:e[2] 

wt = 0 

 

for j in range(len(path) - 1): 

wt += weight_function((path[j], path[j+1], 

self.edge_label(path[j], path[j+1]))) 

return wt 

else: 

return len(path) - 1 

 

def shortest_path_lengths(self, u, by_weight=False, algorithm=None, 

weight_function=None, check_weight=True, 

weight_sums=None): 

r""" 

Computes the length of a shortest path from u to any other vertex. 

 

Returns a dictionary of shortest path lengths keyed by targets, 

excluding all vertices that are not reachable from u. 

 

For more information on the input variables and more examples, we refer 

to :meth:`~GenericGraph.shortest_paths` 

which has the same input variables. 

 

INPUT: 

 

- ``u`` (vertex) - the starting vertex. 

 

- ``by_weight`` (boolean) - if ``True``, the edges in the graph are 

weighted; if ``False``, all edges have weight 1. 

 

- ``algorithm`` (string) - one of the following algorithms: 

 

- ``'BFS'``: performs a BFS from ``u``. Does not work with edge 

weights. 

 

- ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in 

NetworkX (works only with positive weights). 

 

- ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost 

(works only with positive weights). 

 

- ``'Bellman-Ford_Boost'``: the Bellman-Ford algorithm, implemented in 

Boost (works also with negative weights, if there is no negative 

cycle). 

 

- ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` if 

``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are 

positive, ``'Bellman-Ford_Boost'`` otherwise. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

- ``check_weight`` (boolean) - if ``True``, we check that the 

weight_function outputs a number for each edge. 

 

- ``weight_sums`` - Deprecated: now this variable has no effect. Before, 

it was used to decide whether the number of edges or the sum of their 

lengths was outputted. Now we use variable ``by_weight`` to decide. 

 

EXAMPLES: 

 

Unweighted case:: 

 

sage: D = graphs.DodecahedralGraph() 

sage: D.shortest_path_lengths(0) 

{0: 0, 1: 1, 2: 2, 3: 2, 4: 3, 5: 4, 6: 3, 7: 3, 8: 2, 9: 2, 10: 1, 11: 2, 12: 3, 13: 3, 14: 4, 15: 5, 16: 4, 17: 3, 18: 2, 19: 1} 

 

Weighted case:: 

 

sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True) 

sage: G.plot(edge_labels=True).show() # long time 

sage: G.shortest_path_lengths(0, by_weight=True) 

{0: 0, 1: 1, 2: 2, 3: 3, 4: 2} 

 

Using a weight function:: 

 

sage: D = DiGraph([(0,1,{'weight':1}),(1,2,{'weight':3}),(0,2,{'weight':5})]) 

sage: weight_function = lambda e:e[2]['weight'] 

sage: D.shortest_path_lengths(1, algorithm='Dijkstra_NetworkX', by_weight=False) 

{1: 0, 2: 1} 

sage: D.shortest_path_lengths(0, weight_function=weight_function) 

{0: 0, 1: 1, 2: 4} 

sage: D.shortest_path_lengths(1, weight_function=weight_function) 

{1: 0, 2: 3} 

 

Negative weights:: 

 

sage: D = DiGraph([(0,1,{'weight':-1}),(1,2,{'weight':3}),(0,2,{'weight':5})]) 

sage: D.shortest_path_lengths(0, weight_function=weight_function) 

{0: 0, 1: -1, 2: 2} 

 

Negative cycles:: 

 

sage: D = DiGraph([(0,1,{'weight':-5}),(1,2,{'weight':3}),(2,0,{'weight':1})]) 

sage: D.shortest_path_lengths(0, weight_function=weight_function) 

Traceback (most recent call last): 

... 

ValueError: the graph contains a negative cycle 

 

Checking that distances are equal regardless of the algorithm used:: 

 

sage: g = graphs.Grid2dGraph(5,5) 

sage: d1 = g.shortest_path_lengths((0,0), algorithm="BFS") 

sage: d2 = g.shortest_path_lengths((0,0), algorithm="Dijkstra_NetworkX") 

sage: d3 = g.shortest_path_lengths((0,0), algorithm="Dijkstra_Boost") 

sage: d4 = g.shortest_path_lengths((0,0), algorithm="Bellman-Ford_Boost") 

sage: d1 == d2 == d3 == d4 

True 

""" 

if weight_sums is not None: 

deprecation(18938, "Now weight_sums is replaced by by_weight.") 

 

if weight_function is not None: 

by_weight = True 

elif by_weight: 

weight_function = lambda e:e[2] 

else: 

weight_function = lambda e:1 

 

if algorithm is None and not by_weight: 

algorithm = 'BFS' 

 

if by_weight and check_weight: 

self._check_weight_function(weight_function) 

 

if algorithm == 'BFS': 

if by_weight: 

raise ValueError("the 'BFS' algorithm does not work on " + 

"weighted graphs") 

return self._backend.shortest_path_all_vertices(u, cutoff=None, distance_flag=True) 

 

elif algorithm == 'Dijkstra_NetworkX': 

import networkx 

# If this is not present, an error might be raised by NetworkX 

if self.num_verts()==1 and self.vertices()[0]==u: 

return {u:[u]} 

if by_weight: 

if self.is_directed(): 

G = networkx.DiGraph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) 

else: 

G = networkx.Graph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) 

else: 

# Needed to remove labels. 

if self.is_directed(): 

G = networkx.DiGraph(self.edges(labels=False)) 

else: 

G = networkx.Graph(self.edges(labels=False)) 

G.add_nodes_from(self.vertices()) 

return networkx.single_source_dijkstra_path_length(G, u) 

 

elif algorithm in ['Dijkstra_Boost', 'Bellman-Ford_Boost', None]: 

self.weighted(True) 

from sage.graphs.base.boost_graph import shortest_paths 

return shortest_paths(self, u, weight_function, algorithm)[0] 

 

else: 

raise ValueError('unknown algorithm "{}"'.format(algorithm)) 

 

def shortest_path_all_pairs(self, by_weight=False, algorithm=None, 

weight_function=None, check_weight=True, 

default_weight=None): 

r""" 

Computes a shortest path between each pair of vertices. 

 

INPUT: 

 

- ``by_weight`` (boolean) - if ``True``, the edges in the graph are 

weighted; if ``False``, all edges have weight 1. 

 

- ``algorithm`` (string) - one of the following algorithms: 

 

- ``'BFS'`` - the computation is done through a BFS centered on each 

vertex successively. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Cython'`` - the Cython implementation of 

the Floyd-Warshall algorithm. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Python'`` - the Python implementation of 

the Floyd-Warshall algorithm. Works also with weighted graphs, even 

with negative weights (but no negative cycle is allowed). 

 

- ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in 

NetworkX. It works with weighted graphs, but no negative weight is 

allowed. 

 

- ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost 

(works only with positive weights). 

 

- ``'Johnson_Boost'``: the Johnson algorithm, implemented in 

Boost (works also with negative weights, if there is no negative 

cycle). 

 

- ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` if 

``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are 

positive, ``'Floyd-Warshall-Cython'`` otherwise. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

- ``check_weight`` (boolean) - if ``True``, we check that the 

weight_function outputs a number for each edge. 

 

- ``default_weight`` - Deprecated: now it has no effect. Before, it was 

used to assign a weight to edges with no label. Now it has been 

replaced by ``weight_function``. 

 

OUTPUT: 

 

A tuple ``(dist, pred)``. They are both dicts of dicts. The first 

indicates the length ``dist[u][v]`` of the shortest weighted path 

from `u` to `v`. The second is a compact representation of all the 

paths - it indicates the predecessor ``pred[u][v]`` of `v` in the 

shortest path from `u` to `v`. If the algorithm used is 

``Johnson_Boost``, predecessors are not computed. 

 

.. NOTE:: 

 

Only reachable vertices are present in the dictionaries. 

 

.. NOTE:: 

 

There is a Cython version of this method that is usually 

much faster for large graphs, as most of the time is 

actually spent building the final double 

dictionary. Everything on the subject is to be found in the 

:mod:`~sage.graphs.distances_all_pairs` module. 

 

EXAMPLES: 

 

Some standard examples (see :meth:`~GenericGraph.shortest_paths` for 

more examples on how to use the input variables):: 

 

sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True) 

sage: G.plot(edge_labels=True).show() # long time 

sage: dist, pred = G.shortest_path_all_pairs(by_weight = True) 

sage: dist 

{0: {0: 0, 1: 1, 2: 2, 3: 3, 4: 2}, 1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 3}, 2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 3}, 3: {0: 3, 1: 2, 2: 1, 3: 0, 4: 2}, 4: {0: 2, 1: 3, 2: 3, 3: 2, 4: 0}} 

sage: pred 

{0: {0: None, 1: 0, 2: 1, 3: 2, 4: 0}, 1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0}, 2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3}, 3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3}, 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}} 

sage: pred[0] 

{0: None, 1: 0, 2: 1, 3: 2, 4: 0} 

sage: G = Graph( { 0: {1: {'weight':1}}, 1: {2: {'weight':1}}, 2: {3: {'weight':1}}, 3: {4: {'weight':2}}, 4: {0: {'weight':2}} }, sparse=True) 

sage: dist, pred = G.shortest_path_all_pairs(weight_function = lambda e:e[2]['weight']) 

sage: dist 

{0: {0: 0, 1: 1, 2: 2, 3: 3, 4: 2}, 1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 3}, 2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 3}, 3: {0: 3, 1: 2, 2: 1, 3: 0, 4: 2}, 4: {0: 2, 1: 3, 2: 3, 3: 2, 4: 0}} 

sage: pred 

{0: {0: None, 1: 0, 2: 1, 3: 2, 4: 0}, 1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0}, 2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3}, 3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3}, 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}} 

 

So for example the shortest weighted path from `0` to `3` is obtained as 

follows. The predecessor of `3` is ``pred[0][3] == 2``, the predecessor 

of `2` is ``pred[0][2] == 1``, and the predecessor of `1` is 

``pred[0][1] == 0``. 

 

:: 

 

sage: G = Graph( { 0: {1:None}, 1: {2:None}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True ) 

sage: G.shortest_path_all_pairs() 

({0: {0: 0, 1: 1, 2: 2, 3: 2, 4: 1}, 

1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 2}, 

2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 2}, 

3: {0: 2, 1: 2, 2: 1, 3: 0, 4: 1}, 

4: {0: 1, 1: 2, 2: 2, 3: 1, 4: 0}}, 

{0: {0: None, 1: 0, 2: 1, 3: 4, 4: 0}, 

1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0}, 

2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3}, 

3: {0: 4, 1: 2, 2: 3, 3: None, 4: 3}, 

4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}}) 

sage: G.shortest_path_all_pairs(weight_function=lambda e:(e[2] if e[2] is not None else 1)) 

({0: {0: 0, 1: 1, 2: 2, 3: 3, 4: 2}, 

1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 3}, 

2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 3}, 

3: {0: 3, 1: 2, 2: 1, 3: 0, 4: 2}, 

4: {0: 2, 1: 3, 2: 3, 3: 2, 4: 0}}, 

{0: {0: None, 1: 0, 2: 1, 3: 2, 4: 0}, 

1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0}, 

2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3}, 

3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3}, 

4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}}) 

 

Now, ``default_weight`` does not work anymore:: 

 

sage: G.shortest_path_all_pairs(by_weight = True, default_weight=200) 

Traceback (most recent call last): 

... 

ValueError: The weight function cannot find the weight of (0, 1, None). 

 

It can be replaced by choosing an appropriate weight_function:: 

 

sage: G.shortest_path_all_pairs(weight_function=lambda e:(e[2] if e[2] is not None else 200)) 

({0: {0: 0, 1: 200, 2: 5, 3: 4, 4: 2}, 

1: {0: 200, 1: 0, 2: 200, 3: 201, 4: 202}, 

2: {0: 5, 1: 200, 2: 0, 3: 1, 4: 3}, 

3: {0: 4, 1: 201, 2: 1, 3: 0, 4: 2}, 

4: {0: 2, 1: 202, 2: 3, 3: 2, 4: 0}}, 

{0: {0: None, 1: 0, 2: 3, 3: 4, 4: 0}, 

1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0}, 

2: {0: 4, 1: 2, 2: None, 3: 2, 4: 3}, 

3: {0: 4, 1: 2, 2: 3, 3: None, 4: 3}, 

4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}}) 

 

Checking that distances are equal regardless of the algorithm used:: 

 

sage: g = graphs.Grid2dGraph(5,5) 

sage: d1, _ = g.shortest_path_all_pairs(algorithm="BFS") 

sage: d2, _ = g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Cython") 

sage: d3, _ = g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Python") 

sage: d4, _ = g.shortest_path_all_pairs(algorithm="Dijkstra_NetworkX") 

sage: d5, _ = g.shortest_path_all_pairs(algorithm="Dijkstra_Boost") 

sage: d6, _ = g.shortest_path_all_pairs(algorithm="Johnson_Boost") 

sage: d1 == d2 == d3 == d4 == d5 == d6 

True 

 

Checking that distances are equal regardless of the algorithm used:: 

 

sage: g = digraphs.RandomDirectedGNM(6,12) 

sage: d1, _ = g.shortest_path_all_pairs(algorithm="BFS") 

sage: d2, _ = g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Cython") 

sage: d3, _ = g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Python") 

sage: d4, _ = g.shortest_path_all_pairs(algorithm="Dijkstra_NetworkX") 

sage: d5, _ = g.shortest_path_all_pairs(algorithm="Dijkstra_Boost") 

sage: d6, _ = g.shortest_path_all_pairs(algorithm="Johnson_Boost") 

sage: d1 == d2 == d3 == d4 == d5 == d6 

True 

 

Checking that weighted distances are equal regardless of the algorithm used:: 

 

sage: g = Graph() 

sage: import random 

sage: for v in range(5): 

....: for w in range(5): 

....: if v != w: 

....: g.add_edge(v, w, random.uniform(1,10)) 

sage: d1, _ = g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Python") 

sage: d2, _ = g.shortest_path_all_pairs(algorithm="Dijkstra_NetworkX") 

sage: d3, _ = g.shortest_path_all_pairs(algorithm="Dijkstra_Boost") 

sage: d4, _ = g.shortest_path_all_pairs(algorithm="Johnson_Boost") 

sage: d1 == d2 == d3 == d4 

True 

 

Checking a random path is valid:: 

 

sage: dist, path = g.shortest_path_all_pairs(algorithm="BFS") 

sage: u,v = g.random_vertex(), g.random_vertex() 

sage: p = [v] 

sage: while p[0] is not None: 

....: p.insert(0,path[u][p[0]]) 

sage: len(p) == dist[u][v] + 2 

True 

 

Negative weights:: 

 

sage: g = DiGraph([(0,1,-2),(1,0,1)], weighted=True) 

sage: g.shortest_path_all_pairs(by_weight=True) 

Traceback (most recent call last): 

... 

ValueError: The graph contains a negative cycle. 

 

Unreachable vertices are not present in the dictionaries:: 

 

sage: g = DiGraph([(0,1,1),(1,2,2)]) 

sage: g.shortest_path_all_pairs(algorithm='BFS') 

({0: {0: 0, 1: 1, 2: 2}, 1: {1: 0, 2: 1}, 2: {2: 0}}, 

{0: {0: None, 1: 0, 2: 1}, 1: {1: None, 2: 1}, 2: {2: None}}) 

sage: g.shortest_path_all_pairs(algorithm='Dijkstra_NetworkX') 

({0: {0: 0, 1: 1, 2: 2}, 1: {1: 0, 2: 1}, 2: {2: 0}}, 

{0: {0: None, 1: 1, 2: 1}, 1: {1: None, 2: 2}, 2: {2: None}}) 

sage: g.shortest_path_all_pairs(algorithm='Dijkstra_Boost') 

({0: {0: 0, 1: 1, 2: 2}, 1: {1: 0, 2: 1}, 2: {2: 0}}, 

{0: {0: None, 1: 0, 2: 1}, 1: {1: None, 2: 1}, 2: {2: None}}) 

sage: g.shortest_path_all_pairs(algorithm='Floyd-Warshall-Python') 

({0: {0: 0, 1: 1, 2: 2}, 1: {1: 0, 2: 1}, 2: {2: 0}}, 

{0: {0: None, 1: 0, 2: 1}, 1: {1: None, 2: 1}, 2: {2: None}}) 

sage: g.shortest_path_all_pairs(algorithm='Floyd-Warshall-Cython') 

({0: {0: 0, 1: 1, 2: 2}, 1: {1: 0, 2: 1}, 2: {2: 0}}, 

{0: {0: None, 1: 0, 2: 1}, 1: {1: None, 2: 1}, 2: {2: None}}) 

 

In order to change the default behavior if the graph is disconnected, 

we can use default values with dictionaries:: 

 

sage: G = 2*graphs.PathGraph(2) 

sage: d,_ = G.shortest_path_all_pairs() 

sage: import itertools 

sage: from sage.rings.infinity import Infinity 

sage: for u,v in itertools.combinations(G.vertices(),2): 

....: print("dist({}, {}) = {}".format(u,v, d[u].get(v,+Infinity))) 

dist(0, 1) = 1 

dist(0, 2) = +Infinity 

dist(0, 3) = +Infinity 

dist(1, 2) = +Infinity 

dist(1, 3) = +Infinity 

dist(2, 3) = 1 

 

TESTS: 

 

Wrong name for ``algorithm``:: 

 

sage: g.shortest_path_all_pairs(algorithm="Bob") 

Traceback (most recent call last): 

... 

ValueError: unknown algorithm "Bob" 

 

Algorithms that do not work with weights:: 

 

sage: g = Graph({0: {1:1}, 1: {2:1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2}}, sparse=True ) 

sage: g.shortest_path_all_pairs(algorithm="BFS", by_weight=True) 

Traceback (most recent call last): 

... 

ValueError: Algorithm 'BFS' does not work with weights. 

sage: g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Cython", by_weight=True) 

Traceback (most recent call last): 

... 

ValueError: Algorithm 'Floyd-Warshall-Cython' does not work with weights. 

 

Dijkstra with negative weights:: 

 

sage: g = Graph({0: {1:1}, 1: {2:1}, 2: {3: 1}, 3: {4: -2}, 4: {0: -2}}) 

sage: g.shortest_path_all_pairs(algorithm="Dijkstra_Boost", by_weight=True) 

Traceback (most recent call last): 

... 

RuntimeError: Dijkstra algorithm does not work with negative weights. Use Bellman-Ford instead 

""" 

if default_weight is not None: 

deprecation(18938, "Variable default_weight is deprecated: hence," + 

" it is ignored. Please, use weight_function, instead.") 

 

if weight_function is not None: 

by_weight = True 

elif by_weight: 

weight_function = lambda e:e[2] 

 

if algorithm is None: 

if by_weight: 

for e in self.edges(): 

try: 

if weight_function(e) < 0: 

algorithm = "Floyd-Warshall-Python" 

break 

except (ValueError, TypeError): 

raise ValueError("The weight function cannot find the" + 

" weight of " + e + ".") 

if algorithm is None: 

algorithm = "Dijkstra_Boost" 

else: 

algorithm = "BFS" 

 

if by_weight and algorithm in ['BFS', "Floyd-Warshall-Cython"]: 

raise ValueError("Algorithm '" + algorithm + "' does not work " + 

"with weights.") 

 

if algorithm == "BFS": 

from sage.graphs.distances_all_pairs import distances_and_predecessors_all_pairs 

return distances_and_predecessors_all_pairs(self) 

 

elif algorithm == "Floyd-Warshall-Cython": 

from sage.graphs.distances_all_pairs import floyd_warshall 

return floyd_warshall(self, distances = True) 

 

elif algorithm == "Johnson_Boost": 

if not by_weight: 

weight_function = lambda e:1 

from sage.graphs.base.boost_graph import johnson_shortest_paths 

return [johnson_shortest_paths(self, weight_function), None] 

 

elif algorithm == "Dijkstra_Boost": 

from sage.graphs.base.boost_graph import shortest_paths 

dist = dict() 

pred = dict() 

if by_weight and weight_function is None: 

weight_function = lambda e:e[2] 

for u in self: 

dist[u],pred[u]=shortest_paths(self, u, weight_function, algorithm) 

return dist, pred 

 

elif algorithm == "Dijkstra_NetworkX": 

dist = dict() 

pred = dict() 

for u in self: 

paths=self.shortest_paths(u, by_weight=by_weight, 

algorithm=algorithm, 

weight_function=weight_function) 

dist[u] = {v:self._path_length(p, by_weight=by_weight, 

weight_function=weight_function) 

for v,p in iteritems(paths)} 

pred[u] = {v:None if len(p)<=1 else p[1] 

for v,p in iteritems(paths)} 

return dist, pred 

 

elif algorithm != "Floyd-Warshall-Python": 

raise ValueError('unknown algorithm "{}"'.format(algorithm)) 

 

from sage.rings.infinity import Infinity 

 

if by_weight: 

if weight_function is None: 

weight_function = lambda e:e[2] 

if check_weight: 

self._check_weight_function(weight_function) 

 

dist = {} 

pred = {} 

verts = self.vertices() 

for u in verts: 

du = {u:0} 

pu = {u:None} 

 

if self.is_directed(): 

neighbor = self.neighbor_out_iterator(u) 

else: 

neighbor = self.neighbor_iterator(u) 

 

for v in neighbor: 

if by_weight is False: 

du[v] = 1 

else: 

du[v] = weight_function((u, v, self.edge_label(u, v))) 

pu[v] = u 

 

dist[u] = du 

pred[u] = pu 

 

for w in verts: 

dw = dist[w] 

for u in verts: 

du = dist[u] 

for v in verts: 

if du.get(v, Infinity) > du.get(w, Infinity) + dw.get(v, Infinity): 

if u == v: 

raise ValueError("The graph contains a negative cycle.") 

du[v] = du[w] + dw[v] 

pred[u][v] = pred[w][v] 

 

return dist, pred 

 

def wiener_index(self, by_weight=False, algorithm=None, 

weight_function=None, check_weight=True): 

r""" 

Return the Wiener index of the graph. 

 

The graph is expected to have no cycles of negative weight. 

 

The Wiener index of a graph `G` is 

`W(G) = \frac 1 2 \sum_{u,v\in G} d(u,v)` 

where `d(u,v)` denotes the distance between vertices `u` and `v` (see 

[KRG96b]_). 

 

For more information on the input variables and more examples, we refer 

to :meth:`~GenericGraph.shortest_paths` and 

:meth:`~GenericGraph.shortest_path_all_pairs`, 

which have very similar input variables. 

 

INPUT: 

 

- ``by_weight`` (boolean) - if ``True``, the edges in the graph are 

weighted; if ``False``, all edges have weight 1. 

 

- ``algorithm`` (string) - the algorithm to use: 

 

- For ``by_weight==False`` only: 

 

- ``'BFS'`` - the computation is done through a BFS centered on 

each vertex successively. 

 

- ``'Floyd-Warshall-Cython'`` - the Cython implementation of 

the Floyd-Warshall algorithm. Usually slower than ``'BFS'``. 

 

- For graphs without negative weights: 

 

- ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in 

Boost. 

 

- ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in 

NetworkX. Usually slower than ``'Dijkstra_Boost'``. 

 

- For graphs with negative weights: 

 

- ``'Johnson_Boost'``: the Johnson algorithm, implemented in 

Boost. 

 

- ``'Floyd-Warshall-Python'`` - the Python implementation of 

the Floyd-Warshall algorithm. Usually slower than 

``'Johnson_Boost'``. 

 

- ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` for 

unweighted graphs, ``'Dijkstra_Boost'`` if all weights are 

positive, ``'Johnson_Boost'``, otherwise. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

- ``check_weight`` (boolean) - if ``True``, we check that the 

weight_function outputs a number for each edge. 

 

EXAMPLES:: 

 

sage: G = Graph( { 0: {1: None}, 1: {2: None}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True) 

sage: G.wiener_index() 

15 

sage: G.wiener_index(weight_function=lambda e:(e[2] if e[2] is not None else 1)) 

20 

sage: G.wiener_index(weight_function=lambda e:(e[2] if e[2] is not None else 200)) 

820 

sage: G.wiener_index(algorithm='BFS') 

15 

sage: G.wiener_index(algorithm='Floyd-Warshall-Cython') 

15 

sage: G.wiener_index(algorithm='Floyd-Warshall-Python') 

15 

sage: G.wiener_index(algorithm='Dijkstra_Boost') 

15 

sage: G.wiener_index(algorithm='Johnson_Boost') 

15 

sage: G.wiener_index(algorithm='Dijkstra_NetworkX') 

15 

 

TESTS:: 

 

sage: G.wiener_index(algorithm='BFS', weight_function=lambda e:(e[2] if e[2] is not None else 200)) 

Traceback (most recent call last): 

... 

ValueError: BFS algorithm does not work on weighted graphs. 

 

sage: graphs.EmptyGraph().wiener_index() 

Traceback (most recent call last): 

... 

ValueError: Wiener index is not defined for empty or one-element graph 

""" 

by_weight = by_weight or (weight_function is not None) 

 

if self.order() < 2: 

raise ValueError("Wiener index is not defined for empty or one-element graph") 

 

if algorithm=='BFS' or (algorithm is None and not by_weight): 

if by_weight: 

raise ValueError("BFS algorithm does not work on weighted graphs.") 

from .distances_all_pairs import wiener_index 

return wiener_index(self) 

 

if not self.is_connected(): 

from sage.rings.infinity import Infinity 

return Infinity 

 

distances = self.shortest_path_all_pairs(by_weight=by_weight, 

algorithm=algorithm, weight_function=weight_function, 

check_weight=check_weight)[0] 

total = 0 

for u in distances.values(): 

total += sum(u.values()) 

 

return total // 2 

 

def average_distance(self, by_weight=False, algorithm=None, 

weight_function=None): 

r""" 

Returns the average distance between vertices of the graph. 

 

Formally, for a graph `G` this value is equal to 

`\frac 1 {n(n-1)} \sum_{u,v\in G} d(u,v)` where `d(u,v)` 

denotes the distance between vertices `u` and `v` and `n` 

is the number of vertices in `G`. 

 

For more information on the input variables and more examples, we refer 

to :meth:`~GenericGraph.wiener_index` and 

:meth:`~GenericGraph.shortest_path_all_pairs`, 

which have very similar input variables. 

 

INPUT: 

 

- ``by_weight`` (boolean) - if ``True``, the edges in the graph are 

weighted; if ``False``, all edges have weight 1. 

 

- ``algorithm`` (string) - one of the following algorithms: 

 

- ``'BFS'`` - the computation is done through a BFS centered on each 

vertex successively. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Cython'`` - the Cython implementation of 

the Floyd-Warshall algorithm. Works only if ``by_weight==False``. 

 

- ``'Floyd-Warshall-Python'`` - the Python implementation of 

the Floyd-Warshall algorithm. Works also with weighted graphs, even 

with negative weights (but no negative cycle is allowed). 

 

- ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in 

NetworkX. It works with weighted graphs, but no negative weight is 

allowed. 

 

- ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost 

(works only with positive weights). 

 

- ``'Johnson_Boost'``: the Johnson algorithm, implemented in 

Boost (works also with negative weights, if there is no negative 

cycle). 

 

- ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` for 

unweighted graphs, ``'Dijkstra_Boost'`` if all weights are 

positive, ``'Johnson_Boost'``, otherwise. 

 

- ``weight_function`` (function) - a function that inputs an edge 

``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` 

is automatically set to ``True``. If ``None`` and ``by_weight`` is 

``True``, we use the edge label ``l`` as a weight. 

 

- ``check_weight`` (boolean) - if ``True``, we check that the 

weight_function outputs a number for each edge. 

 

EXAMPLES: 

 

From [GYLL93]_:: 

 

sage: g=graphs.PathGraph(10) 

sage: w=lambda x: (x*(x*x -1)/6)/(x*(x-1)/2) 

sage: g.average_distance()==w(10) 

True 

 

REFERENCE: 

 

.. [GYLL93] \I. Gutman, Y.-N. Yeh, S.-L. Lee, and Y.-L. Luo. Some recent 

results in the theory of the Wiener number. *Indian Journal of 

Chemistry*, 32A:651--661, 1993. 

 

TESTS: 

 

Giving an empty graph:: 

 

sage: g = Graph() 

sage: g.average_distance() 

Traceback (most recent call last): 

... 

ValueError: average distance is not defined for empty or one-element graph 

 

:trac:`22885`:: 

 

sage: G = graphs.PetersenGraph() 

sage: G2 = Graph([(u,v,2) for u,v,_ in G.edges()]) 

sage: G2.average_distance() 

5/3 

sage: G2.average_distance(by_weight=True) 

10/3 

""" 

if self.order() < 2: 

raise ValueError("average distance is not defined for empty or one-element graph") 

WI = self.wiener_index(by_weight=by_weight, algorithm=algorithm, 

weight_function=weight_function) 

return 2 * WI / (self.order()*(self.order()-1)) 

 

def szeged_index(self): 

r""" 

Returns the Szeged index of the graph. 

 

For any `uv\in E(G)`, let 

`N_u(uv) = \{w\in G:d(u,w)<d(v,w)\}, n_u(uv)=|N_u(uv)|` 

 

The Szeged index of a graph is then defined as [1]: 

`\sum_{uv \in E(G)}n_u(uv)\times n_v(uv)` 

 

EXAMPLES: 

 

True for any connected graph [1]:: 

 

sage: g=graphs.PetersenGraph() 

sage: g.wiener_index()<= g.szeged_index() 

True 

 

True for all trees [1]:: 

 

sage: g=Graph() 

sage: g.add_edges(graphs.CubeGraph(5).min_spanning_tree()) 

sage: g.wiener_index() == g.szeged_index() 

True 

 

 

REFERENCE: 

 

[1] Klavzar S., Rajapakse A., Gutman I. (1996). The Szeged and the 

Wiener index of graphs. Applied Mathematics Letters, 9 (5), pp. 45-49. 

""" 

distances=self.distance_all_pairs() 

s=0 

for (u,v) in self.edges(labels=None): 

du=distances[u] 

dv=distances[v] 

n1=n2=0 

for w in self: 

if du[w] < dv[w]: 

n1+=1 

elif dv[w] < du[w]: 

n2+=1 

s+=(n1*n2) 

return s 

 

### Searches 

 

def breadth_first_search(self, start, ignore_direction=False, 

distance=None, neighbors=None, 

report_distance=False): 

""" 

Return an iterator over the vertices in a breadth-first ordering. 

 

INPUT: 

 

- ``start`` -- vertex or list of vertices from which to start 

the traversal. 

 

- ``ignore_direction`` -- (default ``False``) only applies to 

directed graphs. If ``True``, searches across edges in either 

direction. 

 

- ``distance`` -- the maximum distance from the ``start`` nodes 

to traverse. The ``start`` nodes are distance zero from 

themselves. 

 

- ``neighbors`` -- a function giving the neighbors of a vertex. 

The function should take a vertex and return a list of 

vertices. For a graph, ``neighbors`` is by default the 

:meth:`.neighbors` function of the graph. For a digraph, 

the ``neighbors`` function defaults to the 

:meth:`~DiGraph.neighbor_out_iterator` function of the graph. 

 

- ``report_distance`` -- (default ``False``) If ``True``, 

reports pairs (vertex, distance) where distance is the 

distance from the ``start`` nodes. If ``False`` only the 

vertices are reported. 

 

.. SEEALSO:: 

 

- :meth:`breadth_first_search <sage.graphs.base.c_graph.CGraphBackend.breadth_first_search>` 

-- breadth-first search for fast compiled graphs. 

 

- :meth:`depth_first_search <sage.graphs.base.c_graph.CGraphBackend.depth_first_search>` 

-- depth-first search for fast compiled graphs. 

 

- :meth:`depth_first_search` -- depth-first search for generic graphs. 

 

EXAMPLES:: 

 

sage: G = Graph( { 0: [1], 1: [2], 2: [3], 3: [4], 4: [0]} ) 

sage: list(G.breadth_first_search(0)) 

[0, 1, 4, 2, 3] 

 

By default, the edge direction of a digraph is respected, but this 

can be overridden by the ``ignore_direction`` parameter:: 

 

sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]}) 

sage: list(D.breadth_first_search(0)) 

[0, 1, 2, 3, 4, 5, 6, 7] 

sage: list(D.breadth_first_search(0, ignore_direction=True)) 

[0, 1, 2, 3, 7, 4, 5, 6] 

 

You can specify a maximum distance in which to search. A 

distance of zero returns the ``start`` vertices:: 

 

sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]}) 

sage: list(D.breadth_first_search(0,distance=0)) 

[0] 

sage: list(D.breadth_first_search(0,distance=1)) 

[0, 1, 2, 3] 

 

Multiple starting vertices can be specified in a list:: 

 

sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]}) 

sage: list(D.breadth_first_search([0])) 

[0, 1, 2, 3, 4, 5, 6, 7] 

sage: list(D.breadth_first_search([0,6])) 

[0, 6, 1, 2, 3, 7, 4, 5] 

sage: list(D.breadth_first_search([0,6],distance=0)) 

[0, 6] 

sage: list(D.breadth_first_search([0,6],distance=1)) 

[0, 6, 1, 2, 3, 7] 

sage: list(D.breadth_first_search(6,ignore_direction=True,distance=2)) 

[6, 3, 7, 0, 5] 

 

More generally, you can specify a ``neighbors`` function. For 

example, you can traverse the graph backwards by setting 

``neighbors`` to be the :meth:`.neighbors_in` function of the graph:: 

 

sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]}) 

sage: list(D.breadth_first_search(5,neighbors=D.neighbors_in, distance=2)) 

[5, 1, 2, 0] 

sage: list(D.breadth_first_search(5,neighbors=D.neighbors_out, distance=2)) 

[5, 7, 0] 

sage: list(D.breadth_first_search(5,neighbors=D.neighbors, distance=2)) 

[5, 1, 2, 7, 0, 4, 6] 

 

It is possible (:trac:`16470`) using the keyword 

``report_distance`` to get pairs (vertex, distance) encoding 

the distance to the starting vertices:: 

 

sage: G = graphs.PetersenGraph() 

sage: list(G.breadth_first_search(0, report_distance=True)) 

[(0, 0), (1, 1), (4, 1), (5, 1), (2, 2), (6, 2), (3, 2), (9, 2), 

(7, 2), (8, 2)] 

sage: list(G.breadth_first_search(0, report_distance=False)) 

[0, 1, 4, 5, 2, 6, 3, 9, 7, 8] 

 

sage: D = DiGraph({0:[1, 3], 1:[0, 2], 2:[0, 3], 3:[4]}) 

sage: D.show() 

sage: list(D.breadth_first_search(4, neighbors=D.neighbor_in_iterator, report_distance=True)) 

[(4, 0), (3, 1), (0, 2), (2, 2), (1, 3)] 

 

sage: C = graphs.CycleGraph(4) 

sage: list(C.breadth_first_search([0,1], report_distance=True)) 

[(0, 0), (1, 0), (3, 1), (2, 1)] 

 

TESTS:: 

 

sage: D = DiGraph({1:[0], 2:[0]}) 

sage: list(D.breadth_first_search(0)) 

[0] 

sage: list(D.breadth_first_search(0, ignore_direction=True)) 

[0, 1, 2] 

""" 

from sage.rings.semirings.non_negative_integer_semiring import NN 

if (distance is not None and distance not in NN): 

raise ValueError("distance must be a non-negative integer, not {0}".format(distance)) 

 

# Preferably use the Cython implementation 

if neighbors is None and not isinstance(start, list) and distance is None and hasattr(self._backend,"breadth_first_search") and not report_distance: 

for v in self._backend.breadth_first_search(start, ignore_direction=ignore_direction): 

yield v 

else: 

if neighbors is None: 

if not self._directed or ignore_direction: 

neighbors = self.neighbor_iterator 

else: 

neighbors = self.neighbor_out_iterator 

seen = set([]) 

if isinstance(start, list): 

queue = [(v, 0) for v in start] 

else: 

queue = [(start, 0)] 

 

# Non-existing start vertex is detected later if distance > 0. 

if distance == 0: 

for v in queue: 

if not v[0] in self: 

raise LookupError("start vertex ({0}) is not a vertex of the graph".format(v[0])) 

 

for v, d in queue: 

if report_distance: 

yield v, d 

else: 

yield v 

seen.add(v) 

 

while len(queue) > 0: 

v, d = queue.pop(0) 

if distance is None or d < distance: 

for w in neighbors(v): 

if w not in seen: 

seen.add(w) 

queue.append((w, d + 1)) 

if report_distance: 

yield w, d + 1 

else: 

yield w 

 

def depth_first_search(self, start, ignore_direction=False, 

distance=None, neighbors=None): 

""" 

Return an iterator over the vertices in a depth-first ordering. 

 

INPUT: 

 

- ``start`` - vertex or list of vertices from which to start 

the traversal 

 

- ``ignore_direction`` - (default False) only applies to 

directed graphs. If True, searches across edges in either 

direction. 

 

- ``distance`` - Deprecated. Broken, do not use. 

 

- ``neighbors`` - a function giving the neighbors of a vertex. 

The function should take a vertex and return a list of 

vertices. For a graph, ``neighbors`` is by default the 

:meth:`.neighbors` function of the graph. For a digraph, 

the ``neighbors`` function defaults to the 

:meth:`~DiGraph.neighbor_out_iterator` function of the graph. 

 

.. SEEALSO:: 

 

- :meth:`breadth_first_search` 

 

- :meth:`breadth_first_search <sage.graphs.base.c_graph.CGraphBackend.breadth_first_search>` 

-- breadth-first search for fast compiled graphs. 

 

- :meth:`depth_first_search <sage.graphs.base.c_graph.CGraphBackend.depth_first_search>` 

-- depth-first search for fast compiled graphs. 

 

EXAMPLES:: 

 

sage: G = Graph( { 0: [1], 1: [2], 2: [3], 3: [4], 4: [0]} ) 

sage: list(G.depth_first_search(0)) 

[0, 4, 3, 2, 1] 

 

By default, the edge direction of a digraph is respected, but this 

can be overridden by the ``ignore_direction`` parameter:: 

 

 

sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]}) 

sage: list(D.depth_first_search(0)) 

[0, 3, 6, 7, 2, 5, 1, 4] 

sage: list(D.depth_first_search(0, ignore_direction=True)) 

[0, 7, 6, 3, 5, 2, 1, 4] 

 

Multiple starting vertices can be specified in a list:: 

 

sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]}) 

sage: list(D.depth_first_search([0])) 

[0, 3, 6, 7, 2, 5, 1, 4] 

sage: list(D.depth_first_search([0,6])) 

[0, 3, 6, 7, 2, 5, 1, 4] 

 

More generally, you can specify a ``neighbors`` function. For 

example, you can traverse the graph backwards by setting 

``neighbors`` to be the :meth:`.neighbors_in` function of the graph:: 

 

sage: D = digraphs.Path(10) 

sage: D.add_path([22,23,24,5]) 

sage: D.add_path([5,33,34,35]) 

sage: list(D.depth_first_search(5, neighbors=D.neighbors_in)) 

[5, 4, 3, 2, 1, 0, 24, 23, 22] 

sage: list(D.breadth_first_search(5, neighbors=D.neighbors_in)) 

[5, 24, 4, 23, 3, 22, 2, 1, 0] 

sage: list(D.depth_first_search(5, neighbors=D.neighbors_out)) 

[5, 6, 7, 8, 9, 33, 34, 35] 

sage: list(D.breadth_first_search(5, neighbors=D.neighbors_out)) 

[5, 33, 6, 34, 7, 35, 8, 9] 

 

TESTS:: 

 

sage: D = DiGraph({1:[0], 2:[0]}) 

sage: list(D.depth_first_search(0)) 

[0] 

sage: list(D.depth_first_search(0, ignore_direction=True)) 

[0, 2, 1] 

 

""" 

from sage.misc.superseded import deprecation 

if distance is not None: 

deprecation(19227, "Parameter 'distance' is broken. Do not use.") 

 

# Preferably use the Cython implementation 

if neighbors is None and not isinstance(start,list) and distance is None and hasattr(self._backend,"depth_first_search"): 

for v in self._backend.depth_first_search(start, ignore_direction = ignore_direction): 

yield v 

else: 

if neighbors is None: 

if not self._directed or ignore_direction: 

neighbors=self.neighbor_iterator 

else: 

neighbors=self.neighbor_out_iterator 

seen=set([]) 

if isinstance(start, list): 

# Reverse the list so that the initial vertices come out in the same order 

queue=[(v,0) for v in reversed(start)] 

else: 

queue=[(start,0)] 

 

while len(queue)>0: 

v,d = queue.pop() 

if v not in seen: 

yield v 

seen.add(v) 

if distance is None or d<distance: 

for w in neighbors(v): 

if w not in seen: 

queue.append((w, d+1)) 

 

def lex_BFS(self,reverse=False,tree=False, initial_vertex = None): 

r""" 

Performs a Lex BFS on the graph. 

 

A Lex BFS ( or Lexicographic Breadth-First Search ) is a Breadth 

First Search used for the recognition of Chordal Graphs. For more 

information, see the 

`Wikipedia article on Lex-BFS 

<http://en.wikipedia.org/wiki/Lexicographic_breadth-first_search>`_. 

 

INPUT: 

 

- ``reverse`` (boolean) -- whether to return the vertices 

in discovery order, or the reverse. 

 

``False`` by default. 

 

- ``tree`` (boolean) -- whether to return the discovery 

directed tree (each vertex being linked to the one that 

saw it for the first time) 

 

``False`` by default. 

 

- ``initial_vertex`` -- the first vertex to consider. 

 

``None`` by default. 

 

ALGORITHM: 

 

This algorithm maintains for each vertex left in the graph 

a code corresponding to the vertices already removed. The 

vertex of maximal code ( according to the lexicographic 

order ) is then removed, and the codes are updated. 

 

This algorithm runs in time `O(n^2)` ( where `n` is the 

number of vertices in the graph ), which is not optimal. 

An optimal algorithm would run in time `O(m)` ( where `m` 

is the number of edges in the graph ), and require the use 

of a doubly-linked list which are not available in python 

and can not really be written efficiently. This could be 

done in Cython, though. 

 

EXAMPLES: 

 

A Lex BFS is obviously an ordering of the vertices:: 

 

sage: g = graphs.PetersenGraph() 

sage: len(g.lex_BFS()) == g.order() 

True 

 

For a Chordal Graph, a reversed Lex BFS is a Perfect 

Elimination Order :: 

 

sage: g = graphs.PathGraph(3).lexicographic_product(graphs.CompleteGraph(2)) 

sage: g.lex_BFS(reverse=True) 

[(2, 1), (2, 0), (1, 1), (1, 0), (0, 1), (0, 0)] 

 

 

And the vertices at the end of the tree of discovery are, for 

chordal graphs, simplicial vertices (their neighborhood is 

a complete graph):: 

 

sage: g = graphs.ClawGraph().lexicographic_product(graphs.CompleteGraph(2)) 

sage: v = g.lex_BFS()[-1] 

sage: peo, tree = g.lex_BFS(initial_vertex = v, tree=True) 

sage: leaves = [v for v in tree if tree.in_degree(v) ==0] 

sage: all([g.subgraph(g.neighbors(v)).is_clique() for v in leaves]) 

True 

 

TESTS: 

 

There were some problems with the following call in the past (:trac:`10899`) -- now 

it should be fine:: 

 

sage: Graph(1).lex_BFS(tree=True) 

([0], Digraph on 1 vertex) 

 

""" 

id_inv = {i: v for i, v in enumerate(self.vertices())} 

code = [[] for i in range(self.order())] 

m = self.am() 

 

l = lambda x : code[x] 

vertices = set(range(self.order())) 

 

value = [] 

pred = [-1]*self.order() 

 

add_element = (lambda y:value.append(id_inv[y])) if not reverse else (lambda y: value.insert(0,id_inv[y])) 

 

# Should we take care of the first vertex we pick ? 

first = True if initial_vertex is not None else False 

 

 

while vertices: 

 

if not first: 

v = max(vertices,key=l) 

else: 

v = self.vertices().index(initial_vertex) 

first = False 

 

vertices.remove(v) 

vector = m.column(v) 

for i in vertices: 

code[i].append(vector[i]) 

if vector[i]: 

pred[i] = v 

add_element(v) 

 

if tree: 

from sage.graphs.digraph import DiGraph 

g = DiGraph(sparse=True) 

g.add_vertices(self.vertices()) 

edges = [(id_inv[i], id_inv[pred[i]]) for i in range(self.order()) if pred[i]!=-1] 

g.add_edges(edges) 

return value, g 

 

else: 

return value 

 

### Constructors 

 

def add_clique(self, vertices, loops=False): 

""" 

Add a clique to the graph with the given vertices. 

 

If the vertices are already present, only the edges are added. 

 

INPUT: 

 

- ``vertices`` -- an iterable with vertices for the clique to 

be added, e.g. a list, set, graph, etc. 

 

- ``loops`` -- (boolean, default: ``False``) whether to add 

edges from every given vertex to itself. This is allowed only 

if the (di)graph allows loops. 

 

EXAMPLES:: 

 

sage: G = Graph() 

sage: G.add_clique(range(4)) 

sage: G.is_isomorphic(graphs.CompleteGraph(4)) 

True 

sage: D = DiGraph() 

sage: D.add_clique(range(4)) 

sage: D.is_isomorphic(digraphs.Complete(4)) 

True 

sage: D = DiGraph(loops=True) 

sage: D.add_clique(range(4), loops=True) 

sage: D.is_isomorphic(digraphs.Complete(4, loops=True)) 

True 

sage: D = DiGraph(loops=False) 

sage: D.add_clique(range(4), loops=True) 

Traceback (most recent call last): 

... 

ValueError: cannot add edge from 0 to 0 in graph without loops 

 

If the list of vertices contains repeated elements, a loop will 

be added at that vertex, even if ``loops=False``:: 

 

sage: G = Graph(loops=True) 

sage: G.add_clique([1,1]) 

sage: G.edges() 

[(1, 1, None)] 

 

This is equivalent to:: 

 

sage: G = Graph(loops=True) 

sage: G.add_clique([1], loops=True) 

sage: G.edges() 

[(1, 1, None)] 

 

TESTS: 

 

Using different kinds of iterable container of vertices, :trac:`22906`:: 

 

sage: from six.moves import range 

sage: G = Graph(4) 

sage: G.add_clique(G) 

sage: G.is_clique() 

True 

sage: G = Graph() 

sage: G.add_clique(set(range(4))) 

sage: G.is_clique() 

True 

sage: G = Graph() 

sage: G.add_clique({i:(i, i+1) for i in range(4)}) 

sage: G.is_clique() 

True 

sage: G.vertices() 

[0, 1, 2, 3] 

sage: D = DiGraph(4, loops=True) 

sage: D.add_clique(range(4), loops=True) 

sage: D.is_clique(directed_clique=True) 

True 

""" 

import itertools 

if loops: 

if self.is_directed(): 

self.add_edges(itertools.product(vertices, repeat=2)) 

else: 

self.add_edges(itertools.combinations_with_replacement(vertices, 2)) 

else: 

if self.is_directed(): 

self.add_edges(itertools.permutations(vertices, 2)) 

else: 

self.add_edges(itertools.combinations(vertices, 2)) 

 

def add_cycle(self, vertices): 

""" 

Adds a cycle to the graph with the given vertices. If the vertices 

are already present, only the edges are added. 

 

For digraphs, adds the directed cycle, whose orientation is 

determined by the list. Adds edges (vertices[u], vertices[u+1]) and 

(vertices[-1], vertices[0]). 

 

INPUT: 

 

- ``vertices`` -- a list of indices for the vertices of 

the cycle to be added. 

 

 

EXAMPLES:: 

 

sage: G = Graph() 

sage: G.add_vertices(range(10)); G 

Graph on 10 vertices 

sage: show(G) 

sage: G.add_cycle(list(range(10,20))) 

sage: show(G) 

sage: G.add_cycle(list(range(10))) 

sage: show(G) 

 

:: 

 

sage: D = DiGraph() 

sage: D.add_cycle(list(range(4))) 

sage: D.edges() 

[(0, 1, None), (1, 2, None), (2, 3, None), (3, 0, None)] 

""" 

if vertices: 

self.add_path(vertices) 

self.add_edge(vertices[-1], vertices[0]) 

 

def add_path(self, vertices): 

""" 

Adds a path to the graph with the given vertices. If the vertices 

are already present, only the edges are added. 

 

For digraphs, adds the directed path vertices[0], ..., 

vertices[-1]. 

 

INPUT: 

 

 

- ``vertices`` - a list of indices for the vertices of 

the path to be added. 

 

 

EXAMPLES:: 

 

sage: G = Graph() 

sage: G.add_vertices(range(10)); G 

Graph on 10 vertices 

sage: show(G) 

sage: G.add_path(list(range(10,20))) 

sage: show(G) 

sage: G.add_path(list(range(10))) 

sage: show(G) 

 

:: 

 

sage: D = DiGraph() 

sage: D.add_path(list(range(4))) 

sage: D.edges() 

[(0, 1, None), (1, 2, None), (2, 3, None)] 

""" 

if not vertices: 

return 

vert1 = vertices[0] 

for v in vertices[1:]: 

self.add_edge(vert1, v) 

vert1 = v 

 

def complement(self): 

"""Returns the complement of the (di)graph. 

 

The complement of a graph has the same vertices, but exactly those 

edges that are not in the original graph. This is not well defined 

for graphs with multiple edges. 

 

EXAMPLES:: 

 

sage: P = graphs.PetersenGraph() 

sage: P.plot() # long time 

Graphics object consisting of 26 graphics primitives 

sage: PC = P.complement() 

sage: PC.plot() # long time 

Graphics object consisting of 41 graphics primitives 

 

:: 

 

sage: graphs.TetrahedralGraph().complement().size() 

0 

sage: graphs.CycleGraph(4).complement().edges() 

[(0, 2, None), (1, 3, None)] 

sage: graphs.CycleGraph(4).complement() 

complement(Cycle graph): Graph on 4 vertices 

sage: G = Graph(multiedges=True, sparse=True) 

sage: G.add_edges([(0,1)]*3) 

sage: G.complement() 

Traceback (most recent call last): 

... 

ValueError: This method is not known to work on graphs with 

multiedges. Perhaps this method can be updated to handle them, but 

in the meantime if you want to use it please disallow multiedges 

using allow_multiple_edges(). 

 

TESTS: 

 

We check that :trac:`15699` is fixed:: 

 

sage: G = graphs.PathGraph(5).copy(immutable=True) 

sage: G.complement() 

complement(Path graph): Graph on 5 vertices 

 

The name is not updated when there was none in the first place:: 

 

sage: g = Graph(graphs.PetersenGraph().edges()); g 

Graph on 10 vertices 

sage: g.complement() 

Graph on 10 vertices 

 

""" 

self._scream_if_not_simple() 

 

G = self.copy(data_structure='dense') 

G._backend.c_graph()[0].complement() 

 

if self.name(): 

G.name("complement({})".format(self.name())) 

 

if self.is_immutable(): 

return G.copy(immutable=True) 

return G 

 

def to_simple(self, to_undirected=True, keep_label='any', immutable=None): 

""" 

Returns a simple version of itself. 

 

In particular, loops and multiple edges are removed, and the graph might 

optionally be converted to an undirected graph. 

 

INPUT: 

 

- ``to_undirected`` - boolean - if ``True``, the graph is also converted 

to an undirected graph. 

 

- ``keep_label`` (``'any','min','max'``): if there are multiple edges 

with different labels, this variable defines which label should be 

kept: any label (``'any'``), the smallest label (``'min'``), or the 

largest (``'max'``). 

 

- ``immutable`` (boolean) -- whether to create a mutable/immutable 

copy. ``immutable=None`` (default) means that the graph and its copy 

will behave the same way. 

 

EXAMPLES:: 

 

sage: G = DiGraph(loops=True,multiedges=True,sparse=True) 

sage: G.add_edges( [ (0,0,None), (1,1,None), (2,2,None), (2,3,1), (2,3,2), (3,2,None) ] ) 

sage: G.edges(labels=False) 

[(0, 0), (1, 1), (2, 2), (2, 3), (2, 3), (3, 2)] 

sage: H=G.to_simple() 

sage: H.edges(labels=False) 

[(2, 3)] 

sage: H.is_directed() 

False 

sage: H.allows_loops() 

False 

sage: H.allows_multiple_edges() 

False 

sage: G.to_simple(to_undirected=False, keep_label='min').edges() 

[(2, 3, 1), (3, 2, None)] 

sage: G.to_simple(to_undirected=False, keep_label='max').edges() 

[(2, 3, 2), (3, 2, None)] 

""" 

if to_undirected: 

from sage.graphs.graph import Graph 

g=Graph(self) 

else: 

g=copy(self) 

g.allow_loops(False) 

g.allow_multiple_edges(False, keep_label=keep_label) 

if immutable is None: 

immutable = self.is_immutable() 

if immutable: 

g = g.copy(immutable=True) 

return g 

 

def disjoint_union(self, other, labels="pairs", immutable=None): 

""" 

Return the disjoint union of self and other. 

 

INPUT: 

 

- ``labels`` - (defaults to 'pairs') If set to 'pairs', each 

element ``v`` in the first graph will be named ``(0,v)`` and 

each element ``u`` in ``other`` will be named ``(1,u)`` in 

the result. If set to 'integers', the elements of the result 

will be relabeled with consecutive integers. 

 

- ``immutable`` (boolean) -- whether to create a mutable/immutable 

disjoint union. ``immutable=None`` (default) means that the graphs 

and their disjoint union will behave the same way. 

 

.. SEEALSO:: 

 

* :meth:`~sage.graphs.generic_graph.GenericGraph.union` 

 

* :meth:`~Graph.join` 

 

EXAMPLES:: 

 

sage: G = graphs.CycleGraph(3) 

sage: H = graphs.CycleGraph(4) 

sage: J = G.disjoint_union(H); J 

Cycle graph disjoint_union Cycle graph: Graph on 7 vertices 

sage: J.vertices() 

[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (1, 3)] 

sage: J = G.disjoint_union(H, labels='integers'); J 

Cycle graph disjoint_union Cycle graph: Graph on 7 vertices 

sage: J.vertices() 

[0, 1, 2, 3, 4, 5, 6] 

sage: (G+H).vertices() # '+'-operator is a shortcut 

[0, 1, 2, 3, 4, 5, 6] 

 

:: 

 

sage: G=Graph({'a': ['b']}) 

sage: G.name("Custom path") 

sage: G.name() 

'Custom path' 

sage: H=graphs.CycleGraph(3) 

sage: J=G.disjoint_union(H); J 

Custom path disjoint_union Cycle graph: Graph on 5 vertices 

sage: J.vertices() 

[(0, 'a'), (0, 'b'), (1, 0), (1, 1), (1, 2)] 

 

""" 

if (self._directed and not other._directed) or (not self._directed and other._directed): 

raise TypeError('both arguments must be of the same class') 

 

if labels not in ['pairs', 'integers']: 

raise ValueError("Parameter labels must be either 'pairs' or 'integers'.") 

if labels == "integers": 

r_self = {}; r_other = {}; i = 0 

for v in self: 

r_self[v] = i; i += 1 

for v in other: 

r_other[v] = i; i += 1 

G = self.relabel(r_self, inplace=False).union(other.relabel(r_other, inplace=False), immutable=immutable) 

else: 

r_self = dict([[v,(0,v)] for v in self]) 

r_other = dict([[v,(1,v)] for v in other]) 

G = self.relabel(r_self, inplace=False).union(other.relabel(r_other, inplace=False), immutable=immutable) 

 

a = self.name() 

if a == '': a = self._repr_() 

b = other.name() 

if b == '': b = other._repr_() 

 

G._name = '{} disjoint_union {}'.format(a, b) 

return G 

 

def union(self, other, immutable=None): 

""" 

Returns the union of self and other. 

 

If the graphs have common vertices, the common vertices will be 

identified. 

 

If one of the two graphs allows loops (or multiple edges), the resulting 

graph will allow loops (or multiple edges). 

 

If both graphs are weighted the resulting graphs is weighted. 

 

If both graphs are immutable, the resulting graph is immutable, unless 

requested otherwise. 

 

INPUT: 

 

- ``immutable`` (boolean) -- whether to create a mutable/immutable 

union. ``immutable=None`` (default) means that the graphs and their 

union will behave the same way. 

 

.. SEEALSO:: 

 

* :meth:`~sage.graphs.generic_graph.GenericGraph.disjoint_union` 

 

* :meth:`~Graph.join` 

 

EXAMPLES:: 

 

sage: G = graphs.CycleGraph(3) 

sage: H = graphs.CycleGraph(4) 

sage: J = G.union(H); J 

Graph on 4 vertices 

sage: J.vertices() 

[0, 1, 2, 3] 

sage: J.edges(labels=False) 

[(0, 1), (0, 2), (0, 3), (1, 2), (2, 3)] 

 

TESTS: 

 

Multiple edges and loops (:trac:`15627`):: 

 

sage: g = Graph(multiedges=True, loops=True) 

sage: g.add_edges(graphs.PetersenGraph().edges()) 

sage: g.add_edges(graphs.PetersenGraph().edges()) 

sage: g.add_edge(0,0) 

sage: g.add_edge(0,0,"Hey") 

sage: g.add_edge(0,9) 

sage: g.add_edge(0,9) 

sage: g.add_edge(0,9) 

sage: (2*g.size()) == (2*g).size() 

True 

 

Immutable input ? Immutable output (:trac:`15627`):: 

 

sage: g = g.copy(immutable=True) 

sage: (2*g)._backend 

<sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...> 

 

Check that weighted is appropriately inherited (:trac:`23843`):: 

 

sage: G1 = Graph(weighted=True) 

sage: G2 = Graph(weighted=False) 

sage: G1.union(G1).weighted() 

True 

sage: G1.union(G2).weighted() or G2.union(G1).weighted() 

False 

 

sage: D1 = DiGraph(weighted=True) 

sage: D2 = DiGraph(weighted=False) 

sage: D1.union(D1).weighted() 

True 

sage: D1.union(D2).weighted() or D2.union(D1).weighted() 

False 

""" 

if (self._directed and not other._directed) or (not self._directed and other._directed): 

raise TypeError('both arguments must be of the same class') 

 

multiedges = self.allows_multiple_edges() or other.allows_multiple_edges() 

loops = self.allows_loops() or other.allows_loops() 

weighted = self.weighted() and other.weighted() 

 

if self._directed: 

from sage.graphs.all import DiGraph 

G = DiGraph(multiedges=multiedges, loops=loops, weighted=weighted) 

else: 

from sage.graphs.all import Graph 

G = Graph(multiedges=multiedges, loops=loops, weighted=weighted) 

G.add_vertices(self.vertices()) 

G.add_vertices(other.vertices()) 

G.add_edges(self.edges()) 

G.add_edges(other.edges()) 

 

if immutable is None: 

immutable = self.is_immutable() and other.is_immutable() 

if immutable: 

G = G.copy(immutable=True) 

 

return G 

 

def cartesian_product(self, other): 

r""" 

Returns the Cartesian product of self and other. 

 

The Cartesian product of `G` and `H` is the graph `L` with vertex set 

`V(L)` equal to the Cartesian product of the vertices `V(G)` and `V(H)`, 

and `((u,v), (w,x))` is an edge iff either - `(u, w)` is an edge of self 

and `v = x`, or - `(v, x)` is an edge of other and `u = w`. 

 

.. SEEALSO:: 

 

- :meth:`~sage.graphs.graph_decompositions.graph_products.is_cartesian_product` 

-- factorization of graphs according to the Cartesian product 

 

- :mod:`~sage.graphs.graph_decompositions.graph_products` 

-- a module on graph products. 

 

TESTS: 

 

Cartesian product of graphs:: 

 

sage: G = Graph([(0,1),(1,2)]) 

sage: H = Graph([('a','b')]) 

sage: C1 = G.cartesian_product(H) 

sage: C1.edges(labels=None) 

[((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'b'), (1, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))] 

sage: C2 = H.cartesian_product(G) 

sage: C1.is_isomorphic(C2) 

True 

 

Construction of a Toroidal grid:: 

 

sage: A = graphs.CycleGraph(3) 

sage: B = graphs.CycleGraph(4) 

sage: T = A.cartesian_product(B) 

sage: T.is_isomorphic( graphs.ToroidalGrid2dGraph(3,4) ) 

True 

 

Cartesian product of digraphs:: 

 

sage: P = DiGraph([(0,1)]) 

sage: B = digraphs.DeBruijn( ['a','b'], 2 ) 

sage: Q = P.cartesian_product(B) 

sage: Q.edges(labels=None) 

[((0, 'aa'), (0, 'aa')), ((0, 'aa'), (0, 'ab')), ((0, 'aa'), (1, 'aa')), ((0, 'ab'), (0, 'ba')), ((0, 'ab'), (0, 'bb')), ((0, 'ab'), (1, 'ab')), ((0, 'ba'), (0, 'aa')), ((0, 'ba'), (0, 'ab')), ((0, 'ba'), (1, 'ba')), ((0, 'bb'), (0, 'ba')), ((0, 'bb'), (0, 'bb')), ((0, 'bb'), (1, 'bb')), ((1, 'aa'), (1, 'aa')), ((1, 'aa'), (1, 'ab')), ((1, 'ab'), (1, 'ba')), ((1, 'ab'), (1, 'bb')), ((1, 'ba'), (1, 'aa')), ((1, 'ba'), (1, 'ab')), ((1, 'bb'), (1, 'ba')), ((1, 'bb'), (1, 'bb'))] 

sage: Q.strongly_connected_components_digraph().num_verts() 

2 

sage: V = Q.strongly_connected_component_containing_vertex( (0, 'aa') ) 

sage: B.is_isomorphic( Q.subgraph(V) ) 

True 

""" 

self._scream_if_not_simple(allow_loops=True) 

if self._directed and other._directed: 

from sage.graphs.all import DiGraph 

G = DiGraph( loops = (self.has_loops() or other.has_loops()) ) 

elif (not self._directed) and (not other._directed): 

from sage.graphs.all import Graph 

G = Graph() 

else: 

raise TypeError('the graphs should be both directed or both undirected') 

 

G.add_vertices( [(u,v) for u in self for v in other] ) 

for u,w in self.edge_iterator(labels=None): 

for v in other: 

G.add_edge((u,v), (w,v)) 

for v,x in other.edge_iterator(labels=None): 

for u in self: 

G.add_edge((u,v), (u,x)) 

return G 

 

def tensor_product(self, other): 

r""" 

Returns the tensor product of self and other. 

 

The tensor product of `G` and `H` is the graph `L` with vertex set 

`V(L)` equal to the Cartesian product of the vertices `V(G)` and `V(H)`, 

and `((u,v), (w,x))` is an edge iff - `(u, w)` is an edge of self, and - 

`(v, x)` is an edge of other. 

 

The tensor product is also known as the categorical product and the 

kronecker product (refering to the kronecker matrix product). See 

:wikipedia:`Wikipedia article on the Kronecker product <Kronecker_product>`. 

 

EXAMPLES:: 

 

sage: Z = graphs.CompleteGraph(2) 

sage: C = graphs.CycleGraph(5) 

sage: T = C.tensor_product(Z); T 

Graph on 10 vertices 

sage: T.size() 

10 

sage: T.plot() # long time 

Graphics object consisting of 21 graphics primitives 

 

:: 

 

sage: D = graphs.DodecahedralGraph() 

sage: P = graphs.PetersenGraph() 

sage: T = D.tensor_product(P); T 

Graph on 200 vertices 

sage: T.size() 

900 

sage: T.plot() # long time 

Graphics object consisting of 1101 graphics primitives 

 

TESTS: 

 

Tensor product of graphs:: 

 

sage: G = Graph([(0,1), (1,2)]) 

sage: H = Graph([('a','b')]) 

sage: T = G.tensor_product(H) 

sage: T.edges(labels=None) 

[((0, 'a'), (1, 'b')), ((0, 'b'), (1, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a'))] 

sage: T.is_isomorphic( H.tensor_product(G) ) 

True 

 

Tensor product of digraphs:: 

 

sage: I = DiGraph([(0,1), (1,2)]) 

sage: J = DiGraph([('a','b')]) 

sage: T = I.tensor_product(J) 

sage: T.edges(labels=None) 

[((0, 'a'), (1, 'b')), ((1, 'a'), (2, 'b'))] 

sage: T.is_isomorphic( J.tensor_product(I) ) 

True 

 

The tensor product of two DeBruijn digraphs of same diameter is a DeBruijn digraph:: 

 

sage: B1 = digraphs.DeBruijn(2, 3) 

sage: B2 = digraphs.DeBruijn(3, 3) 

sage: T = B1.tensor_product( B2 ) 

sage: T.is_isomorphic( digraphs.DeBruijn( 2*3, 3) ) 

True 

""" 

self._scream_if_not_simple(allow_loops=True) 

if self._directed and other._directed: 

from sage.graphs.all import DiGraph 

G = DiGraph( loops = (self.has_loops() or other.has_loops()) ) 

elif (not self._directed) and (not other._directed): 

from sage.graphs.all import Graph 

G = Graph() 

else: 

raise TypeError('the graphs should be both directed or both undirected') 

G.add_vertices( [(u, v) for u in self for v in other] ) 

for u, w in self.edge_iterator(labels=None): 

for v, x in other.edge_iterator(labels=None): 

G.add_edge((u, v), (w, x)) 

if not G._directed: 

G.add_edge((u, x), (w, v)) 

return G 

 

categorical_product = tensor_product 

kronecker_product = tensor_product 

 

def lexicographic_product(self, other): 

r""" 

Returns the lexicographic product of self and other. 

 

The lexicographic product of `G` and `H` is the graph `L` with vertex 

set `V(L)=V(G)\times V(H)`, and `((u,v), (w,x))` is an edge iff : 

 

* `(u, w)` is an edge of `G`, or 

* `u = w` and `(v, x)` is an edge of `H`. 

 

EXAMPLES:: 

 

sage: Z = graphs.CompleteGraph(2) 

sage: C = graphs.CycleGraph(5) 

sage: L = C.lexicographic_product(Z); L 

Graph on 10 vertices 

sage: L.plot() # long time 

Graphics object consisting of 36 graphics primitives 

 

:: 

 

sage: D = graphs.DodecahedralGraph() 

sage: P = graphs.PetersenGraph() 

sage: L = D.lexicographic_product(P); L 

Graph on 200 vertices 

sage: L.plot() # long time 

Graphics object consisting of 3501 graphics primitives 

 

TESTS: 

 

Lexicographic product of graphs:: 

 

sage: G = Graph([(0,1), (1,2)]) 

sage: H = Graph([('a','b')]) 

sage: T = G.lexicographic_product(H) 

sage: T.edges(labels=None) 

[((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))] 

sage: T.is_isomorphic( H.lexicographic_product(G) ) 

False 

 

Lexicographic product of digraphs:: 

 

sage: I = DiGraph([(0,1), (1,2)]) 

sage: J = DiGraph([('a','b')]) 

sage: T = I.lexicographic_product(J) 

sage: T.edges(labels=None) 

[((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))] 

sage: T.is_isomorphic( J.lexicographic_product(I) ) 

False 

""" 

self._scream_if_not_simple(allow_loops=True) 

if self._directed and other._directed: 

from sage.graphs.all import DiGraph 

G = DiGraph( loops = (self.has_loops() or other.has_loops()) ) 

elif (not self._directed) and (not other._directed): 

from sage.graphs.all import Graph 

G = Graph() 

else: 

raise TypeError('the graphs should be both directed or both undirected') 

G.add_vertices( [(u,v) for u in self for v in other] ) 

for u,w in self.edge_iterator(labels=None): 

for v in other: 

for x in other: 

G.add_edge((u,v), (w,x)) 

for u in self: 

for v,x in other.edge_iterator(labels=None): 

G.add_edge((u,v), (u,x)) 

return G 

 

def strong_product(self, other): 

r""" 

Returns the strong product of self and other. 

 

The strong product of `G` and `H` is the graph `L` with vertex set 

`V(L)=V(G)\times V(H)`, and `((u,v), (w,x))` is an edge of `L` iff 

either : 

 

* `(u, w)` is an edge of `G` and `v = x`, or 

* `(v, x)` is an edge of `H` and `u = w`, or 

* `(u, w)` is an edge of `G` and `(v, x)` is an edge of `H`. 

 

In other words, the edges of the strong product is the union of the 

edges of the tensor and Cartesian products. 

 

EXAMPLES:: 

 

sage: Z = graphs.CompleteGraph(2) 

sage: C = graphs.CycleGraph(5) 

sage: S = C.strong_product(Z); S 

Graph on 10 vertices 

sage: S.plot() # long time 

Graphics object consisting of 36 graphics primitives 

 

:: 

 

sage: D = graphs.DodecahedralGraph() 

sage: P = graphs.PetersenGraph() 

sage: S = D.strong_product(P); S 

Graph on 200 vertices 

sage: S.plot() # long time 

Graphics object consisting of 1701 graphics primitives 

 

TESTS: 

 

Strong product of graphs is commutative:: 

 

sage: G = Graph([(0,1), (1,2)]) 

sage: H = Graph([('a','b')]) 

sage: T = G.strong_product(H) 

sage: T.is_isomorphic( H.strong_product(G) ) 

True 

 

Strong product of digraphs is commutative:: 

 

sage: I = DiGraph([(0,1), (1,2)]) 

sage: J = DiGraph([('a','b')]) 

sage: T = I.strong_product(J) 

sage: T.is_isomorphic( J.strong_product(I) ) 

True 

 

Counting the edges (see :trac:`13699`):: 

 

sage: g = graphs.RandomGNP(5,.5) 

sage: gn,gm = g.order(), g.size() 

sage: h = graphs.RandomGNP(5,.5) 

sage: hn,hm = h.order(), h.size() 

sage: product_size = g.strong_product(h).size() 

sage: expected = gm*hn + hm*gn + 2*gm*hm 

sage: if product_size != expected: 

....: print("Something is really wrong here...", product_size, "!=", expected) 

""" 

self._scream_if_not_simple(allow_loops=True) 

if self._directed and other._directed: 

from sage.graphs.all import DiGraph 

G = DiGraph( loops = (self.has_loops() or other.has_loops()) ) 

elif (not self._directed) and (not other._directed): 

from sage.graphs.all import Graph 

G = Graph() 

else: 

raise TypeError('the graphs should be both directed or both undirected') 

 

G.add_vertices( [(u,v) for u in self for v in other] ) 

for u,w in self.edge_iterator(labels=None): 

for v in other: 

G.add_edge((u,v), (w,v)) 

for v,x in other.edge_iterator(labels=None): 

G.add_edge((u,v), (w,x)) 

if not self._directed: 

G.add_edge((w,v), (u,x)) 

for v,x in other.edge_iterator(labels=None): 

for u in self: 

G.add_edge((u,v), (u,x)) 

return G 

 

def disjunctive_product(self, other): 

r""" 

Returns the disjunctive product of self and other. 

 

The disjunctive product of `G` and `H` is the graph `L` with vertex set 

`V(L)=V(G)\times V(H)`, and `((u,v), (w,x))` is an edge iff either : 

 

* `(u, w)` is an edge of `G`, or 

* `(v, x)` is an edge of `H`. 

 

EXAMPLES:: 

 

sage: Z = graphs.CompleteGraph(2) 

sage: D = Z.disjunctive_product(Z); D 

Graph on 4 vertices 

sage: D.plot() # long time 

Graphics object consisting of 11 graphics primitives 

 

:: 

 

sage: C = graphs.CycleGraph(5) 

sage: D = C.disjunctive_product(Z); D 

Graph on 10 vertices 

sage: D.plot() # long time 

Graphics object consisting of 46 graphics primitives 

 

TESTS: 

 

Disjunctive product of graphs:: 

 

sage: G = Graph([(0,1), (1,2)]) 

sage: H = Graph([('a','b')]) 

sage: T = G.disjunctive_product(H) 

sage: T.edges(labels=None) 

[((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), ((0, 'a'), (2, 'b')), ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), ((0, 'b'), (2, 'a')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))] 

sage: T.is_isomorphic( H.disjunctive_product(G) ) 

True 

 

Disjunctive product of digraphs:: 

 

sage: I = DiGraph([(0,1), (1,2)]) 

sage: J = DiGraph([('a','b')]) 

sage: T = I.disjunctive_product(J) 

sage: T.edges(labels=None) 

[((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), ((0, 'a'), (2, 'b')), ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), ((1, 'a'), (0, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (0, 'b')), ((2, 'a'), (1, 'b')), ((2, 'a'), (2, 'b'))] 

sage: T.is_isomorphic( J.disjunctive_product(I) ) 

True 

""" 

self._scream_if_not_simple(allow_loops=True) 

if self._directed and other._directed: 

from sage.graphs.all import DiGraph 

G = DiGraph( loops = (self.has_loops() or other.has_loops()) ) 

elif (not self._directed) and (not other._directed): 

from sage.graphs.all import Graph 

G = Graph() 

else: 

raise TypeError('the graphs should be both directed or both undirected') 

 

G.add_vertices( [(u,v) for u in self for v in other] ) 

for u,w in self.edge_iterator(labels=None): 

for v in other: 

for x in other: 

G.add_edge((u,v), (w,x)) 

for v,x in other.edge_iterator(labels=None): 

for u in self: 

for w in self: 

G.add_edge((u,v), (w,x)) 

return G 

 

def transitive_closure(self): 

r""" 

Computes the transitive closure of a graph and returns it. The 

original graph is not modified. 

 

The transitive closure of a graph G has an edge (x,y) if and only 

if there is a path between x and y in G. 

 

The transitive closure of any strongly connected component of a 

graph is a complete graph. In particular, the transitive closure of 

a connected undirected graph is a complete graph. The transitive 

closure of a directed acyclic graph is a directed acyclic graph 

representing the full partial order. 

 

EXAMPLES:: 

 

sage: g=graphs.PathGraph(4) 

sage: g.transitive_closure() 

Transitive closure of Path graph: Graph on 4 vertices 

sage: g.transitive_closure()==graphs.CompleteGraph(4) 

True 

sage: g=DiGraph({0:[1,2], 1:[3], 2:[4,5]}) 

sage: g.transitive_closure().edges(labels=False) 

[(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 3), (2, 4), (2, 5)] 

 

On an immutable digraph:: 

 

sage: digraphs.Path(5).copy(immutable=True).transitive_closure() 

Transitive closure of Path: Digraph on 5 vertices 

""" 

G = copy(self) 

G.name('Transitive closure of ' + self.name()) 

G.add_edges(((v, e) for v in G for e in G.breadth_first_search(v)), loops=None) 

return G 

 

def transitive_reduction(self): 

r""" 

Returns a transitive reduction of a graph. The original graph is 

not modified. 

 

A transitive reduction H of G has a path from x to y if and only if 

there was a path from x to y in G. Deleting any edge of H destroys 

this property. A transitive reduction is not unique in general. A 

transitive reduction has the same transitive closure as the 

original graph. 

 

A transitive reduction of a complete graph is a tree. A transitive 

reduction of a tree is itself. 

 

EXAMPLES:: 

 

sage: g = graphs.PathGraph(4) 

sage: g.transitive_reduction() == g 

True 

sage: g = graphs.CompleteGraph(5) 

sage: edges = g.transitive_reduction().edges(); len(edges) 

4 

sage: g = DiGraph({0:[1,2], 1:[2,3,4,5], 2:[4,5]}) 

sage: g.transitive_reduction().size() 

5 

""" 

if self.is_directed() and self.is_directed_acyclic(): 

from sage.graphs.generic_graph_pyx import transitive_reduction_acyclic 

return transitive_reduction_acyclic(self) 

 

G = copy(self) 

n = G.order() 

for e in self.edge_iterator(): 

# Try deleting the edge, see if we still have a path 

# between the vertices. 

G.delete_edge(e) 

if G.distance(e[0], e[1]) > n: 

# oops, we shouldn't have deleted it 

G.add_edge(e) 

return G 

 

def is_transitively_reduced(self): 

r""" 

Tests whether the digraph is transitively reduced. 

 

A digraph is transitively reduced if it is equal to its transitive 

reduction. 

 

EXAMPLES:: 

 

sage: d = DiGraph({0:[1],1:[2],2:[3]}) 

sage: d.is_transitively_reduced() 

True 

 

sage: d = DiGraph({0:[1,2],1:[2]}) 

sage: d.is_transitively_reduced() 

False 

 

sage: d = DiGraph({0:[1,2],1:[2],2:[]}) 

sage: d.is_transitively_reduced() 

False 

""" 

from sage.rings.infinity import Infinity 

G = copy(self) 

for e in self.edge_iterator(): 

G.delete_edge(e) 

if G.distance(e[0],e[1]) == Infinity: 

G.add_edge(e) 

else: 

return False 

return True 

 

 

### Visualization 

 

def _color_by_label(self, format='hex', as_function=False, default_color = "black"): 

""" 

Coloring of the edges according to their label for plotting 

 

INPUT: 

 

- ``format`` -- "rgbtuple", "hex", ``True`` (same as "hex"), 

or a function or dictionary assigning colors to labels 

(default: "hex") 

- ``default_color`` -- a color (as a string) or None (default: "black") 

- ``as_function`` -- boolean (default: ``False``) 

 

OUTPUT: A coloring of the edges of this graph. 

 

If ``as_function`` is ``True``, then the coloring is returned 

as a function assigning a color to each label. Otherwise (the 

default, for backward compatibility), the coloring is returned 

as a dictionary assigning to each color the list of the edges 

of the graph of that color. 

 

This is factored out from plot() for use in 3d plots, etc. 

 

If ``format`` is a function, then it is used directly as 

coloring. Otherwise, for each label a default color is chosen 

along a rainbow (see :func:`sage.plot.colors.rainbow`). If 

``format`` is a dictionary, then the colors specified there 

override the default choices. 

 

EXAMPLES: 

 

We consider the Cayley graph of the symmetric group, whose 

edges are labelled by the numbers 1,2, and 3:: 

 

sage: G = SymmetricGroup(4).cayley_graph() 

sage: set(G.edge_labels()) 

{1, 2, 3} 

 

We first request the coloring as a function:: 

 

sage: f = G._color_by_label(as_function=True) 

sage: [f(1), f(2), f(3)] 

['#ff0000', '#00ff00', '#0000ff'] 

sage: f = G._color_by_label({1: "blue", 2: "red", 3: "green"}, as_function=True) 

sage: [f(1), f(2), f(3)] 

['blue', 'red', 'green'] 

sage: f = G._color_by_label({1: "red"}, as_function=True) 

sage: [f(1), f(2), f(3)] 

['red', 'black', 'black'] 

sage: f = G._color_by_label({1: "red"}, as_function=True, default_color = 'blue') 

sage: [f(1), f(2), f(3)] 

['red', 'blue', 'blue'] 

 

The default output is a dictionary assigning edges to colors:: 

 

sage: G._color_by_label() 

{'#0000ff': [((1,3,2,4), (1,4)(2,3), 3), ...], 

'#00ff00': [((1,3,2,4), (1,2,4), 2), ...], 

'#ff0000': [((1,3,2,4), (1,3)(2,4), 1), ...]} 

 

sage: G._color_by_label({1: "blue", 2: "red", 3: "green"}) 

{'blue': [((1,3,2,4), (1,3)(2,4), 1), ...], 

'green': [((1,3,2,4), (1,4)(2,3), 3), ...], 

'red': [((1,3,2,4), (1,2,4), 2), ...]} 

 

TESTS: 

 

We check what happens when several labels have the same color:: 

 

sage: result = G._color_by_label({1: "blue", 2: "blue", 3: "green"}) 

sage: result.keys() 

['blue', 'green'] 

sage: len(result['blue']) 

48 

sage: len(result['green']) 

24 

""" 

if format is True: 

format = "hex" 

if isinstance(format, str): 

# Find all labels; this is slower and huglier than: 

# labels = set(edge[2] for edge in self.edge_iterator()) 

# but works with non hashable edge labels, and keeps backward 

# compatibility for the label ordering. 

labels = [] 

for edge in self.edge_iterator(): 

label = edge[2] 

if label not in labels: 

labels.append(label) 

 

from sage.plot.colors import rainbow 

colors = rainbow(len(labels), format=format) 

color_of_label = dict(zip(labels, colors)) 

color_of_label = color_of_label.__getitem__ 

elif isinstance(format, dict): 

color_of_label = lambda label: format.get(label, default_color) 

else: 

# This assumes that ``format`` is already a function 

color_of_label = format 

 

if as_function: 

return color_of_label 

 

edges_by_color = {} 

for edge in self.edge_iterator(): 

color = color_of_label(edge[2]) 

if color in edges_by_color: 

edges_by_color[color].append(edge) 

else: 

edges_by_color[color] = [edge] 

return edges_by_color 

 

def latex_options(self): 

r""" 

Returns an instance of 

:class:`~sage.graphs.graph_latex.GraphLatex` for the graph. 

 

Changes to this object will affect the LaTeX version of the graph. For 

a full explanation of how to use LaTeX to render graphs, see the 

introduction to the :mod:`~sage.graphs.graph_latex` module. 

 

EXAMPLES:: 

 

sage: g = graphs.PetersenGraph() 

sage: opts = g.latex_options() 

sage: opts 

LaTeX options for Petersen graph: {} 

sage: opts.set_option('tkz_style', 'Classic') 

sage: opts 

LaTeX options for Petersen graph: {'tkz_style': 'Classic'} 

""" 

if self._latex_opts is None: 

from sage.graphs.graph_latex import GraphLatex 

self._latex_opts = GraphLatex(self) 

return self._latex_opts 

 

def set_latex_options(self, **kwds): 

r""" 

Sets multiple options for rendering a graph with LaTeX. 

 

INPUT: 

 

- ``kwds`` - any number of option/value pairs to set many graph 

latex options at once (a variable number, in any 

order). Existing values are overwritten, new values are 

added. Existing values can be cleared by setting the value 

to ``None``. Possible options are documented at 

:meth:`sage.graphs.graph_latex.GraphLatex.set_option`. 

 

This method is a convenience for setting the options of a graph 

directly on an instance of the graph. For a full explanation of 

how to use LaTeX to render graphs, see the introduction to the 

:mod:`~sage.graphs.graph_latex` module. 

 

EXAMPLES:: 

 

sage: g = graphs.PetersenGraph() 

sage: g.set_latex_options(tkz_style = 'Welsh') 

sage: opts = g.latex_options() 

sage: opts.get_option('tkz_style') 

'Welsh' 

""" 

opts = self.latex_options() 

opts.set_options(**kwds) 

 

 

def layout(self, layout = None, pos = None, dim = 2, save_pos = False, **options): 

""" 

Returns a layout for the vertices of this graph. 

 

INPUT: 

 

- layout -- one of "acyclic", "circular", "ranked", "graphviz", "planar", "spring", or "tree" 

 

- pos -- a dictionary of positions or None (the default) 

 

- save_pos -- a boolean 

 

- layout options -- (see below) 

 

If ``layout=algorithm`` is specified, this algorithm is used 

to compute the positions. 

 

Otherwise, if ``pos`` is specified, use the given positions. 

 

Otherwise, try to fetch previously computed and saved positions. 

 

Otherwise use the default layout (usually the spring layout) 

 

If ``save_pos = True``, the layout is saved for later use. 

 

EXAMPLES:: 

 

sage: g = digraphs.ButterflyGraph(1) 

sage: g.layout() 

{('0', 0): [2.69..., 0.43...], 

('0', 1): [1.35..., 0.86...], 

('1', 0): [0.89..., -0.42...], 

('1', 1): [2.26..., -0.87...]} 

 

sage: g.layout(layout="acyclic_dummy", save_pos=True) 

{('0', 0): [0.3..., 0], 

('0', 1): [0.3..., 1], 

('1', 0): [0.6..., 0], 

('1', 1): [0.6..., 1]} 

 

sage: g.layout(dim = 3) 

{('0', 0): [0.68..., 0.50..., -0.24...], 

('0', 1): [1.02..., -0.02..., 0.93...], 

('1', 0): [2.06..., -0.49..., 0.23...], 

('1', 1): [1.74..., 0.01..., -0.92...]} 

 

Here is the list of all the available layout options:: 

 

sage: from sage.graphs.graph_plot import layout_options 

sage: for key, value in list(sorted(layout_options.items())): 

....: print("option {} : {}".format(key, value)) 

option by_component : Whether to do the spring layout by connected component -- a boolean. 

option dim : The dimension of the layout -- 2 or 3. 

option heights : A dictionary mapping heights to the list of vertices at this height. 

option iterations : The number of times to execute the spring layout algorithm. 

option layout : A layout algorithm -- one of : "acyclic", "circular" (plots the graph with vertices evenly distributed on a circle), "ranked", "graphviz", "planar", "spring" (traditional spring layout, using the graph's current positions as initial positions), or "tree" (the tree will be plotted in levels, depending on minimum distance for the root). 

option prog : Which graphviz layout program to use -- one of "circo", "dot", "fdp", "neato", or "twopi". 

option save_pos : Whether or not to save the computed position for the graph. 

option spring : Use spring layout to finalize the current layout. 

option tree_orientation : The direction of tree branches -- 'up', 'down', 'left' or 'right'. 

option tree_root : A vertex designation for drawing trees. A vertex of the tree to be used as the root for the ``layout='tree'`` option. If no root is specified, then one is chosen close to the center of the tree. Ignored unless ``layout='tree'`` 

 

Some of them only apply to certain layout algorithms. For 

details, see :meth:`.layout_acyclic`, :meth:`.layout_planar`, 

:meth:`.layout_circular`, :meth:`.layout_spring`, ... 

 

.. WARNING:: unknown optional arguments are silently ignored 

 

.. WARNING:: 

 

``graphviz`` and ``dot2tex`` are currently required 

to obtain a nice 'acyclic' layout. See 

:meth:`.layout_graphviz` for installation instructions. 

 

A subclass may implement another layout algorithm `blah`, by 

implementing a method ``.layout_blah``. It may override 

the default layout by overriding :meth:`.layout_default`, and 

similarly override the predefined layouts. 

 

.. TODO:: 

 

use this feature for all the predefined graphs classes 

(like for the Petersen graph, ...), rather than systematically 

building the layout at construction time. 

""" 

if layout is None: 

if pos is None: 

pos = self.get_pos(dim = dim) 

if pos is None: 

layout = 'default' 

 

if hasattr(self, "layout_%s"%layout): 

pos = getattr(self, "layout_%s"%layout)(dim = dim, **options) 

elif layout is not None: 

raise ValueError("unknown layout algorithm: %s"%layout) 

 

if len(pos) < self.order(): 

pos = self.layout_extend_randomly(pos, dim = dim) 

 

if save_pos: 

self.set_pos(pos, dim = dim) 

return pos 

 

 

def layout_spring(self, by_component = True, **options): 

""" 

Computes a spring layout for this graph 

 

INPUT: 

 

- ``iterations`` -- a positive integer 

- ``dim`` -- 2 or 3 (default: 2) 

 

OUTPUT: a dictionary mapping vertices to positions 

 

Returns a layout computed by randomly arranging the vertices 

along the given heights 

 

EXAMPLES:: 

 

sage: g = graphs.LadderGraph(3) #TODO!!!! 

sage: g.layout_spring() 

{0: [0.73..., -0.29...], 

1: [1.37..., 0.30...], 

2: [2.08..., 0.89...], 

3: [1.23..., -0.83...], 

4: [1.88..., -0.30...], 

5: [2.53..., 0.22...]} 

sage: g = graphs.LadderGraph(7) 

sage: g.plot(layout = "spring") 

Graphics object consisting of 34 graphics primitives 

""" 

return spring_layout_fast(self, by_component = by_component, **options) 

 

layout_default = layout_spring 

 

def layout_ranked(self, heights = None, dim = 2, spring = False, **options): 

""" 

Computes a ranked layout for this graph 

 

INPUT: 

 

- heights -- a dictionary mapping heights to the list of vertices at this height 

 

OUTPUT: a dictionary mapping vertices to positions 

 

Returns a layout computed by randomly arranging the vertices 

along the given heights 

 

EXAMPLES:: 

 

sage: g = graphs.LadderGraph(3) 

sage: g.layout_ranked(heights = dict( (i,[i, i+3]) for i in range(3) )) 

{0: [0.668..., 0], 

1: [0.667..., 1], 

2: [0.677..., 2], 

3: [1.34..., 0], 

4: [1.33..., 1], 

5: [1.33..., 2]} 

sage: g = graphs.LadderGraph(7) 

sage: g.plot(layout = "ranked", heights = dict( (i,[i, i+7]) for i in range(7) )) 

Graphics object consisting of 34 graphics primitives 

""" 

assert heights is not None 

 

from sage.misc.randstate import current_randstate 

random = current_randstate().python_random().random 

 

if self.order() == 0: 

return {} 

 

pos = {} 

mmax = max([len(ccc) for ccc in heights.values()]) 

ymin = min(heights.keys()) 

ymax = max(heights.keys()) 

dist = (max(ymax-ymin, 1)) / (mmax+1.0) 

for height in heights: 

num_xs = len(heights[height]) 

if num_xs == 0: 

continue 

j = (mmax - num_xs)/2.0 

for k in range(num_xs): 

pos[heights[height][k]] = [ dist*(j+k+1) + random()*(dist*0.03) for i in range(dim-1) ] + [height] 

if spring: 

# This does not work that well in 2d, since the vertices on 

# the same level are unlikely to cross. It is also hard to 

# set a good equilibrium distance (parameter k in 

# run_spring). If k<1, the layout gets squished 

# horizontally. If k>1, then two adjacent vertices in 

# consecutive levels tend to be further away than desired. 

newpos = spring_layout_fast(self, 

vpos = pos, 

dim = dim, 

height = True, 

**options) 

# spring_layout_fast actually *does* touch the last coordinates 

# (conversion to floats + translation) 

# We restore back the original height. 

for x in self.vertices(): 

newpos[x][dim-1] = pos[x][dim-1] 

pos = newpos 

return pos 

 

def layout_extend_randomly(self, pos, dim = 2): 

""" 

Extends randomly a partial layout 

 

INPUT: 

 

- ``pos``: a dictionary mapping vertices to positions 

 

OUTPUT: a dictionary mapping vertices to positions 

 

The vertices not referenced in ``pos`` are assigned random 

positions within the box delimited by the other vertices. 

 

EXAMPLES:: 

 

sage: H = digraphs.ButterflyGraph(1) 

sage: H.layout_extend_randomly({('0',0): (0,0), ('1',1): (1,1)}) 

{('0', 0): (0, 0), 

('0', 1): [0.0446..., 0.332...], 

('1', 0): [0.111..., 0.514...], 

('1', 1): (1, 1)} 

""" 

assert dim == 2 # 3d not yet implemented 

from sage.misc.randstate import current_randstate 

random = current_randstate().python_random().random 

 

xmin, xmax,ymin, ymax = self._layout_bounding_box(pos) 

 

dx = xmax - xmin 

dy = ymax - ymin 

# Check each vertex position is in pos, add position 

# randomly within the plot range if none is defined 

for v in self: 

if not v in pos: 

pos[v] = [xmin + dx*random(), ymin + dy*random()] 

return pos 

 

 

def layout_circular(self, dim = 2, **options): 

""" 

Computes a circular layout for this graph 

 

OUTPUT: a dictionary mapping vertices to positions 

 

EXAMPLES:: 

 

sage: G = graphs.CirculantGraph(7,[1,3]) 

sage: G.layout_circular() 

{0: [6.12...e-17, 1.0], 

1: [-0.78..., 0.62...], 

2: [-0.97..., -0.22...], 

3: [-0.43..., -0.90...], 

4: [0.43..., -0.90...], 

5: [0.97..., -0.22...], 

6: [0.78..., 0.62...]} 

sage: G.plot(layout = "circular") 

Graphics object consisting of 22 graphics primitives 

""" 

assert dim == 2, "3D circular layout not implemented" 

from math import sin, cos, pi 

verts = self.vertices() 

n = len(verts) 

pos = {} 

for i in range(n): 

x = float(cos((pi/2) + ((2*pi)/n)*i)) 

y = float(sin((pi/2) + ((2*pi)/n)*i)) 

pos[verts[i]] = [x,y] 

return pos 

 

def layout_tree(self, tree_orientation="down", tree_root=None, 

dim=2, **options): 

r""" 

Compute an ordered tree layout for this graph, which should 

be a tree (no non-oriented cycles). 

 

INPUT: 

 

- ``tree_root`` -- the root vertex. By default ``None``. In 

this case, a vertex is chosen close to the center of the 

tree. 

 

- ``tree_orientation`` -- the direction in which the tree is 

growing, can be ``'up'``, ``'down'``, ``'left'`` or 

``'right'`` (default is ``'down'``) 

 

If the tree has been given a planar embedding (fixed circular 

order on the set of neighbors of every vertex) using 

``set_embedding``, the algorithm will create a layout that 

respects this embedding. 

 

OUTPUT: a dictionary mapping vertices to positions 

 

EXAMPLES:: 

 

sage: T = graphs.RandomLobster(25, 0.3, 0.3) 

sage: T.show(layout='tree', tree_orientation='up') 

 

sage: G = graphs.HoffmanSingletonGraph() 

sage: T = Graph() 

sage: T.add_edges(G.min_spanning_tree(starting_vertex=0)) 

sage: T.show(layout='tree', tree_root=0) 

 

sage: G = graphs.BalancedTree(2, 2) 

sage: G.layout_tree(tree_root=0) 

{0: (1.5, 0), 

1: (2.5, -1), 

2: (0.5, -1), 

3: (3.0, -2), 

4: (2.0, -2), 

5: (1.0, -2), 

6: (0.0, -2)} 

 

sage: G = graphs.BalancedTree(2,4) 

sage: G.plot(layout="tree", tree_root = 0, tree_orientation = "up") 

Graphics object consisting of 62 graphics primitives 

 

sage: G = graphs.RandomTree(80) 

sage: G.plot(layout="tree", tree_orientation = "right") 

Graphics object consisting of 160 graphics primitives 

 

Using the embedding when it exists:: 

 

sage: T = Graph([[0,1],[0,6],[0,3],[1,2],[1,5],[3,4],[3,7],[3,8]]) 

sage: T.set_embedding({0:[1,6,3],1:[2,5,0],2:[1],3:[4,7,8,0], 

....: 4:[3],5:[1],6:[0],7:[3],8:[3]}) 

sage: T.layout_tree() 

{0: (2.166..., 0), 

1: (3.5, -1), 

2: (4.0, -2), 

3: (1.0, -1), 

4: (2.0, -2), 

5: (3.0, -2), 

6: (2.0, -1), 

7: (1.0, -2), 

8: (0.0, -2)} 

sage: T.plot(layout="tree", tree_root=3) 

Graphics object consisting of 18 graphics primitives 

 

TESTS:: 

 

sage: G = graphs.CycleGraph(3) 

sage: G.plot(layout='tree') 

Traceback (most recent call last): 

... 

RuntimeError: Cannot use tree layout on this graph: self.is_tree() returns False. 

""" 

if dim != 2: 

raise ValueError('only implemented in 2D') 

 

from sage.graphs.all import Graph 

if not Graph(self).is_tree(): 

raise RuntimeError("Cannot use tree layout on this graph: " 

"self.is_tree() returns False.") 

 

try: 

emb = self.get_embedding() 

use_embedding = True 

except ValueError: 

use_embedding = False 

 

n = self.order() 

vertices = self.vertices() 

 

if tree_root is None: 

root = self.center()[0] 

else: 

root = tree_root 

 

pos = {} 

 

# The children and parent of each vertex 

if not use_embedding: 

children = {root: self.neighbors(root)} 

else: 

children = {root: emb[root]} 

parent = {u: root for u in children[root]} 

 

# stack[i] is the list of children of stick[i] which have not been given 

# a position yet. 

stack = [list(children[root])] 

stick = [root] 

 

# obstruction[y] is the smallest value of x to which a vertex at height 

# y can be assigned. All vertices at height y which have already been 

# assigned are on the left of (x-1,y). 

obstruction = [0.0] * self.num_verts() 

 

if tree_orientation in ['down', 'left']: 

o = -1 

elif tree_orientation in ['up', 'right']: 

o = 1 

else: 

raise ValueError('orientation should be "up", "down", "left" or "right"') 

 

def slide(v, dx): 

""" 

shift the vertex ``v`` and its descendants to the right by ``dx`` 

 

Precondition: ``v`` and its descendants have already had their 

positions computed. 

""" 

level = [v] 

while level: 

nextlevel = [] 

for u in level: 

x, y = pos[u] 

x += dx 

obstruction[y] = max(x + 1, obstruction[y]) 

pos[u] = x, y 

nextlevel += children[u] 

 

level = nextlevel 

 

while stack: 

C = stack[-1] 

 

# If all the children of stick[-1] have been given a position 

if not C: 

p = stick.pop() 

stack.pop() 

cp = children[p] 

y = o * len(stack) 

 

if not cp: 

# If p has no children, we draw it at the leftmost position 

# which has not been forbidden 

x = obstruction[y] 

pos[p] = x, y 

else: 

# If p has children, we put v on a vertical line going 

# through the barycenter of its children 

x = sum([pos[c][0] for c in cp]) / len(cp) 

pos[p] = x, y 

ox = obstruction[y] 

if x < ox: 

slide(p, ox - x) 

x = ox 

 

# If the vertex to the right of p has not children, we want it 

# at distance 1 from p 

obstruction[y] = x + 1 

 

# Otherwise, we take one of the children and add it to the 

# stack. Note that this vertex is removed from the list C. 

else: 

t = C.pop() 

 

pt = parent[t] 

 

if not use_embedding: 

ct = [u for u in self.neighbors(t) if u != pt] 

else: 

ct = emb[t] 

idx = ct.index(pt) 

ct = ct[idx + 1:] + ct[:idx] 

 

children[t] = ct 

for c in ct: 

parent[c] = t 

 

stack.append(list(ct)) 

stick.append(t) 

 

if tree_orientation in ['right', 'left']: 

return {p: (py, px) for p, (px, py) in iteritems(pos)} 

 

return pos 

 

def layout_graphviz(self, dim = 2, prog = 'dot', **options): 

""" 

Calls ``graphviz`` to compute a layout of the vertices of this graph. 

 

INPUT: 

 

- ``prog`` -- one of "dot", "neato", "twopi", "circo", or "fdp" 

 

EXAMPLES:: 

 

sage: g = digraphs.ButterflyGraph(2) 

sage: g.layout_graphviz() # optional - dot2tex graphviz 

{('...', ...): [...,...], 

('...', ...): [...,...], 

('...', ...): [...,...], 

('...', ...): [...,...], 

('...', ...): [...,...], 

('...', ...): [...,...], 

('...', ...): [...,...], 

('...', ...): [...,...], 

('...', ...): [...,...], 

('...', ...): [...,...], 

('...', ...): [...,...], 

('...', ...): [...,...]} 

sage: g.plot(layout = "graphviz") # optional - dot2tex graphviz 

Graphics object consisting of 29 graphics primitives 

 

Note: the actual coordinates are not deterministic 

 

By default, an acyclic layout is computed using ``graphviz``'s 

``dot`` layout program. One may specify an alternative layout 

program:: 

 

sage: g.plot(layout = "graphviz", prog = "dot") # optional - dot2tex graphviz 

Graphics object consisting of 29 graphics primitives 

sage: g.plot(layout = "graphviz", prog = "neato") # optional - dot2tex graphviz 

Graphics object consisting of 29 graphics primitives 

sage: g.plot(layout = "graphviz", prog = "twopi") # optional - dot2tex graphviz 

Graphics object consisting of 29 graphics primitives 

sage: g.plot(layout = "graphviz", prog = "fdp") # optional - dot2tex graphviz 

Graphics object consisting of 29 graphics primitives 

sage: g = graphs.BalancedTree(5,2) 

sage: g.plot(layout = "graphviz", prog = "circo") # optional - dot2tex graphviz 

Graphics object consisting of 62 graphics primitives 

 

.. TODO:: 

 

Put here some cool examples showcasing graphviz features. 

 

This requires ``graphviz`` and the ``dot2tex`` spkg. Here are 

some installation tips: 

 

- Install graphviz >= 2.14 so that the programs dot, neato, ... 

are in your path. The graphviz suite can be download from 

http://graphviz.org. 

 

- Install dot2tex with ``sage -i dot2tex`` 

 

.. TODO:: 

 

Use the graphviz functionality of Networkx 1.0 once it 

will be merged into Sage. 

 

TESTS: 

 

Make sure that :trac:`12364` is fixed:: 

 

sage: m = WordMorphism('a->abb,b->ba') 

sage: w = m.fixed_point('a') 

sage: prefix = Word(list(w[:100])) 

sage: pals = prefix.palindromes() 

sage: poset = Poset((pals, lambda x,y: x.is_factor(y))) 

sage: H = poset.hasse_diagram() 

sage: d = H.layout_graphviz() # optional - dot2tex graphviz 

""" 

assert_have_dot2tex() 

assert dim == 2, "3D graphviz layout not implemented" 

 

key = self._keys_for_vertices() 

key_to_vertex = dict( (key(v), v) for v in self ) 

 

import dot2tex 

positions = dot2tex.dot2tex(self.graphviz_string(**options), format = "positions", prog = prog) 

 

return dict( (key_to_vertex[key], pos) for (key, pos) in iteritems(positions) ) 

 

def _layout_bounding_box(self, pos): 

""" 

INPUT: 

 

- pos -- a dictionary of positions 

 

Returns a bounding box around the specified positions 

 

EXAMPLES:: 

 

sage: Graph()._layout_bounding_box( {} ) 

[-1, 1, -1, 1] 

sage: Graph()._layout_bounding_box( {0: [3,5], 1: [2,7], 2: [-4,2] } ) 

[-4, 3, 2, 7] 

sage: Graph()._layout_bounding_box( {0: [3,5], 1: [3.00000000001,4.999999999999999] } ) 

[2, 4.00000000001000, 4.00000000000000, 6] 

""" 

xs = [pos[v][0] for v in pos] 

ys = [pos[v][1] for v in pos] 

if len(xs) == 0: 

xmin = -1 

xmax = 1 

ymin = -1 

ymax = 1 

else: 

xmin = min(xs) 

xmax = max(xs) 

ymin = min(ys) 

ymax = max(ys) 

 

if xmax - xmin < 0.00000001: 

xmax += 1 

xmin -= 1 

 

if ymax - ymin < 0.00000001: 

ymax += 1 

ymin -= 1 

 

return [xmin, xmax, ymin, ymax] 

 

def graphplot(self, **options): 

""" 

Returns a GraphPlot object. 

 

EXAMPLES: 

 

Creating a graphplot object uses the same options as graph.plot():: 

 

sage: g = Graph({}, loops=True, multiedges=True, sparse=True) 

sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), 

....: (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) 

sage: GP = g.graphplot(edge_labels=True, color_by_label=True, edge_style='dashed') 

sage: GP.plot() 

Graphics object consisting of 26 graphics primitives 

 

We can modify the graphplot object. Notice that the changes are cumulative:: 

 

sage: GP.set_edges(edge_style='solid') 

sage: GP.plot() 

Graphics object consisting of 26 graphics primitives 

sage: GP.set_vertices(talk=True) 

sage: GP.plot() 

Graphics object consisting of 26 graphics primitives 

""" 

from sage.graphs.graph_plot import GraphPlot 

return GraphPlot(graph=self, options=options) 

 

def _rich_repr_(self, display_manager, **kwds): 

""" 

Rich Output Magic Method 

 

See :mod:`sage.repl.rich_output` for details. 

 

EXAMPLES:: 

 

sage: from sage.repl.rich_output import get_display_manager 

sage: dm = get_display_manager() 

sage: Graph()._rich_repr_(dm, edge_labels=True) 

OutputPlainText container 

 

The ``supplemental_plot`` preference lets us control whether 

this object is shown as text or picture+text:: 

 

sage: dm.preferences.supplemental_plot 

'never' 

sage: del dm.preferences.supplemental_plot 

sage: graphs.RandomGNP(20,0.0) 

RandomGNP(20,0.000000000000000): Graph on 20 vertices (use the .plot() method to plot) 

sage: dm.preferences.supplemental_plot = 'never' 

""" 

prefs = display_manager.preferences 

is_small = (0 < self.num_verts() < 20) 

can_plot = (prefs.supplemental_plot != 'never') 

plot_graph = can_plot and (prefs.supplemental_plot == 'always' or is_small) 

# Under certain circumstances we display the plot as graphics 

if plot_graph: 

plot_kwds = dict(kwds) 

plot_kwds.setdefault('title', repr(self)) 

output = self.plot(**plot_kwds)._rich_repr_(display_manager) 

if output is not None: 

return output 

# create text for non-graphical output 

if can_plot: 

text = '{0} (use the .plot() method to plot)'.format(repr(self)) 

else: 

text = repr(self) 

# latex() produces huge tikz environment, override 

tp = display_manager.types 

if (prefs.text == 'latex' and tp.OutputLatex in display_manager.supported_output()): 

return tp.OutputLatex(r'\text{{{0}}}'.format(text)) 

return tp.OutputPlainText(text) 

 

@options() 

def plot(self, **options): 

r""" 

Returns a graphics object representing the (di)graph. 

 

INPUT: 

 

- ``pos`` - an optional positioning dictionary 

 

- ``layout`` - what kind of layout to use, takes precedence 

over pos 

 

- 'circular' -- plots the graph with vertices evenly 

distributed on a circle 

 

- 'spring' - uses the traditional spring layout, using the 

graph's current positions as initial positions 

 

- 'tree' - the (di)graph must be a tree. One can specify 

the root of the tree using the keyword tree_root, 

otherwise a root will be selected at random. Then the 

tree will be plotted in levels, depending on minimum 

distance for the root. 

 

- ``vertex_labels`` - whether to print vertex labels 

 

- ``edge_labels`` - whether to print edge labels. By default, 

False, but if True, the result of str(l) is printed on the 

edge for each label l. Labels equal to None are not printed 

(to set edge labels, see set_edge_label). 

 

- ``edge_labels_background`` - The color of the edge labels 

background. The default is "white". To achieve a transparent 

background use "transparent". 

 

- ``vertex_size`` - size of vertices displayed 

 

- ``vertex_shape`` - the shape to draw the vertices, for 

example ``"o"`` for circle or ``"s"`` for square. Whole list 

is available at https://matplotlib.org/api/markers_api.html. 

(Not available for multiedge digraphs.) 

 

- ``graph_border`` - whether to include a box around the graph 

 

- ``vertex_colors`` - optional dictionary to specify vertex 

colors: each key is a color recognizable by matplotlib, and 

each corresponding entry is a list of vertices. If a vertex 

is not listed, it looks invisible on the resulting plot (it 

doesn't get drawn). 

 

- ``edge_colors`` - a dictionary specifying edge colors: each 

key is a color recognized by matplotlib, and each entry is a 

list of edges. 

 

- ``partition`` - a partition of the vertex set. if specified, 

plot will show each cell in a different color. vertex_colors 

takes precedence. 

 

- ``talk`` - if true, prints large vertices with white 

backgrounds so that labels are legible on slides 

 

- ``iterations`` - how many iterations of the spring layout 

algorithm to go through, if applicable 

 

- ``color_by_label`` - a boolean or dictionary or function (default: 

False) whether to color each edge with a different color according 

to its label; the colors are chosen along a rainbow, unless 

they are specified by a function or dictionary mapping 

labels to colors; this option is incompatible with 

``edge_color`` and ``edge_colors``. 

 

- ``heights`` - if specified, this is a dictionary from a set 

of floating point heights to a set of vertices 

 

- ``edge_style`` - keyword arguments passed into the 

edge-drawing routine. This currently only works for 

directed graphs, since we pass off the undirected graph to 

networkx 

 

- ``tree_root`` - a vertex of the tree to be used as the root 

for the layout="tree" option. If no root is specified, then one 

is chosen at random. Ignored unless layout='tree'. 

 

- ``tree_orientation`` - "up" or "down" (default is "down"). 

If "up" (resp., "down"), then the root of the tree will 

appear on the bottom (resp., top) and the tree will grow 

upwards (resp. downwards). Ignored unless layout='tree'. 

 

- ``save_pos`` - save position computed during plotting 

 

.. NOTE:: 

 

- This method supports any parameter accepted by 

:meth:`sage.plot.graphics.Graphics.show`. 

 

- See the documentation of the :mod:`sage.graphs.graph_plot` module 

for information and examples of how to define parameters that will 

be applied to **all** graph plots. 

 

- Default parameters for this method *and a specific graph* can also 

be set through the :class:`~sage.misc.decorators.options` 

mechanism. For more information on this different way to set 

default parameters, see the help of the :class:`options decorator 

<sage.misc.decorators.options>`. 

 

- See also the :mod:`sage.graphs.graph_latex` module for ways to use 

LaTeX to produce an image of a graph. 

 

EXAMPLES:: 

 

sage: from sage.graphs.graph_plot import graphplot_options 

sage: list(sorted(graphplot_options.items())) 

[...] 

 

sage: from math import sin, cos, pi 

sage: P = graphs.PetersenGraph() 

sage: d = {'#FF0000':[0,5], '#FF9900':[1,6], '#FFFF00':[2,7], '#00FF00':[3,8], '#0000FF':[4,9]} 

sage: pos_dict = {} 

sage: for i in range(5): 

....: x = float(cos(pi/2 + ((2*pi)/5)*i)) 

....: y = float(sin(pi/2 + ((2*pi)/5)*i)) 

....: pos_dict[i] = [x,y] 

... 

sage: for i in range(5, 10): 

....: x = float(0.5*cos(pi/2 + ((2*pi)/5)*i)) 

....: y = float(0.5*sin(pi/2 + ((2*pi)/5)*i)) 

....: pos_dict[i] = [x,y] 

... 

sage: pl = P.plot(pos=pos_dict, vertex_colors=d) 

sage: pl.show() 

 

:: 

 

sage: C = graphs.CubeGraph(8) 

sage: P = C.plot(vertex_labels=False, vertex_size=0, graph_border=True) 

sage: P.show() 

 

:: 

 

sage: G = graphs.HeawoodGraph() 

sage: for u,v,l in G.edges(): 

....: G.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')') 

sage: G.plot(edge_labels=True).show() 

 

:: 

 

sage: D = DiGraph( { 0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4], 4: [17, 5], 5: [6, 15], 6: [7], 7: [8, 14], 8: [9], 9: [10, 13], 10: [11], 11: [12, 18], 12: [16, 13], 13: [14], 14: [15], 15: [16], 16: [17], 17: [18], 18: [19], 19: []} , sparse=True) 

sage: for u,v,l in D.edges(): 

....: D.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')') 

sage: D.plot(edge_labels=True, layout='circular').show() 

 

:: 

 

sage: from sage.plot.colors import rainbow 

sage: C = graphs.CubeGraph(5) 

sage: R = rainbow(5) 

sage: edge_colors = {} 

sage: for i in range(5): 

....: edge_colors[R[i]] = [] 

sage: for u,v,l in C.edges(): 

....: for i in range(5): 

....: if u[i] != v[i]: 

....: edge_colors[R[i]].append((u,v,l)) 

sage: C.plot(vertex_labels=False, vertex_size=0, edge_colors=edge_colors).show() 

 

:: 

 

sage: D = graphs.DodecahedralGraph() 

sage: Pi = [[6,5,15,14,7],[16,13,8,2,4],[12,17,9,3,1],[0,19,18,10,11]] 

sage: D.show(partition=Pi) 

 

:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.allow_loops(True) 

sage: G.add_edge(0,0) 

sage: G.show() 

 

:: 

 

sage: D = DiGraph({0:[0,1], 1:[2], 2:[3]}, loops=True) 

sage: D.show() 

sage: D.show(edge_colors={(0,1,0):[(0,1,None),(1,2,None)],(0,0,0):[(2,3,None)]}) 

 

:: 

 

sage: pos = {0:[0.0, 1.5], 1:[-0.8, 0.3], 2:[-0.6, -0.8], 3:[0.6, -0.8], 4:[0.8, 0.3]} 

sage: g = Graph({0:[1], 1:[2], 2:[3], 3:[4], 4:[0]}) 

sage: g.plot(pos=pos, layout='spring', iterations=0) 

Graphics object consisting of 11 graphics primitives 

 

:: 

 

sage: G = Graph() 

sage: P = G.plot() 

sage: P.axes() 

False 

sage: G = DiGraph() 

sage: P = G.plot() 

sage: P.axes() 

False 

 

:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.get_pos() 

{0: (6.12..., 1.0...), 

1: (-0.95..., 0.30...), 

2: (-0.58..., -0.80...), 

3: (0.58..., -0.80...), 

4: (0.95..., 0.30...), 

5: (1.53..., 0.5...), 

6: (-0.47..., 0.15...), 

7: (-0.29..., -0.40...), 

8: (0.29..., -0.40...), 

9: (0.47..., 0.15...)} 

sage: P = G.plot(save_pos=True, layout='spring') 

 

The following illustrates the format of a position dictionary. 

 

sage: G.get_pos() # currently random across platforms, see #9593 

{0: [1.17..., -0.855...], 

1: [1.81..., -0.0990...], 

2: [1.35..., 0.184...], 

3: [1.51..., 0.644...], 

4: [2.00..., -0.507...], 

5: [0.597..., -0.236...], 

6: [2.04..., 0.687...], 

7: [1.46..., -0.473...], 

8: [0.902..., 0.773...], 

9: [2.48..., -0.119...]} 

 

:: 

 

sage: T = list(graphs.trees(7)) 

sage: t = T[3] 

sage: t.plot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]}) 

Graphics object consisting of 14 graphics primitives 

 

:: 

 

sage: T = list(graphs.trees(7)) 

sage: t = T[3] 

sage: t.plot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]}) 

Graphics object consisting of 14 graphics primitives 

sage: t.set_edge_label(0,1,-7) 

sage: t.set_edge_label(0,5,3) 

sage: t.set_edge_label(0,5,99) 

sage: t.set_edge_label(1,2,1000) 

sage: t.set_edge_label(3,2,'spam') 

sage: t.set_edge_label(2,6,3/2) 

sage: t.set_edge_label(0,4,66) 

sage: t.plot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]}, edge_labels=True) 

Graphics object consisting of 20 graphics primitives 

 

:: 

 

sage: T = list(graphs.trees(7)) 

sage: t = T[3] 

sage: t.plot(layout='tree') 

Graphics object consisting of 14 graphics primitives 

 

:: 

 

sage: t = DiGraph('JCC???@A??GO??CO??GO??') 

sage: t.plot(layout='tree', tree_root=0, tree_orientation="up") 

Graphics object consisting of 22 graphics primitives 

sage: D = DiGraph({0:[1,2,3], 2:[1,4], 3:[0]}) 

sage: D.plot() 

Graphics object consisting of 16 graphics primitives 

 

sage: D = DiGraph(multiedges=True,sparse=True) 

sage: for i in range(5): 

....: D.add_edge((i,i+1,'a')) 

....: D.add_edge((i,i-1,'b')) 

sage: D.plot(edge_labels=True,edge_colors=D._color_by_label()) 

Graphics object consisting of 34 graphics primitives 

sage: D.plot(edge_labels=True, color_by_label={'a':'blue', 'b':'red'}, edge_style='dashed') 

Graphics object consisting of 34 graphics primitives 

 

sage: g = Graph({}, loops=True, multiedges=True,sparse=True) 

sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), 

....: (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) 

sage: g.plot(edge_labels=True, color_by_label=True, edge_style='dashed') 

Graphics object consisting of 26 graphics primitives 

 

:: 

 

sage: S = SupersingularModule(389) 

sage: H = S.hecke_matrix(2) 

sage: D = DiGraph(H,sparse=True) 

sage: P = D.plot() 

 

:: 

 

sage: G=Graph({'a':['a','b','b','b','e'],'b':['c','d','e'],'c':['c','d','d','d'],'d':['e']},sparse=True) 

sage: G.show(pos={'a':[0,1],'b':[1,1],'c':[2,0],'d':[1,0],'e':[0,0]}) 

 

TESTS:: 

 

sage: G = DiGraph({0:{1:'a', 2:'a'}, 1:{0:'b'}, 2:{0:'c'}}) 

sage: p = G.plot(edge_labels=True, color_by_label={'a':'yellow', 'b':'purple'}); p 

Graphics object consisting of 14 graphics primitives 

sage: sorted([x.options()['rgbcolor'] for x in p if isinstance(x, sage.plot.arrow.CurveArrow)]) 

['black', 'purple', 'yellow', 'yellow'] 

""" 

return self.graphplot(**options).plot() 

 

def show(self, method = "matplotlib", **kwds): 

""" 

Shows the (di)graph. 

 

INPUT: 

 

- ``method`` -- set to ``"matplotlib"`` (default) to display the graph 

using matplotlib, or to ``"js"`` to visualize it in a browser using 

`d3.js <http://d3js.org/>`_. 

 

- Any other argument supported by the drawing functions: 

 

- ``"matplotlib"`` -- see :meth:`GenericGraph.plot 

<sage.graphs.generic_graph.GenericGraph.plot>` and 

:meth:`sage.plot.graphics.Graphics.show`. 

 

- ``"js"`` -- see :meth:`~sage.graphs.graph_plot_js.gen_html_code`. 

 

EXAMPLES:: 

 

sage: C = graphs.CubeGraph(8) 

sage: P = C.plot(vertex_labels=False, vertex_size=0, graph_border=True) 

sage: P.show() # long time (3s on sage.math, 2011) 

 

""" 

if method == "js": 

from sage.graphs.graph_plot_js import gen_html_code 

from sage.doctest import DOCTEST_MODE 

filename = gen_html_code(self, **kwds) 

 

if DOCTEST_MODE: 

return 

from sage.misc.viewer import browser 

import os 

os.system('%s %s 2>/dev/null 1>/dev/null &'% (browser(), filename)) 

return 

 

from .graph_plot import graphplot_options 

# This dictionary only contains the options that graphplot 

# understands. These options are removed from kwds at the same 

# time. 

plot_kwds = {k:kwds.pop(k) for k in graphplot_options if k in kwds} 

 

return self.graphplot(**plot_kwds).show(**kwds) 

 

def plot3d(self, bgcolor=(1,1,1), 

vertex_colors=None, vertex_size=0.06, vertex_labels=False, 

edge_colors=None, edge_size=0.02, edge_size2=0.0325, 

pos3d=None, color_by_label=False, 

engine='jmol', **kwds): 

r""" 

Plot a graph in three dimensions. 

 

See also the :mod:`sage.graphs.graph_latex` module for ways to use LaTeX 

to produce an image of a graph. 

 

INPUT: 

 

- ``bgcolor`` - rgb tuple (default: (1,1,1)) 

 

- ``vertex_size`` - float (default: 0.06) 

 

- ``vertex_labels`` -- a boolean (default: False) 

whether to display vertices using text labels instead of spheres 

 

- ``vertex_colors`` - optional dictionary to specify 

vertex colors: each key is a color recognizable by tachyon (rgb 

tuple (default: (1,0,0))), and each corresponding entry is a list 

of vertices. If a vertex is not listed, it looks invisible on the 

resulting plot (it doesn't get drawn). 

 

- ``edge_colors`` - a dictionary specifying edge 

colors: each key is a color recognized by tachyon ( default: 

(0,0,0) ), and each entry is a list of edges. 

 

- ``color_by_label`` - a boolean or dictionary or function (default: 

False) whether to color each edge with a different color according 

to its label; the colors are chosen along a rainbow, unless 

they are specified by a function or dictionary mapping 

labels to colors; this option is incompatible with 

``edge_color`` and ``edge_colors``. 

 

- ``edge_size`` - float (default: 0.02) 

 

- ``edge_size2`` - float (default: 0.0325), used for 

Tachyon sleeves 

 

- ``pos3d`` - a position dictionary for the vertices 

 

- ``layout``, ``iterations``, ... - layout options; see :meth:`.layout` 

 

- ``engine`` - which renderer to use. Options: 

 

- ``'jmol'`` - default 

 

- ``'tachyon'`` 

 

- ``xres`` - resolution 

 

- ``yres`` - resolution 

 

- ``**kwds`` - passed on to the rendering engine 

 

 

EXAMPLES:: 

 

sage: G = graphs.CubeGraph(5) 

sage: G.plot3d(iterations=500, edge_size=None, vertex_size=0.04) # long time 

Graphics3d Object 

 

We plot a fairly complicated Cayley graph:: 

 

sage: A5 = AlternatingGroup(5); A5 

Alternating group of order 5!/2 as a permutation group 

sage: G = A5.cayley_graph() 

sage: G.plot3d(vertex_size=0.03, edge_size=0.01, vertex_colors={(1,1,1):G.vertices()}, bgcolor=(0,0,0), color_by_label=True, iterations=200) # long time 

Graphics3d Object 

 

Some Tachyon examples:: 

 

sage: D = graphs.DodecahedralGraph() 

sage: P3D = D.plot3d(engine='tachyon') 

sage: P3D.show() # long time 

 

:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.plot3d(engine='tachyon', vertex_colors={(0,0,1):G.vertices()}).show() # long time 

 

:: 

 

sage: C = graphs.CubeGraph(4) 

sage: C.plot3d(engine='tachyon', edge_colors={(0,1,0):C.edges()}, vertex_colors={(1,1,1):C.vertices()}, bgcolor=(0,0,0)).show() # long time 

 

:: 

 

sage: K = graphs.CompleteGraph(3) 

sage: K.plot3d(engine='tachyon', edge_colors={(1,0,0):[(0,1,None)], (0,1,0):[(0,2,None)], (0,0,1):[(1,2,None)]}).show() # long time 

 

A directed version of the dodecahedron 

 

:: 

 

sage: D = DiGraph( { 0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4], 4: [17, 5], 5: [6, 15], 6: [7], 7: [8, 14], 8: [9], 9: [10, 13], 10: [11], 11: [12, 18], 12: [16, 13], 13: [14], 14: [15], 15: [16], 16: [17], 17: [18], 18: [19], 19: []} ) 

sage: D.plot3d().show() # long time 

 

:: 

 

sage: P = graphs.PetersenGraph().to_directed() 

sage: from sage.plot.colors import rainbow 

sage: edges = P.edges() 

sage: R = rainbow(len(edges), 'rgbtuple') 

sage: edge_colors = {} 

sage: for i in range(len(edges)): 

....: edge_colors[R[i]] = [edges[i]] 

sage: P.plot3d(engine='tachyon', edge_colors=edge_colors).show() # long time 

 

 

:: 

 

sage: G=Graph({'a':['a','b','b','b','e'],'b':['c','d','e'],'c':['c','d','d','d'],'d':['e']},sparse=True) 

sage: G.show3d() 

Traceback (most recent call last): 

... 

NotImplementedError: 3D plotting of multiple edges or loops not implemented. 

 

TESTS:: 

 

sage: G = DiGraph({0:{1:'a', 2:'a'}, 1:{0:'b'}, 2:{0:'c'}}) 

sage: p = G.plot3d(edge_labels=True, color_by_label={'a':'yellow', 'b':'cyan'}) 

sage: s = p.x3d_str() 

 

This 3D plot contains four yellow objects (two cylinders and 

two cones), two black objects and 2 cyan objects:: 

 

sage: s.count("Material diffuseColor='1.0 1.0 0.0'") 

4 

sage: s.count("Material diffuseColor='0.0 0.0 0.0'") 

2 

sage: s.count("Material diffuseColor='0.0 1.0 1.0'") 

2 

 

.. SEEALSO:: 

 

- :meth:`plot` 

- :meth:`graphviz_string` 

""" 

from . import graph_plot 

layout_options = dict( (key,kwds[key]) for key in kwds.keys() if key in graph_plot.layout_options ) 

kwds = dict( (key,kwds[key]) for key in kwds.keys() if key not in graph_plot.layout_options ) 

if pos3d is None: 

pos3d = self.layout(dim=3, **layout_options) 

 

if self.has_multiple_edges() or self.has_loops(): 

raise NotImplementedError("3D plotting of multiple edges or loops not implemented.") 

if engine == 'jmol': 

from sage.plot.plot3d.all import sphere, line3d, arrow3d, text3d 

from sage.plot.plot3d.texture import Texture 

kwds.setdefault('aspect_ratio', [1,1,1]) 

verts = self.vertices() 

 

if vertex_colors is None: 

vertex_colors = { (1,0,0) : verts } 

 

if color_by_label: 

if edge_colors is None: 

# do the coloring 

edge_colors = self._color_by_label(format=color_by_label) 

elif edge_colors is None: 

edge_colors = { (0,0,0) : self.edges() } 

 

# by default turn off the frame 

if 'frame' not in kwds: 

kwds['frame'] = False 

# by default make the background given by bgcolor 

if 'background' not in kwds: 

kwds['background'] = bgcolor 

try: 

graphic = 0 

for color in vertex_colors: 

texture = Texture(color=color, ambient=0.1, diffuse=0.9, specular=0.03) 

for v in vertex_colors[color]: 

if vertex_labels: 

graphic += text3d(repr(v), pos3d[v]) 

else: 

graphic += sphere(center=pos3d[v], size=vertex_size, texture=texture, **kwds) 

if self._directed: 

for color in edge_colors: 

for u, v, l in edge_colors[color]: 

graphic += arrow3d(pos3d[u], pos3d[v], radius=edge_size, color=color, closed=False, **kwds) 

 

else: 

for color in edge_colors: 

texture = Texture(color=color, ambient=0.1, diffuse=0.9, specular=0.03) 

for u, v, l in edge_colors[color]: 

graphic += line3d([pos3d[u], pos3d[v]], radius=edge_size, texture=texture, closed=False, **kwds) 

 

return graphic 

 

except KeyError: 

raise KeyError("Oops! You haven't specified positions for all the vertices.") 

 

elif engine == 'tachyon': 

TT, pos3d = tachyon_vertex_plot(self, bgcolor=bgcolor, vertex_colors=vertex_colors, 

vertex_size=vertex_size, pos3d=pos3d, **kwds) 

edges = self.edges() 

 

if color_by_label: 

if edge_colors is None: 

# do the coloring 

edge_colors = self._color_by_label(format=color_by_label) 

 

if edge_colors is None: 

edge_colors = { (0,0,0) : edges } 

 

i = 0 

 

for color in edge_colors: 

i += 1 

TT.texture('edge_color_%d'%i, ambient=0.1, diffuse=0.9, specular=0.03, opacity=1.0, color=color) 

if self._directed: 

for u,v,l in edge_colors[color]: 

TT.fcylinder( (pos3d[u][0],pos3d[u][1],pos3d[u][2]), 

(pos3d[v][0],pos3d[v][1],pos3d[v][2]), edge_size,'edge_color_%d'%i) 

TT.fcylinder( (0.25*pos3d[u][0] + 0.75*pos3d[v][0], 

0.25*pos3d[u][1] + 0.75*pos3d[v][1], 

0.25*pos3d[u][2] + 0.75*pos3d[v][2],), 

(pos3d[v][0],pos3d[v][1],pos3d[v][2]), edge_size2,'edge_color_%d'%i) 

else: 

for u, v, l in edge_colors[color]: 

TT.fcylinder( (pos3d[u][0],pos3d[u][1],pos3d[u][2]), (pos3d[v][0],pos3d[v][1],pos3d[v][2]), edge_size,'edge_color_%d'%i) 

 

return TT 

 

else: 

raise TypeError("Rendering engine (%s) not implemented."%engine) 

 

def show3d(self, bgcolor=(1,1,1), vertex_colors=None, vertex_size=0.06, 

edge_colors=None, edge_size=0.02, edge_size2=0.0325, 

pos3d=None, color_by_label=False, 

engine='jmol', **kwds): 

""" 

Plots the graph using Tachyon, and shows the resulting plot. 

 

INPUT: 

 

 

- ``bgcolor`` - rgb tuple (default: (1,1,1)) 

 

- ``vertex_size`` - float (default: 0.06) 

 

- ``vertex_colors`` - optional dictionary to specify 

vertex colors: each key is a color recognizable by tachyon (rgb 

tuple (default: (1,0,0))), and each corresponding entry is a list 

of vertices. If a vertex is not listed, it looks invisible on the 

resulting plot (it doesn't get drawn). 

 

- ``edge_colors`` - a dictionary specifying edge 

colors: each key is a color recognized by tachyon ( default: 

(0,0,0) ), and each entry is a list of edges. 

 

- ``edge_size`` - float (default: 0.02) 

 

- ``edge_size2`` - float (default: 0.0325), used for 

Tachyon sleeves 

 

- ``pos3d`` - a position dictionary for the vertices 

 

- ``iterations`` - how many iterations of the spring 

layout algorithm to go through, if applicable 

 

- ``engine`` - which renderer to use. Options: 

 

- ``'jmol'`` - default 'tachyon' 

 

- ``xres`` - resolution 

 

- ``yres`` - resolution 

 

- ``**kwds`` - passed on to the Tachyon command 

 

 

EXAMPLES:: 

 

sage: G = graphs.CubeGraph(5) 

sage: G.show3d(iterations=500, edge_size=None, vertex_size=0.04) # long time 

 

We plot a fairly complicated Cayley graph:: 

 

sage: A5 = AlternatingGroup(5); A5 

Alternating group of order 5!/2 as a permutation group 

sage: G = A5.cayley_graph() 

sage: G.show3d(vertex_size=0.03, edge_size=0.01, edge_size2=0.02, vertex_colors={(1,1,1):G.vertices()}, bgcolor=(0,0,0), color_by_label=True, iterations=200) # long time 

 

Some Tachyon examples:: 

 

sage: D = graphs.DodecahedralGraph() 

sage: D.show3d(engine='tachyon') # long time 

 

:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.show3d(engine='tachyon', vertex_colors={(0,0,1):G.vertices()}) # long time 

 

:: 

 

sage: C = graphs.CubeGraph(4) 

sage: C.show3d(engine='tachyon', edge_colors={(0,1,0):C.edges()}, vertex_colors={(1,1,1):C.vertices()}, bgcolor=(0,0,0)) # long time 

 

:: 

 

sage: K = graphs.CompleteGraph(3) 

sage: K.show3d(engine='tachyon', edge_colors={(1,0,0):[(0,1,None)], (0,1,0):[(0,2,None)], (0,0,1):[(1,2,None)]}) # long time 

""" 

self.plot3d(bgcolor=bgcolor, vertex_colors=vertex_colors, 

edge_colors=edge_colors, vertex_size=vertex_size, engine=engine, 

edge_size=edge_size, edge_size2=edge_size2, pos3d=pos3d, 

color_by_label=color_by_label, **kwds).show() 

 

def _keys_for_vertices(self): 

""" 

Returns a function mapping each vertex to a unique identifier. 

 

The identifier is stable iff all vertex labels are unique. It 

is a string not starting with a number, as required by 

dot2tex. 

 

EXAMPLES:: 

 

sage: g = graphs.Grid2dGraph(5,5) 

sage: g._keys_for_vertices() 

<function get_label at ...> 

 

TESTS: 

 

We check that :trac:`21916` is fixed:: 

 

sage: g = graphs.PetersenGraph() 

sage: key = g._keys_for_vertices() 

sage: g.add_vertex("a") 

sage: s = g.graphviz_string() 

""" 

label = dict() 

for i, v in enumerate(self.vertices()): 

label[v] = 'node_{0}'.format(i) 

def get_label(vertex): 

return label[vertex] 

return get_label 

 

### String representation to be used by other programs 

@options(labels="string", 

vertex_labels=True,edge_labels=False, 

edge_color=None,edge_colors=None, 

edge_options = (), 

color_by_label=False, 

rankdir='down', 

subgraph_clusters=[], 

) 

def graphviz_string(self, **options): 

r""" 

Return a representation in the `dot` language. 

 

The `dot` language is a text based format for graphs. It is used 

by the software suite `graphviz`. The specifications of the 

language are available on the web (see the reference [dotspec]_). 

 

INPUT: 

 

- ``labels`` -- "string" or "latex" (default: "string"). If 

labels is string latex command are not interpreted. This 

option stands for both vertex labels and edge labels. 

 

- ``vertex_labels`` -- boolean (default: True) whether to add 

the labels on vertices. 

 

- ``edge_labels`` -- boolean (default: False) whether to add 

the labels on edges. 

 

- ``edge_color`` -- (default: None) specify a default color 

for the edges. 

 

- ``edge_colors`` -- (default: None) a dictionary whose keys 

are colors and values are list of edges. The list of edges 

need not to be complete in which case the default color is 

used. 

 

- ``color_by_label`` -- (default: False) a boolean or 

dictionary or function whether to color each edge with a 

different color according to its label; the colors are 

chosen along a rainbow, unless they are specified by a 

function or dictionary mapping labels to colors; this option 

is incompatible with ``edge_color`` and ``edge_colors``. 

 

- ``edge_options`` -- a function (or tuple thereof) mapping 

edges to a dictionary of options for this edge. 

 

- ``rankdir`` -- 'left', 'right', 'up', or 'down' 

(default: 'down', for consistency with `graphviz`): 

the preferred ranking direction for acyclic layouts; 

see the `rankdir` option of `graphviz`. 

 

- ``subgraph_clusters`` -- (default: []) a list of lists of vertices, 

From [dotspec]_: "If supported, the layout engine will do the layout 

so that the nodes belonging to the cluster are drawn together, with 

the entire drawing of the cluster contained within a bounding 

rectangle. Note that, for good and bad, cluster subgraphs are not 

part of the DOT language, but solely a syntactic convention adhered to 

by certain of the layout engines." 

 

EXAMPLES:: 

 

sage: G = Graph({0:{1:None,2:None}, 1:{0:None,2:None}, 2:{0:None,1:None,3:'foo'}, 3:{2:'foo'}},sparse=True) 

sage: print(G.graphviz_string(edge_labels=True)) 

graph { 

node_0 [label="0"]; 

node_1 [label="1"]; 

node_2 [label="2"]; 

node_3 [label="3"]; 

<BLANKLINE> 

node_0 -- node_1; 

node_0 -- node_2; 

node_1 -- node_2; 

node_2 -- node_3 [label="foo"]; 

} 

 

A variant, with the labels in latex, for post-processing with ``dot2tex``:: 

 

sage: print(G.graphviz_string(edge_labels=True,labels = "latex")) 

graph { 

node [shape="plaintext"]; 

node_0 [label=" ", texlbl="$0$"]; 

node_1 [label=" ", texlbl="$1$"]; 

node_2 [label=" ", texlbl="$2$"]; 

node_3 [label=" ", texlbl="$3$"]; 

<BLANKLINE> 

node_0 -- node_1; 

node_0 -- node_2; 

node_1 -- node_2; 

node_2 -- node_3 [label=" ", texlbl="$\text{\texttt{foo}}$"]; 

} 

 

Same, with a digraph and a color for edges:: 

 

sage: G = DiGraph({0:{1:None,2:None}, 1:{2:None}, 2:{3:'foo'}, 3:{}} ,sparse=True) 

sage: print(G.graphviz_string(edge_color="red")) 

digraph { 

node_0 [label="0"]; 

node_1 [label="1"]; 

node_2 [label="2"]; 

node_3 [label="3"]; 

<BLANKLINE> 

edge [color="red"]; 

node_0 -> node_1; 

node_0 -> node_2; 

node_1 -> node_2; 

node_2 -> node_3; 

} 

 

A digraph using latex labels for vertices and edges:: 

 

sage: f(x) = -1/x 

sage: g(x) = 1/(x+1) 

sage: G = DiGraph() 

sage: G.add_edges([(i,f(i),f) for i in (1,2,1/2,1/4)]) 

sage: G.add_edges([(i,g(i),g) for i in (1,2,1/2,1/4)]) 

sage: print(G.graphviz_string(labels="latex",edge_labels=True)) # random 

digraph { 

node [shape="plaintext"]; 

node_10 [label=" ", texlbl="$1$"]; 

node_11 [label=" ", texlbl="$2$"]; 

node_3 [label=" ", texlbl="$-\frac{1}{2}$"]; 

node_6 [label=" ", texlbl="$\frac{1}{2}$"]; 

node_7 [label=" ", texlbl="$\frac{1}{2}$"]; 

node_5 [label=" ", texlbl="$\frac{1}{3}$"]; 

node_8 [label=" ", texlbl="$\frac{2}{3}$"]; 

node_4 [label=" ", texlbl="$\frac{1}{4}$"]; 

node_1 [label=" ", texlbl="$-2$"]; 

node_9 [label=" ", texlbl="$\frac{4}{5}$"]; 

node_0 [label=" ", texlbl="$-4$"]; 

node_2 [label=" ", texlbl="$-1$"]; 

<BLANKLINE> 

node_10 -> node_2 [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$"]; 

node_10 -> node_6 [label=" ", texlbl="$x \ {\mapsto}\ \frac{1}{x + 1}$"]; 

node_11 -> node_3 [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$"]; 

node_11 -> node_5 [label=" ", texlbl="$x \ {\mapsto}\ \frac{1}{x + 1}$"]; 

node_7 -> node_1 [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$"]; 

node_7 -> node_8 [label=" ", texlbl="$x \ {\mapsto}\ \frac{1}{x + 1}$"]; 

node_4 -> node_0 [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$"]; 

node_4 -> node_9 [label=" ", texlbl="$x \ {\mapsto}\ \frac{1}{x + 1}$"]; 

} 

 

sage: print(G.graphviz_string(labels="latex",color_by_label=True)) # random 

digraph { 

node [shape="plaintext"]; 

node_10 [label=" ", texlbl="$1$"]; 

node_11 [label=" ", texlbl="$2$"]; 

node_3 [label=" ", texlbl="$-\frac{1}{2}$"]; 

node_6 [label=" ", texlbl="$\frac{1}{2}$"]; 

node_7 [label=" ", texlbl="$\frac{1}{2}$"]; 

node_5 [label=" ", texlbl="$\frac{1}{3}$"]; 

node_8 [label=" ", texlbl="$\frac{2}{3}$"]; 

node_4 [label=" ", texlbl="$\frac{1}{4}$"]; 

node_1 [label=" ", texlbl="$-2$"]; 

node_9 [label=" ", texlbl="$\frac{4}{5}$"]; 

node_0 [label=" ", texlbl="$-4$"]; 

node_2 [label=" ", texlbl="$-1$"]; 

<BLANKLINE> 

node_10 -> node_2 [color = "#ff0000"]; 

node_10 -> node_6 [color = "#00ffff"]; 

node_11 -> node_3 [color = "#ff0000"]; 

node_11 -> node_5 [color = "#00ffff"]; 

node_7 -> node_1 [color = "#ff0000"]; 

node_7 -> node_8 [color = "#00ffff"]; 

node_4 -> node_0 [color = "#ff0000"]; 

node_4 -> node_9 [color = "#00ffff"]; 

} 

 

sage: print(G.graphviz_string(labels="latex",color_by_label={ f: "red", g: "blue" })) # random 

digraph { 

node [shape="plaintext"]; 

node_10 [label=" ", texlbl="$1$"]; 

node_11 [label=" ", texlbl="$2$"]; 

node_3 [label=" ", texlbl="$-\frac{1}{2}$"]; 

node_6 [label=" ", texlbl="$\frac{1}{2}$"]; 

node_7 [label=" ", texlbl="$\frac{1}{2}$"]; 

node_5 [label=" ", texlbl="$\frac{1}{3}$"]; 

node_8 [label=" ", texlbl="$\frac{2}{3}$"]; 

node_4 [label=" ", texlbl="$\frac{1}{4}$"]; 

node_1 [label=" ", texlbl="$-2$"]; 

node_9 [label=" ", texlbl="$\frac{4}{5}$"]; 

node_0 [label=" ", texlbl="$-4$"]; 

node_2 [label=" ", texlbl="$-1$"]; 

<BLANKLINE> 

node_10 -> node_2 [color = "red"]; 

node_10 -> node_6 [color = "blue"]; 

node_11 -> node_3 [color = "red"]; 

node_11 -> node_5 [color = "blue"]; 

node_7 -> node_1 [color = "red"]; 

node_7 -> node_8 [color = "blue"]; 

node_4 -> node_0 [color = "red"]; 

node_4 -> node_9 [color = "blue"]; 

} 

 

By default ``graphviz`` renders digraphs using a hierarchical 

layout, ranking the vertices down from top to bottom. Here we 

specify alternative ranking directions for this layout:: 

 

sage: D = DiGraph([[1,2]]) 

sage: print(D.graphviz_string(rankdir="up")) 

digraph { 

rankdir=BT 

node_0 [label="1"]; 

node_1 [label="2"]; 

<BLANKLINE> 

node_0 -> node_1; 

} 

sage: print(D.graphviz_string(rankdir="down")) 

digraph { 

node_0 [label="1"]; 

node_1 [label="2"]; 

<BLANKLINE> 

node_0 -> node_1; 

} 

sage: print(D.graphviz_string(rankdir="left")) 

digraph { 

rankdir=RL 

node_0 [label="1"]; 

node_1 [label="2"]; 

<BLANKLINE> 

node_0 -> node_1; 

} 

sage: print(D.graphviz_string(rankdir="right")) 

digraph { 

rankdir=LR 

node_0 [label="1"]; 

node_1 [label="2"]; 

<BLANKLINE> 

node_0 -> node_1; 

} 

 

Edge-specific options can also be specified by providing a 

function (or tuple thereof) which maps each edge to a 

dictionary of options. Valid options are "color", "backward" 

(a boolean), "dot" (a string containing a sequence of options 

in dot format), "label" (a string), "label_style" ("string" or 

"latex"), "edge_string" ("--" or "->"). Here we state that the 

graph should be laid out so that edges starting from ``1`` are 

going backward (e.g. going up instead of down):: 

 

sage: def edge_options((u,v,label)): 

....: return { "backward": u == 1 } 

sage: print(G.graphviz_string(edge_options = edge_options)) # random 

digraph { 

node_10 [label="1"]; 

node_11 [label="2"]; 

node_3 [label="-1/2"]; 

node_6 [label="1/2"]; 

node_7 [label="1/2"]; 

node_5 [label="1/3"]; 

node_8 [label="2/3"]; 

node_4 [label="1/4"]; 

node_1 [label="-2"]; 

node_9 [label="4/5"]; 

node_0 [label="-4"]; 

node_2 [label="-1"]; 

<BLANKLINE> 

node_2 -> node_10 [dir=back]; 

node_6 -> node_10 [dir=back]; 

node_11 -> node_3; 

node_11 -> node_5; 

node_7 -> node_1; 

node_7 -> node_8; 

node_4 -> node_0; 

node_4 -> node_9; 

} 

 

We now test all options:: 

 

sage: def edge_options((u,v,label)): 

....: options = { "color": { f: "red", g: "blue" }[label] } 

....: if (u,v) == (1/2, -2): options["label"] = "coucou"; options["label_style"] = "string" 

....: if (u,v) == (1/2,2/3): options["dot"] = "x=1,y=2" 

....: if (u,v) == (1, -1): options["label_style"] = "latex" 

....: if (u,v) == (1, 1/2): options["edge_string"] = "<-" 

....: if (u,v) == (1/2, 1): options["backward"] = True 

....: return options 

sage: print(G.graphviz_string(edge_options = edge_options)) # random 

digraph { 

node_10 [label="1"]; 

node_11 [label="2"]; 

node_3 [label="-1/2"]; 

node_6 [label="1/2"]; 

node_7 [label="1/2"]; 

node_5 [label="1/3"]; 

node_8 [label="2/3"]; 

node_4 [label="1/4"]; 

node_1 [label="-2"]; 

node_9 [label="4/5"]; 

node_0 [label="-4"]; 

node_2 [label="-1"]; 

<BLANKLINE> 

node_10 -> node_2 [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$", color = "red"]; 

node_10 <- node_6 [color = "blue"]; 

node_11 -> node_3 [color = "red"]; 

node_11 -> node_5 [color = "blue"]; 

node_7 -> node_1 [label="coucou", color = "red"]; 

node_7 -> node_8 [x=1,y=2, color = "blue"]; 

node_4 -> node_0 [color = "red"]; 

node_4 -> node_9 [color = "blue"]; 

} 

 

TESTS: 

 

The following digraph has tuples as vertices:: 

 

sage: print(digraphs.ButterflyGraph(1).graphviz_string()) 

digraph { 

node_3 [label="('1', 1)"]; 

node_0 [label="('0', 0)"]; 

node_2 [label="('1', 0)"]; 

node_1 [label="('0', 1)"]; 

<BLANKLINE> 

node_0 -> node_3; 

node_0 -> node_1; 

node_2 -> node_3; 

node_2 -> node_1; 

} 

 

The following digraph has vertices with newlines in their 

string representations:: 

 

sage: m1 = matrix(3,3) 

sage: m2 = matrix(3,3, 1) 

sage: m1.set_immutable() 

sage: m2.set_immutable() 

sage: g = DiGraph({ m1: [m2] }) 

sage: print(g.graphviz_string()) 

digraph { 

node_0 [label="[0 0 0]\n\ 

[0 0 0]\n\ 

[0 0 0]"]; 

node_1 [label="[1 0 0]\n\ 

[0 1 0]\n\ 

[0 0 1]"]; 

<BLANKLINE> 

node_0 -> node_1; 

} 

 

Using cluster subgraphs:: 

 

sage: d = {i:[i+1] for i in range(5)} 

sage: G = Graph(d) 

sage: print(G.graphviz_string(subgraph_clusters=[[0,2,4],[1,3,5]])) 

graph { 

node_0 [label="0"]; 

node_1 [label="1"]; 

node_2 [label="2"]; 

node_3 [label="3"]; 

node_4 [label="4"]; 

node_5 [label="5"]; 

<BLANKLINE> 

subgraph cluster_0{style=filled; 

color=black; 

fillcolor=azure; 

node_0; 

node_2; 

node_4; 

} 

<BLANKLINE> 

subgraph cluster_1{style=filled; 

color=black; 

fillcolor=azure; 

node_1; 

node_3; 

node_5; 

} 

<BLANKLINE> 

node_0 -- node_1; 

node_1 -- node_2; 

node_2 -- node_3; 

node_3 -- node_4; 

node_4 -- node_5; 

} 

 

Check that :trac:`22950` is fixed:: 

 

sage: D = DiGraph({1: [2]}) 

sage: D.graphviz_string(edge_colors={'blue': [[1,2]]}) 

'digraph {\n node_0 [label="1"];\n node_1 [label="2"];\n\n 

node_0 -> node_1 [color = "blue"];\n}' 

 

REFERENCES: 

 

.. [dotspec] http://www.graphviz.org/doc/info/lang.html 

 

""" 

from sage.graphs.dot2tex_utils import quoted_latex, quoted_str 

 

if self.is_directed(): 

graph_string = "digraph" 

default_edge_string = "->" 

else: 

graph_string = "graph" 

default_edge_string = "--" 

 

edge_option_functions = options['edge_options'] 

if not isinstance(edge_option_functions, (tuple,list)): 

edge_option_functions = [edge_option_functions] 

else: 

edge_option_functions = list(edge_option_functions) 

 

if options['edge_color'] is not None: 

default_color = options['edge_color'] 

else: 

default_color = None 

 

if options['color_by_label'] is not False: 

color_by_label = self._color_by_label(format = options['color_by_label'], as_function = True, default_color=default_color) 

edge_option_functions.append(lambda u_v_label: {"color": color_by_label(u_v_label[2])}) 

elif options['edge_colors'] is not None: 

if not isinstance(options['edge_colors'],dict): 

raise ValueError("incorrect format for edge_colors") 

color_by_edge = {} 

for color in options['edge_colors'].keys(): 

for edge in options['edge_colors'][color]: 

assert isinstance(edge, (list, tuple)) and len(edge) >= 2 and len(edge) <= 3,\ 

"%s is not a valid format for edge"%(edge) 

u = edge[0] 

v = edge[1] 

assert self.has_edge(*edge), "%s is not an edge"%(edge) 

if len(edge) == 2: 

if self.has_multiple_edges(): 

for label in self.edge_label(u,v): 

color_by_edge[(u,v,label)] = color 

else: 

label = self.edge_label(u,v) 

color_by_edge[(u,v,label)] = color 

elif len(edge) == 3: 

color_by_edge[edge] = color 

 

edge_option_functions.append(lambda edge: {"color": color_by_edge[edge]} if edge in color_by_edge else {}) 

 

else: 

edges_by_color = [] 

not_colored_edges = self.edge_iterator(labels=True) 

 

key = self._keys_for_vertices() 

 

s = '%s {\n' % graph_string 

if options['rankdir'] != "down": 

directions = {'up': 'BT', 'down': 'TB', 'left': 'RL', 'right': 'LR'} 

if options['rankdir'] not in directions: 

raise ValueError("rankdir should be one of %s"%directions.keys()) 

s += ' rankdir=%s\n'%(directions[options['rankdir']]) 

if (options['vertex_labels'] and 

options['labels'] == "latex"): # not a perfect option name 

# TODO: why do we set this only for latex labels? 

s += ' node [shape="plaintext"];\n' 

 

# vertices for loop 

for v in self.vertex_iterator(): 

if not options['vertex_labels']: 

node_options = "" 

elif options['labels'] == "latex": 

node_options = " [label=\" \", texlbl=\"$%s$\"]"%quoted_latex(v) 

else: 

node_options = " [label=\"%s\"]" %quoted_str(v) 

 

s += ' %s %s;\n'%(key(v),node_options) 

s += "\n" 

 

# subgraphs clusters for loop 

subgraph_clusters = options['subgraph_clusters'] 

for i,cluster in enumerate(subgraph_clusters): 

s += 'subgraph cluster_%s{style=filled;\n' % i 

s += 'color=black;\n' 

s += 'fillcolor=azure;\n' 

for v in cluster: 

s += ' %s;\n' % key(v) 

s += '}\n\n' 

 

if default_color is not None: 

s += 'edge [color="%s"];\n'%default_color 

 

# edges for loop 

for (u,v,label) in self.edge_iterator(): 

edge_options = { 

'backward': False, 

'dot': None, 

'edge_string': default_edge_string, 

'color' : default_color, 

'label' : label, 

'label_style': options['labels'] if options['edge_labels'] else None 

} 

for f in edge_option_functions: 

edge_options.update(f((u,v,label))) 

 

dot_options = [] 

 

if edge_options['dot'] is not None: 

assert isinstance(edge_options['dot'], str) 

dot_options.append(edge_options['dot']) 

 

label = edge_options['label'] 

if label is not None and edge_options['label_style'] is not None: 

if edge_options['label_style'] == 'latex': 

dot_options.append('label=" ", texlbl="$%s$"'%quoted_latex(label)) 

else: 

dot_options.append('label="%s"'% label) 

 

if edge_options['color'] != default_color: 

dot_options.append('color = "%s"'%edge_options['color']) 

 

if edge_options['backward']: 

v,u = u,v 

dot_options.append('dir=back') 

 

s+= ' %s %s %s' % (key(u), edge_options['edge_string'], key(v)) 

if len(dot_options) > 0: 

s += " [" + ", ".join(dot_options)+"]" 

s+= ";\n" 

s += "}" 

 

return s 

 

def graphviz_to_file_named(self, filename, **options): 

r""" 

Write a representation in the dot in a file. 

 

The dot language is a plaintext format for graph structures. See the 

documentation of :meth:`.graphviz_string` for available options. 

 

INPUT: 

 

``filename`` - the name of the file to write in 

 

``options`` - options for the graphviz string 

 

EXAMPLES:: 

 

sage: G = Graph({0:{1:None,2:None}, 1:{0:None,2:None}, 2:{0:None,1:None,3:'foo'}, 3:{2:'foo'}},sparse=True) 

sage: tempfile = os.path.join(SAGE_TMP, 'temp_graphviz') 

sage: G.graphviz_to_file_named(tempfile, edge_labels=True) 

sage: print(open(tempfile).read()) 

graph { 

node_0 [label="0"]; 

node_1 [label="1"]; 

node_2 [label="2"]; 

node_3 [label="3"]; 

<BLANKLINE> 

node_0 -- node_1; 

node_0 -- node_2; 

node_1 -- node_2; 

node_2 -- node_3 [label="foo"]; 

} 

""" 

open(filename, 'wt').write(self.graphviz_string(**options)) 

 

### Spectrum 

 

def spectrum(self, laplacian=False): 

r""" 

Returns a list of the eigenvalues of the adjacency matrix. 

 

INPUT: 

 

- ``laplacian`` - if ``True``, use the Laplacian matrix 

(see :meth:`kirchhoff_matrix`) 

 

OUTPUT: 

 

A list of the eigenvalues, including multiplicities, sorted 

with the largest eigenvalue first. 

 

.. SEEALSO:: 

 

The method :meth:`spectral_radius` returns floating point 

approximation of the maximum eigenvalue. 

 

EXAMPLES:: 

 

sage: P = graphs.PetersenGraph() 

sage: P.spectrum() 

[3, 1, 1, 1, 1, 1, -2, -2, -2, -2] 

sage: P.spectrum(laplacian=True) 

[5, 5, 5, 5, 2, 2, 2, 2, 2, 0] 

sage: D = P.to_directed() 

sage: D.delete_edge(7,9) 

sage: D.spectrum() 

[2.9032119259..., 1, 1, 1, 1, 0.8060634335..., -1.7092753594..., -2, -2, -2] 

 

:: 

 

sage: C = graphs.CycleGraph(8) 

sage: C.spectrum() 

[2, 1.4142135623..., 1.4142135623..., 0, 0, -1.4142135623..., -1.4142135623..., -2] 

 

A digraph may have complex eigenvalues. Previously, the complex parts 

of graph eigenvalues were being dropped. For a 3-cycle, we have:: 

 

sage: T = DiGraph({0:[1], 1:[2], 2:[0]}) 

sage: T.spectrum() 

[1, -0.5000000000... + 0.8660254037...*I, -0.5000000000... - 0.8660254037...*I] 

 

TESTS: 

 

The Laplacian matrix of a graph is the negative of the adjacency matrix with the degree of each vertex on the diagonal. So for a regular graph, if `\delta` is an eigenvalue of a regular graph of degree `r`, then `r-\delta` will be an eigenvalue of the Laplacian. The Hoffman-Singleton graph is regular of degree 7, so the following will test both the Laplacian construction and the computation of eigenvalues. :: 

 

sage: H = graphs.HoffmanSingletonGraph() 

sage: evals = H.spectrum() 

sage: lap = [7-x for x in evals] 

sage: lap.sort(reverse=True) 

sage: lap == H.spectrum(laplacian=True) 

True 

""" 

# Ideally the spectrum should return something like a Factorization object 

# containing each eigenvalue once, along with its multiplicity. 

# This function, returning a list. could then just be renamed "eigenvalues" 

if laplacian: 

M = self.kirchhoff_matrix() 

else: 

M = self.adjacency_matrix() 

evals = M.eigenvalues() 

evals.sort(reverse=True) 

return evals 

 

def characteristic_polynomial(self, var='x', laplacian=False): 

r""" 

Returns the characteristic polynomial of the adjacency matrix of 

the (di)graph. 

 

Let `G` be a (simple) graph with adjacency matrix `A`. Let `I` be the 

identity matrix of dimensions the same as `A`. The characteristic 

polynomial of `G` is defined as the determinant `\det(xI - A)`. 

 

.. note:: 

 

``characteristic_polynomial`` and ``charpoly`` are aliases and 

thus provide exactly the same method. 

 

INPUT: 

 

- ``x`` -- (default: ``'x'``) the variable of the characteristic 

polynomial. 

 

- ``laplacian`` -- (default: ``False``) if ``True``, use the 

Laplacian matrix. 

 

.. SEEALSO:: 

 

- :meth:`kirchhoff_matrix` 

 

- :meth:`laplacian_matrix` 

 

EXAMPLES:: 

 

sage: P = graphs.PetersenGraph() 

sage: P.characteristic_polynomial() 

x^10 - 15*x^8 + 75*x^6 - 24*x^5 - 165*x^4 + 120*x^3 + 120*x^2 - 160*x + 48 

sage: P.charpoly() 

x^10 - 15*x^8 + 75*x^6 - 24*x^5 - 165*x^4 + 120*x^3 + 120*x^2 - 160*x + 48 

sage: P.characteristic_polynomial(laplacian=True) 

x^10 - 30*x^9 + 390*x^8 - 2880*x^7 + 13305*x^6 - 

39882*x^5 + 77640*x^4 - 94800*x^3 + 66000*x^2 - 20000*x 

""" 

if laplacian: 

return self.kirchhoff_matrix().charpoly(var=var) 

else: 

return self.adjacency_matrix().charpoly(var=var) 

 

# alias, consistent with linear algebra code 

charpoly = characteristic_polynomial 

 

def eigenvectors(self, laplacian=False): 

r""" 

Returns the *right* eigenvectors of the adjacency matrix of the graph. 

 

INPUT: 

 

- ``laplacian`` - if True, use the Laplacian matrix 

(see :meth:`kirchhoff_matrix`) 

 

OUTPUT: 

 

A list of triples. Each triple begins with an eigenvalue of 

the adjacency matrix of the graph. This is followed by 

a list of eigenvectors for the eigenvalue, when the 

eigenvectors are placed on the right side of the matrix. 

Together, the eigenvectors form a basis for the eigenspace. 

The triple concludes with the algebraic multiplicity of 

the eigenvalue. 

 

For some graphs, the exact eigenspaces provided by 

:meth:`eigenspaces` provide additional insight into 

the structure of the eigenspaces. 

 

EXAMPLES:: 

 

sage: P = graphs.PetersenGraph() 

sage: P.eigenvectors() 

[(3, [ 

(1, 1, 1, 1, 1, 1, 1, 1, 1, 1) 

], 1), (-2, [ 

(1, 0, 0, 0, -1, -1, -1, 0, 1, 1), 

(0, 1, 0, 0, -1, 0, -2, -1, 1, 2), 

(0, 0, 1, 0, -1, 1, -1, -2, 0, 2), 

(0, 0, 0, 1, -1, 1, 0, -1, -1, 1) 

], 4), (1, [ 

(1, 0, 0, 0, 0, 1, -1, 0, 0, -1), 

(0, 1, 0, 0, 0, -1, 1, -1, 0, 0), 

(0, 0, 1, 0, 0, 0, -1, 1, -1, 0), 

(0, 0, 0, 1, 0, 0, 0, -1, 1, -1), 

(0, 0, 0, 0, 1, -1, 0, 0, -1, 1) 

], 5)] 

 

Eigenspaces for the Laplacian should be identical since the 

Petersen graph is regular. However, since the output also 

contains the eigenvalues, the two outputs are slightly 

different. :: 

 

sage: P.eigenvectors(laplacian=True) 

[(0, [ 

(1, 1, 1, 1, 1, 1, 1, 1, 1, 1) 

], 1), (5, [ 

(1, 0, 0, 0, -1, -1, -1, 0, 1, 1), 

(0, 1, 0, 0, -1, 0, -2, -1, 1, 2), 

(0, 0, 1, 0, -1, 1, -1, -2, 0, 2), 

(0, 0, 0, 1, -1, 1, 0, -1, -1, 1) 

], 4), (2, [ 

(1, 0, 0, 0, 0, 1, -1, 0, 0, -1), 

(0, 1, 0, 0, 0, -1, 1, -1, 0, 0), 

(0, 0, 1, 0, 0, 0, -1, 1, -1, 0), 

(0, 0, 0, 1, 0, 0, 0, -1, 1, -1), 

(0, 0, 0, 0, 1, -1, 0, 0, -1, 1) 

], 5)] 

 

:: 

 

sage: C = graphs.CycleGraph(8) 

sage: C.eigenvectors() 

[(2, [ 

(1, 1, 1, 1, 1, 1, 1, 1) 

], 1), (-2, [ 

(1, -1, 1, -1, 1, -1, 1, -1) 

], 1), (0, [ 

(1, 0, -1, 0, 1, 0, -1, 0), 

(0, 1, 0, -1, 0, 1, 0, -1) 

], 2), (-1.4142135623..., [(1, 0, -1, 1.4142135623..., -1, 0, 1, -1.4142135623...), (0, 1, -1.4142135623..., 1, 0, -1, 1.4142135623..., -1)], 2), (1.4142135623..., [(1, 0, -1, -1.4142135623..., -1, 0, 1, 1.4142135623...), (0, 1, 1.4142135623..., 1, 0, -1, -1.4142135623..., -1)], 2)] 

 

A digraph may have complex eigenvalues. Previously, the complex parts 

of graph eigenvalues were being dropped. For a 3-cycle, we have:: 

 

sage: T = DiGraph({0:[1], 1:[2], 2:[0]}) 

sage: T.eigenvectors() 

[(1, [ 

(1, 1, 1) 

], 1), (-0.5000000000... - 0.8660254037...*I, [(1, -0.5000000000... - 0.8660254037...*I, -0.5000000000... + 0.8660254037...*I)], 1), (-0.5000000000... + 0.8660254037...*I, [(1, -0.5000000000... + 0.8660254037...*I, -0.5000000000... - 0.8660254037...*I)], 1)] 

""" 

if laplacian: 

M = self.kirchhoff_matrix() 

else: 

M = self.adjacency_matrix() 

return M.right_eigenvectors() 

 

def eigenspaces(self, laplacian=False): 

r""" 

Returns the *right* eigenspaces of the adjacency matrix of the graph. 

 

INPUT: 

 

- ``laplacian`` - if True, use the Laplacian matrix 

(see :meth:`kirchhoff_matrix`) 

 

OUTPUT: 

 

A list of pairs. Each pair is an eigenvalue of the 

adjacency matrix of the graph, followed by 

the vector space that is the eigenspace for that eigenvalue, 

when the eigenvectors are placed on the right of the matrix. 

 

For some graphs, some of the eigenspaces are described 

exactly by vector spaces over a 

:func:`~sage.rings.number_field.number_field.NumberField`. 

For numerical eigenvectors use :meth:`eigenvectors`. 

 

EXAMPLES:: 

 

sage: P = graphs.PetersenGraph() 

sage: P.eigenspaces() 

[ 

(3, Vector space of degree 10 and dimension 1 over Rational Field 

User basis matrix: 

[1 1 1 1 1 1 1 1 1 1]), 

(-2, Vector space of degree 10 and dimension 4 over Rational Field 

User basis matrix: 

[ 1 0 0 0 -1 -1 -1 0 1 1] 

[ 0 1 0 0 -1 0 -2 -1 1 2] 

[ 0 0 1 0 -1 1 -1 -2 0 2] 

[ 0 0 0 1 -1 1 0 -1 -1 1]), 

(1, Vector space of degree 10 and dimension 5 over Rational Field 

User basis matrix: 

[ 1 0 0 0 0 1 -1 0 0 -1] 

[ 0 1 0 0 0 -1 1 -1 0 0] 

[ 0 0 1 0 0 0 -1 1 -1 0] 

[ 0 0 0 1 0 0 0 -1 1 -1] 

[ 0 0 0 0 1 -1 0 0 -1 1]) 

] 

 

Eigenspaces for the Laplacian should be identical since the 

Petersen graph is regular. However, since the output also 

contains the eigenvalues, the two outputs are slightly 

different. :: 

 

sage: P.eigenspaces(laplacian=True) 

[ 

(0, Vector space of degree 10 and dimension 1 over Rational Field 

User basis matrix: 

[1 1 1 1 1 1 1 1 1 1]), 

(5, Vector space of degree 10 and dimension 4 over Rational Field 

User basis matrix: 

[ 1 0 0 0 -1 -1 -1 0 1 1] 

[ 0 1 0 0 -1 0 -2 -1 1 2] 

[ 0 0 1 0 -1 1 -1 -2 0 2] 

[ 0 0 0 1 -1 1 0 -1 -1 1]), 

(2, Vector space of degree 10 and dimension 5 over Rational Field 

User basis matrix: 

[ 1 0 0 0 0 1 -1 0 0 -1] 

[ 0 1 0 0 0 -1 1 -1 0 0] 

[ 0 0 1 0 0 0 -1 1 -1 0] 

[ 0 0 0 1 0 0 0 -1 1 -1] 

[ 0 0 0 0 1 -1 0 0 -1 1]) 

] 

 

Notice how one eigenspace below is described with a square root of 

2. For the two possible values (positive and negative) there is a 

corresponding eigenspace. :: 

 

sage: C = graphs.CycleGraph(8) 

sage: C.eigenspaces() 

[ 

(2, Vector space of degree 8 and dimension 1 over Rational Field 

User basis matrix: 

[1 1 1 1 1 1 1 1]), 

(-2, Vector space of degree 8 and dimension 1 over Rational Field 

User basis matrix: 

[ 1 -1 1 -1 1 -1 1 -1]), 

(0, Vector space of degree 8 and dimension 2 over Rational Field 

User basis matrix: 

[ 1 0 -1 0 1 0 -1 0] 

[ 0 1 0 -1 0 1 0 -1]), 

(a3, Vector space of degree 8 and dimension 2 over Number Field in a3 with defining polynomial x^2 - 2 

User basis matrix: 

[ 1 0 -1 -a3 -1 0 1 a3] 

[ 0 1 a3 1 0 -1 -a3 -1]) 

] 

 

A digraph may have complex eigenvalues and eigenvectors. 

For a 3-cycle, we have:: 

 

sage: T = DiGraph({0:[1], 1:[2], 2:[0]}) 

sage: T.eigenspaces() 

[ 

(1, Vector space of degree 3 and dimension 1 over Rational Field 

User basis matrix: 

[1 1 1]), 

(a1, Vector space of degree 3 and dimension 1 over Number Field in a1 with defining polynomial x^2 + x + 1 

User basis matrix: 

[ 1 a1 -a1 - 1]) 

] 

""" 

if laplacian: 

M = self.kirchhoff_matrix() 

else: 

M = self.adjacency_matrix() 

# could pass format='all' to get QQbar eigenvalues and eigenspaces 

# which would be a change in default behavior 

return M.right_eigenspaces(format='galois', algebraic_multiplicity=False) 

 

### Automorphism and isomorphism 

 

def relabel(self, perm=None, inplace=True, return_map=False, check_input = True, complete_partial_function = True, immutable = None): 

r""" 

Relabels the vertices of ``self`` 

 

INPUT: 

 

- ``perm`` -- a function, dictionary, list, permutation, or 

``None`` (default: ``None``) 

 

- ``inplace`` -- a boolean (default: ``True``) 

 

- ``return_map`` -- a boolean (default: ``False``) 

 

- ``check_input`` (boolean) -- whether to test input for 

correctness. *This can potentially be very time-consuming !*. 

 

- ``complete_partial_function`` (boolean) -- whether to automatically 

complete the permutation if some elements of the graph are not 

associated with any new name. In this case, those elements are not 

relabeled *This can potentially be very time-consuming !*. 

 

- ``immutable`` (boolean) -- with ``inplace=False``, whether to create 

a mutable/immutable relabelled copy. ``immutable=None`` (default) 

means that the graph and its copy will behave the same way. 

 

If ``perm`` is a function ``f``, then each vertex ``v`` is 

relabeled to ``f(v)``. 

 

If ``perm`` is a dictionary ``d``, then each vertex ``v`` (which should 

be a key of ``d``) is relabeled to ``d[v]``. Similarly, if ``perm`` is 

a list or tuple ``l`` of length ``n``, then the first vertex returned by 

``G.vertices()`` is relabeled to ``l[0]``, the second to ``l[1]``, ... 

 

If ``perm`` is a permutation, then each vertex ``v`` is 

relabeled to ``perm(v)``. Caveat: this assumes that the 

vertices are labelled `\{0,1,...,n-1\}`; since permutations 

act by default on the set `\{1,2,...,n\}`, this is achieved by 

identifying `n` and `0`. 

 

If ``perm`` is ``None``, the graph is relabeled to be on the 

vertices `\{0,1,...,n-1\}`. This is *not* any kind of canonical 

labeling, but neither a random relabeling. 

 

If ``inplace`` is ``True``, the graph is modified in place and 

``None`` is returned. Otherwise a relabeled copy of the graph 

is returned. 

 

If ``return_map`` is ``True`` a dictionary representing the 

relabelling map is returned (incompatible with ``inplace==False``). 

 

EXAMPLES:: 

 

sage: G = graphs.PathGraph(3) 

sage: G.am() 

[0 1 0] 

[1 0 1] 

[0 1 0] 

 

Relabeling using a dictionary. Note that the dictionary does not define 

the new label of vertex `0`:: 

 

sage: G.relabel({1:2,2:1}, inplace=False).am() 

[0 0 1] 

[0 0 1] 

[1 1 0] 

 

This is because the method automatically "extends" the relabeling to the 

missing vertices (whose label will not change). Checking that all 

vertices have an image can require some time, and this feature can be 

disabled (at your own risk):: 

 

sage: G.relabel({1:2,2:1}, inplace=False, complete_partial_function = False).am() 

Traceback (most recent call last): 

... 

KeyError: 0 

 

Relabeling using a list:: 

 

sage: G.relabel([0,2,1], inplace=False).am() 

[0 0 1] 

[0 0 1] 

[1 1 0] 

 

Relabeling using a tuple:: 

 

sage: G.relabel((0,2,1), inplace=False).am() 

[0 0 1] 

[0 0 1] 

[1 1 0] 

 

Relabeling using a Sage permutation:: 

 

sage: G = graphs.PathGraph(3) 

sage: from sage.groups.perm_gps.permgroup_named import SymmetricGroup 

sage: S = SymmetricGroup(3) 

sage: gamma = S('(1,2)') 

sage: G.relabel(gamma, inplace=False).am() 

[0 0 1] 

[0 0 1] 

[1 1 0] 

 

A way to get a random relabeling:: 

 

sage: set_random_seed(0) # Results are reproducible 

sage: D = DiGraph({1: [2], 3: [4]}) 

sage: D.relabel(Permutations(D.vertices()).random_element()) 

sage: D.sources() 

[1, 4] 

 

Relabeling using an injective function:: 

 

sage: G.edges() 

[(0, 1, None), (1, 2, None)] 

sage: H = G.relabel(lambda i: i+10, inplace=False) 

sage: H.vertices() 

[10, 11, 12] 

sage: H.edges() 

[(10, 11, None), (11, 12, None)] 

 

Relabeling using a non injective function has no meaning:: 

 

sage: G.edges() 

[(0, 1, None), (1, 2, None)] 

sage: G.relabel(lambda i: 0, inplace=False) 

Traceback (most recent call last): 

... 

NotImplementedError: Non injective relabeling 

 

But this test can be disabled, which can lead to ... problems:: 

 

sage: G.edges() 

[(0, 1, None), (1, 2, None)] 

sage: G.relabel(lambda i: 0, check_input = False) 

sage: G.edges() 

[] 

 

Relabeling to simpler labels:: 

 

sage: G = graphs.CubeGraph(3) 

sage: G.vertices() 

['000', '001', '010', '011', '100', '101', '110', '111'] 

sage: G.relabel() 

sage: G.vertices() 

[0, 1, 2, 3, 4, 5, 6, 7] 

 

Recovering the relabeling with ``return_map``:: 

 

sage: G = graphs.CubeGraph(3) 

sage: expecting = {'000': 0, '001': 1, '010': 2, '011': 3, '100': 4, '101': 5, '110': 6, '111': 7} 

sage: G.relabel(return_map=True) == expecting 

True 

 

:: 

 

sage: G = graphs.PathGraph(3) 

sage: G.relabel(lambda i: i+10, return_map=True) 

{0: 10, 1: 11, 2: 12} 

 

TESTS:: 

 

sage: P = Graph(graphs.PetersenGraph()) 

sage: P.delete_edge([0,1]) 

sage: P.add_edge((4,5)) 

sage: P.add_edge((2,6)) 

sage: P.delete_vertices([0,1]) 

sage: P.relabel() 

 

The attributes are properly updated too:: 

 

sage: G = graphs.PathGraph(5) 

sage: G.set_vertices({0: 'before', 1: 'delete', 2: 'after'}) 

sage: G.delete_vertex(1) 

sage: G.relabel() 

sage: G.get_vertices() 

{0: 'before', 1: 'after', 2: None, 3: None} 

sage: G.get_pos() 

{0: (0, 0), 1: (2, 0), 2: (3, 0), 3: (4, 0)} 

 

Check that :trac:`12477` is fixed:: 

 

sage: g = Graph({1:[2,3]}) 

sage: rel = {1:'a', 2:'b'} 

sage: g.relabel(rel) 

sage: g.vertices() 

[3, 'a', 'b'] 

sage: rel 

{1: 'a', 2: 'b'} 

 

Immutable graphs cannot be relabeled:: 

 

sage: Graph(graphs.PetersenGraph(), immutable=True).relabel({}) 

Traceback (most recent call last): 

... 

ValueError: To relabel an immutable graph use inplace=False 

 

:trac:`16257`:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.relabel( [ i+1 for i in range(G.order()) ], inplace=True ) 

sage: G.relabel( [ i+1 for i in range(G.order()) ], inplace=True ) 

""" 

from sage.groups.perm_gps.permgroup_element import PermutationGroupElement 

 

if not inplace: 

G = copy(self) 

perm2 = G.relabel(perm, 

return_map= return_map, 

check_input = check_input, 

complete_partial_function = complete_partial_function) 

 

if immutable is None: 

immutable = self.is_immutable() 

if immutable: 

G = self.__class__(G, immutable = True) 

 

if return_map: 

return G, perm2 

else: 

return G 

 

if immutable: 

raise ValueError("To make an immutable copy use inplace=False") 

 

if self.is_immutable(): 

raise ValueError("To relabel an immutable graph use inplace=False") 

 

# If perm is not a dictionary, we build one ! 

 

if perm is None: 

 

# vertices() returns a sorted list: 

# this guarantees consistent relabeling 

perm = {v: i for i, v in enumerate(self.vertices())} 

complete_partial_function = False 

check_input = False 

 

elif isinstance(perm, dict): 

 

# If all vertices do not have a new label, the code will touch the 

# dictionary. Let us keep the one we received from the user clean ! 

perm = copy(perm) 

 

elif isinstance(perm, (list, tuple)): 

perm = dict(zip(self.vertices(),perm)) 

 

elif isinstance(perm, PermutationGroupElement): 

n = self.order() 

ddict = {} 

for i in range(1,n): 

ddict[i] = perm(i)%n 

if n > 0: 

ddict[0] = perm(n)%n 

perm = ddict 

 

elif callable(perm): 

perm = dict( [ i, perm(i) ] for i in self.vertices() ) 

complete_partial_function = False 

 

else: 

raise TypeError("Type of perm is not supported for relabeling.") 

 

# Whether to complete the relabeling function if some vertices do not 

# appear in the permutation. 

if complete_partial_function: 

for v in self: 

if v not in perm: 

perm[v] = v 

 

# Whether to check input 

if check_input: 

if len(set(perm.values())) < len(perm): 

raise NotImplementedError("Non injective relabeling") 

 

for v in perm: 

if v in self: 

try: 

hash(perm[v]) 

except TypeError: 

raise ValueError("perm dictionary must be of the format {a:a1, b:b1, ...} where a,b,... are vertices and a1,b1,... are hashable") 

 

self._backend.relabel(perm, self._directed) 

 

attributes_to_update = ('_pos', '_assoc', '_embedding') 

for attr in attributes_to_update: 

if hasattr(self, attr) and getattr(self, attr) is not None: 

new_attr = {} 

for v,value in iteritems(getattr(self, attr)): 

new_attr[perm[v]] = value 

 

setattr(self, attr, new_attr) 

 

if return_map: 

return perm 

 

def degree_to_cell(self, vertex, cell): 

""" 

Returns the number of edges from vertex to an edge in cell. In the 

case of a digraph, returns a tuple (in_degree, out_degree). 

 

EXAMPLES:: 

 

sage: G = graphs.CubeGraph(3) 

sage: cell = G.vertices()[:3] 

sage: G.degree_to_cell('011', cell) 

2 

sage: G.degree_to_cell('111', cell) 

0 

 

:: 

 

sage: D = DiGraph({ 0:[1,2,3], 1:[3,4], 3:[4,5]}) 

sage: cell = [0,1,2] 

sage: D.degree_to_cell(5, cell) 

(0, 0) 

sage: D.degree_to_cell(3, cell) 

(2, 0) 

sage: D.degree_to_cell(0, cell) 

(0, 2) 

""" 

if self._directed: 

in_neighbors_in_cell = set([a for a,_,_ in self.incoming_edges(vertex)]) & set(cell) 

out_neighbors_in_cell = set([a for _,a,_ in self.outgoing_edges(vertex)]) & set(cell) 

return (len(in_neighbors_in_cell), len(out_neighbors_in_cell)) 

else: 

neighbors_in_cell = set(self.neighbors(vertex)) & set(cell) 

return len(neighbors_in_cell) 

 

def is_equitable(self, partition, quotient_matrix=False): 

""" 

Checks whether the given partition is equitable with respect to 

self. 

 

A partition is equitable with respect to a graph if for every pair 

of cells C1, C2 of the partition, the number of edges from a vertex 

of C1 to C2 is the same, over all vertices in C1. 

 

INPUT: 

 

 

- ``partition`` - a list of lists 

 

- ``quotient_matrix`` - (default False) if True, and 

the partition is equitable, returns a matrix over the integers 

whose rows and columns represent cells of the partition, and whose 

i,j entry is the number of vertices in cell j adjacent to each 

vertex in cell i (since the partition is equitable, this is well 

defined) 

 

 

EXAMPLES:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.is_equitable([[0,4],[1,3,5,9],[2,6,8],[7]]) 

False 

sage: G.is_equitable([[0,4],[1,3,5,9],[2,6,8,7]]) 

True 

sage: G.is_equitable([[0,4],[1,3,5,9],[2,6,8,7]], quotient_matrix=True) 

[1 2 0] 

[1 0 2] 

[0 2 1] 

 

:: 

 

sage: ss = (graphs.WheelGraph(6)).line_graph(labels=False) 

sage: prt = [[(0, 1)], [(0, 2), (0, 3), (0, 4), (1, 2), (1, 4)], [(2, 3), (3, 4)]] 

 

:: 

 

sage: ss.is_equitable(prt) 

Traceback (most recent call last): 

... 

TypeError: Partition ([[(0, 1)], [(0, 2), (0, 3), (0, 4), (1, 2), (1, 4)], [(2, 3), (3, 4)]]) is not valid for this graph: vertices are incorrect. 

 

:: 

 

sage: ss = (graphs.WheelGraph(5)).line_graph(labels=False) 

sage: ss.is_equitable(prt) 

False 

""" 

from sage.misc.flatten import flatten 

from sage.misc.misc import uniq 

if sorted(flatten(partition, max_level=1)) != self.vertices(): 

raise TypeError("Partition (%s) is not valid for this graph: vertices are incorrect."%partition) 

if any(len(cell)==0 for cell in partition): 

raise TypeError("Partition (%s) is not valid for this graph: there is a cell of length 0."%partition) 

if quotient_matrix: 

from sage.matrix.constructor import Matrix 

from sage.rings.integer_ring import IntegerRing 

n = len(partition) 

M = Matrix(IntegerRing(), n) 

for i in range(n): 

for j in range(n): 

cell_i = partition[i] 

cell_j = partition[j] 

degrees = [self.degree_to_cell(u, cell_j) for u in cell_i] 

if len(uniq(degrees)) > 1: 

return False 

if self._directed: 

M[i, j] = degrees[0][0] 

else: 

M[i, j] = degrees[0] 

return M 

else: 

for cell1 in partition: 

for cell2 in partition: 

degrees = [self.degree_to_cell(u, cell2) for u in cell1] 

if len(uniq(degrees)) > 1: 

return False 

return True 

 

def coarsest_equitable_refinement(self, partition, sparse=True): 

""" 

Returns the coarsest partition which is finer than the input 

partition, and equitable with respect to self. 

 

A partition is equitable with respect to a graph if for every pair 

of cells C1, C2 of the partition, the number of edges from a vertex 

of C1 to C2 is the same, over all vertices in C1. 

 

A partition P1 is finer than P2 (P2 is coarser than P1) if every 

cell of P1 is a subset of a cell of P2. 

 

INPUT: 

 

 

- ``partition`` - a list of lists 

 

- ``sparse`` - (default False) whether to use sparse 

or dense representation- for small graphs, use dense for speed 

 

 

EXAMPLES:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.coarsest_equitable_refinement([[0],list(range(1,10))]) 

[[0], [2, 3, 6, 7, 8, 9], [1, 4, 5]] 

sage: G = graphs.CubeGraph(3) 

sage: verts = G.vertices() 

sage: Pi = [verts[:1], verts[1:]] 

sage: Pi 

[['000'], ['001', '010', '011', '100', '101', '110', '111']] 

sage: G.coarsest_equitable_refinement(Pi) 

[['000'], ['011', '101', '110'], ['111'], ['001', '010', '100']] 

 

Note that given an equitable partition, this function returns that 

partition:: 

 

sage: P = graphs.PetersenGraph() 

sage: prt = [[0], [1, 4, 5], [2, 3, 6, 7, 8, 9]] 

sage: P.coarsest_equitable_refinement(prt) 

[[0], [1, 4, 5], [2, 3, 6, 7, 8, 9]] 

 

:: 

 

sage: ss = (graphs.WheelGraph(6)).line_graph(labels=False) 

sage: prt = [[(0, 1)], [(0, 2), (0, 3), (0, 4), (1, 2), (1, 4)], [(2, 3), (3, 4)]] 

sage: ss.coarsest_equitable_refinement(prt) 

Traceback (most recent call last): 

... 

TypeError: Partition ([[(0, 1)], [(0, 2), (0, 3), (0, 4), (1, 2), (1, 4)], [(2, 3), (3, 4)]]) is not valid for this graph: vertices are incorrect. 

 

:: 

 

sage: ss = (graphs.WheelGraph(5)).line_graph(labels=False) 

sage: ss.coarsest_equitable_refinement(prt) 

[[(0, 1)], [(1, 2), (1, 4)], [(0, 3)], [(0, 2), (0, 4)], [(2, 3), (3, 4)]] 

 

ALGORITHM: Brendan D. McKay's Master's Thesis, University of 

Melbourne, 1976. 

""" 

from sage.misc.flatten import flatten 

if sorted(flatten(partition, max_level=1)) != self.vertices(): 

raise TypeError("Partition (%s) is not valid for this graph: vertices are incorrect."%partition) 

if any(len(cell)==0 for cell in partition): 

raise TypeError("Partition (%s) is not valid for this graph: there is a cell of length 0."%partition) 

if self.has_multiple_edges(): 

raise TypeError("Refinement function does not support multiple edges.") 

G = copy(self) 

perm_to = G.relabel(return_map=True) 

partition = [[perm_to[b] for b in cell] for cell in partition] 

perm_from = {} 

for v in self: 

perm_from[perm_to[v]] = v 

n = G.num_verts() 

if sparse: 

from sage.graphs.base.sparse_graph import SparseGraph 

CG = SparseGraph(n) 

else: 

from sage.graphs.base.dense_graph import DenseGraph 

CG = DenseGraph(n) 

for i in range(n): 

for j in range(n): 

if G.has_edge(i,j): 

CG.add_arc(i,j) 

 

from sage.groups.perm_gps.partn_ref.refinement_graphs import coarsest_equitable_refinement 

result = coarsest_equitable_refinement(CG, partition, G._directed) 

return [[perm_from[b] for b in cell] for cell in result] 

 

def automorphism_group(self, partition=None, verbosity=0, 

edge_labels=False, order=False, 

return_group=True, orbits=False, algorithm=None): 

""" 

Return the automorphism group of the graph. 

 

With ``partition`` this can also return the largest subgroup 

of the automorphism group of the (di)graph whose orbit 

partition is finer than the partition given. 

 

INPUT: 

 

- ``partition`` - default is the unit partition, 

otherwise computes the subgroup of the full automorphism group 

respecting the partition. 

 

- ``edge_labels`` - default False, otherwise allows 

only permutations respecting edge labels. Note that this option 

is not supported if ``algorithm="bliss"`` 

 

- ``order`` - (default False) if True, compute the 

order of the automorphism group 

 

- ``return_group`` - default True 

 

- ``orbits`` - returns the orbits of the group acting 

on the vertices of the graph 

 

- ``algorithm`` - If ``algorithm = "bliss"`` the automorphism group is 

computed using the optional package bliss 

(http://www.tcs.tkk.fi/Software/bliss/index.html). Setting it to 

"sage" uses Sage's implementation. If set to ``None`` (default), bliss 

is used when available. 

 

OUTPUT: The order of the output is group, order, orbits. However, there 

are options to turn each of these on or off. 

 

EXAMPLES: 

 

Graphs:: 

 

sage: graphs_query = GraphQuery(display_cols=['graph6'],num_vertices=4) 

sage: L = graphs_query.get_graphs_list() 

sage: graphs_list.show_graphs(L) 

sage: for g in L: 

....: G = g.automorphism_group() 

....: G.order(), G.gens() 

(24, [(2,3), (1,2), (0,1)]) 

(4, [(2,3), (0,1)]) 

(2, [(1,2)]) 

(6, [(1,2), (0,1)]) 

(6, [(2,3), (1,2)]) 

(8, [(1,2), (0,1)(2,3)]) 

(2, [(0,1)(2,3)]) 

(2, [(1,2)]) 

(8, [(2,3), (0,1), (0,2)(1,3)]) 

(4, [(2,3), (0,1)]) 

(24, [(2,3), (1,2), (0,1)]) 

sage: C = graphs.CubeGraph(4) 

sage: G = C.automorphism_group() 

sage: M = G.character_table() # random order of rows, thus abs() below 

sage: QQ(M.determinant()).abs() 

712483534798848 

sage: G.order() 

384 

 

:: 

 

sage: D = graphs.DodecahedralGraph() 

sage: G = D.automorphism_group() 

sage: A5 = AlternatingGroup(5) 

sage: Z2 = CyclicPermutationGroup(2) 

sage: H = A5.direct_product(Z2)[0] #see documentation for direct_product to explain the [0] 

sage: G.is_isomorphic(H) 

True 

 

Multigraphs:: 

 

sage: G = Graph(multiedges=True,sparse=True) 

sage: G.add_edge(('a', 'b')) 

sage: G.add_edge(('a', 'b')) 

sage: G.add_edge(('a', 'b')) 

sage: G.automorphism_group() 

Permutation Group with generators [('a','b')] 

 

Digraphs:: 

 

sage: D = DiGraph( { 0:[1], 1:[2], 2:[3], 3:[4], 4:[0] } ) 

sage: D.automorphism_group() 

Permutation Group with generators [(0,1,2,3,4)] 

 

Edge labeled graphs:: 

 

sage: G = Graph(sparse=True) 

sage: G.add_edges( [(0,1,'a'),(1,2,'b'),(2,3,'c'),(3,4,'b'),(4,0,'a')] ) 

sage: G.automorphism_group(edge_labels=True) 

Permutation Group with generators [(1,4)(2,3)] 

 

:: 

 

sage: G = Graph({0 : {1 : 7}}) 

sage: G.automorphism_group(edge_labels=True) 

Permutation Group with generators [(0,1)] 

 

sage: foo = Graph(sparse=True) 

sage: bar = Graph(implementation='c_graph',sparse=True) 

sage: foo.add_edges([(0,1,1),(1,2,2), (2,3,3)]) 

sage: bar.add_edges([(0,1,1),(1,2,2), (2,3,3)]) 

sage: foo.automorphism_group(edge_labels=True) 

Permutation Group with generators [()] 

sage: foo.automorphism_group() 

Permutation Group with generators [(0,3)(1,2)] 

sage: bar.automorphism_group(edge_labels=True) 

Permutation Group with generators [()] 

 

You can also ask for just the order of the group:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.automorphism_group(return_group=False, order=True) 

120 

 

Or, just the orbits (note that each graph here is vertex transitive) 

 

:: 

 

sage: G = graphs.PetersenGraph() 

sage: G.automorphism_group(return_group=False, orbits=True,algorithm='sage') 

[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] 

sage: G.automorphism_group(partition=[[0],list(range(1,10))], return_group=False, orbits=True,algorithm='sage') 

[[0], [2, 3, 6, 7, 8, 9], [1, 4, 5]] 

sage: C = graphs.CubeGraph(3) 

sage: C.automorphism_group(orbits=True, return_group=False,algorithm='sage') 

[['000', '001', '010', '011', '100', '101', '110', '111']] 

 

One can also use the faster algorithm for computing the automorphism 

group of the graph - bliss:: 

 

sage: G = graphs.HallJankoGraph() # optional - bliss 

sage: A1 = G.automorphism_group() # optional - bliss 

sage: A2 = G.automorphism_group(algorithm='bliss') # optional - bliss 

sage: A1.is_isomorphic(A2) # optional - bliss 

True 

 

TESTS: 

 

We get a KeyError when given an invalid partition (:trac:`6087`):: 

 

sage: g=graphs.CubeGraph(3) 

sage: g.relabel() 

sage: g.automorphism_group(partition=[[0,1,2],[3,4,5]],algorithm='sage') 

Traceback (most recent call last): 

... 

KeyError: 6 

 

Labeled automorphism group:: 

 

sage: digraphs.DeBruijn(3,2).automorphism_group(algorithm='sage') 

Permutation Group with generators [('01','02')('10','20')('11','22')('12','21'), ('00','11')('01','10')('02','12')('20','21')] 

sage: d = digraphs.DeBruijn(3,2) 

sage: d.allow_multiple_edges(True) 

sage: d.add_edge(d.edges()[0]) 

sage: d.automorphism_group(algorithm='sage') 

Permutation Group with generators [('01','02')('10','20')('11','22')('12','21')] 

 

The labeling is correct:: 

 

sage: g = graphs.PetersenGraph() 

sage: ag = g.automorphism_group() 

sage: for u,v in g.edges(labels = False): 

....: if len(ag.orbit((u,v),action="OnPairs")) != 30: 

....: print("ARggggggggggggg !!!") 

 

Empty group, correct domain:: 

 

sage: Graph({'a':['a'], 'b':[]}).automorphism_group() 

Permutation Group with generators [()] 

sage: Graph({'a':['a'], 'b':[]}).automorphism_group().domain() 

{'a', 'b'} 

 

We can check that the subgroups are labelled correctly 

(:trac:`15656`):: 

 

sage: G1 = Graph(':H`ECw@HGXGAGUG`e') 

sage: G = G1.automorphism_group() 

sage: G.subgroups() 

[Subgroup of (Permutation Group with generators [(0,7)(1,4)(2,3)(6,8)]) generated by [()], 

Subgroup of (Permutation Group with generators [(0,7)(1,4)(2,3)(6,8)]) generated by [(0,7)(1,4)(2,3)(6,8)]] 

""" 

try: 

from sage.graphs.bliss import automorphism_group 

have_bliss = True 

except ImportError: 

have_bliss = False 

 

# See trac #21704 

if self.has_multiple_edges(): 

if algorithm == 'bliss': 

raise NotImplementedError("algorithm 'bliss' can not be used for graph with multiedges") 

have_bliss = False 

 

if (algorithm == 'bliss' or # explicit choice from the user; or 

(algorithm is None and # by default 

not edge_labels and 

have_bliss)): 

if edge_labels: 

raise ValueError("bliss cannot be used when edge_labels is True") 

 

if not have_bliss: 

from sage.misc.package import PackageNotFoundError 

raise PackageNotFoundError("bliss") 

 

A = automorphism_group(self, partition) 

 

# If the user only wants the automorphism group, lets return it 

# without much hassle 

if return_group and not (orbits or order): 

return A 

 

ret = [] 

if return_group: 

ret.append(A) 

if order: 

ret.append(A.order()) 

if orbits: 

ret.append(A.orbits()) 

 

if return_group + order + orbits == 1: 

return ret[0] 

return ret 

 

if (algorithm is not None and 

algorithm != "sage"): 

raise ValueError("'algorithm' must be equal to 'bliss', 'sage', or None") 

 

from sage.groups.perm_gps.partn_ref.refinement_graphs import search_tree 

from sage.groups.perm_gps.permgroup import PermutationGroup 

dig = (self._directed or self.has_loops()) 

 

if partition is None: 

partition = [self.vertices()] 

if edge_labels or self.has_multiple_edges(): 

G, partition, relabeling = graph_isom_equivalent_non_edge_labeled_graph(self, partition, return_relabeling=True, ignore_edge_labels=(not edge_labels)) 

G_vertices = sum(partition, []) 

G_to = {} 

for i in range(len(G_vertices)): 

G_to[G_vertices[i]] = i 

from sage.graphs.all import Graph, DiGraph 

DoDG = DiGraph if self._directed else Graph 

H = DoDG(len(G_vertices), implementation='c_graph', loops=G.allows_loops()) 

HB = H._backend 

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

u = G_to[u]; v = G_to[v] 

HB.add_edge(u,v,None,G._directed) 

GC = HB.c_graph()[0] 

partition = [[G_to[v] for v in cell] for cell in partition] 

A = search_tree(GC, partition, lab=False, dict_rep=True, dig=dig, verbosity=verbosity, order=order) 

if order: 

a,b,c = A 

else: 

a,b = A 

b_new = {} 

for v in G_to: 

b_new[v] = b[G_to[v]] 

b = b_new 

# b is a translation of the labellings 

acting_vertices = {} 

translation_d = {} 

m = G.order() 

for v in self: 

if b[relabeling[v]] == m: 

translation_d[v] = self.order() 

acting_vertices[v] = 0 

else: 

translation_d[v] = b[relabeling[v]] 

acting_vertices[v] = b[relabeling[v]] 

real_aut_gp = [] 

n = self.order() 

for gen in a: 

gen_restr = [0]*n 

for v in self.vertex_iterator(): 

gen_restr[acting_vertices[v]] = gen[acting_vertices[v]] 

if gen_restr not in real_aut_gp: 

real_aut_gp.append(gen_restr) 

id = list(range(n)) 

if id in real_aut_gp: 

real_aut_gp.remove(id) 

a = real_aut_gp 

b = translation_d 

else: 

G_vertices = sum(partition, []) 

G_to = {} 

for i in range(len(G_vertices)): 

G_to[G_vertices[i]] = i 

from sage.graphs.all import Graph, DiGraph 

DoDG = DiGraph if self._directed else Graph 

H = DoDG(len(G_vertices), implementation='c_graph', loops=self.allows_loops()) 

HB = H._backend 

for u,v in self.edge_iterator(labels=False): 

u = G_to[u]; v = G_to[v] 

HB.add_edge(u,v,None,self._directed) 

GC = HB.c_graph()[0] 

partition = [[G_to[v] for v in cell] for cell in partition] 

 

if return_group: 

A = search_tree(GC, partition, dict_rep=True, lab=False, dig=dig, verbosity=verbosity, order=order) 

if order: 

a,b,c = A 

else: 

a,b = A 

b_new = {} 

for v in G_to: 

b_new[v] = b[G_to[v]] 

b = b_new 

else: 

a = search_tree(GC, partition, dict_rep=False, lab=False, dig=dig, verbosity=verbosity, order=order) 

if order: 

a,c = a 

 

output = [] 

if return_group: 

if len(a) != 0: 

# We translate the integer permutations into a collection of 

# cycles. 

from sage.combinat.permutation import Permutation 

gens = [Permutation([x+1 for x in aa]).to_cycles() for aa in a] 

 

# We relabel the cycles using the vertices' names instead of integers 

n = self.order() 

int_to_vertex = {((i+1) if i != n else 1):v for v,i in iteritems(b)} 

gens = [ [ tuple([int_to_vertex[i] for i in cycle]) for cycle in gen] for gen in gens] 

output.append(PermutationGroup(gens = gens, domain = int_to_vertex.values())) 

else: 

output.append(PermutationGroup([[]], domain = self.vertices())) 

if order: 

output.append(c) 

if orbits: 

G_from = {} 

for v in G_to: 

G_from[G_to[v]] = v 

from sage.groups.perm_gps.partn_ref.refinement_graphs import get_orbits 

output.append([[G_from[v] for v in W] for W in get_orbits(a, self.num_verts())]) 

 

# A Python switch statement! 

return { 0: None, 

1: output[0], 

2: tuple(output), 

3: tuple(output), 

4: tuple(output) 

}[len(output)] 

 

def is_vertex_transitive(self, partition=None, verbosity=0, 

edge_labels=False, order=False, 

return_group=True, orbits=False): 

""" 

Returns whether the automorphism group of self is transitive within 

the partition provided, by default the unit partition of the 

vertices of self (thus by default tests for vertex transitivity in 

the usual sense). 

 

EXAMPLES:: 

 

sage: G = Graph({0:[1],1:[2]}) 

sage: G.is_vertex_transitive() 

False 

sage: P = graphs.PetersenGraph() 

sage: P.is_vertex_transitive() 

True 

sage: D = graphs.DodecahedralGraph() 

sage: D.is_vertex_transitive() 

True 

sage: R = graphs.RandomGNP(2000, .01) 

sage: R.is_vertex_transitive() 

False 

""" 

if partition is None: 

partition = [self.vertices()] 

 

for p in partition: 

if len(p) == 0: 

continue 

d = self.degree(p[0]) 

if not all(self.degree(x) == d for x in p): 

return False 

 

new_partition = self.automorphism_group(partition, 

verbosity=verbosity, edge_labels=edge_labels, 

order=False, return_group=False, orbits=True) 

 

return (len(partition) == len(new_partition)) 

 

def is_hamiltonian(self): 

r""" 

Tests whether the current graph is Hamiltonian. 

 

A graph (resp. digraph) is said to be Hamiltonian 

if it contains as a subgraph a cycle (resp. a circuit) 

going through all the vertices. 

 

Testing for Hamiltonicity being NP-Complete, this 

algorithm could run for some time depending on 

the instance. 

 

ALGORITHM: 

 

See ``Graph.traveling_salesman_problem``. 

 

OUTPUT: 

 

Returns ``True`` if a Hamiltonian cycle/circuit exists, and 

``False`` otherwise. 

 

NOTE: 

 

This function, as ``hamiltonian_cycle`` and 

``traveling_salesman_problem``, computes a Hamiltonian 

cycle if it exists: the user should *NOT* test for 

Hamiltonicity using ``is_hamiltonian`` before calling 

``hamiltonian_cycle`` or ``traveling_salesman_problem`` 

as it would result in computing it twice. 

 

EXAMPLES: 

 

The Heawood Graph is known to be Hamiltonian :: 

 

sage: g = graphs.HeawoodGraph() 

sage: g.is_hamiltonian() 

True 

 

The Petergraph, though, is not :: 

 

sage: g = graphs.PetersenGraph() 

sage: g.is_hamiltonian() 

False 

 

TESTS:: 

 

sage: g = graphs.ChvatalGraph() 

sage: g.is_hamiltonian() 

True 

 

:trac:`16210`:: 

 

sage: g=graphs.CycleGraph(10) 

sage: g.allow_loops(True) 

sage: g.add_edge(0,0) 

sage: g.is_hamiltonian() 

True 

""" 

from sage.categories.sets_cat import EmptySetError 

try: 

tsp = self.traveling_salesman_problem(use_edge_labels = False) 

return True 

 

except EmptySetError: 

return False 

 

def is_isomorphic(self, other, certificate=False, verbosity=0, edge_labels=False): 

r""" 

Tests for isomorphism between self and other. 

 

INPUT: 

 

- ``certificate`` - if True, then output is `(a, b)`, where `a` 

is a boolean and `b` is either a map or ``None``. 

 

- ``edge_labels`` - default ``False``, otherwise allows 

only permutations respecting edge labels. 

 

OUTPUT: 

 

- either a boolean or, if ``certificate`` is ``True``, a tuple consisting 

of a boolean and a map or ``None`` 

 

EXAMPLES: 

 

Graphs:: 

 

sage: from sage.groups.perm_gps.permgroup_named import SymmetricGroup 

sage: D = graphs.DodecahedralGraph() 

sage: E = copy(D) 

sage: gamma = SymmetricGroup(20).random_element() 

sage: E.relabel(gamma) 

sage: D.is_isomorphic(E) 

True 

 

:: 

 

sage: D = graphs.DodecahedralGraph() 

sage: S = SymmetricGroup(20) 

sage: gamma = S.random_element() 

sage: E = copy(D) 

sage: E.relabel(gamma) 

sage: a,b = D.is_isomorphic(E, certificate=True); a 

True 

sage: from sage.plot.graphics import GraphicsArray 

sage: from sage.graphs.generic_graph_pyx import spring_layout_fast 

sage: position_D = spring_layout_fast(D) 

sage: position_E = {} 

sage: for vert in position_D: 

....: position_E[b[vert]] = position_D[vert] 

sage: GraphicsArray([D.plot(pos=position_D), E.plot(pos=position_E)]).show() # long time 

 

:: 

 

sage: g=graphs.HeawoodGraph() 

sage: g.is_isomorphic(g) 

True 

 

Multigraphs:: 

 

sage: G = Graph(multiedges=True,sparse=True) 

sage: G.add_edge((0,1,1)) 

sage: G.add_edge((0,1,2)) 

sage: G.add_edge((0,1,3)) 

sage: G.add_edge((0,1,4)) 

sage: H = Graph(multiedges=True,sparse=True) 

sage: H.add_edge((3,4)) 

sage: H.add_edge((3,4)) 

sage: H.add_edge((3,4)) 

sage: H.add_edge((3,4)) 

sage: G.is_isomorphic(H) 

True 

 

Digraphs:: 

 

sage: A = DiGraph( { 0 : [1,2] } ) 

sage: B = DiGraph( { 1 : [0,2] } ) 

sage: A.is_isomorphic(B, certificate=True) 

(True, {0: 1, 1: 0, 2: 2}) 

 

Edge labeled graphs:: 

 

sage: G = Graph(sparse=True) 

sage: G.add_edges( [(0,1,'a'),(1,2,'b'),(2,3,'c'),(3,4,'b'),(4,0,'a')] ) 

sage: H = G.relabel([1,2,3,4,0], inplace=False) 

sage: G.is_isomorphic(H, edge_labels=True) 

True 

 

Edge labeled digraphs:: 

 

sage: G = DiGraph() 

sage: G.add_edges( [(0,1,'a'),(1,2,'b'),(2,3,'c'),(3,4,'b'),(4,0,'a')] ) 

sage: H = G.relabel([1,2,3,4,0], inplace=False) 

sage: G.is_isomorphic(H, edge_labels=True) 

True 

sage: G.is_isomorphic(H, edge_labels=True, certificate=True) 

(True, {0: 1, 1: 2, 2: 3, 3: 4, 4: 0}) 

 

TESTS:: 

 

sage: g1 = '~?A[~~{ACbCwV_~__OOcCW_fAA{CF{CCAAAC__bCCCwOOV___~____OOOOcCCCW___fAAAA'+\ 

....: '{CCCF{CCCCAAAAAC____bCCCCCwOOOOV_____~_O@ACG_@ACGOo@ACG?{?`A?GV_GO@AC}@?_OGC'+\ 

....: 'C?_OI@?K?I@?_OM?_OGD?F_A@OGC@{A@?_OG?O@?gCA?@_GCA@O?B_@OGCA?BoA@?gC?@{A?GO`?'+\ 

....: '??_GO@AC??E?O`?CG??[?O`A?G??{?GO`A???|A?_GOC`AC@_OCGACEAGS?HA?_SA`aO@G?cOC_N'+\ 

....: 'G_C@AOP?GnO@_GACOE?g?`OGACCOGaGOc?HA?`GORCG_AO@B?K@[`A?OCI@A@By?_K@?SCABA?H?'+\ 

....: 'SA?a@GC`CH?Q?C_c?cGRC@G_AOCOa@Ax?QC?_GOo_CNg@A?oC@CaCGO@CGA_O`?GSGPAGOC_@OO_'+\ 

....: 'aCHaG?cO@CB?_`Ax?GQC?_cAOCG^OGAC@_D?IGO`?D?O_I?HAOO`AGOHA?cC?oAO`AW_Q?HCACAC'+\ 

....: 'GO`[_OCHA?_cCACG^O_@CAGO`A?GCOGc@?I?OQOC?IGC_o@CAGCCE?A@DBG_OA@C_CP?OG_VA_CO'+\ 

....: 'G@D?_OA_DFgA@CO?aH?Ga@?a?_I?S@A@@Oa@?@P@GCO_AACO_a_?`K_GCQ@?cAOG_OGAwQ@?K?cC'+\ 

....: 'GH?I?ABy@C?G_S@@GCA@C`?OI?_D?OP@G?IGGP@O_AGCP?aG?GCPAX?cA?OGSGCGCAGCJ`?oAGCC'+\ 

....: 'HAA?A_CG^O@CAG_GCSCAGCCGOCG@OA_`?`?g_OACG_`CAGOAO_H?a_?`AXA?OGcAAOP?a@?CGVAC'+\ 

....: 'OG@_AGG`OA_?O`|?Ga?COKAAGCA@O`A?a?S@?HCG`?_?gO`AGGaC?PCAOGI?A@GO`K_CQ@?GO_`O'+\ 

....: 'GCAACGVAG@_COOCQ?g?I?O`ByC?G_P?O`A?H@G?_P?`OAGC?gD?_C@_GCAGDG_OA@CCPC?AOQ??g'+\ 

....: '_R@_AGCO____OCC_@OAbaOC?g@C_H?AOOC@?a`y?PC?G`@OOH??cOG_OOAG@_COAP?WA?_KAGC@C'+\ 

....: '_CQ@?HAACH??c@P?_AWGaC?P?gA_C_GAD?I?Awa?S@?K?`C_GAOGCS?@|?COGaA@CAAOQ?AGCAGO'+\ 

....: 'ACOG@_G_aC@_G@CA@@AHA?OGc?WAAH@G?P?_?cH_`CAGOGACc@@GA?S?CGVCG@OA_CICAOOC?PO?'+\ 

....: 'OG^OG_@CAC_cC?AOP?_OICG@?oAGCO_GO_GB@?_OG`AH?cA?OH?`P??cC_O?SCGR@O_AGCAI?Q?_'+\ 

....: 'GGS?D?O`[OI?_D@@CCA?cCA_?_O`By?_PC?IGAGOQ?@A@?aO`A?Q@?K?__`_E?_GCA@CGO`C_GCQ'+\ 

....: '@A?gAOQ?@C?DCACGR@GCO_AGPA@@GAA?A_CO`Aw_I?S@?SCB@?OC_?_P@ACNgOC@A?aCGOCAGCA@'+\ 

....: 'CA?H@GG_C@AOGa?OOG_O?g_OA?oDC_AO@GOCc?@P?_A@D??cC``O?cGAOGD?@OA_CAGCA?_cwKA?'+\ 

....: '`?OWGG?_PO?I?S?H@?^OGAC@_Aa@CAGC?a@?_Q?@H?_OCHA?OQA_P?_G_O?WA?_IG_Q?HC@A@ADC'+\ 

....: 'A?AI?AC_?QAWOHA?cAGG_I?S?G_OG@GA?`[D?O_IA?`GGCS?OA_?c@?Q?^OAC@_G_Ca@CA@?OGCO'+\ 

....: 'H@G@A@?GQC?_Q@GP?_OG?IGGB?OCGaG?cO@A__QGC?E?A@CH@G?GRAGOC_@GGOW@O?O_OGa?_c?G'+\ 

....: 'V@CGA_OOaC?a_?a?A_CcC@?CNgA?oC@GGE@?_OH?a@?_?QA`A@?QC?_KGGO_OGCAa@?A?_KCGPC@'+\ 

....: 'G_AOAGPGC?D@?a_A?@GGO`KH?Q?C_QGAA_?gOG_OA?_GG`AwH?SA?`?cAI?A@D?I?@?QA?`By?K@'+\ 

....: '?O`GGACA@CGCA@CC_?WO`?`A?OCH?`OCA@COG?I?oC@ACGPCG_AO@_aAA?Aa?g?GD@G?CO`AWOc?'+\ 

....: 'HA?OcG_?g@OGCAAAOC@ACJ_`OGACAGCS?CAGI?A`@?OCACG^' 

sage: g2 = '~?A[??osR?WARSETCJ_QWASehOXQg`QwChK?qSeFQ_sTIaWIV?XIR?KAC?B?`?COCG?o?O_'+\ 

....: '@_?`??B?`?o@_O_WCOCHC@_?`W?E?AD_O?WCCeO?WCSEGAGAIaA@_?aw?OK?ER?`?@_HQXA?B@Q_'+\ 

....: 'pA?a@Qg_`?o?h[?GOK@IR?@A?BEQcoCG?K\IB?GOCWiTC?GOKWIV??CGEKdH_H_?CB?`?DC??_WC'+\ 

....: 'G?SO?AP?O_?g_?D_?`?C__?D_?`?CCo??@_O_XDC???WCGEGg_??a?`G_aa??E?AD_@cC??K?CJ?'+\ 

....: '@@K?O?WCCe?aa?G?KAIB?Gg_A?a?ag_@DC?OK?CV??EOO@?o?XK??GH`A?B?Qco?Gg`A?B@Q_o?C'+\ 

....: 'SO`?P?hSO?@DCGOK?IV???K_`A@_HQWC??_cCG?KXIRG?@D?GO?WySEG?@D?GOCWiTCC??a_CGEK'+\ 

....: 'DJ_@??K_@A@bHQWAW?@@K??_WCG?g_?CSO?A@_O_@P??Gg_?Ca?`?@P??Gg_?D_?`?C__?EOO?Ao'+\ 

....: '?O_AAW?@@K???WCGEPP??Gg_??B?`?pDC??aa??AGACaAIG?@DC??K?CJ?BGG?@cC??K?CJ?@@K?'+\ 

....: '?_e?G?KAAR?PP??Gg_A?B?a_oAIG?@DC?OCOCTC?Gg_?CSO@?o?P[??X@??K__A@_?qW??OR??GH'+\ 

....: '`A?B?Qco?Gg_?CSO`?@_hOW?AIG?@DCGOCOITC??PP??Gg`A@_@Qw??@cC??qACGE?dH_O?AAW?@'+\ 

....: '@GGO?WqSeO?AIG?@D?GO?WySEG?@DC??a_CGAKTIaA??PP??Gg@A@b@Qw?O?BGG?@c?GOKXIR?KA'+\ 

....: 'C?H_?CCo?A@_O_?WCG@P??Gg_?CB?`?COCG@P??Gg_?Ca?`?E?AC?g_?CSO?Ao?O_@_?`@GG?@cC'+\ 

....: '??k?CG??WCGOR??GH_??B?`?o@_O`DC??aa???KACB?a?`AIG?@DC??COCHC@_?`AIG?@DC??K?C'+\ 

....: 'J??o?O`cC??qA??E?AD_O?WC?OR??GH_A?B?_cq?B?_AIG?@DC?O?WCSEGAGA?Gg_?CSO@?P?PSO'+\ 

....: 'OK?C?PP??Gg_A@_?aw?OK?C?X@??K__A@_?qWCG?K??GH_?CCo`?@_HQXA?B??AIG?@DCGO?WISE'+\ 

....: 'GOCO??PP??Gg`A?a@Qg_`?o??@DC??aaCGE?DJ_@A@_??BGG?@cCGOK@IR?@A?BO?AAW?@@GGO?W'+\ 

....: 'qSe?`?@g?@DC??a_CG?K\IB?GOCQ??PP??Gg@A?bDQg_@A@_O?AIG?@D?GOKWIV??CGE@??K__?E'+\ 

....: 'O?`?pchK?_SA_OI@OGD?gCA_SA@OI?c@H?Q?c_H?QOC_HGAOCc?QOC_HGAOCc@GAQ?c@H?QD?gCA'+\ 

....: '_SA@OI@?gD?_SA_OKA_SA@OI@?gD?_SA_OI@OHI?c_H?QOC_HGAOCc@GAQ?eC_H?QOC_HGAOCc@G'+\ 

....: 'AQ?c@XD?_SA_OI@OGD?gCA_SA@PKGO`A@ACGSGO`?`ACICGO_?ACGOcGO`?O`AC`ACHACGO???^?'+\ 

....: '????}Bw????Fo^???????Fo?}?????Bw?^?Bw?????GO`AO`AC`ACGACGOcGO`??aCGO_O`ADACG'+\ 

....: 'OGO`A@ACGOA???@{?N_@{?????Fo?}????OFo????N_}????@{????Bw?OACGOgO`A@ACGSGO`?`'+\ 

....: 'ACG?OaCGO_GO`AO`AC`ACGACGO_@G???Fo^?????}Bw????Fo??AC@{?????Fo?}?Fo?????^??A'+\ 

....: 'OGO`AO`AC@ACGQCGO_GO`A?HAACGOgO`A@ACGOGO`A`ACG?GQ??^?Bw?????N_@{?????Fo?QC??'+\ 

....: 'Fo^?????}????@{Fo???CHACGO_O`ACACGOgO`A@ACGO@AOcGO`?O`AC`ACGACGOcGO`?@GQFo??'+\ 

....: '??N_????^@{????Bw??`GRw?????N_@{?????Fo?}???HAO_OI@OGD?gCA_SA@OI@?gDK_??C@GA'+\ 

....: 'Q?c@H?Q?c_H?QOC_HEW????????????????????????~~~~~' 

sage: G1 = Graph(g1) 

sage: G2 = Graph(g2) 

sage: G1.is_isomorphic(G2) 

True 

 

Ensure that isomorphic looped graphs with non-range vertex labels report 

correctly (:trac:`10814`, fixed by :trac:`8395`):: 

 

sage: G1 = Graph({1:[0,1]}) 

sage: G2 = Graph({2:[0,2]}) 

sage: G1.is_isomorphic(G2) 

True 

sage: G = Graph(multiedges = True, loops = True) 

sage: H = Graph(multiedges = True, loops = True) 

sage: G.add_edges([(0,1,0),(1,0,1),(1,1,2),(0,0,3)]) 

sage: H.add_edges([(0,1,3),(1,0,2),(1,1,1),(0,0,0)]) 

sage: G.is_isomorphic(H, certificate=True) 

(True, {0: 0, 1: 1}) 

sage: set_random_seed(0) 

sage: D = digraphs.RandomDirectedGNP(6, .2) 

sage: D.is_isomorphic(D, certificate=True) 

(True, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5}) 

sage: D.is_isomorphic(D,edge_labels=True, certificate=True) 

(True, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5}) 

 

Ensure that :trac:`11620` is fixed:: 

 

sage: G1 = DiGraph([(0, 0, 'c'), (0, 4, 'b'), (0, 5, 'c'), 

....: (0, 5, 't'), (1, 1, 'c'), (1, 3,'c'), (1, 3, 't'), (1, 5, 'b'), 

....: (2, 2, 'c'), (2, 3, 'b'), (2, 4, 'c'),(2, 4, 't'), (3, 1, 't'), 

....: (3, 2, 'b'), (3, 2, 'c'), (3, 4, 'c'), (4, 0,'b'), (4, 0, 'c'), 

....: (4, 2, 't'), (4, 5, 'c'), (5, 0, 't'), (5, 1, 'b'), (5, 1, 'c'), 

....: (5, 3, 'c')], loops=True, multiedges=True) 

sage: G2 = G1.relabel({0:4, 1:5, 2:3, 3:2, 4:1,5:0}, inplace=False) 

sage: G1.canonical_label(edge_labels=True) == G2.canonical_label(edge_labels=True) 

True 

sage: G1.is_isomorphic(G2,edge_labels=True) 

True 

 

Ensure that :trac:`13114` is fixed :: 

 

 

sage: g = Graph([(0, 0, 0), (0, 2, 0), (1, 1, 0), (1, 2, 0), (1, 2, 1), (2, 2, 0)], multiedges=True, loops=True) 

sage: gg = Graph([(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 2, 0), (2, 2, 0), (2, 2, 1)], multiedges=True, loops=True) 

sage: g.is_isomorphic(gg) 

False 

 

Ensure that :trac:`14777` is fixed :: 

 

sage: g = Graph() 

sage: h = Graph() 

sage: g.is_isomorphic(h) 

True 

 

as well as :trac:`18613`:: 

 

sage: g.is_isomorphic(h, certificate=True) 

(True, None) 

 

Ensure that :trac:`24964` is fixed :: 

 

sage: A = DiGraph([(6,7,'a'), (6,7,'b')], multiedges=True) 

sage: B = DiGraph([('x','y','u'), ('x','y','v')], multiedges=True) 

sage: A.is_isomorphic(B, certificate=True) 

(True, {6: 'x', 7: 'y'}) 

sage: A.is_isomorphic(B, certificate=True, edge_labels=True) 

(False, None) 

 

""" 

 

if self.order() == other.order() == 0: 

return (True, None) if certificate else True 

 

if (self.is_directed() != other.is_directed() or self.order() != other.order() or 

self.size() != other.size() or self.degree_sequence() != other.degree_sequence()): 

if certificate: 

return False,None 

else: 

return False 

 

from sage.groups.perm_gps.partn_ref.refinement_graphs import isomorphic 

 

self_vertices = self.vertices() 

other_vertices = other.vertices() 

if edge_labels or self.has_multiple_edges(): 

if edge_labels and sorted(self.edge_labels()) != sorted(other.edge_labels()): 

return (False, None) if certificate else False 

else: 

G, partition, relabeling, G_edge_labels = graph_isom_equivalent_non_edge_labeled_graph(self, return_relabeling=True, ignore_edge_labels=(not edge_labels), return_edge_labels=True) 

self_vertices = sum(partition,[]) 

G2, partition2, relabeling2, G2_edge_labels = graph_isom_equivalent_non_edge_labeled_graph(other, return_relabeling=True, ignore_edge_labels=(not edge_labels), return_edge_labels=True) 

if [len(_) for _ in partition] != [len(_) for _ in partition2]: 

return (False, None) if certificate else False 

multilabel = (lambda e:e) if edge_labels else (lambda e:[[None, el[1]] for el in e]) 

if [multilabel(_) for _ in G_edge_labels] != [multilabel(_) for _ in G2_edge_labels]: 

return (False, None) if certificate else False 

partition2 = sum(partition2,[]) 

other_vertices = partition2 

else: 

G = self; partition = [self_vertices] 

G2 = other; partition2 = other_vertices 

G_to = {} 

for i in range(len(self_vertices)): 

G_to[self_vertices[i]] = i 

from sage.graphs.all import Graph, DiGraph 

DoDG = DiGraph if self._directed else Graph 

H = DoDG(len(self_vertices), implementation='c_graph', loops=G.allows_loops()) 

HB = H._backend 

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

u = G_to[u]; v = G_to[v] 

HB.add_edge(u,v,None,G._directed) 

G = HB.c_graph()[0] 

partition = [[G_to[v] for v in cell] for cell in partition] 

GC = G 

G2_to = {} 

for i in range(len(other_vertices)): 

G2_to[other_vertices[i]] = i 

H2 = DoDG(len(other_vertices), implementation='c_graph', loops=G2.allows_loops()) 

H2B = H2._backend 

for u,v in G2.edge_iterator(labels=False): 

u = G2_to[u]; v = G2_to[v] 

H2B.add_edge(u,v,None,G2._directed) 

G2 = H2B.c_graph()[0] 

partition2 = [G2_to[v] for v in partition2] 

GC2 = G2 

isom = isomorphic(GC, GC2, partition, partition2, (self._directed or self.has_loops()), 1) 

 

if not isom and certificate: 

return False, None 

elif not isom: 

return False 

elif not certificate: 

return True 

else: 

isom_trans = {} 

if edge_labels or self.has_multiple_edges(): 

relabeling2_inv = {} 

for x in relabeling2: 

relabeling2_inv[relabeling2[x]] = x 

for v in self.vertices(): 

isom_trans[v] = relabeling2_inv[other_vertices[isom[G_to[relabeling[v]]]]] 

else: 

for v in self.vertices(): 

isom_trans[v] = other_vertices[isom[G_to[v]]] 

return True, isom_trans 

 

def canonical_label(self, partition=None, certificate=False, verbosity=0, 

edge_labels=False, algorithm=None, return_graph=True): 

r""" 

Return the canonical graph. 

 

A canonical graph is the representative graph of an isomorphism 

class by some canonization function `c`. If `G` and `H` are graphs, 

then `G \cong c(G)`, and `c(G) == c(H)` if and only if `G \cong H`. 

 

See :wikipedia:`Graph_canonization`. 

 

INPUT: 

 

- ``partition`` -- if given, the canonical label with respect 

to this set partition will be computed. The default is the unit 

set partition. 

 

- ``certificate`` -- boolean (default: ``False``). When set to 

``True``, a dictionary mapping from the vertices of the (di)graph 

to its canonical label will also be returned. 

 

- ``edge_labels`` -- boolean (default: ``False``). When set to 

``True``, allows only permutations respecting edge labels. 

 

- ``algorithm`` -- a string (default: ``None``). The algorithm to use; 

currently available: 

 

* ``'bliss'``: use the optional package bliss 

(http://www.tcs.tkk.fi/Software/bliss/index.html); can not 

be combined with ``edge_labels`` 

* ``'sage'``: always use Sage's implementation. 

* ``None`` (default): use bliss when available and possible 

 

.. NOTE:: 

 

Make sure you always compare canonical forms obtained by the 

same algorithm. 

 

- ``return_graph`` -- boolean (default: ``True``). When set to 

``False``, returns the list of edges of the the canonical graph 

instead of the canonical graph; only available when ``'bliss'`` 

is explicitly set as algorithm. 

 

- ``verbosity`` -- deprecated, does nothing 

 

EXAMPLES: 

 

Canonization changes isomorphism to equality:: 

 

sage: g1 = graphs.GridGraph([2,3]) 

sage: g2 = Graph({1: [2, 4], 3: [2, 6], 5: [4, 2, 6]}) 

sage: g1 == g2 

False 

sage: g1.is_isomorphic(g2) 

True 

sage: g1.canonical_label() == g2.canonical_label() 

True 

 

We can get the relabeling used for canonization:: 

 

sage: g, c = g1.canonical_label(algorithm='sage', certificate=True) 

sage: g 

Grid Graph for [2, 3]: Graph on 6 vertices 

sage: c 

{(0, 0): 3, (0, 1): 4, (0, 2): 2, (1, 0): 0, (1, 1): 5, (1, 2): 1} 

 

Multigraphs and directed graphs work too:: 

 

sage: G = Graph(multiedges=True,sparse=True) 

sage: G.add_edge((0,1)) 

sage: G.add_edge((0,1)) 

sage: G.add_edge((0,1)) 

sage: G.canonical_label() 

Multi-graph on 2 vertices 

sage: Graph('A?', implementation='c_graph').canonical_label() 

Graph on 2 vertices 

 

sage: P = graphs.PetersenGraph() 

sage: DP = P.to_directed() 

sage: DP.canonical_label(algorithm='sage').adjacency_matrix() 

[0 0 0 0 0 0 0 1 1 1] 

[0 0 0 0 1 0 1 0 0 1] 

[0 0 0 1 0 0 1 0 1 0] 

[0 0 1 0 0 1 0 0 0 1] 

[0 1 0 0 0 1 0 0 1 0] 

[0 0 0 1 1 0 0 1 0 0] 

[0 1 1 0 0 0 0 1 0 0] 

[1 0 0 0 0 1 1 0 0 0] 

[1 0 1 0 1 0 0 0 0 0] 

[1 1 0 1 0 0 0 0 0 0] 

 

Edge labeled graphs:: 

 

sage: G = Graph(sparse=True) 

sage: G.add_edges( [(0,1,'a'),(1,2,'b'),(2,3,'c'),(3,4,'b'),(4,0,'a')] ) 

sage: G.canonical_label(edge_labels=True) 

Graph on 5 vertices 

sage: G.canonical_label(edge_labels=True, certificate=True) 

(Graph on 5 vertices, {0: 4, 1: 3, 2: 0, 3: 1, 4: 2}) 

 

Canonical forms can be computed by bliss as well. Different 

canonization algorithms give different graphs:: 

 

sage: g = Graph({'a': ['b'], 'c': ['d']}) 

sage: g_sage = g.canonical_label(algorithm='sage') 

sage: g_bliss = g.canonical_label(algorithm='bliss') # optional - bliss 

sage: g_sage.edges(labels=False) 

[(0, 3), (1, 2)] 

sage: g_bliss.edges(labels=False) # optional - bliss 

[(0, 1), (2, 3)] 

 

TESTS:: 

 

sage: G = Graph({'a': ['b']}) 

sage: G.canonical_label(algorithm='sage', certificate=True) 

(Graph on 2 vertices, {'a': 0, 'b': 1}) 

 

Check for immutable graphs (:trac:`16602`):: 

 

sage: G = Graph([[1, 2], [2, 3]], immutable=True) 

sage: C = G.canonical_label(); C 

Graph on 3 vertices 

sage: C.vertices() 

[0, 1, 2] 

 

Corner cases:: 

 

sage: g = Graph() 

sage: g.canonical_label(algorithm='sage') 

Graph on 0 vertices 

sage: g.canonical_label(algorithm='bliss') # optional - bliss 

Graph on 0 vertices 

sage: g = Graph({'x': []}) 

sage: g.canonical_label(algorithm='sage').vertices() 

[0] 

sage: g.canonical_label(algorithm='bliss').vertices() # optional - bliss 

[0] 

 

Check that the name is preserved with both algorithms:: 

 

sage: g = graphs.PathGraph(1) 

sage: g.canonical_label(algorithm='sage') 

Path graph: Graph on 1 vertex 

sage: g.canonical_label(algorithm='bliss') # optional - bliss 

Path graph: Graph on 1 vertex 

""" 

# Deprecation 

if verbosity != 0: 

deprecation(19517, "Verbosity-parameter is removed.") 

 

# Check parameter combinations 

if algorithm not in [None, 'sage', 'bliss']: 

raise ValueError("'algorithm' must be equal to 'bliss', 'sage', or None") 

if algorithm != 'bliss' and not return_graph: 

raise ValueError("return_graph=False can only be used with algorithm='bliss'") 

has_multiedges = self.has_multiple_edges() 

if has_multiedges and algorithm == 'bliss': # See trac #21704 

raise NotImplementedError("algorithm 'bliss' can not be used for graph with multiedges") 

if edge_labels and algorithm == 'bliss': 

raise NotImplementedError("algorithm 'bliss' can not be used when edge_labels=True") 

 

# Check bliss if explicitly requested, raise if not found. 

if algorithm == 'bliss': 

from sage.graphs.bliss import canonical_form 

 

# By default use bliss when possible 

if algorithm is None: 

algorithm = 'sage' 

if not has_multiedges and not edge_labels: 

try: 

from sage.graphs.bliss import canonical_form 

algorithm = 'bliss' 

except ImportError: 

pass 

 

if algorithm == 'bliss': 

if return_graph: 

vert_dict = canonical_form(self, partition, certificate=True)[1] 

if not certificate: 

return self.relabel(vert_dict, inplace=False) 

return (self.relabel(vert_dict, inplace=False), vert_dict) 

return canonical_form(self, partition, return_graph, certificate) 

 

# algorithm == 'sage': 

from sage.groups.perm_gps.partn_ref.refinement_graphs import search_tree 

 

dig = (self.has_loops() or self._directed) 

if partition is None: 

partition = [self.vertices()] 

if edge_labels or self.has_multiple_edges(): 

G, partition, relabeling = graph_isom_equivalent_non_edge_labeled_graph(self, partition, return_relabeling=True) 

G_vertices = sum(partition, []) 

G_to = {} 

for i in range(len(G_vertices)): 

G_to[G_vertices[i]] = i 

from sage.graphs.all import Graph, DiGraph 

DoDG = DiGraph if self._directed else Graph 

H = DoDG(len(G_vertices), implementation='c_graph', loops=G.allows_loops()) 

HB = H._backend 

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

u = G_to[u]; v = G_to[v] 

HB.add_edge(u,v,None,G._directed) 

GC = HB.c_graph()[0] 

partition = [[G_to[v] for v in cell] for cell in partition] 

a,b,c = search_tree(GC, partition, certificate=True, dig=dig) 

# c is a permutation to the canonical label of G, which depends only on isomorphism class of self. 

H = copy(self) 

c_new = {} 

for v in self.vertices(): 

c_new[v] = c[G_to[relabeling[v]]] 

H.relabel(c_new) 

if certificate: 

return H, c_new 

else: 

return H 

G_vertices = sum(partition, []) 

G_to = {} 

for i in range(len(G_vertices)): 

G_to[G_vertices[i]] = i 

from sage.graphs.all import Graph, DiGraph 

DoDG = DiGraph if self._directed else Graph 

H = DoDG(len(G_vertices), implementation='c_graph', loops=self.allows_loops()) 

HB = H._backend 

for u,v in self.edge_iterator(labels=False): 

u = G_to[u]; v = G_to[v] 

HB.add_edge(u, v, None, self._directed) 

GC = HB.c_graph()[0] 

partition = [[G_to[v] for v in cell] for cell in partition] 

a,b,c = search_tree(GC, partition, certificate=True, dig=dig) 

H = copy(self) 

c_new = {} 

for v in G_to: 

c_new[v] = c[G_to[v]] 

H.relabel(c_new) 

if certificate: 

return H, c_new 

else: 

return H 

 

def is_cayley(self, return_group = False, mapping = False, 

generators = False, allow_disconnected = False): 

r""" 

Check whether the graph is a Cayley graph. 

 

If none of the parameters are ``True``, return a boolean indicating 

whether the graph is a Cayley graph. Otherwise, return a tuple 

containing said boolean and the requested data. If the graph is not 

a Cayley graph, each of the data will be ``None``. 

 

The empty graph is defined to be not a Cayley graph. 

 

.. NOTE:: 

 

For this routine to work on all graphs, the optional packages 

``gap_packages`` and ``database_gap`` need to be installed: to do 

so, it is enough to run ``sage -i gap_packages database_gap``. 

 

INPUT: 

 

- ``return_group`` (boolean; ``False``) -- If True, return a group for 

which the graph is a Cayley graph. 

 

- ``mapping`` (boolean; ``False``) -- If True, return a mapping from 

vertices to group elements. 

 

- ``generators`` (boolean; ``False``) -- If True, return the generating 

set of the Cayley graph. 

 

- ``allow_disconnected`` (boolean; ``False``) -- If True, disconnected 

graphs are considered Cayley if they can be obtained from the Cayley 

construction with a generating set that does not generate the group. 

 

ALGORITHM: 

 

For connected graphs, find a regular subgroup of the automorphism 

group. For disconnected graphs, check that the graph is 

vertex-transitive and perform the check on one of its connected 

components. If a simple graph has density over 1/2, perform the check 

on its complement as its disconnectedness may increase performance. 

 

EXAMPLES: 

 

A Petersen Graph is not a Cayley graph:: 

 

sage: g = graphs.PetersenGraph() 

sage: g.is_cayley() 

False 

 

A Cayley digraph is a Cayley graph:: 

 

sage: C7 = groups.permutation.Cyclic(7) 

sage: S = [(1,2,3,4,5,6,7), (1,3,5,7,2,4,6), (1,5,2,6,3,7,4)] 

sage: d = C7.cayley_graph(generators=S) 

sage: d.is_cayley() 

True 

 

Graphs with loops and multiedges will have identity and repeated 

elements, respectively, among the generators:: 

 

sage: g = Graph(graphs.PaleyGraph(9), loops=True, multiedges=True) 

sage: g.add_edges([(u, u) for u in g]) 

sage: g.add_edges([(u, u+1) for u in g]) 

sage: _, S = g.is_cayley(generators=True) 

sage: S # random 

[(), 

(0,2,1)(a,a + 2,a + 1)(2*a,2*a + 2,2*a + 1), 

(0,2,1)(a,a + 2,a + 1)(2*a,2*a + 2,2*a + 1), 

(0,1,2)(a,a + 1,a + 2)(2*a,2*a + 1,2*a + 2), 

(0,1,2)(a,a + 1,a + 2)(2*a,2*a + 1,2*a + 2), 

(0,2*a + 2,a + 1)(1,2*a,a + 2)(2,2*a + 1,a), 

(0,a + 1,2*a + 2)(1,a + 2,2*a)(2,a,2*a + 1)] 

 

TESTS: 

 

Cayley graphs can be reconstructed from the group and generating set:: 

 

sage: g = graphs.PaleyGraph(9) 

sage: _, G, S = g.is_cayley(return_group=True, generators=True) 

sage: Graph(G.cayley_graph(generators=S)).is_isomorphic(g) 

True 

 

A disconnected graphs may also be a Cayley graph:: 

 

sage: g = graphs.PaleyGraph(9) 

sage: h = g.disjoint_union(g) 

sage: h = h.disjoint_union(h) 

sage: h = h.disjoint_union(g) 

sage: _, G, d, S = h.is_cayley(return_group=True, mapping=True, generators=True, allow_disconnected=True) 

sage: all(set(d[u] for u in h.neighbors(v)) == set(d[v]*x for x in S) for v in h) 

True 

 

The method also works efficiently with dense simple graphs:: 

 

sage: graphs.CompleteBipartiteGraph(50, 50).is_cayley() 

True 

 

TESTS:: 

 

sage: graphs.EmptyGraph().is_cayley() 

False 

sage: graphs.EmptyGraph().is_cayley(return_group = True, 

....: mapping = False, 

....: generators = True) 

(False, False, False) 

""" 

if self.order() == 0: 

n = return_group + mapping + generators 

if n == 0: 

return False 

return tuple([False] * (n+1)) 

 

compute_map = mapping or generators 

certificate = return_group or compute_map 

c, G, map, genset = False, None, None, None 

if not self.is_connected(): 

if allow_disconnected and self.is_vertex_transitive(): 

C = self.connected_components_subgraphs() 

if certificate: 

c, CG = C[0].is_cayley(return_group = True) 

if c: 

from sage.groups.perm_gps.permgroup import PermutationGroup 

I = [C[0].is_isomorphic(g, certificate=True)[1] for g in C] 

# gens generate the direct product of CG and a cyclic group 

gens = [sum([[tuple([M[x] for x in p]) 

for p in h.cycle_tuples()] for M in I], []) 

for h in CG.gens()] + \ 

[[tuple([M[v] for M in I]) 

for v in C[0].vertices()]] 

G = PermutationGroup(gens, domain = self.vertices()) 

else: 

c = C[0].is_cayley(return_group = False) 

elif not self.allows_loops() and not self.allows_multiple_edges() and \ 

self.density() > Rational(1)/Rational(2): 

if certificate: 

c, G = self.complement().is_cayley(return_group = True, 

allow_disconnected = True) 

else: 

c = self.complement().is_cayley(return_group = False, 

allow_disconnected = True) 

else: 

A = self.automorphism_group() 

if certificate: 

G = A.has_regular_subgroup(return_group = True) 

c = G is not None 

else: 

c = A.has_regular_subgroup(return_group = False) 

if c and compute_map: 

v = next(self.vertex_iterator()) 

map = {(f**-1)(v): f for f in G} 

if generators: 

# self.(out_)neighbors ignores multiedges, 

# so we use edge_iterator instead 

adj = [y if v == x else x 

for x, y, z in self.edge_iterator(v)] 

genset = [map[u] for u in adj] 

if certificate: 

out = [c] 

if return_group: 

out.append(G) 

if mapping: 

out.append(map) 

if generators: 

out.append(genset) 

return tuple(out) 

else: 

return c 

 

def is_self_complementary(self): 

r""" 

Check whether the graph is self-complementary. 

 

A (di)graph is self-complementary if it is isomorphic to its (di)graph 

complement. For instance, the path graph `P_4` and the cycle graph `C_5` 

are self-complementary. 

 

.. SEEALSO:: 

 

- :wikipedia:`Self-complementary_graph` 

- :oeis:`A000171` for the numbers of self-complementary graphs of order `n` 

- :oeis:`A003086` for the numbers of self-complementary digraphs of order `n`. 

 

EXAMPLES: 

 

The only self-complementary path graph is `P_4`:: 

 

sage: graphs.PathGraph(4).is_self_complementary() 

True 

sage: graphs.PathGraph(5).is_self_complementary() 

False 

 

The only self-complementary directed path is `P_2`:: 

 

sage: digraphs.Path(2).is_self_complementary() 

True 

sage: digraphs.Path(3).is_self_complementary() 

False 

 

Every Paley graph is self-complementary:: 

 

sage: G = graphs.PaleyGraph(9) 

sage: G.is_self_complementary() 

True 

 

TESTS: 

 

Trivial graphs and digraphs:: 

 

sage: Graph(0).is_self_complementary() 

True 

sage: Graph(1).is_self_complementary() 

True 

sage: DiGraph(0).is_self_complementary() 

True 

sage: DiGraph(1).is_self_complementary() 

True 

 

Graph with the right number of edges that is not self-complementary:: 

 

sage: G = graphs.CompleteGraph(6) 

sage: G.add_path([0, 6, 7, 8]) 

sage: G.size() == G.order() * (G.order() - 1) // 4 

True 

sage: G.is_self_complementary() 

False 

 

The (di)graph must be simple:: 

 

sage: Graph(loops=True, multiedges=True).is_self_complementary() 

Traceback (most recent call last): 

... 

ValueError: This method is not known to work on graphs with 

multiedges/loops. Perhaps this method can be updated to handle them, 

but in the meantime if you want to use it please disallow 

multiedges/loops using allow_multiple_edges() and allow_loops(). 

sage: DiGraph(loops=True, multiedges=True).is_self_complementary() 

Traceback (most recent call last): 

... 

ValueError: This method is not known to work on graphs with 

multiedges/loops. Perhaps this method can be updated to handle them, 

but in the meantime if you want to use it please disallow 

multiedges/loops using allow_multiple_edges() and allow_loops(). 

""" 

self._scream_if_not_simple() 

 

if self.order() < 2: 

return True 

 

# A self-complementary graph has half the number of possible edges 

b = self.order() * (self.order() - 1) / (1 if self.is_directed() else 2) 

if b % 2 or b != 2 * self.size(): 

return False 

 

# A self-complementary (di)graph must be connected 

if not self.is_connected(): 

return False 

 

# A self-complementary graph of order >= 4 has diameter 2 or 3 

if not self.is_directed() and self.diameter() > 3: 

return False 

 

return self.is_isomorphic(self.complement()) 

 

 

# Aliases to functions defined in other modules 

from sage.graphs.distances_all_pairs import distances_distribution 

from sage.graphs.base.boost_graph import dominator_tree 

from sage.graphs.base.static_sparse_graph import spectral_radius 

from sage.graphs.line_graph import line_graph 

 

 

def tachyon_vertex_plot(g, bgcolor=(1,1,1), 

vertex_colors=None, 

vertex_size=0.06, 

pos3d=None, 

**kwds): 

""" 

Helper function for plotting graphs in 3d with Tachyon. Returns a 

plot containing only the vertices, as well as the 3d position 

dictionary used for the plot. 

 

INPUT: 

- `pos3d` - a 3D layout of the vertices 

- various rendering options 

 

EXAMPLES:: 

 

sage: G = graphs.TetrahedralGraph() 

sage: from sage.graphs.generic_graph import tachyon_vertex_plot 

sage: T,p = tachyon_vertex_plot(G, pos3d = G.layout(dim=3)) 

sage: type(T) 

<class 'sage.plot.plot3d.tachyon.Tachyon'> 

sage: type(p) 

<... 'dict'> 

""" 

assert pos3d is not None 

from math import sqrt 

from sage.plot.plot3d.tachyon import Tachyon 

 

c = [0,0,0] 

r = [] 

verts = g.vertices() 

 

if vertex_colors is None: 

vertex_colors = { (1,0,0) : verts } 

try: 

for v in verts: 

c[0] += pos3d[v][0] 

c[1] += pos3d[v][1] 

c[2] += pos3d[v][2] 

except KeyError: 

raise KeyError("Oops! You haven't specified positions for all the vertices.") 

 

order = g.order() 

c[0] = c[0]/order 

c[1] = c[1]/order 

c[2] = c[2]/order 

for v in verts: 

pos3d[v][0] = pos3d[v][0] - c[0] 

pos3d[v][1] = pos3d[v][1] - c[1] 

pos3d[v][2] = pos3d[v][2] - c[2] 

r.append(abs(sqrt((pos3d[v][0])**2 + (pos3d[v][1])**2 + (pos3d[v][2])**2))) 

r = max(r) 

if r == 0: 

r = 1 

for v in verts: 

pos3d[v][0] = pos3d[v][0]/r 

pos3d[v][1] = pos3d[v][1]/r 

pos3d[v][2] = pos3d[v][2]/r 

TT = Tachyon(camera_center=(1.4,1.4,1.4), antialiasing=13, **kwds) 

TT.light((4,3,2), 0.02, (1,1,1)) 

TT.texture('bg', ambient=1, diffuse=1, specular=0, opacity=1.0, color=bgcolor) 

TT.plane((-1.6,-1.6,-1.6), (1.6,1.6,1.6), 'bg') 

 

i = 0 

for color in vertex_colors: 

i += 1 

TT.texture('node_color_%d'%i, ambient=0.1, diffuse=0.9, 

specular=0.03, opacity=1.0, color=color) 

for v in vertex_colors[color]: 

TT.sphere((pos3d[v][0],pos3d[v][1],pos3d[v][2]), vertex_size, 'node_color_%d'%i) 

 

return TT, pos3d 

 

def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_label=None, return_relabeling=False, return_edge_labels=False, inplace=False, ignore_edge_labels=False): 

""" 

Helper function for canonical labeling of edge labeled (di)graphs. 

 

Translates to a bipartite incidence-structure type graph 

appropriate for computing canonical labels of edge labeled and/or multi-edge graphs. 

Note that this is actually computationally equivalent to 

implementing a change on an inner loop of the main algorithm- 

namely making the refinement procedure sort for each label. 

 

If the graph is a multigraph, it is translated to a non-multigraph, 

where each edge is labeled with a dictionary describing how many 

edges of each label were originally there. Then in either case we 

are working on a graph without multiple edges. At this point, we 

create another (bipartite) graph, whose left vertices are the 

original vertices of the graph, and whose right vertices represent 

the edges. We partition the left vertices as they were originally, 

and the right vertices by common labels: only automorphisms taking 

edges to like-labeled edges are allowed, and this additional 

partition information enforces this on the bipartite graph. 

 

INPUT: 

 

- ``g`` -- Graph or DiGraph 

- ``partition`` -- (default:None) if given, the partition of the vertices is as well relabeled 

- ``standard_label`` -- (default:None) the standard label is not considered to be changed 

- ``return_relabeling`` -- (default: False) if True, a dictionary containing the relabeling is returned 

- ``return_edge_labels`` -- (default: False) if True, the different edge_labels are returned (useful if inplace is True) 

- ``inplace`` -- (default:False) if True, g is modified, otherwise the result is returned. Note that attributes of g are *not* copied for speed issues, only edges and vertices. 

 

OUTPUT: 

 

- if not inplace: the unlabeled graph without multiple edges 

- the partition of the vertices 

- if return_relabeling: a dictionary containing the relabeling 

- if return_edge_labels: the list of (former) edge labels is returned 

 

EXAMPLES:: 

 

sage: from sage.graphs.generic_graph import graph_isom_equivalent_non_edge_labeled_graph 

 

sage: G = Graph(multiedges=True,sparse=True) 

sage: G.add_edges( (0,1,i) for i in range(10) ) 

sage: G.add_edge(1,2,'string') 

sage: G.add_edge(2,123) 

sage: g = graph_isom_equivalent_non_edge_labeled_graph(G, partition=[[0,123],[1,2]]); g 

[Graph on 6 vertices, [[0, 3], [1, 2], [4], [5]]] 

 

sage: g = graph_isom_equivalent_non_edge_labeled_graph(G); g 

[Graph on 6 vertices, [[0, 1, 2, 3], [4], [5]]] 

sage: g[0].edges() 

[(0, 4, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 5, None)] 

 

sage: g = graph_isom_equivalent_non_edge_labeled_graph(G,standard_label='string',return_edge_labels=True); g 

[Graph on 6 vertices, [[0, 1, 2, 3], [5], [4]], [[[None, 1]], [[0, 1], [1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1], [8, 1], [9, 1]], [['string', 1]]]] 

sage: g[0].edges() 

[(0, 4, None), (1, 2, None), (1, 4, None), (2, 5, None), (3, 5, None)] 

 

sage: graph_isom_equivalent_non_edge_labeled_graph(G,inplace=True) 

[[[0, 1, 2, 3], [4], [5]]] 

sage: G.edges() 

[(0, 4, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 5, None)] 

 

TESTS: 

 

Ensure that :trac:`14108` is fixed:: 

 

sage: G=DiGraph({0:[0,0,0],1:[1,1,1]}) 

sage: H=DiGraph({0:[0,0,0,0],1:[1,1]}) 

sage: G.is_isomorphic(H) 

False 

sage: H=DiGraph({0:[0,0,0,0],1:[1,1]}) 

sage: HH=DiGraph({0:[0,0,0],1:[1,1,1]}) 

sage: H.is_isomorphic(HH) 

False 

sage: H.is_isomorphic(HH, edge_labels=True) 

False 

 

""" 

from sage.graphs.all import Graph, DiGraph 

 

g_has_multiple_edges = g.has_multiple_edges() 

 

if g_has_multiple_edges: 

if g._directed: 

G = DiGraph(loops=g.allows_loops(),sparse=True) 

edge_iter = g._backend.iterator_in_edges(g,True) 

else: 

G = Graph(loops=g.allows_loops(),sparse=True) 

edge_iter = g._backend.iterator_edges(g,True) 

for u,v,l in edge_iter: 

if ignore_edge_labels: 

l = None 

if not G.has_edge(u,v): 

G.add_edge(u,v,[[l,1]]) 

else: 

label_list = copy( G.edge_label(u,v) ) 

seen_label = False 

for i in range(len(label_list)): 

if label_list[i][0] == l: 

label_list[i][1] += 1 

G.set_edge_label(u,v,label_list) 

seen_label = True 

break 

if not seen_label: 

label_list.append([l,1]) 

label_list.sort() 

G.set_edge_label(u,v,label_list) 

if G.order() < g.order(): 

G.add_vertices(g) 

if inplace: 

g._backend = G._backend 

elif not inplace: 

G = copy( g ) 

else: 

G = g 

 

G_order = G.order() 

V = list(range(G_order)) 

if G.vertices() != V: 

relabel_dict = G.relabel(return_map=True) 

else: 

relabel_dict = dict( (i,i) for i in range(G_order) ) 

if partition is None: 

partition = [V] 

else: 

partition = [ [ relabel_dict[i] for i in part ] for part in partition ] 

 

if G._directed: 

edge_iter = G._backend.iterator_in_edges(G,True) 

else: 

edge_iter = G._backend.iterator_edges(G,True) 

 

edges = [ edge for edge in edge_iter ] 

edge_labels = sorted([ label for v1,v2,label in edges if not label == standard_label]) 

i = 1 

 

# edge_labels is sorted. We now remove values which are not unique 

while i < len(edge_labels): 

if edge_labels[i] == edge_labels[i-1]: 

edge_labels.pop(i) 

else: 

i += 1 

i = G_order 

edge_partition = [(el,[]) for el in edge_labels] 

 

if g_has_multiple_edges: standard_label = [[standard_label,1]] 

 

for u,v,l in edges: 

if not l == standard_label: 

for el, part in edge_partition: 

if el == l: 

part.append(i) 

break 

 

G._backend.add_edge(u,i,None,True) 

G._backend.add_edge(i,v,None,True) 

G.delete_edge(u,v) 

i += 1 

elif standard_label is not None: 

G._backend.set_edge_label(u,v,None,True) 

 

# Should we pay attention to edge labels ? 

if ignore_edge_labels: 

 

# If there are no multiple edges, we can just say that all edges are 

# equivalent to each other without any further consideration. 

if not g_has_multiple_edges: 

edge_partition = [el[1] for el in sorted(edge_partition)] 

edge_partition = [sum(edge_partition,[])] 

 

# An edge between u and v with label l and multiplicity k being encoded 

# as an uv edge with label [l,k], we must not assume that an edge with 

# multiplicity 2 is equivalent to a simple edge ! 

# Hence, we still distinguish edges with different multiplicity 

if g_has_multiple_edges: 

 

# Compute the multiplicity the label 

multiplicity = lambda x : sum((y[1] for y in x)) 

 

# Sort the edge according to their multiplicity 

edge_partition = sorted([[multiplicity(el),part] for el, part in sorted(edge_partition)]) 

 

# Gather together the edges with same multiplicity 

i = 1 

while i < len(edge_partition): 

if edge_partition[i][0] == edge_partition[i-1][0]: 

edge_partition[i-1][1].extend(edge_partition[i][1]) 

edge_partition.pop(i) 

else: 

i += 1 

 

# now edge_partition has shape [[multiplicity, list_of_edges], 

# [multiplicity, liste of edges], ...], and we can flatted it to 

# [list of edges, list of edges, ...] 

edge_partition = [el[1] for el in sorted(edge_partition)] 

 

# Now the edges are partitionned according to the multiplicity they 

# represent, and edge labels are forgotten. 

 

else: 

edge_partition = [el[1] for el in sorted(edge_partition)] 

 

new_partition = [ part for part in partition + edge_partition if not part == [] ] 

 

return_data = [] 

if not inplace: 

return_data.append( G ) 

return_data.append( new_partition ) 

if return_relabeling: 

return_data.append( relabel_dict ) 

if return_edge_labels: 

return_data.append( edge_labels ) 

return return_data