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

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

r""" 

Finite State Machines, Automata, Transducers 

 

This module adds support for finite state machines, automata and 

transducers. 

 

For creating automata and transducers you can use classes 

 

- :class:`Automaton` and :class:`Transducer` 

(or the more general class :class:`FiniteStateMachine`) 

 

or the generators 

 

- :class:`automata <sage.combinat.finite_state_machine_generators.AutomatonGenerators>` and 

:class:`transducers <sage.combinat.finite_state_machine_generators.TransducerGenerators>` 

 

which contain :doc:`preconstructed and commonly used automata and transducers 

<finite_state_machine_generators>`. See also the 

:ref:`examples <finite_state_machine_examples>` below. 

 

 

Contents 

======== 

 

:class:`FiniteStateMachine` and derived classes :class:`Transducer` and :class:`Automaton` 

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

 

 

Accessing parts of a finite state machine 

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

 

:meth:`~FiniteStateMachine.state` | Get a state by its label 

:meth:`~FiniteStateMachine.states` | List of states 

:meth:`~FiniteStateMachine.iter_states` | Iterator over the states 

:meth:`~FiniteStateMachine.initial_states` | List of initial states 

:meth:`~FiniteStateMachine.iter_initial_states` | Iterator over initial states 

:meth:`~FiniteStateMachine.final_states` | List of final states 

:meth:`~FiniteStateMachine.iter_final_states` | Iterator over final states 

:meth:`~FiniteStateMachine.transition` | Get a transition by its states and labels 

:meth:`~FiniteStateMachine.transitions` | List of transitions 

:meth:`~FiniteStateMachine.iter_transitions` | Iterator over the transitions 

:meth:`~FiniteStateMachine.predecessors` | List of predecessors of a state 

:meth:`~FiniteStateMachine.induced_sub_finite_state_machine` | Induced sub-machine 

:meth:`~FiniteStateMachine.accessible_components` | Accessible components 

:meth:`~FiniteStateMachine.coaccessible_components` | Coaccessible components 

:meth:`~FiniteStateMachine.final_components` | Final components (connected components which cannot be left again) 

 

 

(Modified) Copies 

^^^^^^^^^^^^^^^^^ 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~FiniteStateMachine.empty_copy` | Returns an empty deep copy 

:meth:`~FiniteStateMachine.deepcopy` | Returns a deep copy 

:meth:`~FiniteStateMachine.relabeled` | Returns a relabeled deep copy 

:meth:`Automaton.with_output` | Extends an automaton to a transducer 

 

 

Manipulation 

^^^^^^^^^^^^ 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~FiniteStateMachine.add_state` | Add a state 

:meth:`~FiniteStateMachine.add_states` | Add states 

:meth:`~FiniteStateMachine.delete_state` | Delete a state 

:meth:`~FiniteStateMachine.add_transition` | Add a transition 

:meth:`~FiniteStateMachine.add_transitions_from_function` | Add transitions 

:attr:`~FiniteStateMachine.input_alphabet` | Input alphabet 

:attr:`~FiniteStateMachine.output_alphabet` | Output alphabet 

:attr:`~FiniteStateMachine.on_duplicate_transition` | Hook for handling duplicate transitions 

:meth:`~FiniteStateMachine.add_from_transition_function` | Add transitions by a transition function 

:meth:`~FiniteStateMachine.delete_transition` | Delete a transition 

:meth:`~FiniteStateMachine.remove_epsilon_transitions` | Remove epsilon transitions (not implemented) 

:meth:`~FiniteStateMachine.split_transitions` | Split transitions with input words of length ``> 1`` 

:meth:`~FiniteStateMachine.determine_alphabets` | Determine input and output alphabets 

:meth:`~FiniteStateMachine.determine_input_alphabet` | Determine input alphabet 

:meth:`~FiniteStateMachine.determine_output_alphabet` | Determine output alphabet 

:meth:`~FiniteStateMachine.construct_final_word_out` | Construct final output by implicitly reading trailing letters; cf. :meth:`~FiniteStateMachine.with_final_word_out` 

 

 

Properties 

^^^^^^^^^^ 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~FiniteStateMachine.has_state` | Checks for a state 

:meth:`~FiniteStateMachine.has_initial_state` | Checks for an initial state 

:meth:`~FiniteStateMachine.has_initial_states` | Checks for initial states 

:meth:`~FiniteStateMachine.has_final_state` | Checks for an final state 

:meth:`~FiniteStateMachine.has_final_states` | Checks for final states 

:meth:`~FiniteStateMachine.has_transition` | Checks for a transition 

:meth:`~FiniteStateMachine.is_deterministic` | Checks for a deterministic machine 

:meth:`~FiniteStateMachine.is_complete` | Checks for a complete machine 

:meth:`~FiniteStateMachine.is_connected` | Checks for a connected machine 

:meth:`Automaton.is_equivalent` | Checks for equivalent automata 

:meth:`~FiniteStateMachine.is_Markov_chain` | Checks for a Markov chain 

:meth:`~FiniteStateMachine.is_monochromatic` | Checks whether the colors of all states are equal 

:meth:`~FiniteStateMachine.number_of_words` | Determine the number of successful paths 

:meth:`~FiniteStateMachine.asymptotic_moments` | Main terms of expectation and variance of sums of labels 

:meth:`~FiniteStateMachine.moments_waiting_time` | Moments of the waiting time for first true output 

:meth:`~FiniteStateMachine.epsilon_successors` | Epsilon successors of a state 

:meth:`Automaton.shannon_parry_markov_chain` | Compute Markov chain with Parry measure 

 

Operations 

^^^^^^^^^^ 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~FiniteStateMachine.disjoint_union` | Disjoint union 

:meth:`~FiniteStateMachine.concatenation` | Concatenation 

:meth:`~FiniteStateMachine.kleene_star` | Kleene star 

:meth:`Automaton.complement` | Complement of an automaton 

:meth:`Automaton.intersection` | Intersection of automata 

:meth:`Transducer.intersection` | Intersection of transducers 

:meth:`Transducer.cartesian_product` | Cartesian product of a transducer with another finite state machine 

:meth:`~FiniteStateMachine.product_FiniteStateMachine` | Product of finite state machines 

:meth:`~FiniteStateMachine.composition` | Composition (output of other is input of self) 

:meth:`~FiniteStateMachine.__call__` | Composition with other finite state machine 

:meth:`~FiniteStateMachine.input_projection` | Input projection (output is deleted) 

:meth:`~FiniteStateMachine.output_projection` | Output projection (old output is new input) 

:meth:`~FiniteStateMachine.projection` | Input or output projection 

:meth:`~FiniteStateMachine.transposition` | Transposition (all transitions are reversed) 

:meth:`~FiniteStateMachine.with_final_word_out` | Machine with final output constructed by implicitly reading trailing letters, cf. :meth:`~FiniteStateMachine.construct_final_word_out` for inplace version 

:meth:`Automaton.determinisation` | Determinisation of an automaton 

:meth:`~FiniteStateMachine.completion` | Completion of a finite state machine 

:meth:`~FiniteStateMachine.process` | Process input 

:meth:`~FiniteStateMachine.__call__` | Process input with shortened output 

:meth:`Automaton.process` | Process input of an automaton (output differs from general case) 

:meth:`Transducer.process` | Process input of a transducer (output differs from general case) 

:meth:`~FiniteStateMachine.iter_process` | Return process iterator 

:meth:`~FiniteStateMachine.language` | Return all possible output words 

:meth:`Automaton.language` | Return all possible accepted words 

 

 

Simplification 

^^^^^^^^^^^^^^ 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

 

:meth:`~FiniteStateMachine.prepone_output` | Prepone output where possible 

:meth:`~FiniteStateMachine.equivalence_classes` | List of equivalent states 

:meth:`~FiniteStateMachine.quotient` | Quotient with respect to equivalence classes 

:meth:`~FiniteStateMachine.merged_transitions` | Merge transitions while adding input 

:meth:`~FiniteStateMachine.markov_chain_simplification` | Simplification of a Markov chain 

:meth:`Automaton.minimization` | Minimization of an automaton 

:meth:`Transducer.simplification` | Simplification of a transducer 

 

 

Conversion 

^^^^^^^^^^ 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~FiniteStateMachine.adjacency_matrix` | (Weighted) adjacency :class:`matrix <sage.matrix.constructor.MatrixFactory>` 

:meth:`~FiniteStateMachine.graph` | Underlying :class:`DiGraph` 

:meth:`~FiniteStateMachine.plot` | Plot 

 

 

LaTeX output 

++++++++++++ 

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~FiniteStateMachine.latex_options` | Set options 

:meth:`~FiniteStateMachine.set_coordinates` | Set coordinates of the states 

:meth:`~FiniteStateMachine.default_format_transition_label` | Default formatting of words in transition labels 

:meth:`~FiniteStateMachine.format_letter_negative` | Format negative numbers as overlined number 

:meth:`~FiniteStateMachine.format_transition_label_reversed` | Format words in transition labels in reversed order 

 

.. SEEALSO:: 

 

:ref:`finite_state_machine_LaTeX_output` 

 

 

:class:`FSMState` 

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

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:attr:`~FSMState.final_word_out` | Final output of a state 

:attr:`~FSMState.is_final` | Describes whether a state is final or not 

:attr:`~FSMState.is_initial` | Describes whether a state is initial or not 

:attr:`~FSMState.initial_probability` | Probability of starting in this state as part of a Markov chain 

:meth:`~FSMState.label` | Label of a state 

:meth:`~FSMState.relabeled` | Returns a relabeled deep copy of a state 

:meth:`~FSMState.fully_equal` | Checks whether two states are fully equal (including all attributes) 

 

 

:class:`FSMTransition` 

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

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:attr:`~FSMTransition.from_state` | State in which transition starts 

:attr:`~FSMTransition.to_state` | State in which transition ends 

:attr:`~FSMTransition.word_in` | Input word of the transition 

:attr:`~FSMTransition.word_out` | Output word of the transition 

:meth:`~FSMTransition.deepcopy` | Returns a deep copy of the transition 

 

 

:class:`FSMProcessIterator` 

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

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:meth:`~FSMProcessIterator.next` | Makes one step in processing the input tape 

:meth:`~FSMProcessIterator.preview_word` | Reads a word from the input tape 

:meth:`~FSMProcessIterator.result` | Returns the finished branches during process 

 

 

Helper Functions 

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

 

.. csv-table:: 

:class: contentstable 

:widths: 30, 70 

:delim: | 

 

:func:`equal` | Checks whether all elements of ``iterator`` are equal 

:func:`full_group_by` | Group iterable by values of some key 

:func:`startswith` | Determine whether list starts with the given prefix 

:func:`FSMLetterSymbol` | Returns a string associated to the input letter 

:func:`FSMWordSymbol` | Returns a string associated to a word 

:func:`is_FSMState` | Tests whether an object inherits from :class:`FSMState` 

:func:`is_FSMTransition` | Tests whether an object inherits from :class:`FSMTransition` 

:func:`is_FiniteStateMachine` | Tests whether an object inherits from :class:`FiniteStateMachine` 

:func:`duplicate_transition_ignore` | Default function for handling duplicate transitions 

:func:`duplicate_transition_raise_error` | Raise error when inserting a duplicate transition 

:func:`duplicate_transition_add_input` | Add input when inserting a duplicate transition 

 

 

.. _finite_state_machine_examples: 

 

Examples 

======== 

 

We start with a general :class:`FiniteStateMachine`. Later there will 

be also an :class:`Automaton` and a :class:`Transducer`. 

 

A simple finite state machine 

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

 

We can easily create a finite state machine by 

 

:: 

 

sage: fsm = FiniteStateMachine() 

sage: fsm 

Empty finite state machine 

 

By default this is the empty finite state machine, so not very 

interesting. Let's create and add some states and transitions:: 

 

sage: day = fsm.add_state('day') 

sage: night = fsm.add_state('night') 

sage: sunrise = fsm.add_transition(night, day) 

sage: sunset = fsm.add_transition(day, night) 

 

Let us look at ``sunset`` more closely:: 

 

sage: sunset 

Transition from 'day' to 'night': -|- 

 

Note that could also have created and added the transitions directly 

by:: 

 

sage: fsm.add_transition('day', 'night') 

Transition from 'day' to 'night': -|- 

 

This would have had added the states automatically, since they are 

present in the transitions. 

 

Anyhow, we got the following finite state machine:: 

 

sage: fsm 

Finite state machine with 2 states 

 

We can also obtain the underlying :class:`directed graph <DiGraph>` by 

 

:: 

 

sage: fsm.graph() 

Looped multi-digraph on 2 vertices 

 

To visualize a finite state machine, we can use 

:func:`~sage.misc.latex.latex` and run the result through LaTeX, 

see the section on :ref:`finite_state_machine_LaTeX_output` 

below. 

 

Alternatively, we could have created the finite state machine above 

simply by 

 

:: 

 

sage: FiniteStateMachine([('night', 'day'), ('day', 'night')]) 

Finite state machine with 2 states 

 

See :class:`FiniteStateMachine` for a lot of possibilities to create 

finite state machines. 

 

.. _finite_state_machine_recognizing_NAFs_example: 

 

A simple Automaton (recognizing NAFs) 

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

 

We want to build an automaton which recognizes non-adjacent forms 

(NAFs), i.e., sequences which have no adjacent non-zeros. 

We use `0`, `1`, and `-1` as digits:: 

 

sage: NAF = Automaton( 

....: {'A': [('A', 0), ('B', 1), ('B', -1)], 'B': [('A', 0)]}) 

sage: NAF.state('A').is_initial = True 

sage: NAF.state('A').is_final = True 

sage: NAF.state('B').is_final = True 

sage: NAF 

Automaton with 2 states 

 

Of course, we could have specified the initial and final states 

directly in the definition of ``NAF`` by ``initial_states=['A']`` and 

``final_states=['A', 'B']``. 

 

So let's test the automaton with some input:: 

 

sage: NAF([0]) 

True 

sage: NAF([0, 1]) 

True 

sage: NAF([1, -1]) 

False 

sage: NAF([0, -1, 0, 1]) 

True 

sage: NAF([0, -1, -1, -1, 0]) 

False 

sage: NAF([-1, 0, 0, 1, 1]) 

False 

 

Alternatively, we could call that by 

 

:: 

 

sage: NAF.process([0, -1, 0, 1]) 

(True, 'B') 

 

which gives additionally the state in which we arrived. 

 

We can also let an automaton act on a :doc:`word <words/words>`:: 

 

sage: W = Words([-1, 0, 1]); W 

Finite and infinite words over {-1, 0, 1} 

sage: w = W([1, 0, 1, 0, -1]); w 

word: 1,0,1,0,-1 

sage: NAF(w) 

True 

 

Recognizing NAFs via Automata Operations 

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

 

Alternatively, we can use automata operations to recognize NAFs; for 

simplicity, we only use the input alphabet ``[0, 1]``. On the one 

hand, we can construct such an automaton by forbidding the word 

``11``:: 

 

sage: forbidden = automata.ContainsWord([1, 1], input_alphabet=[0, 1]) 

sage: NAF_negative = forbidden.complement() 

sage: NAF_negative([1, 1, 0, 1]) 

False 

sage: NAF_negative([1, 0, 1, 0, 1]) 

True 

 

On the other hand, we can write this as a regular expression and 

translate that into automata operations:: 

 

sage: zero = automata.Word([0]) 

sage: one = automata.Word([1]) 

sage: epsilon = automata.EmptyWord(input_alphabet=[0, 1]) 

sage: NAF_positive = (zero + one*zero).kleene_star() * (epsilon + one) 

 

We check that the two approaches are equivalent:: 

 

sage: NAF_negative.is_equivalent(NAF_positive) 

True 

 

.. SEEALSO:: 

 

:meth:`~sage.combinat.finite_state_machine_generators.AutomatonGenerators.ContainsWord`, 

:meth:`~sage.combinat.finite_state_machine_generators.AutomatonGenerators.Word`, 

:meth:`~Automaton.complement`, 

:meth:`~FiniteStateMachine.kleene_star`, 

:meth:`~sage.combinat.finite_state_machine_generators.AutomatonGenerators.EmptyWord`, 

:meth:`~Automaton.is_equivalent`. 

 

.. _finite_state_machine_LaTeX_output: 

 

LaTeX output 

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

 

We can visualize a finite state machine by converting it to LaTeX by 

using the usual function :func:`~sage.misc.latex.latex`. Within LaTeX, 

TikZ is used for typesetting the graphics, see the 

:wikipedia:`PGF/TikZ`. 

 

:: 

 

sage: print(latex(NAF)) 

\begin{tikzpicture}[auto, initial text=, >=latex] 

\node[state, accepting, initial] (v0) at (3.000000, 0.000000) {$\text{\texttt{A}}$}; 

\node[state, accepting] (v1) at (-3.000000, 0.000000) {$\text{\texttt{B}}$}; 

\path[->] (v0) edge[loop above] node {$0$} (); 

\path[->] (v0.185.00) edge node[rotate=360.00, anchor=north] {$1, -1$} (v1.355.00); 

\path[->] (v1.5.00) edge node[rotate=0.00, anchor=south] {$0$} (v0.175.00); 

\end{tikzpicture} 

 

We can turn this into a graphical representation. 

 

:: 

 

sage: view(NAF) # not tested 

 

To actually see this, use the live documentation in the Sage notebook 

and execute the cells in this and the previous section. 

 

Several options can be set to customize the output, see 

:meth:`~FiniteStateMachine.latex_options` for details. In particular, 

we use :meth:`~FiniteStateMachine.format_letter_negative` to format 

`-1` as `\overline{1}`. 

 

:: 

 

sage: NAF.latex_options( 

....: coordinates={'A': (0, 0), 

....: 'B': (6, 0)}, 

....: initial_where={'A': 'below'}, 

....: format_letter=NAF.format_letter_negative, 

....: format_state_label=lambda x: 

....: r'\mathcal{%s}' % x.label() 

....: ) 

sage: print(latex(NAF)) 

\begin{tikzpicture}[auto, initial text=, >=latex] 

\node[state, accepting, initial, initial where=below] (v0) at (0.000000, 0.000000) {$\mathcal{A}$}; 

\node[state, accepting] (v1) at (6.000000, 0.000000) {$\mathcal{B}$}; 

\path[->] (v0) edge[loop above] node {$0$} (); 

\path[->] (v0.5.00) edge node[rotate=0.00, anchor=south] {$1, \overline{1}$} (v1.175.00); 

\path[->] (v1.185.00) edge node[rotate=360.00, anchor=north] {$0$} (v0.355.00); 

\end{tikzpicture} 

sage: view(NAF) # not tested 

 

To use the output of :func:`~sage.misc.latex.latex` in your own 

`\LaTeX` file, you have to include 

 

.. code-block:: latex 

 

\usepackage{tikz} 

\usetikzlibrary{automata} 

 

into the preamble of your file. 

 

A simple transducer (binary inverter) 

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

 

Let's build a simple transducer, which rewrites a binary word by 

iverting each bit:: 

 

sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, 

....: initial_states=['A'], final_states=['A']) 

 

We can look at the states and transitions:: 

 

sage: inverter.states() 

['A'] 

sage: for t in inverter.transitions(): 

....: print(t) 

Transition from 'A' to 'A': 0|1 

Transition from 'A' to 'A': 1|0 

 

Now we apply a word to it and see what the transducer does:: 

 

sage: inverter([0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1]) 

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

 

``True`` means, that we landed in a final state, that state is labeled 

``'A'``, and we also got an output. 

 

.. _finite_state_machine_division_by_3_example: 

 

 

Transducers and (in)finite Words 

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

 

A transducer can also act on everything iterable, in particular, on 

Sage's :doc:`words <words/words>`. 

 

:: 

 

sage: W = Words([0, 1]); W 

Finite and infinite words over {0, 1} 

 

Let us take the inverter from the previous section and feed some 

finite word into it:: 

 

sage: w = W([1, 1, 0, 1]); w 

word: 1101 

sage: inverter(w) 

word: 0010 

 

We see that the output is again a word (this is a consequence of 

calling :meth:`~Transducer.process` with ``automatic_output_type``). 

 

We can even input something infinite like an infinite word:: 

 

sage: tm = words.ThueMorseWord(); tm 

word: 0110100110010110100101100110100110010110... 

sage: inverter(tm) 

word: 1001011001101001011010011001011001101001... 

 

 

A transducer which performs division by `3` in binary 

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

 

Now we build a transducer, which divides a binary number by `3`. 

The labels of the states are the remainder of the division. 

The transition function is 

 

:: 

 

sage: def f(state_from, read): 

....: if state_from + read <= 1: 

....: state_to = 2*state_from + read 

....: write = 0 

....: else: 

....: state_to = 2*state_from + read - 3 

....: write = 1 

....: return (state_to, write) 

 

which assumes reading a binary number from left to right. 

We get the transducer with 

 

:: 

 

sage: D = Transducer(f, initial_states=[0], final_states=[0], 

....: input_alphabet=[0, 1]) 

 

Let us try to divide `12` by `3`:: 

 

sage: D([1, 1, 0, 0]) 

[0, 1, 0, 0] 

 

Now we want to divide `13` by `3`:: 

 

sage: D([1, 1, 0, 1]) 

Traceback (most recent call last): 

... 

ValueError: Invalid input sequence. 

 

The raised ``ValueError`` 

means `13` is not divisible by `3`. 

 

.. _finite_state_machine_gray_code_example: 

 

Gray Code 

--------- 

 

The Gray code is a binary :wikipedia:`numeral system <Numeral_system>` 

where two successive values differ in only one bit, cf. the 

:wikipedia:`Gray_code`. The Gray code of an integer `n` is obtained by 

a bitwise xor between the binary expansion of `n` and the binary 

expansion of `\lfloor n/2\rfloor`; the latter corresponds to a 

shift by one position in binary. 

 

The purpose of this example is to construct a transducer converting the 

standard binary expansion to the Gray code by translating this 

construction into operations with transducers. 

 

For this construction, the least significant digit is at 

the left-most position. 

Note that it is easier to shift everything to 

the right first, i.e., multiply by `2` instead of building 

`\lfloor n/2\rfloor`. Then, we take the input xor with the right 

shift of the input and forget the first letter. 

 

We first construct a transducer shifting the binary expansion to the 

right. This requires storing the previously read digit in a state. 

 

:: 

 

sage: def shift_right_transition(state, digit): 

....: if state == 'I': 

....: return (digit, None) 

....: else: 

....: return (digit, state) 

sage: shift_right_transducer = Transducer( 

....: shift_right_transition, 

....: initial_states=['I'], 

....: input_alphabet=[0, 1], 

....: final_states=[0]) 

sage: shift_right_transducer.transitions() 

[Transition from 'I' to 0: 0|-, 

Transition from 'I' to 1: 1|-, 

Transition from 0 to 0: 0|0, 

Transition from 0 to 1: 1|0, 

Transition from 1 to 0: 0|1, 

Transition from 1 to 1: 1|1] 

sage: shift_right_transducer([0, 1, 1, 0]) 

[0, 1, 1] 

sage: shift_right_transducer([1, 0, 0]) 

[1, 0] 

 

The output of the shifts above look a bit weird (from a right-shift 

transducer, we would expect, for example, that ``[1, 0, 0]`` was 

mapped to ``[0, 1, 0]``), since we write ``None`` instead of the zero 

at the left. Further, note that only `0` is listed as a final state 

as we have to enforce that a most significant zero is read as the last 

input letter in order to flush the last digit:: 

 

sage: shift_right_transducer([0, 1, 0, 1]) 

Traceback (most recent call last): 

... 

ValueError: Invalid input sequence. 

 

Next, we construct the transducer performing the xor operation. We also 

have to take ``None`` into account as our ``shift_right_transducer`` 

waits one iteration until it starts writing output. This corresponds 

with our intention to forget the first letter. 

 

:: 

 

sage: def xor_transition(state, digits): 

....: if digits[0] is None or digits[1] is None: 

....: return (0, None) 

....: else: 

....: return (0, digits[0].__xor__(digits[1])) 

sage: from itertools import product 

sage: xor_transducer = Transducer( 

....: xor_transition, 

....: initial_states=[0], 

....: final_states=[0], 

....: input_alphabet=list(product([None, 0, 1], [0, 1]))) 

sage: xor_transducer.transitions() 

[Transition from 0 to 0: (None, 0)|-, 

Transition from 0 to 0: (None, 1)|-, 

Transition from 0 to 0: (0, 0)|0, 

Transition from 0 to 0: (0, 1)|1, 

Transition from 0 to 0: (1, 0)|1, 

Transition from 0 to 0: (1, 1)|0] 

sage: xor_transducer([(None, 0), (None, 1), (0, 0), (0, 1), (1, 0), (1, 1)]) 

[0, 1, 1, 0] 

sage: xor_transducer([(0, None)]) 

Traceback (most recent call last): 

... 

ValueError: Invalid input sequence. 

 

The transducer computing the Gray code is then constructed as a 

:meth:`Cartesian product <Transducer.cartesian_product>` between the 

shifted version and the original input (represented here by the 

``shift_right_transducer`` and the :meth:`identity transducer 

<sage.combinat.finite_state_machine_generators.TransducerGenerators.Identity>`, 

respectively). This Cartesian product is then fed into the 

``xor_transducer`` as a :meth:`composition 

<FiniteStateMachine.composition>` of transducers. 

 

:: 

 

sage: product_transducer = shift_right_transducer.cartesian_product(transducers.Identity([0, 1])) 

sage: Gray_transducer = xor_transducer(product_transducer) 

 

We use :meth:`~FiniteStateMachine.construct_final_word_out` to make sure that all output 

is written; otherwise, we would have to make sure that a sufficient number of trailing 

zeros is read. 

 

:: 

 

sage: Gray_transducer.construct_final_word_out([0]) 

sage: Gray_transducer.transitions() 

[Transition from (('I', 0), 0) to ((0, 0), 0): 0|-, 

Transition from (('I', 0), 0) to ((1, 0), 0): 1|-, 

Transition from ((0, 0), 0) to ((0, 0), 0): 0|0, 

Transition from ((0, 0), 0) to ((1, 0), 0): 1|1, 

Transition from ((1, 0), 0) to ((0, 0), 0): 0|1, 

Transition from ((1, 0), 0) to ((1, 0), 0): 1|0] 

 

There is a :meth:`prepackaged transducer 

<sage.combinat.finite_state_machine_generators.TransducerGenerators.GrayCode>` 

for Gray code, let's see whether they agree. We have to use 

:meth:`~FiniteStateMachine.relabeled` to relabel our states with 

integers. 

 

:: 

 

sage: constructed = Gray_transducer.relabeled() 

sage: packaged = transducers.GrayCode() 

sage: constructed == packaged 

True 

 

Finally, we check that this indeed computes the Gray code of the first 

10 non-negative integers. 

 

:: 

 

sage: for n in srange(10): 

....: Gray_transducer(n.bits()) 

[] 

[1] 

[1, 1] 

[0, 1] 

[0, 1, 1] 

[1, 1, 1] 

[1, 0, 1] 

[0, 0, 1] 

[0, 0, 1, 1] 

[1, 0, 1, 1] 

 

 

Using the hook-functions 

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

 

Let's use the :ref:`previous example "division by 

3" <finite_state_machine_division_by_3_example>` to demonstrate the optional 

state and transition parameters ``hook``. 

 

First, we define what those functions should do. In our case, this is 

just saying in which state we are and which transition we take 

 

:: 

 

sage: def state_hook(process, state, output): 

....: print("We are now in State %s." % (state.label(),)) 

sage: from sage.combinat.finite_state_machine import FSMWordSymbol 

sage: def transition_hook(transition, process): 

....: print("Currently we go from %s to %s, " 

....: "reading %s and writing %s." % ( 

....: transition.from_state, transition.to_state, 

....: FSMWordSymbol(transition.word_in), 

....: FSMWordSymbol(transition.word_out))) 

 

Now, let's add these hook-functions to the existing transducer:: 

 

sage: for s in D.iter_states(): 

....: s.hook = state_hook 

sage: for t in D.iter_transitions(): 

....: t.hook = transition_hook 

 

Rerunning the process again now gives the following output:: 

 

sage: D.process([1, 1, 0, 1], check_epsilon_transitions=False) 

We are now in State 0. 

Currently we go from 0 to 1, reading 1 and writing 0. 

We are now in State 1. 

Currently we go from 1 to 0, reading 1 and writing 1. 

We are now in State 0. 

Currently we go from 0 to 0, reading 0 and writing 0. 

We are now in State 0. 

Currently we go from 0 to 1, reading 1 and writing 0. 

We are now in State 1. 

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

 

The example above just explains the basic idea of using 

hook-functions. In the following, we will use those hooks more 

seriously. 

 

.. WARNING:: 

 

The hooks of the states are also called while exploring the epsilon 

successors of a state (during processing). In the example above, we 

used ``check_epsilon_transitions=False`` to avoid this (and also 

therefore got a cleaner output). 

 

.. WARNING:: 

 

The arguments used when calling a hook have changed in 

:trac:`16538` from ``hook(state, process)`` to 

``hook(process, state, output)``. If you are using 

an old-style hook, a deprecation warning is displayed. 

 

 

Detecting sequences with same number of `0` and `1` 

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

 

Suppose we have a binary input and want to accept all sequences with 

the same number of `0` and `1`. This cannot be done with a finite 

automaton. Anyhow, we can make usage of the hook functions to extend 

our finite automaton by a counter:: 

 

sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition 

sage: C = FiniteStateMachine() 

sage: def update_counter(process, state, output): 

....: l = process.preview_word() 

....: process.fsm.counter += 1 if l == 1 else -1 

....: if process.fsm.counter > 0: 

....: next_state = 'positive' 

....: elif process.fsm.counter < 0: 

....: next_state = 'negative' 

....: else: 

....: next_state = 'zero' 

....: return FSMTransition(state, process.fsm.state(next_state), 

....: l, process.fsm.counter) 

sage: C.add_state(FSMState('zero', hook=update_counter, 

....: is_initial=True, is_final=True)) 

'zero' 

sage: C.add_state(FSMState('positive', hook=update_counter)) 

'positive' 

sage: C.add_state(FSMState('negative', hook=update_counter)) 

'negative' 

 

Now, let's input some sequence:: 

 

sage: C.counter = 0; C([1, 1, 1, 1, 0, 0]) 

(False, 'positive', [1, 2, 3, 4, 3, 2]) 

 

The result is False, since there are four `1` but only two `0`. We 

land in the state ``positive`` and we can also see the values of the 

counter in each step. 

 

Let's try some other examples:: 

 

sage: C.counter = 0; C([1, 1, 0, 0]) 

(True, 'zero', [1, 2, 1, 0]) 

sage: C.counter = 0; C([0, 1, 0, 0]) 

(False, 'negative', [-1, 0, -1, -2]) 

 

See also methods :meth:`Automaton.process` and 

:meth:`Transducer.process` (or even 

:meth:`FiniteStateMachine.process`), the explanation of the parameter 

``hook`` and the examples in :class:`FSMState` and 

:class:`FSMTransition`, and the description and examples in 

:class:`FSMProcessIterator` for more information on processing and 

hooks. 

 

REFERENCES: 

 

.. [HKP2015] Clemens Heuberger, Sara Kropf, and Helmut Prodinger, 

*Output sum of transducers: Limiting distribution and periodic 

fluctuation*, 

`Electron. J. Combin. 22 (2015), #P2.19 <http://www.combinatorics.org/ojs/index.php/eljc/article/view/v22i2p19>`_. 

 

.. [HKW2015] Clemens Heuberger, Sara Kropf and Stephan Wagner, 

*Variances and Covariances in the Central Limit Theorem for the Output 

of a Transducer*, European J. Combin. 49 (2015), 167-187, 

:doi:`10.1016/j.ejc.2015.03.004`. 

 

AUTHORS: 

 

- Daniel Krenn (2012-03-27): initial version 

- Clemens Heuberger (2012-04-05): initial version 

- Sara Kropf (2012-04-17): initial version 

- Clemens Heuberger (2013-08-21): release candidate for Sage patch 

- Daniel Krenn (2013-08-21): release candidate for Sage patch 

- Sara Kropf (2013-08-21): release candidate for Sage patch 

- Clemens Heuberger (2013-09-02): documentation improved 

- Daniel Krenn (2013-09-13): comments from trac worked in 

- Clemens Heuberger (2013-11-03): output (labels) of determinisation, 

product, composition, etc. changed (for consistency), 

representation of state changed, documentation improved 

- Daniel Krenn (2013-11-04): whitespaces in documentation corrected 

- Clemens Heuberger (2013-11-04): full_group_by added 

- Daniel Krenn (2013-11-04): next release candidate for Sage patch 

- Sara Kropf (2013-11-08): fix for adjacency matrix 

- Clemens Heuberger (2013-11-11): fix for prepone_output 

- Daniel Krenn (2013-11-11): comments from :trac:`15078` included: 

docstring of FiniteStateMachine rewritten, Automaton and Transducer 

inherited from FiniteStateMachine 

- Daniel Krenn (2013-11-25): documentation improved according to 

comments from :trac:`15078` 

- Clemens Heuberger, Daniel Krenn, Sara Kropf (2014-02-21--2014-07-18): 

A huge bunch of improvements. Details see 

:trac:`15841`, :trac:`15847`, :trac:`15848`, :trac:`15849`, :trac:`15850`, :trac:`15922`, :trac:`15923`, :trac:`15924`, 

:trac:`15925`, :trac:`15928`, :trac:`15960`, :trac:`15961`, :trac:`15962`, :trac:`15963`, :trac:`15975`, :trac:`16016`, 

:trac:`16024`, :trac:`16061`, :trac:`16128`, :trac:`16132`, :trac:`16138`, :trac:`16139`, :trac:`16140`, :trac:`16143`, 

:trac:`16144`, :trac:`16145`, :trac:`16146`, :trac:`16191`, :trac:`16200`, :trac:`16205`, :trac:`16206`, :trac:`16207`, 

:trac:`16229`, :trac:`16253`, :trac:`16254`, :trac:`16255`, :trac:`16266`, :trac:`16355`, :trac:`16357`, :trac:`16387`, 

:trac:`16425`, :trac:`16539`, :trac:`16555`, :trac:`16557`, :trac:`16588`, :trac:`16589`, :trac:`16666`, :trac:`16668`, 

:trac:`16674`, :trac:`16675`, :trac:`16677`. 

- Daniel Krenn (2015-09-14): cleanup :trac:`18227` 

 

ACKNOWLEDGEMENT: 

 

- Clemens Heuberger, Daniel Krenn and Sara Kropf are supported by the 

Austrian Science Fund (FWF): P 24644-N26. 

 

Methods 

======= 

""" 

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

# Copyright (C) 2012--2015 Clemens Heuberger <clemens.heuberger@aau.at> 

# 2012--2015 Daniel Krenn <dev@danielkrenn.at> 

# 2012--2015 Sara Kropf <sara.kropf@aau.at> 

# 

# Distributed under the terms of the GNU General Public License (GPL) 

# as published by the Free Software Foundation; either version 2 of 

# the License, or (at your option) any later version. 

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

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

from __future__ import print_function 

 

from builtins import zip 

import six 

from six.moves import range 

 

from six import itervalues 

from six.moves import zip_longest 

 

import collections 

import itertools 

 

import sage 

 

 

def full_group_by(l, key=lambda x: x): 

""" 

Group iterable ``l`` by values of ``key``. 

 

INPUT: 

 

- iterable ``l`` 

- key function ``key`` 

 

OUTPUT: 

 

A list of pairs ``(k, elements)`` such that ``key(e)=k`` for all 

``e`` in ``elements``. 

 

This is similar to ``itertools.groupby`` except that lists are 

returned instead of iterables and no prior sorting is required. 

 

We do not require 

 

- that the keys are sortable (in contrast to the 

approach via ``sorted`` and ``itertools.groupby``) and 

- that the keys are hashable (in contrast to the 

implementation proposed in `<http://stackoverflow.com/a/15250161>`_). 

 

However, it is required 

 

- that distinct keys have distinct ``str``-representations. 

 

The implementation is inspired by 

`<http://stackoverflow.com/a/15250161>`_, but non-hashable keys are 

allowed. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import full_group_by 

sage: t = [2/x, 1/x, 2/x] 

sage: r = full_group_by([0, 1, 2], key=lambda i:t[i]) 

sage: sorted(r, key=lambda p:p[1]) 

[(2/x, [0, 2]), (1/x, [1])] 

sage: from itertools import groupby 

sage: for k, elements in groupby(sorted([0, 1, 2], 

....: key=lambda i:t[i]), 

....: key=lambda i:t[i]): 

....: print("{} {}".format(k, list(elements))) 

2/x [0] 

1/x [1] 

2/x [2] 

 

Note that the behavior is different from ``itertools.groupby`` 

because neither `1/x<2/x` nor `2/x<1/x` does hold. 

 

Here, the result ``r`` has been sorted in order to guarantee a 

consistent order for the doctest suite. 

""" 

elements = collections.defaultdict(list) 

original_keys = {} 

for item in l: 

k = key(item) 

s = str(k) 

if s in original_keys: 

if original_keys[s]!=k: 

raise ValueError("Two distinct elements with representation " 

"%s " % s) 

else: 

original_keys[s]=k 

elements[s].append(item) 

return [(original_keys[s], values ) for (s, values) in elements.items()] 

 

 

def equal(iterator): 

""" 

Checks whether all elements of ``iterator`` are equal. 

 

INPUT: 

 

- ``iterator`` -- an iterator of the elements to check 

 

OUTPUT: 

 

``True`` or ``False``. 

 

This implements `<http://stackoverflow.com/a/3844832/1052778>`_. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import equal 

sage: equal([0, 0, 0]) 

True 

sage: equal([0, 1, 0]) 

False 

sage: equal([]) 

True 

sage: equal(iter([None, None])) 

True 

 

We can test other properties of the elements than the elements 

themselves. In the following example, we check whether all tuples 

have the same lengths:: 

 

sage: equal(len(x) for x in [(1, 2), (2, 3), (3, 1)]) 

True 

sage: equal(len(x) for x in [(1, 2), (1, 2, 3), (3, 1)]) 

False 

""" 

try: 

iterator = iter(iterator) 

first = next(iterator) 

return all(first == rest for rest in iterator) 

except StopIteration: 

return True 

 

 

def startswith(list, prefix): 

""" 

Determine whether list starts with the given prefix. 

 

INPUT: 

 

- ``list`` -- list 

- ``prefix`` -- list representing the prefix 

 

OUTPUT: 

 

``True`` or ``False``. 

 

Similar to :meth:`str.startswith`. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import startswith 

sage: startswith([1, 2, 3], [1, 2]) 

True 

sage: startswith([1], [1, 2]) 

False 

sage: startswith([1, 3, 2], [1, 2]) 

False 

""" 

 

return list[:len(prefix)] == prefix 

 

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

 

 

FSMEmptyWordSymbol = '-' 

EmptyWordLaTeX = r'\varepsilon' 

EndOfWordLaTeX = r'\$' 

tikz_automata_where = {"right": 0, 

"above": 90, 

"left": 180, 

"below": 270} 

 

 

def FSMLetterSymbol(letter): 

""" 

Returns a string associated to the input letter. 

 

INPUT: 

 

- ``letter`` -- the input letter or ``None`` (representing the 

empty word). 

 

OUTPUT: 

 

If ``letter`` is ``None`` the symbol for the empty word 

``FSMEmptyWordSymbol`` is returned, otherwise the string 

associated to the letter. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMLetterSymbol 

sage: FSMLetterSymbol(0) 

'0' 

sage: FSMLetterSymbol(None) 

'-' 

""" 

return FSMEmptyWordSymbol if letter is None else repr(letter) 

 

 

def FSMWordSymbol(word): 

""" 

Returns a string of ``word``. It may returns the symbol of the 

empty word ``FSMEmptyWordSymbol``. 

 

INPUT: 

 

- ``word`` -- the input word. 

 

OUTPUT: 

 

A string of ``word``. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMWordSymbol 

sage: FSMWordSymbol([0, 1, 1]) 

'0,1,1' 

""" 

if not isinstance(word, list): 

return FSMLetterSymbol(word) 

if len(word) == 0: 

return FSMEmptyWordSymbol 

s = '' 

for letter in word: 

s += (',' if len(s) > 0 else '') + FSMLetterSymbol(letter) 

return s 

 

 

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

 

 

def is_FSMState(S): 

""" 

Tests whether or not ``S`` inherits from :class:`FSMState`. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import is_FSMState, FSMState 

sage: is_FSMState(FSMState('A')) 

True 

""" 

return isinstance(S, FSMState) 

 

 

class FSMState(sage.structure.sage_object.SageObject): 

""" 

Class for a state of a finite state machine. 

 

INPUT: 

 

- ``label`` -- the label of the state. 

 

- ``word_out`` -- (default: ``None``) a word that is written when 

the state is reached. 

 

- ``is_initial`` -- (default: ``False``) 

 

- ``is_final`` -- (default: ``False``) 

 

- ``final_word_out`` -- (default: ``None``) a word that is written when 

the state is reached as the last state of some input; only for final 

states. 

 

- ``initial_probability`` -- (default: ``None``) The probability of 

starting in this state if it is a state of a Markov chain. 

 

- ``hook`` -- (default: ``None``) A function which is called when 

the state is reached during processing input. It takes two input 

parameters: the first is the current state (to allow using the same 

hook for several states), the second is the current process 

iterator object (to have full access to everything; e.g. the 

next letter from the input tape can be read in). It can output 

the next transition, i.e. the transition to take next. If it 

returns ``None`` the process iterator chooses. Moreover, this 

function can raise a ``StopIteration`` exception to stop 

processing of a finite state machine the input immediately. See 

also the example below. 

 

- ``color`` -- (default: ``None``) In order to distinguish states, 

they can be given an arbitrary "color" (an arbitrary object). 

This is used in :meth:`FiniteStateMachine.equivalence_classes`: 

states of different colors are never considered to be 

equivalent. Note that :meth:`Automaton.determinisation` requires 

that ``color`` is hashable. 

 

- ``allow_label_None`` -- (default: ``False``) If ``True`` allows also 

``None`` as label. Note that a state with label ``None`` is used in 

:class:`FSMProcessIterator`. 

 

OUTPUT: 

 

Returns a state of a finite state machine. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('state 1', word_out=0, is_initial=True) 

sage: A 

'state 1' 

sage: A.label() 

'state 1' 

sage: B = FSMState('state 2') 

sage: A == B 

False 

 

We can also define a final output word of a final state which is 

used if the input of a transducer leads to this state. Such final 

output words are used in subsequential transducers. :: 

 

sage: C = FSMState('state 3', is_final=True, final_word_out='end') 

sage: C.final_word_out 

['end'] 

 

The final output word can be a single letter, ``None`` or a list of 

letters:: 

 

sage: A = FSMState('A') 

sage: A.is_final = True 

sage: A.final_word_out = 2 

sage: A.final_word_out 

[2] 

sage: A.final_word_out = [2, 3] 

sage: A.final_word_out 

[2, 3] 

 

Only final states can have a final output word which is not 

``None``:: 

 

sage: B = FSMState('B') 

sage: B.final_word_out is None 

True 

sage: B.final_word_out = 2 

Traceback (most recent call last): 

... 

ValueError: Only final states can have a final output word, 

but state B is not final. 

 

Setting the ``final_word_out`` of a final state to ``None`` is the 

same as setting it to ``[]`` and is also the default for a final 

state:: 

 

sage: C = FSMState('C', is_final=True) 

sage: C.final_word_out 

[] 

sage: C.final_word_out = None 

sage: C.final_word_out 

[] 

sage: C.final_word_out = [] 

sage: C.final_word_out 

[] 

 

It is not allowed to use ``None`` as a label:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: FSMState(None) 

Traceback (most recent call last): 

... 

ValueError: Label None reserved for a special state, 

choose another label. 

 

This can be overridden by:: 

 

sage: FSMState(None, allow_label_None=True) 

None 

 

Note that :meth:`Automaton.determinisation` requires that ``color`` 

is hashable:: 

 

sage: A = Automaton([[0, 0, 0]], initial_states=[0]) 

sage: A.state(0).color = [] 

sage: A.determinisation() 

Traceback (most recent call last): 

... 

TypeError: unhashable type: 'list' 

sage: A.state(0).color = () 

sage: A.determinisation() 

Automaton with 1 state 

 

We can use a hook function of a state to stop processing. This is 

done by raising a ``StopIteration`` exception. The following code 

demonstrates this:: 

 

sage: T = Transducer([(0, 1, 9, 'a'), (1, 2, 9, 'b'), 

....: (2, 3, 9, 'c'), (3, 4, 9, 'd')], 

....: initial_states=[0], 

....: final_states=[4], 

....: input_alphabet=[9]) 

sage: def stop(process, state, output): 

....: raise StopIteration() 

sage: T.state(3).hook = stop 

sage: T.process([9, 9, 9, 9]) 

(False, 3, ['a', 'b', 'c']) 

""" 

 

is_initial = False 

""" 

Describes whether the state is initial. 

 

EXAMPLES:: 

 

sage: T = Automaton([(0,0,0)]) 

sage: T.initial_states() 

[] 

sage: T.state(0).is_initial = True 

sage: T.initial_states() 

[0] 

""" 

 

initial_probability = None 

""" 

The probability of starting in this state if it is part of a Markov chain. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: S = FSMState('state', initial_probability=1/3) 

sage: S.initial_probability 

1/3 

""" 

 

 

def __init__(self, label, word_out=None, 

is_initial=False, is_final=False, final_word_out=None, 

initial_probability=None, 

hook=None, color=None, allow_label_None=False): 

""" 

See :class:`FSMState` for more information. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: FSMState('final', is_final=True) 

'final' 

 

TESTS:: 

 

sage: A = FSMState('A', is_final=True) 

sage: A.final_word_out 

[] 

sage: A.is_final = True 

sage: A = FSMState('A', is_final=True, final_word_out='end') 

sage: A.final_word_out 

['end'] 

sage: A = FSMState('A', is_final=True, 

....: final_word_out=['e', 'n', 'd']) 

sage: A.final_word_out 

['e', 'n', 'd'] 

sage: A = FSMState('A', is_final=True, final_word_out=[]) 

sage: A.final_word_out 

[] 

sage: A = FSMState('A', is_final=True, final_word_out=None) 

sage: A.final_word_out 

[] 

sage: A = FSMState('A', is_final=False) 

sage: A.final_word_out is None 

True 

sage: A.is_final = False 

sage: A = FSMState('A', is_final=False, final_word_out='end') 

Traceback (most recent call last): 

... 

ValueError: Only final states can have a final output word, 

but state A is not final. 

sage: A = FSMState('A', is_final=False, 

....: final_word_out=['e', 'n', 'd']) 

Traceback (most recent call last): 

... 

ValueError: Only final states can have a final output word, 

but state A is not final. 

sage: A = FSMState('A', is_final=False, final_word_out=None) 

sage: A.final_word_out is None 

True 

sage: A = FSMState('A', is_final=False, final_word_out=[]) 

Traceback (most recent call last): 

... 

ValueError: Only final states can have a final output word, 

but state A is not final. 

""" 

if not allow_label_None and label is None: 

raise ValueError("Label None reserved for a special state, " 

"choose another label.") 

self._label_ = label 

 

if isinstance(word_out, list): 

self.word_out = word_out 

elif word_out is not None: 

self.word_out = [word_out] 

else: 

self.word_out = [] 

 

self.is_initial = is_initial 

self._final_word_out_ = None 

self.is_final = is_final 

self.final_word_out = final_word_out 

self.initial_probability = initial_probability 

 

if hook is not None: 

if hasattr(hook, '__call__'): 

self.hook = hook 

else: 

raise TypeError('Wrong argument for hook.') 

 

self.color = color 

 

 

def __lt__(self, other): 

""" 

Returns True if label of ``self`` is less than label of 

``other``. 

 

INPUT: 

 

- `other` -- a state. 

 

OUTPUT: 

 

True or False. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: FSMState(0) < FSMState(1) 

True 

""" 

return self.label() < other.label() 

 

 

@property 

def final_word_out(self): 

""" 

The final output word of a final state which is written if the 

state is reached as the last state of the input of the finite 

state machine. For a non-final state, the value is ``None``. 

 

``final_word_out`` can be a single letter, a list or ``None``, 

but for a final-state, it is always saved as a list. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A', is_final=True, final_word_out=2) 

sage: A.final_word_out 

[2] 

sage: A.final_word_out = 3 

sage: A.final_word_out 

[3] 

sage: A.final_word_out = [3, 4] 

sage: A.final_word_out 

[3, 4] 

sage: A.final_word_out = None 

sage: A.final_word_out 

[] 

sage: B = FSMState('B') 

sage: B.final_word_out is None 

True 

 

A non-final state cannot have a final output word:: 

 

sage: B.final_word_out = [3, 4] 

Traceback (most recent call last): 

... 

ValueError: Only final states can have a final 

output word, but state B is not final. 

""" 

return self._final_word_out_ 

 

 

@final_word_out.setter 

def final_word_out(self, final_word_out): 

""" 

Sets the value of the final output word of a final state. 

 

INPUT: 

 

- ``final_word_out`` -- a list, any element or ``None``. 

 

OUTPUT: 

 

Nothing. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: B = FSMState('B') 

sage: B.final_word_out = [] 

Traceback (most recent call last): 

... 

ValueError: Only final states can have a final 

output word, but state B is not final. 

sage: B.final_word_out = None 

sage: B.final_word_out is None 

True 

 

The exception is raised also when the initial state is a tuple 

(see :trac:`18990`):: 

 

sage: A = Transducer(initial_states=[(0, 0)]) 

sage: A.state((0, 0)).final_word_out = [] 

Traceback (most recent call last): 

... 

ValueError: Only final states can have a final output word, 

but state (0, 0) is not final. 

 

No exception is raised if we set the state to be a final one:: 

 

sage: A.state((0, 0)).is_final=True 

sage: A.state((0, 0)).final_word_out = [] 

sage: A.state((0, 0)).final_word_out == [] 

True 

""" 

if not self.is_final: 

if final_word_out is not None: 

raise ValueError("Only final states can have a " 

"final output word, but state %s is not final." 

% (self.label(),)) 

else: 

self._final_word_out_ = None 

elif isinstance(final_word_out, list): 

self._final_word_out_ = final_word_out 

elif final_word_out is not None: 

self._final_word_out_ = [final_word_out] 

else: 

self._final_word_out_ = [] 

 

 

@property 

def is_final(self): 

""" 

Describes whether the state is final or not. 

 

``True`` if the state is final and ``False`` otherwise. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A', is_final=True, final_word_out=3) 

sage: A.is_final 

True 

sage: A.is_final = False 

Traceback (most recent call last): 

... 

ValueError: State A cannot be non-final, because it has a 

final output word. Only final states can have a final output 

word. 

sage: A.final_word_out = None 

sage: A.is_final = False 

sage: A.is_final 

False 

""" 

return (self.final_word_out is not None) 

 

 

@is_final.setter 

def is_final(self, is_final): 

""" 

Defines the state as a final state or a non-final state. 

 

INPUT: 

 

- ``is_final`` -- ``True`` if the state should be final and 

``False`` otherwise. 

 

OUTPUT: 

 

Nothing. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A', is_final=True) 

sage: A.final_word_out 

[] 

sage: A.is_final = False 

sage: A.final_word_out is None 

True 

sage: A = FSMState('A', is_final=True, final_word_out='a') 

sage: A.is_final = False 

Traceback (most recent call last): 

... 

ValueError: State A cannot be non-final, because it has a 

final output word. Only final states can have a final output 

word. 

 

The exception is raised also when the final state is a tuple 

(see :trac:`18990`):: 

 

sage: A = Transducer(final_states=[(0, 0)]) 

sage: A.state((0, 0)).final_word_out = [1] 

sage: A.state((0, 0)).is_final = False 

Traceback (most recent call last): 

... 

ValueError: State (0, 0) cannot be non-final, because it has 

a final output word. Only final states can have a final 

output word. 

 

No exception is raised if we empty the final_word_out of the 

state:: 

 

sage: A.state((0, 0)).final_word_out = [] 

sage: A.state((0, 0)).is_final = False 

sage: A.state((0, 0)).is_final 

False 

 

sage: A = FSMState('A', is_final=True, final_word_out=[]) 

sage: A.is_final = False 

sage: A.final_word_out is None 

True 

""" 

if is_final and self.final_word_out is None: 

self._final_word_out_ = [] 

elif not is_final: 

if not self.final_word_out: 

self._final_word_out_ = None 

else: 

raise ValueError("State %s cannot be non-final, because it " 

"has a final output word. Only final states " 

"can have a final output word. " 

% (self.label(),)) 

 

 

def label(self): 

""" 

Returns the label of the state. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

The label of the state. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('state') 

sage: A.label() 

'state' 

""" 

return self._label_ 

 

 

def __copy__(self): 

""" 

Returns a (shallow) copy of the state. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A new state. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A') 

sage: A.is_initial = True 

sage: A.is_final = True 

sage: A.final_word_out = [1] 

sage: A.color = 'green' 

sage: A.initial_probability = 1/2 

sage: B = copy(A) 

sage: B.fully_equal(A) 

True 

sage: A.label() is B.label() 

True 

sage: A.is_initial is B.is_initial 

True 

sage: A.is_final is B.is_final 

True 

sage: A.final_word_out is B.final_word_out 

True 

sage: A.color is B.color 

True 

sage: A.initial_probability is B.initial_probability 

True 

""" 

new = FSMState(self.label(), self.word_out, 

self.is_initial, self.is_final, 

color=self.color, 

final_word_out=self.final_word_out, 

initial_probability=self.initial_probability) 

if hasattr(self, 'hook'): 

new.hook = self.hook 

return new 

 

 

copy = __copy__ 

 

 

def __deepcopy__(self, memo): 

""" 

Returns a deep copy of the state. 

 

INPUT: 

 

- ``memo`` -- a dictionary storing already processed elements. 

 

OUTPUT: 

 

A new state. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A') 

sage: deepcopy(A) 

'A' 

""" 

from copy import deepcopy 

 

try: 

label = self._deepcopy_relabel_ 

except AttributeError: 

label = deepcopy(self.label(), memo) 

new = FSMState(label, deepcopy(self.word_out, memo), 

self.is_initial, self.is_final) 

if hasattr(self, 'hook'): 

new.hook = deepcopy(self.hook, memo) 

new.color = deepcopy(self.color, memo) 

new.final_word_out = deepcopy(self.final_word_out, memo) 

new.initial_probability = deepcopy(self.initial_probability, memo) 

return new 

 

 

def deepcopy(self, memo=None): 

""" 

Returns a deep copy of the state. 

 

INPUT: 

 

- ``memo`` -- (default: ``None``) a dictionary storing already 

processed elements. 

 

OUTPUT: 

 

A new state. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState((1, 3), color=[1, 2], 

....: is_final=True, final_word_out=3, 

....: initial_probability=1/3) 

sage: B = deepcopy(A) 

sage: B 

(1, 3) 

sage: B.label == A.label 

True 

sage: B.label is A.label 

False 

sage: B.color == A.color 

True 

sage: B.color is A.color 

False 

sage: B.is_final == A.is_final 

True 

sage: B.is_final is A.is_final 

True 

sage: B.final_word_out == A.final_word_out 

True 

sage: B.final_word_out is A.final_word_out 

False 

sage: B.initial_probability == A.initial_probability 

True 

sage: B.initial_probability is A.initial_probability 

False 

""" 

from copy import deepcopy 

return deepcopy(self, memo) 

 

 

def relabeled(self, label, memo=None): 

""" 

Returns a deep copy of the state with a new label. 

 

INPUT: 

 

- ``label`` -- the label of new state. 

 

- ``memo`` -- (default: ``None``) a dictionary storing already 

processed elements. 

 

OUTPUT: 

 

A new state. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A') 

sage: A.relabeled('B') 

'B' 

 

""" 

from copy import deepcopy 

self._deepcopy_relabel_ = label 

new = deepcopy(self, memo) 

del self._deepcopy_relabel_ 

return new 

 

 

def __hash__(self): 

""" 

Returns a hash value for the object. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

The hash of this state. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A') 

sage: hash(A) #random 

-269909568 

""" 

return hash(self.label()) 

 

 

def _repr_(self): 

""" 

Returns the string "label". 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A string. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: FSMState('A')._repr_() 

"'A'" 

""" 

return repr(self.label()) 

 

 

def __eq__(left, right): 

""" 

Returns True if two states are the same, i.e., if they have 

the same labels. 

 

INPUT: 

 

- ``left`` -- a state. 

 

- ``right`` -- a state. 

 

OUTPUT: 

 

True or False. 

 

Note that the hooks and whether the states are initial or 

final are not checked. To fully compare two states (including 

these attributes), use :meth:`.fully_equal`. 

 

As only the labels are used when hashing a state, only the 

labels can actually be compared by the equality relation. 

Note that the labels are unique within one finite state machine, 

so this may only lead to ambiguities when comparing states 

belonging to different finite state machines. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A') 

sage: B = FSMState('A', is_initial=True) 

sage: A == B 

True 

""" 

if not is_FSMState(right): 

return False 

return left.label() == right.label() 

 

 

def __ne__(left, right): 

""" 

Tests for inequality, complement of __eq__. 

 

INPUT: 

 

- ``left`` -- a state. 

 

- ``right`` -- a state. 

 

OUTPUT: 

 

True or False. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A', is_initial=True) 

sage: B = FSMState('A', is_final=True) 

sage: A != B 

False 

""" 

return (not (left == right)) 

 

 

def fully_equal(left, right, compare_color=True): 

""" 

Checks whether two states are fully equal, i.e., including all 

attributes except ``hook``. 

 

INPUT: 

 

- ``left`` -- a state. 

 

- ``right`` -- a state. 

 

- ``compare_color`` -- If ``True`` (default) colors are 

compared as well, otherwise not. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

Note that usual comparison by ``==`` does only compare the labels. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A') 

sage: B = FSMState('A', is_initial=True) 

sage: A.fully_equal(B) 

False 

sage: A == B 

True 

sage: A.is_initial = True; A.color = 'green' 

sage: A.fully_equal(B) 

False 

sage: A.fully_equal(B, compare_color=False) 

True 

""" 

color = not compare_color or left.color == right.color 

return (left == right and 

left.is_initial == right.is_initial and 

left.is_final == right.is_final and 

left.final_word_out == right.final_word_out and 

left.word_out == right.word_out and 

color and 

left.initial_probability == right.initial_probability) 

 

 

def __bool__(self): 

""" 

Returns True. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

True or False. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: bool(FSMState('A')) 

True 

""" 

return True # A state cannot be zero (see __init__) 

 

 

__nonzero__ = __bool__ 

 

 

def _epsilon_successors_(self, fsm=None): 

""" 

Returns the dictionary with states reachable from ``self`` 

without reading anything from an input tape as keys. The 

values are lists of outputs. 

 

INPUT: 

 

- ``fsm`` -- the finite state machine to which ``self`` 

belongs. 

 

OUTPUT: 

 

A dictionary mapping states to a list of output words. 

 

The states in the output are the epsilon successors of 

``self``. Each word of the list of words is an output word 

written when taking a path from ``self`` to the corresponding 

state. 

 

TESTS:: 

 

sage: T = Transducer([(0, 1, None, 'a'), (1, 2, None, 'b')]) 

sage: T.state(0)._epsilon_successors_(T) 

{1: [['a']], 2: [['a', 'b']]} 

sage: T.state(1)._epsilon_successors_(T) 

{2: [['b']]} 

sage: T.state(2)._epsilon_successors_(T) 

{} 

 

:: 

 

sage: T.state(0)._epsilon_successors_() 

{1: [['a']], 2: [['a', 'b']]} 

 

:: 

 

sage: T.add_transition(2, 0, None, 'c') 

Transition from 2 to 0: -|'c' 

sage: T.state(0)._epsilon_successors_() 

{0: [['a', 'b', 'c']], 1: [['a']], 2: [['a', 'b']]} 

 

:: 

 

sage: T.add_transition(0, 2, None, ['a', 'b']) 

Transition from 0 to 2: -|'a','b' 

sage: T.state(0)._epsilon_successors_() 

{0: [['a', 'b', 'c']], 1: [['a']], 2: [['a', 'b']]} 

""" 

if not hasattr(self, 'transitions'): 

raise ValueError('State %s does not belong to a ' 

'finite state machine.' % (self,)) 

 

it = _FSMProcessIteratorEpsilon_(fsm, input_tape=[], 

initial_state=self) 

# TODO: optimize the following lines (use already calculated 

# epsilon successors) 

for _ in it: 

pass 

_epsilon_successors_dict_ = it.visited_states 

_epsilon_successors_dict_[self].remove([]) # delete starting state 

if not _epsilon_successors_dict_[self]: 

del _epsilon_successors_dict_[self] 

for s, outputs in six.iteritems(_epsilon_successors_dict_): 

_epsilon_successors_dict_[s] = [t for t, _ in 

itertools.groupby(sorted(outputs))] 

return _epsilon_successors_dict_ 

 

 

def _in_epsilon_cycle_(self, fsm=None): 

""" 

Returns whether ``self`` is in an epsilon-cycle or not. 

 

INPUT: 

 

- ``fsm`` -- the finite state machine to which ``self`` 

belongs. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

TESTS:: 

 

sage: A = Automaton([(0, 1, None, 'a'), (1, 2, None, 'b'), 

....: (2, 0, None, 'c'), (4, 1, None, 'd')]) 

sage: A.state(0)._epsilon_successors_(A) 

{0: [['a', 'b', 'c']], 1: [['a']], 2: [['a', 'b']]} 

sage: A.state(0)._in_epsilon_cycle_(A) 

True 

sage: A.state(4)._epsilon_successors_(A) 

{0: [['d', 'b', 'c']], 1: [['d'], ['d', 'b', 'c', 'a']], 

2: [['d', 'b']]} 

sage: A.state(4)._in_epsilon_cycle_(A) 

False 

""" 

return self in self._epsilon_successors_(fsm) 

 

 

def _epsilon_cycle_output_empty_(self, fsm=None): 

""" 

Returns whether all epsilon-cycles in which ``self`` is 

contained have an empty output (i.e., do not write any output 

word). 

 

INPUT: 

 

- ``fsm`` -- the finite state machine to which ``self`` 

belongs. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

A ``ValueError`` is raised when ``self`` is not in an epsilon 

cycle. 

 

TESTS:: 

 

sage: A = Automaton([(0, 1, None, 'a'), (1, 2, None, None), 

....: (2, 0, None, None), (4, 1, None, None)]) 

sage: A.state(0)._epsilon_successors_(A) 

{0: [['a']], 1: [['a']], 2: [['a']]} 

sage: A.state(0)._epsilon_cycle_output_empty_(A) 

False 

sage: A.state(4)._epsilon_cycle_output_empty_(A) 

Traceback (most recent call last): 

... 

ValueError: State 4 is not in an epsilon cycle. 

sage: A = Automaton([(0, 1, None, None), (1, 2, None, None), 

....: (2, 0, None, None), (4, 1, None, None)]) 

sage: A.state(0)._epsilon_successors_(A) 

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

sage: A.state(0)._epsilon_cycle_output_empty_(A) 

True 

sage: A.process([], initial_state=A.state(0)) 

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

sage: A.add_transition(0, 0, None, 'x') 

Transition from 0 to 0: -|'x' 

sage: A.state(0)._epsilon_successors_(A) 

{0: [[], ['x']], 1: [[]], 2: [[]]} 

sage: A.state(0)._epsilon_cycle_output_empty_(A) 

False 

sage: A.process([], initial_state=A.state(0)) 

Traceback (most recent call last): 

... 

RuntimeError: State 0 is in an epsilon cycle (no input), 

but output is written. 

sage: T = Transducer([(0, 1, None, None), (1, 2, None, None), 

....: (2, 0, None, None), (0, 0, None, None)]) 

sage: T.state(0)._epsilon_successors_(T) 

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

sage: T.state(0)._epsilon_cycle_output_empty_(T) 

True 

""" 

try: 

return not any(self._epsilon_successors_(fsm)[self]) 

except KeyError: 

raise ValueError("State %s is not in an epsilon cycle." % (self,)) 

 

 

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

 

 

def is_FSMTransition(T): 

""" 

Tests whether or not ``T`` inherits from :class:`FSMTransition`. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import is_FSMTransition, FSMTransition 

sage: is_FSMTransition(FSMTransition('A', 'B')) 

True 

""" 

return isinstance(T, FSMTransition) 

 

 

class FSMTransition(sage.structure.sage_object.SageObject): 

""" 

Class for a transition of a finite state machine. 

 

INPUT: 

 

- ``from_state`` -- state from which transition starts. 

 

- ``to_state`` -- state in which transition ends. 

 

- ``word_in`` -- the input word of the transitions (when the 

finite state machine is used as automaton) 

 

- ``word_out`` -- the output word of the transitions (when the 

finite state machine is used as transducer) 

 

OUTPUT: 

 

A transition of a finite state machine. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition 

sage: A = FSMState('A') 

sage: B = FSMState('B') 

sage: S = FSMTransition(A, B, 0, 1) 

sage: T = FSMTransition('A', 'B', 0, 1) 

sage: T == S 

True 

sage: U = FSMTransition('A', 'B', 0) 

sage: U == T 

False 

 

""" 

 

from_state = None 

"""State from which the transition starts. Read-only.""" 

 

to_state = None 

"""State in which the transition ends. Read-only.""" 

 

word_in = None 

"""Input word of the transition. Read-only.""" 

 

word_out = None 

"""Output word of the transition. Read-only.""" 

 

 

def __init__(self, from_state, to_state, 

word_in=None, word_out=None, 

hook=None): 

""" 

See :class:`FSMTransition` for more information. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: FSMTransition('A', 'B', 0, 1) 

Transition from 'A' to 'B': 0|1 

""" 

if is_FSMState(from_state): 

self.from_state = from_state 

else: 

self.from_state = FSMState(from_state) 

if is_FSMState(to_state): 

self.to_state = to_state 

else: 

self.to_state = FSMState(to_state) 

 

if isinstance(word_in, list): 

self.word_in = word_in 

elif word_in is not None: 

self.word_in = [word_in] 

else: 

self.word_in = [] 

 

if isinstance(word_out, list): 

self.word_out = word_out 

elif word_out is not None: 

self.word_out = [word_out] 

else: 

self.word_out = [] 

 

if hook is not None: 

if hasattr(hook, '__call__'): 

self.hook = hook 

else: 

raise TypeError('Wrong argument for hook.') 

 

 

def __lt__(self, other): 

""" 

Returns True if ``self`` is less than ``other`` with respect to the 

key ``(self.from_state, self.word_in, self.to_state, self.word_out)``. 

 

INPUT: 

 

- `other` -- a transition. 

 

OUTPUT: 

 

True or False. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: FSMTransition(0,1,0,0) < FSMTransition(1,0,0,0) 

True 

""" 

return (self.from_state, self.word_in, self.to_state, self.word_out) < \ 

(other.from_state, other.word_in, other.to_state, other.word_out) 

 

 

def __copy__(self): 

""" 

Returns a (shallow) copy of the transition. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A new transition. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: t = FSMTransition('A', 'B', 0) 

sage: copy(t) 

Transition from 'A' to 'B': 0|- 

""" 

new = FSMTransition(self.from_state, self.to_state, 

self.word_in, self.word_out) 

if hasattr(self, 'hook'): 

new.hook = self.hook 

return new 

 

 

copy = __copy__ 

 

 

def __deepcopy__(self, memo): 

""" 

Returns a deep copy of the transition. 

 

INPUT: 

 

- ``memo`` -- a dictionary storing already processed elements. 

 

OUTPUT: 

 

A new transition. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: t = FSMTransition('A', 'B', 0) 

sage: deepcopy(t) 

Transition from 'A' to 'B': 0|- 

""" 

from copy import deepcopy 

new = FSMTransition(deepcopy(self.from_state, memo), 

deepcopy(self.to_state, memo), 

deepcopy(self.word_in, memo), 

deepcopy(self.word_out, memo)) 

if hasattr(self, 'hook'): 

new.hook = deepcopy(self.hook, memo) 

return new 

 

 

def deepcopy(self, memo=None): 

""" 

Returns a deep copy of the transition. 

 

INPUT: 

 

- ``memo`` -- (default: ``None``) a dictionary storing already 

processed elements. 

 

OUTPUT: 

 

A new transition. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: t = FSMTransition('A', 'B', 0) 

sage: deepcopy(t) 

Transition from 'A' to 'B': 0|- 

""" 

from copy import deepcopy 

return deepcopy(self, memo) 

 

 

def __hash__(self): 

""" 

Since transitions are mutable, they should not be hashable, so 

we return a type error. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

The hash of this transition. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: hash(FSMTransition('A', 'B')) 

Traceback (most recent call last): 

... 

TypeError: Transitions are mutable, and thus not hashable. 

 

""" 

raise TypeError("Transitions are mutable, and thus not hashable.") 

 

 

def _repr_(self): 

""" 

Represents a transitions as from state to state and input, output. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A string. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: FSMTransition('A', 'B', 0, 0)._repr_() 

"Transition from 'A' to 'B': 0|0" 

 

""" 

return "Transition from %s to %s: %s" % (repr(self.from_state), 

repr(self.to_state), 

self._in_out_label_()) 

 

 

def _in_out_label_(self): 

""" 

Returns the input and output of a transition as 

"word_in|word_out". 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A string of the input and output labels. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: FSMTransition('A', 'B', 0, 1)._in_out_label_() 

'0|1' 

""" 

return "%s|%s" % (FSMWordSymbol(self.word_in), 

FSMWordSymbol(self.word_out)) 

 

 

def __eq__(left, right): 

""" 

Returns True if the two transitions are the same, i.e., if the 

both go from the same states to the same states and read and 

write the same words. 

 

Note that the hooks are not checked. 

 

INPUT: 

 

- ``left`` -- a transition. 

 

- ``right`` -- a transition. 

 

OUTPUT: 

 

True or False. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition 

sage: A = FSMState('A', is_initial=True) 

sage: t1 = FSMTransition('A', 'B', 0, 1) 

sage: t2 = FSMTransition(A, 'B', 0, 1) 

sage: t1 == t2 

True 

""" 

if not is_FSMTransition(right): 

raise TypeError('Only instances of FSMTransition ' \ 

'can be compared.') 

return left.from_state == right.from_state \ 

and left.to_state == right.to_state \ 

and left.word_in == right.word_in \ 

and left.word_out == right.word_out 

 

 

def __ne__(left, right): 

""" 

 

INPUT: 

 

- ``left`` -- a transition. 

 

- ``right`` -- a transition. 

 

OUTPUT: 

 

True or False. 

Tests for inequality, complement of __eq__. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition 

sage: A = FSMState('A', is_initial=True) 

sage: t1 = FSMTransition('A', 'B', 0, 1) 

sage: t2 = FSMTransition(A, 'B', 0, 1) 

sage: t1 != t2 

False 

""" 

return (not (left == right)) 

 

 

def __bool__(self): 

""" 

Returns True. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

True or False. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: bool(FSMTransition('A', 'B', 0)) 

True 

""" 

return True # A transition cannot be zero (see __init__) 

 

__nonzero__ = __bool__ 

 

 

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

 

 

def is_FiniteStateMachine(FSM): 

""" 

Tests whether or not ``FSM`` inherits from :class:`FiniteStateMachine`. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import is_FiniteStateMachine 

sage: is_FiniteStateMachine(FiniteStateMachine()) 

True 

sage: is_FiniteStateMachine(Automaton()) 

True 

sage: is_FiniteStateMachine(Transducer()) 

True 

""" 

return isinstance(FSM, FiniteStateMachine) 

 

 

def duplicate_transition_ignore(old_transition, new_transition): 

""" 

Default function for handling duplicate transitions in finite 

state machines. This implementation ignores the occurrence. 

 

See the documentation of the ``on_duplicate_transition`` parameter 

of :class:`FiniteStateMachine`. 

 

INPUT: 

 

- ``old_transition`` -- A transition in a finite state machine. 

 

- ``new_transition`` -- A transition, identical to ``old_transition``, 

which is to be inserted into the finite state machine. 

 

OUTPUT: 

 

The same transition, unchanged. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import duplicate_transition_ignore 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: duplicate_transition_ignore(FSMTransition(0, 0, 1), 

....: FSMTransition(0, 0, 1)) 

Transition from 0 to 0: 1|- 

""" 

return old_transition 

 

 

def duplicate_transition_raise_error(old_transition, new_transition): 

""" 

Alternative function for handling duplicate transitions in finite 

state machines. This implementation raises a ``ValueError``. 

 

See the documentation of the ``on_duplicate_transition`` parameter 

of :class:`FiniteStateMachine`. 

 

INPUT: 

 

- ``old_transition`` -- A transition in a finite state machine. 

 

- ``new_transition`` -- A transition, identical to ``old_transition``, 

which is to be inserted into the finite state machine. 

 

OUTPUT: 

 

Nothing. A ``ValueError`` is raised. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import duplicate_transition_raise_error 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: duplicate_transition_raise_error(FSMTransition(0, 0, 1), 

....: FSMTransition(0, 0, 1)) 

Traceback (most recent call last): 

... 

ValueError: Attempting to re-insert transition Transition from 0 to 0: 1|- 

""" 

raise ValueError("Attempting to re-insert transition %s" % old_transition) 

 

 

def duplicate_transition_add_input(old_transition, new_transition): 

""" 

Alternative function for handling duplicate transitions in finite 

state machines. This implementation adds the input label of the 

new transition to the input label of the old transition. This is 

intended for the case where a Markov chain is modelled by a finite 

state machine using the input labels as transition probabilities. 

 

See the documentation of the ``on_duplicate_transition`` parameter 

of :class:`FiniteStateMachine`. 

 

INPUT: 

 

- ``old_transition`` -- A transition in a finite state machine. 

 

- ``new_transition`` -- A transition, identical to ``old_transition``, 

which is to be inserted into the finite state machine. 

 

OUTPUT: 

 

A transition whose input weight is the sum of the input 

weights of ``old_transition`` and ``new_transition``. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: duplicate_transition_add_input(FSMTransition('a', 'a', 1/2), 

....: FSMTransition('a', 'a', 1/2)) 

Transition from 'a' to 'a': 1|- 

 

Input labels must be lists of length 1:: 

 

sage: duplicate_transition_add_input(FSMTransition('a', 'a', [1, 1]), 

....: FSMTransition('a', 'a', [1, 1])) 

Traceback (most recent call last): 

... 

TypeError: Trying to use duplicate_transition_add_input on 

"Transition from 'a' to 'a': 1,1|-" and 

"Transition from 'a' to 'a': 1,1|-", 

but input words are assumed to be lists of length 1 

""" 

if (hasattr(old_transition.word_in, '__iter__') 

and len(old_transition.word_in) == 1 

and hasattr(new_transition.word_in, '__iter__') 

and len(new_transition.word_in) == 1): 

old_transition.word_in = [old_transition.word_in[0] 

+ new_transition.word_in[0]] 

else: 

raise TypeError('Trying to use duplicate_transition_add_input on ' + 

'"%s" and "%s", ' % (old_transition, new_transition) + 

'but input words are assumed to be lists of length 1') 

return old_transition 

 

 

class FiniteStateMachine(sage.structure.sage_object.SageObject): 

""" 

Class for a finite state machine. 

 

A finite state machine is a finite set of states connected by 

transitions. 

 

INPUT: 

 

- ``data`` -- can be any of the following: 

 

#. a dictionary of dictionaries (of transitions), 

 

#. a dictionary of lists (of states or transitions), 

 

#. a list (of transitions), 

 

#. a function (transition function), 

 

#. an other instance of a finite state machine. 

 

- ``initial_states`` and ``final_states`` -- the initial and 

final states of this machine 

 

- ``input_alphabet`` and ``output_alphabet`` -- the input and 

output alphabets of this machine 

 

- ``determine_alphabets`` -- If ``True``, then the function 

:meth:`.determine_alphabets` is called after ``data`` was read and 

processed, if ``False``, then not. If it is ``None``, then it is 

decided during the construction of the finite state machine 

whether :meth:`.determine_alphabets` should be called. 

 

- ``with_final_word_out`` -- If given (not ``None``), then the 

function :meth:`.with_final_word_out` (more precisely, its inplace 

pendant :meth:`.construct_final_word_out`) is called with input 

``letters=with_final_word_out`` at the end of the creation 

process. 

 

- ``store_states_dict`` -- If ``True``, then additionally the states 

are stored in an internal dictionary for speed up. 

 

- ``on_duplicate_transition`` -- A function which is called when a 

transition is inserted into ``self`` which already existed (same 

``from_state``, same ``to_state``, same ``word_in``, same ``word_out``). 

 

This function is assumed to take two arguments, the first being 

the already existing transition, the second being the new 

transition (as an :class:`FSMTransition`). The function must 

return the (possibly modified) original transition. 

 

By default, we have ``on_duplicate_transition=None``, which is 

interpreted as 

``on_duplicate_transition=duplicate_transition_ignore``, where 

``duplicate_transition_ignore`` is a predefined function 

ignoring the occurrence. Other such predefined functions are 

``duplicate_transition_raise_error`` and 

``duplicate_transition_add_input``. 

 

OUTPUT: 

 

A finite state machine. 

 

The object creation of :class:`Automaton` and :class:`Transducer` 

is the same as the one described here (i.e. just replace the word 

``FiniteStateMachine`` by ``Automaton`` or ``Transducer``). 

 

Each transition of an automaton has an input label. Automata can, 

for example, be determinised (see 

:meth:`Automaton.determinisation`) and minimized (see 

:meth:`Automaton.minimization`). Each transition of a transducer 

has an input and an output label. Transducers can, for example, be 

simplified (see :meth:`Transducer.simplification`). 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition 

 

See documentation for more examples. 

 

We illustrate the different input formats: 

 

#. The input-data can be a dictionary of dictionaries, where 

 

- the keys of the outer dictionary are state-labels (from-states of 

transitions), 

- the keys of the inner dictionaries are state-labels (to-states of 

transitions), 

- the values of the inner dictionaries specify the transition 

more precisely. 

 

The easiest is to use a tuple consisting of an input and an 

output word:: 

 

sage: FiniteStateMachine({'a':{'b':(0, 1), 'c':(1, 1)}}) 

Finite state machine with 3 states 

 

Instead of the tuple anything iterable (e.g. a list) can be 

used as well. 

 

If you want to use the arguments of :class:`FSMTransition` 

directly, you can use a dictionary:: 

 

sage: FiniteStateMachine({'a':{'b':{'word_in':0, 'word_out':1}, 

....: 'c':{'word_in':1, 'word_out':1}}}) 

Finite state machine with 3 states 

 

In the case you already have instances of 

:class:`FSMTransition`, it is possible to use them directly:: 

 

sage: FiniteStateMachine({'a':{'b':FSMTransition('a', 'b', 0, 1), 

....: 'c':FSMTransition('a', 'c', 1, 1)}}) 

Finite state machine with 3 states 

 

#. The input-data can be a dictionary of lists, where the keys 

are states or label of states. 

 

The list-elements can be states:: 

 

sage: a = FSMState('a') 

sage: b = FSMState('b') 

sage: c = FSMState('c') 

sage: FiniteStateMachine({a:[b, c]}) 

Finite state machine with 3 states 

 

Or the list-elements can simply be labels of states:: 

 

sage: FiniteStateMachine({'a':['b', 'c']}) 

Finite state machine with 3 states 

 

The list-elements can also be transitions:: 

 

sage: FiniteStateMachine({'a':[FSMTransition('a', 'b', 0, 1), 

....: FSMTransition('a', 'c', 1, 1)]}) 

Finite state machine with 3 states 

 

Or they can be tuples of a label, an input word and an output 

word specifying a transition:: 

 

sage: FiniteStateMachine({'a':[('b', 0, 1), ('c', 1, 1)]}) 

Finite state machine with 3 states 

 

#. The input-data can be a list, where its elements specify 

transitions:: 

 

sage: FiniteStateMachine([FSMTransition('a', 'b', 0, 1), 

....: FSMTransition('a', 'c', 1, 1)]) 

Finite state machine with 3 states 

 

It is possible to skip ``FSMTransition`` in the example above:: 

 

sage: FiniteStateMachine([('a', 'b', 0, 1), ('a', 'c', 1, 1)]) 

Finite state machine with 3 states 

 

The parameters of the transition are given in tuples. Anyhow, 

anything iterable (e.g. a list) is possible. 

 

You can also name the parameters of the transition. For this 

purpose you take a dictionary:: 

 

sage: FiniteStateMachine([{'from_state':'a', 'to_state':'b', 

....: 'word_in':0, 'word_out':1}, 

....: {'from_state':'a', 'to_state':'c', 

....: 'word_in':1, 'word_out':1}]) 

Finite state machine with 3 states 

 

Other arguments, which :class:`FSMTransition` accepts, can be 

added, too. 

 

#. The input-data can also be function acting as transition 

function: 

 

This function has two input arguments: 

 

#. a label of a state (from which the transition starts), 

 

#. a letter of the (input-)alphabet (as input-label of the transition). 

 

It returns a tuple with the following entries: 

 

#. a label of a state (to which state the transition goes), 

 

#. a letter of or a word over the (output-)alphabet (as 

output-label of the transition). 

 

It may also output a list of such tuples if several 

transitions from the from-state and the input letter exist 

(this means that the finite state machine is 

non-deterministic). 

 

If the transition does not exist, the function should raise a 

``LookupError`` or return an empty list. 

 

When constructing a finite state machine in this way, some 

initial states and an input alphabet have to be specified. 

 

:: 

 

sage: def f(state_from, read): 

....: if int(state_from) + read <= 2: 

....: state_to = 2*int(state_from)+read 

....: write = 0 

....: else: 

....: state_to = 2*int(state_from) + read - 5 

....: write = 1 

....: return (str(state_to), write) 

sage: F = FiniteStateMachine(f, input_alphabet=[0, 1], 

....: initial_states=['0'], 

....: final_states=['0']) 

sage: F([1, 0, 1]) 

(True, '0', [0, 0, 1]) 

 

#. The input-data can be an other instance of a finite state machine:: 

 

sage: F = FiniteStateMachine() 

sage: G = Transducer(F) 

sage: G == F 

True 

 

The other parameters cannot be specified in that case. If you 

want to change these, use the attributes 

:attr:`FSMState.is_initial`, :attr:`FSMState.is_final`, 

:attr:`input_alphabet`, :attr:`output_alphabet`, 

:attr:`on_duplicate_transition` and methods 

:meth:`.determine_alphabets`, 

:meth:`.construct_final_word_out` on the new machine, 

respectively. 

 

The following examples demonstrate the use of ``on_duplicate_transition``:: 

 

sage: F = FiniteStateMachine([['a', 'a', 1/2], ['a', 'a', 1/2]]) 

sage: F.transitions() 

[Transition from 'a' to 'a': 1/2|-] 

 

:: 

 

sage: from sage.combinat.finite_state_machine import duplicate_transition_raise_error 

sage: F1 = FiniteStateMachine([['a', 'a', 1/2], ['a', 'a', 1/2]], 

....: on_duplicate_transition=duplicate_transition_raise_error) 

Traceback (most recent call last): 

... 

ValueError: Attempting to re-insert transition Transition from 'a' to 'a': 1/2|- 

 

Use ``duplicate_transition_add_input`` to emulate a Markov chain, 

the input labels are considered as transition probabilities:: 

 

sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input 

sage: F = FiniteStateMachine([['a', 'a', 1/2], ['a', 'a', 1/2]], 

....: on_duplicate_transition=duplicate_transition_add_input) 

sage: F.transitions() 

[Transition from 'a' to 'a': 1|-] 

 

Use ``with_final_word_out`` to construct final output:: 

 

sage: T = Transducer([(0, 1, 0, 0), (1, 0, 0, 0)], 

....: initial_states=[0], 

....: final_states=[0], 

....: with_final_word_out=0) 

sage: for s in T.iter_final_states(): 

....: print("{} {}".format(s, s.final_word_out)) 

0 [] 

1 [0] 

 

TESTS:: 

 

sage: a = FSMState('S_a', 'a') 

sage: b = FSMState('S_b', 'b') 

sage: c = FSMState('S_c', 'c') 

sage: d = FSMState('S_d', 'd') 

sage: FiniteStateMachine({a:[b, c], b:[b, c, d], 

....: c:[a, b], d:[a, c]}) 

Finite state machine with 4 states 

 

We have several constructions which lead to the same finite 

state machine:: 

 

sage: A = FSMState('A') 

sage: B = FSMState('B') 

sage: C = FSMState('C') 

sage: FSM1 = FiniteStateMachine( 

....: {A:{B:{'word_in':0, 'word_out':1}, 

....: C:{'word_in':1, 'word_out':1}}}) 

sage: FSM2 = FiniteStateMachine({A:{B:(0, 1), C:(1, 1)}}) 

sage: FSM3 = FiniteStateMachine( 

....: {A:{B:FSMTransition(A, B, 0, 1), 

....: C:FSMTransition(A, C, 1, 1)}}) 

sage: FSM4 = FiniteStateMachine({A:[(B, 0, 1), (C, 1, 1)]}) 

sage: FSM5 = FiniteStateMachine( 

....: {A:[FSMTransition(A, B, 0, 1), FSMTransition(A, C, 1, 1)]}) 

sage: FSM6 = FiniteStateMachine( 

....: [{'from_state':A, 'to_state':B, 'word_in':0, 'word_out':1}, 

....: {'from_state':A, 'to_state':C, 'word_in':1, 'word_out':1}]) 

sage: FSM7 = FiniteStateMachine([(A, B, 0, 1), (A, C, 1, 1)]) 

sage: FSM8 = FiniteStateMachine( 

....: [FSMTransition(A, B, 0, 1), FSMTransition(A, C, 1, 1)]) 

 

sage: FSM1 == FSM2 == FSM3 == FSM4 == FSM5 == FSM6 == FSM7 == FSM8 

True 

 

It is possible to skip ``FSMTransition`` in the example above. 

 

Some more tests for different input-data:: 

 

sage: FiniteStateMachine({'a':{'a':[0, 0], 'b':[1, 1]}, 

....: 'b':{'b':[1, 0]}}) 

Finite state machine with 2 states 

 

sage: a = FSMState('S_a', 'a') 

sage: b = FSMState('S_b', 'b') 

sage: c = FSMState('S_c', 'c') 

sage: d = FSMState('S_d', 'd') 

sage: t1 = FSMTransition(a, b) 

sage: t2 = FSMTransition(b, c) 

sage: t3 = FSMTransition(b, d) 

sage: t4 = FSMTransition(c, d) 

sage: FiniteStateMachine([t1, t2, t3, t4]) 

Finite state machine with 4 states 

 

We test that no input parameter is allowed when creating a finite 

state machine from an existing instance:: 

 

sage: F = FiniteStateMachine() 

sage: FiniteStateMachine(F, initial_states=[1]) 

Traceback (most recent call last): 

... 

ValueError: initial_states cannot be specified when 

copying another finite state machine. 

sage: FiniteStateMachine(F, final_states=[1]) 

Traceback (most recent call last): 

... 

ValueError: final_states cannot be specified when 

copying another finite state machine. 

sage: FiniteStateMachine(F, input_alphabet=[1]) 

Traceback (most recent call last): 

... 

ValueError: input_alphabet cannot be specified when 

copying another finite state machine. 

sage: FiniteStateMachine(F, output_alphabet=[1]) 

Traceback (most recent call last): 

... 

ValueError: output_alphabet cannot be specified when 

copying another finite state machine. 

sage: from sage.combinat.finite_state_machine import ( 

....: duplicate_transition_add_input) 

sage: FiniteStateMachine(F, 

....: on_duplicate_transition=duplicate_transition_add_input) 

Traceback (most recent call last): 

... 

ValueError: on_duplicate_transition cannot be specified when 

copying another finite state machine. 

sage: FiniteStateMachine(F, determine_alphabets=False) 

Traceback (most recent call last): 

... 

ValueError: determine_alphabets cannot be specified when 

copying another finite state machine. 

sage: FiniteStateMachine(F, with_final_word_out=[1]) 

Traceback (most recent call last): 

... 

ValueError: with_final_word_out cannot be specified when 

copying another finite state machine. 

 

:trac:`19454` rewrote automatic detection of the alphabets:: 

 

sage: def transition_function(state, letter): 

....: return (0, 3 + letter) 

sage: T1 = Transducer(transition_function, 

....: input_alphabet=[0, 1], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: T1.output_alphabet 

[3, 4] 

sage: T2 = Transducer([(0, 0, 0, 3), (0, 0, 0, 4)], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: T2.output_alphabet 

[3, 4] 

sage: T = Transducer([(0, 0, 1, 2)]) 

sage: (T.input_alphabet, T.output_alphabet) 

([1], [2]) 

sage: T = Transducer([(0, 0, 1, 2)], determine_alphabets=False) 

sage: (T.input_alphabet, T.output_alphabet) 

(None, None) 

sage: T = Transducer([(0, 0, 1, 2)], input_alphabet=[0, 1]) 

sage: (T.input_alphabet, T.output_alphabet) 

([0, 1], [2]) 

sage: T = Transducer([(0, 0, 1, 2)], output_alphabet=[2, 3]) 

sage: (T.input_alphabet, T.output_alphabet) 

([1], [2, 3]) 

sage: T = Transducer([(0, 0, 1, 2)], input_alphabet=[0, 1], 

....: output_alphabet=[2, 3]) 

sage: (T.input_alphabet, T.output_alphabet) 

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

 

.. automethod:: __call__ 

""" 

 

on_duplicate_transition = duplicate_transition_ignore 

""" 

Which function to call when a duplicate transition is inserted. 

 

It can be set by the parameter ``on_duplicate_transition`` when 

initializing a finite state machine, see 

:class:`FiniteStateMachine`. 

 

.. SEEALSO:: 

 

:class:`FiniteStateMachine`, :meth:`is_Markov_chain`, 

:meth:`markov_chain_simplification`. 

""" 

 

input_alphabet = None 

""" 

A list of letters representing the input alphabet of the finite 

state machine. 

 

It can be set by the parameter ``input_alphabet`` when initializing 

a finite state machine, see :class:`FiniteStateMachine`. 

 

It can also be set by the method :meth:`determine_alphabets`. 

 

.. SEEALSO:: 

 

:class:`FiniteStateMachine`, :meth:`determine_alphabets`, 

:attr:`output_alphabet`. 

""" 

 

output_alphabet = None 

""" 

A list of letters representing the output alphabet of the finite 

state machine. 

 

It can be set by the parameter ``output_alphabet`` when initializing 

a finite state machine, see :class:`FiniteStateMachine`. 

 

It can also be set by the method :meth:`determine_alphabets`. 

 

.. SEEALSO:: 

 

:class:`FiniteStateMachine`, 

:meth:`determine_alphabets`, 

:attr:`input_alphabet`. 

""" 

 

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

# init 

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

 

 

def __init__(self, 

data=None, 

initial_states=None, final_states=None, 

input_alphabet=None, output_alphabet=None, 

determine_alphabets=None, 

with_final_word_out=None, 

store_states_dict=True, 

on_duplicate_transition=None): 

""" 

See :class:`FiniteStateMachine` for more information. 

 

TESTS:: 

 

sage: FiniteStateMachine() 

Empty finite state machine 

""" 

self._states_ = [] # List of states in the finite state 

# machine. Each state stores a list of 

# outgoing transitions. 

if store_states_dict: 

self._states_dict_ = {} 

 

self._allow_composition_ = True 

 

if is_FiniteStateMachine(data): 

if initial_states is not None: 

raise ValueError( 

"initial_states cannot be specified when copying " 

"another finite state machine.") 

if final_states is not None: 

raise ValueError( 

"final_states cannot be specified when copying " 

"another finite state machine.") 

if input_alphabet is not None: 

raise ValueError( 

"input_alphabet cannot be specified when copying " 

"another finite state machine.") 

if output_alphabet is not None: 

raise ValueError( 

"output_alphabet cannot be specified when copying " 

"another finite state machine.") 

if on_duplicate_transition is not None: 

raise ValueError( 

"on_duplicate_transition cannot be specified when " 

"copying another finite state machine.") 

if determine_alphabets is not None: 

raise ValueError( 

"determine_alphabets cannot be specified when " 

"copying another finite state machine.") 

if with_final_word_out is not None: 

raise ValueError( 

"with_final_word_out cannot be specified when " 

"copying another finite state machine.") 

 

self._copy_from_other_(data) 

return 

 

 

if initial_states is not None: 

if not hasattr(initial_states, '__iter__'): 

raise TypeError('Initial states must be iterable ' \ 

'(e.g. a list of states).') 

for s in initial_states: 

state = self.add_state(s) 

state.is_initial = True 

 

if final_states is not None: 

if not hasattr(final_states, '__iter__'): 

raise TypeError('Final states must be iterable ' \ 

'(e.g. a list of states).') 

for s in final_states: 

state = self.add_state(s) 

state.is_final = True 

 

self.input_alphabet = input_alphabet 

self.output_alphabet = output_alphabet 

 

if on_duplicate_transition is None: 

on_duplicate_transition = duplicate_transition_ignore 

if hasattr(on_duplicate_transition, '__call__'): 

self.on_duplicate_transition=on_duplicate_transition 

else: 

raise TypeError('on_duplicate_transition must be callable') 

 

if data is None: 

pass 

elif hasattr(data, 'iteritems'): 

# data is a dict (or something similar), 

# format: key = from_state, value = iterator of transitions 

for (sf, iter_transitions) in six.iteritems(data): 

self.add_state(sf) 

if hasattr(iter_transitions, 'iteritems'): 

for (st, transition) in six.iteritems(iter_transitions): 

self.add_state(st) 

if is_FSMTransition(transition): 

self.add_transition(transition) 

elif hasattr(transition, 'iteritems'): 

self.add_transition(sf, st, **transition) 

elif hasattr(transition, '__iter__'): 

self.add_transition(sf, st, *transition) 

else: 

self.add_transition(sf, st, transition) 

elif hasattr(iter_transitions, '__iter__'): 

for transition in iter_transitions: 

if hasattr(transition, '__iter__'): 

L = [sf] 

L.extend(transition) 

elif is_FSMTransition(transition): 

L = transition 

else: 

L = [sf, transition] 

self.add_transition(L) 

else: 

raise TypeError('Wrong input data for transition.') 

elif hasattr(data, '__iter__'): 

# data is a something that is iterable, 

# items are transitions 

for transition in data: 

if is_FSMTransition(transition): 

self.add_transition(transition) 

elif hasattr(transition, 'iteritems'): 

self.add_transition(transition) 

elif hasattr(transition, '__iter__'): 

self.add_transition(transition) 

else: 

raise TypeError('Wrong input data for transition.') 

elif hasattr(data, '__call__'): 

self.add_from_transition_function(data) 

else: 

raise TypeError('Cannot decide what to do with data.') 

 

if determine_alphabets is None and data is not None: 

if input_alphabet is None: 

self.determine_input_alphabet() 

if output_alphabet is None: 

self.determine_output_alphabet() 

elif determine_alphabets: 

self.determine_alphabets() 

 

if with_final_word_out is not None: 

self.construct_final_word_out(with_final_word_out) 

 

 

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

# copy and hash 

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

 

 

def __copy__(self): 

""" 

Returns a (shallow) copy of the finite state machine. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A new finite state machine. 

 

TESTS:: 

 

sage: copy(FiniteStateMachine()) 

Traceback (most recent call last): 

... 

NotImplementedError 

""" 

raise NotImplementedError 

 

 

copy = __copy__ 

 

 

def empty_copy(self, memo=None, new_class=None): 

""" 

Returns an empty deep copy of the finite state machine, i.e., 

``input_alphabet``, ``output_alphabet``, ``on_duplicate_transition`` 

are preserved, but states and transitions are not. 

 

INPUT: 

 

- ``memo`` -- a dictionary storing already processed elements. 

 

- ``new_class`` -- a class for the copy. By default 

(``None``), the class of ``self`` is used. 

 

OUTPUT: 

 

A new finite state machine. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import duplicate_transition_raise_error 

sage: F = FiniteStateMachine([('A', 'A', 0, 2), ('A', 'A', 1, 3)], 

....: input_alphabet=[0, 1], 

....: output_alphabet=[2, 3], 

....: on_duplicate_transition=duplicate_transition_raise_error) 

sage: FE = F.empty_copy(); FE 

Empty finite state machine 

sage: FE.input_alphabet 

[0, 1] 

sage: FE.output_alphabet 

[2, 3] 

sage: FE.on_duplicate_transition == duplicate_transition_raise_error 

True 

 

TESTS:: 

 

sage: T = Transducer() 

sage: type(T.empty_copy()) 

<class 'sage.combinat.finite_state_machine.Transducer'> 

sage: type(T.empty_copy(new_class=Automaton)) 

<class 'sage.combinat.finite_state_machine.Automaton'> 

""" 

if new_class is None: 

new = self.__class__() 

else: 

new = new_class() 

new._copy_from_other_(self, memo=memo, empty=True) 

return new 

 

 

def __deepcopy__(self, memo): 

""" 

Returns a deep copy of the finite state machine. 

 

INPUT: 

 

- ``memo`` -- a dictionary storing already processed elements. 

 

OUTPUT: 

 

A new finite state machine. 

 

EXAMPLES:: 

 

sage: F = FiniteStateMachine([('A', 'A', 0, 1), ('A', 'A', 1, 0)]) 

sage: deepcopy(F) 

Finite state machine with 1 state 

""" 

new = self.__class__() 

new._copy_from_other_(self) 

return new 

 

 

def deepcopy(self, memo=None): 

""" 

Returns a deep copy of the finite state machine. 

 

INPUT: 

 

- ``memo`` -- (default: ``None``) a dictionary storing already 

processed elements. 

 

OUTPUT: 

 

A new finite state machine. 

 

EXAMPLES:: 

 

sage: F = FiniteStateMachine([('A', 'A', 0, 1), ('A', 'A', 1, 0)]) 

sage: deepcopy(F) 

Finite state machine with 1 state 

 

TESTS: 

 

Make sure that the links between transitions and states 

are still intact:: 

 

sage: C = deepcopy(F) 

sage: C.transitions()[0].from_state is C.state('A') 

True 

sage: C.transitions()[0].to_state is C.state('A') 

True 

 

""" 

from copy import deepcopy 

return deepcopy(self, memo) 

 

 

def _copy_from_other_(self, other, memo=None, empty=False): 

""" 

Copy all data from other to self, to be used in the constructor. 

 

INPUT: 

 

- ``other`` -- a :class:`FiniteStateMachine`. 

 

OUTPUT: 

 

Nothing. 

 

EXAMPLES:: 

 

sage: A = Automaton([(0, 0, 0)], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: B = Automaton() 

sage: B._copy_from_other_(A) 

sage: A == B 

True 

""" 

from copy import deepcopy 

if memo is None: 

memo = {} 

self.input_alphabet = deepcopy(other.input_alphabet, memo) 

self.output_alphabet = deepcopy(other.output_alphabet, memo) 

self.on_duplicate_transition = other.on_duplicate_transition 

 

if not empty: 

relabel = hasattr(other, '_deepcopy_relabel_') 

relabel_iter = itertools.count(0) 

for state in other.iter_states(): 

if relabel: 

if other._deepcopy_labels_ is None: 

state._deepcopy_relabel_ = next(relabel_iter) 

elif hasattr(other._deepcopy_labels_, '__call__'): 

state._deepcopy_relabel_ = \ 

other._deepcopy_labels_(state.label()) 

elif hasattr(other._deepcopy_labels_, '__getitem__'): 

state._deepcopy_relabel_ = \ 

other._deepcopy_labels_[state.label()] 

else: 

raise TypeError("labels must be None, a callable " 

"or a dictionary.") 

s = deepcopy(state, memo) 

if relabel: 

del state._deepcopy_relabel_ 

self.add_state(s) 

for transition in other.iter_transitions(): 

self.add_transition(deepcopy(transition, memo)) 

 

 

def relabeled(self, memo=None, labels=None): 

""" 

Returns a deep copy of the finite state machine, but the 

states are relabeled. 

 

INPUT: 

 

- ``memo`` -- (default: ``None``) a dictionary storing already 

processed elements. 

 

- ``labels`` -- (default: ``None``) a dictionary or callable 

mapping old labels to new labels. If ``None``, then the new 

labels are integers starting with 0. 

 

OUTPUT: 

 

A new finite state machine. 

 

EXAMPLES:: 

 

sage: FSM1 = FiniteStateMachine([('A', 'B'), ('B', 'C'), ('C', 'A')]) 

sage: FSM1.states() 

['A', 'B', 'C'] 

sage: FSM2 = FSM1.relabeled() 

sage: FSM2.states() 

[0, 1, 2] 

sage: FSM3 = FSM1.relabeled(labels={'A': 'a', 'B': 'b', 'C': 'c'}) 

sage: FSM3.states() 

['a', 'b', 'c'] 

sage: FSM4 = FSM2.relabeled(labels=lambda x: 2*x) 

sage: FSM4.states() 

[0, 2, 4] 

 

TESTS:: 

 

sage: FSM2.relabeled(labels=1) 

Traceback (most recent call last): 

... 

TypeError: labels must be None, a callable or a dictionary. 

""" 

from copy import deepcopy 

 

self._deepcopy_relabel_ = True 

self._deepcopy_labels_ = labels 

new = deepcopy(self, memo) 

del self._deepcopy_relabel_ 

del self._deepcopy_labels_ 

return new 

 

 

def induced_sub_finite_state_machine(self, states): 

""" 

Returns a sub-finite-state-machine of the finite state machine 

induced by the given states. 

 

INPUT: 

 

- ``states`` -- a list (or an iterator) of states (either labels or 

instances of :class:`FSMState`) of the sub-finite-state-machine. 

 

OUTPUT: 

 

A new finite state machine. It consists (of deep copies) of 

the given states and (deep copies) of all transitions of ``self`` 

between these states. 

 

EXAMPLES:: 

 

sage: FSM = FiniteStateMachine([(0, 1, 0), (0, 2, 0), 

....: (1, 2, 0), (2, 0, 0)]) 

sage: sub_FSM = FSM.induced_sub_finite_state_machine([0, 1]) 

sage: sub_FSM.states() 

[0, 1] 

sage: sub_FSM.transitions() 

[Transition from 0 to 1: 0|-] 

sage: FSM.induced_sub_finite_state_machine([3]) 

Traceback (most recent call last): 

... 

ValueError: 3 is not a state of this finite state machine. 

 

TESTS: 

 

Make sure that the links between transitions and states 

are still intact:: 

 

sage: sub_FSM.transitions()[0].from_state is sub_FSM.state(0) 

True 

 

""" 

from copy import deepcopy 

 

good_states = set() 

for state in states: 

if not self.has_state(state): 

raise ValueError("%s is not a state of this finite state machine." % state) 

good_states.add(self.state(state)) 

 

memo = {} 

new = self.empty_copy(memo=memo) 

for state in good_states: 

s = deepcopy(state, memo) 

new.add_state(s) 

 

for state in good_states: 

for transition in self.iter_transitions(state): 

if transition.to_state in good_states: 

new.add_transition(deepcopy(transition, memo)) 

 

return new 

 

 

def __hash__(self): 

""" 

Since finite state machines are mutable, they should not be 

hashable, so we return a type error. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

The hash of this finite state machine. 

 

EXAMPLES:: 

 

sage: hash(FiniteStateMachine()) 

Traceback (most recent call last): 

... 

TypeError: Finite state machines are mutable, and thus not hashable. 

""" 

if getattr(self, "_immutable", False): 

return hash((tuple(self.states()), tuple(self.transitions()))) 

raise TypeError("Finite state machines are mutable, " \ 

"and thus not hashable.") 

 

 

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

# operators 

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

 

 

def __or__(self, other): 

""" 

Return the disjoint union of this and another finite state machine. 

 

INPUT: 

 

- ``other`` -- a finite state machine. 

 

OUTPUT: 

 

A new finite state machine. 

 

.. SEEALSO:: 

 

:meth:`.disjoint_union`, :meth:`.__and__`, 

:meth:`Automaton.intersection`, 

:meth:`Transducer.intersection`. 

 

TESTS:: 

 

sage: FiniteStateMachine() | FiniteStateMachine([('A', 'B')]) 

Finite state machine with 2 states 

sage: FiniteStateMachine() | 42 

Traceback (most recent call last): 

... 

TypeError: Can only add finite state machine 

""" 

if is_FiniteStateMachine(other): 

return self.disjoint_union(other) 

else: 

raise TypeError("Can only add finite state machine") 

 

 

__add__ = __or__ 

 

 

def __iadd__(self, other): 

""" 

TESTS:: 

 

sage: F = FiniteStateMachine() 

sage: F += FiniteStateMachine() 

Traceback (most recent call last): 

... 

NotImplementedError 

""" 

raise NotImplementedError 

 

 

def __and__(self, other): 

""" 

Returns the intersection of ``self`` with ``other``. 

 

TESTS:: 

 

sage: FiniteStateMachine() & FiniteStateMachine([('A', 'B')]) 

Traceback (most recent call last): 

... 

NotImplementedError 

""" 

if is_FiniteStateMachine(other): 

return self.intersection(other) 

 

 

def __imul__(self, other): 

""" 

TESTS:: 

 

sage: F = FiniteStateMachine() 

sage: F *= FiniteStateMachine() 

Traceback (most recent call last): 

... 

NotImplementedError 

""" 

raise NotImplementedError 

 

 

def __call__(self, *args, **kwargs): 

""" 

Call either method :meth:`.composition` or :meth:`.process` 

(with ``full_output=False``). If the input is not finite 

(``is_finite`` of input is ``False``), then 

:meth:`.iter_process` (with ``iterator_type='simple'``) is 

called. Moreover, the flag ``automatic_output_type`` is set 

(unless ``format_output`` is specified). 

See the documentation of these functions for possible 

parameters. 

 

EXAMPLES: 

 

The following code performs a :meth:`composition`:: 

 

sage: F = Transducer([('A', 'B', 1, 0), ('B', 'B', 1, 1), 

....: ('B', 'B', 0, 0)], 

....: initial_states=['A'], final_states=['B']) 

sage: G = Transducer([(1, 1, 0, 0), (1, 2, 1, 0), 

....: (2, 2, 0, 1), (2, 1, 1, 1)], 

....: initial_states=[1], final_states=[1]) 

sage: H = G(F) 

sage: H.states() 

[('A', 1), ('B', 1), ('B', 2)] 

 

An automaton or transducer can also act on an input (an list 

or other iterable of letters):: 

 

sage: binary_inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, 

....: initial_states=['A'], final_states=['A']) 

sage: binary_inverter([0, 1, 0, 0, 1, 1]) 

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

 

We can also let them act on :doc:`words <words/words>`:: 

 

sage: W = Words([0, 1]); W 

Finite and infinite words over {0, 1} 

sage: binary_inverter(W([0, 1, 1, 0, 1, 1])) 

word: 100100 

 

Infinite words work as well:: 

 

sage: words.FibonacciWord() 

word: 0100101001001010010100100101001001010010... 

sage: binary_inverter(words.FibonacciWord()) 

word: 1011010110110101101011011010110110101101... 

 

When only one successful path is found in a non-deterministic 

transducer, the result of that path is returned. 

 

:: 

 

sage: T = Transducer([(0, 1, 0, 1), (0, 2, 0, 2)], 

....: initial_states=[0], final_states=[1]) 

sage: T.process([0]) 

[(True, 1, [1]), (False, 2, [2])] 

sage: T([0]) 

[1] 

 

.. SEEALSO:: 

 

:meth:`.composition`, 

:meth:`~FiniteStateMachine.process`, 

:meth:`~FiniteStateMachine.iter_process`, 

:meth:`Automaton.process`, 

:meth:`Transducer.process`. 

 

TESTS:: 

 

sage: F = FiniteStateMachine([(0, 1, 1, 'a'), (0, 2, 2, 'b')], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: A = Automaton([(0, 1, 1), (0, 2, 2)], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: T = Transducer([(0, 1, 1, 'a'), (0, 2, 2, 'b')], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: F([1]) 

(True, 1, ['a']) 

sage: A([1]) 

True 

sage: T([1]) 

['a'] 

sage: F([2]) 

(False, 2, ['b']) 

sage: A([2]) 

False 

sage: T([2]) 

Traceback (most recent call last): 

... 

ValueError: Invalid input sequence. 

sage: F([3]) 

(False, None, None) 

sage: A([3]) 

False 

sage: T([3]) 

Traceback (most recent call last): 

... 

ValueError: Invalid input sequence. 

 

:: 

 

sage: F = FiniteStateMachine([(11, 11, 1, 'a'), (11, 12, 2, 'b'), 

....: (11, 13, 3, 'c'), (11, 14, 4, 'd'), 

....: (12, 13, 3, 'e'), (12, 13, 3, 'f'), 

....: (12, 14, 4, 'g'), (12, 14, 4, 'h'), 

....: (12, 13, 2, 'i'), (12, 14, 2, 'j')], 

....: initial_states=[11], 

....: final_states=[13]) 

sage: def f(o): 

....: return ''.join(o) 

sage: F([0], format_output=f) 

(False, None, None) 

sage: F([3], format_output=f) 

(True, 13, 'c') 

sage: F([4], format_output=f) 

(False, 14, 'd') 

sage: F([2, 2], format_output=f) 

Traceback (most recent call last): 

... 

ValueError: Got more than one output, but only allowed to show 

one. Change list_of_outputs option. 

sage: F([2, 2], format_output=f, list_of_outputs=True) 

[(True, 13, 'bi'), (False, 14, 'bj')] 

sage: F([2, 3], format_output=f) 

Traceback (most recent call last): 

... 

ValueError: Got more than one output, but only allowed to show 

one. Change list_of_outputs option. 

sage: F([2, 3], format_output=f, list_of_outputs=True) 

[(True, 13, 'be'), (True, 13, 'bf')] 

sage: F([2, 4], format_output=f) 

Traceback (most recent call last): 

... 

ValueError: Got more than one output, but only allowed to show 

one. Change list_of_outputs option. 

sage: F([2, 4], format_output=f, list_of_outputs=True) 

[(False, 14, 'bg'), (False, 14, 'bh')] 

 

:: 

 

sage: A = Automaton([(11, 11, 1), (11, 12, 2), 

....: (11, 13, 3), (11, 14, 4), 

....: (12, 13, 3), (12, 14, 4), 

....: (12, 32, 3), (12, 42, 4), 

....: (12, 13, 2), (12, 14, 2)], 

....: initial_states=[11], 

....: final_states=[13, 32]) 

sage: def f(o): 

....: return ''.join(o) 

sage: A([0], format_output=f) 

False 

sage: A([3], format_output=f) 

True 

sage: A([4], format_output=f) 

False 

sage: A([2, 2], format_output=f) 

True 

sage: A([2, 2], format_output=f, list_of_outputs=True) 

[True, False] 

sage: A([2, 3], format_output=f) 

True 

sage: A([2, 3], format_output=f, list_of_outputs=True) 

[True, True] 

sage: A([2, 4], format_output=f) 

False 

sage: A([2, 4], format_output=f, list_of_outputs=True) 

[False, False] 

 

:: 

 

sage: T = Transducer([(11, 11, 1, 'a'), (11, 12, 2, 'b'), 

....: (11, 13, 3, 'c'), (11, 14, 4, 'd'), 

....: (12, 13, 3, 'e'), (12, 13, 3, 'f'), 

....: (12, 14, 4, 'g'), (12, 14, 4, 'h'), 

....: (12, 13, 2, 'i'), (12, 14, 2, 'j')], 

....: initial_states=[11], 

....: final_states=[13]) 

sage: def f(o): 

....: return ''.join(o) 

sage: T([0], format_output=f) 

Traceback (most recent call last): 

... 

ValueError: Invalid input sequence. 

sage: T([3], format_output=f) 

'c' 

sage: T([4], format_output=f) 

Traceback (most recent call last): 

... 

ValueError: Invalid input sequence. 

sage: T([2, 2], format_output=f) 

'bi' 

sage: T([2, 2], format_output=f, list_of_outputs=True) 

['bi', None] 

sage: T([2, 2], format_output=f, 

....: list_of_outputs=True, only_accepted=True) 

['bi'] 

sage: T.process([2, 2], format_output=f, list_of_outputs=True) 

[(True, 13, 'bi'), (False, 14, 'bj')] 

sage: T([2, 3], format_output=f) 

Traceback (most recent call last): 

... 

ValueError: Found more than one accepting path. 

sage: T([2, 3], format_output=f, list_of_outputs=True) 

['be', 'bf'] 

sage: T([2, 4], format_output=f) 

Traceback (most recent call last): 

... 

ValueError: Invalid input sequence. 

sage: T([2, 4], format_output=f, list_of_outputs=True) 

[None, None] 

 

:: 

 

sage: from itertools import islice 

sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, 

....: initial_states=['A'], final_states=['A']) 

sage: inverter(words.FibonacciWord()) 

word: 1011010110110101101011011010110110101101... 

sage: inverter(words.FibonacciWord(), automatic_output_type=True) 

word: 1011010110110101101011011010110110101101... 

sage: tuple(islice(inverter(words.FibonacciWord(), 

....: automatic_output_type=False), 10)) 

(1, 0, 1, 1, 0, 1, 0, 1, 1, 0) 

sage: type(inverter((1, 0, 1, 1, 0, 1, 0, 1, 1, 0), 

....: automatic_output_type=False)) 

<... 'list'> 

sage: type(inverter((1, 0, 1, 1, 0, 1, 0, 1, 1, 0), 

....: automatic_output_type=True)) 

<... 'tuple'> 

""" 

if len(args) == 0: 

raise TypeError("Called with too few arguments.") 

if is_FiniteStateMachine(args[0]): 

return self.composition(*args, **kwargs) 

if hasattr(args[0], '__iter__'): 

if not 'full_output' in kwargs: 

kwargs['full_output'] = False 

if not 'list_of_outputs' in kwargs: 

kwargs['list_of_outputs'] = False 

if not 'automatic_output_type' in kwargs: 

kwargs['automatic_output_type'] = not 'format_output' in kwargs 

input_tape = args[0] 

if hasattr(input_tape, 'is_finite') and \ 

not input_tape.is_finite(): 

if not 'iterator_type' in kwargs: 

kwargs['iterator_type'] = 'simple' 

return self.iter_process(*args, **kwargs) 

return self.process(*args, **kwargs) 

raise TypeError("Do not know what to do with that arguments.") 

 

 

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

# tests 

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

 

 

def __bool__(self): 

""" 

Returns True if the finite state machine consists of at least 

one state. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

True or False. 

 

TESTS:: 

 

sage: bool(FiniteStateMachine()) 

False 

""" 

return len(self._states_) > 0 

 

 

__nonzero__ = __bool__ 

 

 

def __eq__(left, right): 

""" 

Returns ``True`` if the two finite state machines are equal, 

i.e., if they have the same states and the same transitions. 

 

INPUT: 

 

- ``left`` -- a finite state machine. 

 

- ``right`` -- a finite state machine. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

Note that this function compares all attributes of a state (by 

using :meth:`FSMState.fully_equal`) except for colors. Colors 

are handled as follows: If the colors coincide, then the 

finite state machines are also considered equal. If not, then 

they are considered as equal if both finite state machines are 

monochromatic. 

 

EXAMPLES:: 

 

sage: F = FiniteStateMachine([('A', 'B', 1)]) 

sage: F == FiniteStateMachine() 

False 

sage: G = FiniteStateMachine([('A', 'B', 1)], 

....: initial_states=['A']) 

sage: F == G 

False 

sage: F.state('A').is_initial = True 

sage: F == G 

True 

 

This shows the behavior when the states have colors:: 

 

sage: F.state('A').color = 'red' 

sage: G.state('A').color = 'red' 

sage: F == G 

True 

sage: G.state('A').color = 'blue' 

sage: F == G 

False 

sage: F.state('B').color = 'red' 

sage: F.is_monochromatic() 

True 

sage: G.state('B').color = 'blue' 

sage: G.is_monochromatic() 

True 

sage: F == G 

True 

""" 

if not is_FiniteStateMachine(right): 

raise TypeError('Only instances of FiniteStateMachine ' 

'can be compared.') 

if len(left._states_) != len(right._states_): 

return False 

colors_equal = True 

for state in left.iter_states(): 

try: 

right_state = right.state(state.label()) 

except LookupError: 

return False 

 

# we handle colors separately 

if not state.fully_equal(right_state, compare_color=False): 

return False 

if state.color != right_state.color: 

colors_equal = False 

 

left_transitions = state.transitions 

right_transitions = right.state(state).transitions 

if len(left_transitions) != len(right_transitions): 

return False 

for t in left_transitions: 

if t not in right_transitions: 

return False 

 

# handle colors 

if colors_equal: 

return True 

if left.is_monochromatic() and right.is_monochromatic(): 

return True 

return False 

 

 

def __ne__(left, right): 

""" 

Tests for inequality, complement of :meth:`.__eq__`. 

 

INPUT: 

 

- ``left`` -- a finite state machine. 

 

- ``right`` -- a finite state machine. 

 

OUTPUT: 

 

True or False. 

 

EXAMPLES:: 

 

sage: E = FiniteStateMachine([('A', 'B', 0)]) 

sage: F = Automaton([('A', 'B', 0)]) 

sage: G = Transducer([('A', 'B', 0, 1)]) 

sage: E == F 

True 

sage: E == G 

False 

""" 

return (not (left == right)) 

 

 

def __contains__(self, item): 

""" 

Returns true, if the finite state machine contains the 

state or transition item. Note that only the labels of the 

states and the input and output words are tested. 

 

INPUT: 

 

- ``item`` -- a state or a transition. 

 

OUTPUT: 

 

True or False. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition 

sage: F = FiniteStateMachine([('A', 'B', 0), ('B', 'A', 1)]) 

sage: FSMState('A', is_initial=True) in F 

True 

sage: 'A' in F 

False 

sage: FSMTransition('A', 'B', 0) in F 

True 

""" 

if is_FSMState(item): 

return self.has_state(item) 

if is_FSMTransition(item): 

return self.has_transition(item) 

return False 

 

 

def is_Markov_chain(self, is_zero=None): 

""" 

Checks whether ``self`` is a Markov chain where the transition 

probabilities are modeled as input labels. 

 

INPUT: 

 

- ``is_zero`` -- by default (``is_zero=None``), checking for 

zero is simply done by 

:meth:`~sage.structure.element.Element.is_zero`. This 

parameter can be used to provide a more sophisticated check 

for zero, e.g. in the case of symbolic probabilities, see 

the examples below. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

:attr:`on_duplicate_transition` must be 

:func:`duplicate_transition_add_input`, the sum of the input weights 

of the transitions leaving a state must add up to 1 and the sum of 

initial probabilities must add up to 1 (or all be ``None``). 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input 

sage: F = Transducer([[0, 0, 1/4, 0], [0, 1, 3/4, 1], 

....: [1, 0, 1/2, 0], [1, 1, 1/2, 1]], 

....: on_duplicate_transition=duplicate_transition_add_input) 

sage: F.is_Markov_chain() 

True 

 

:attr:`on_duplicate_transition` must be 

:func:`duplicate_transition_add_input`:: 

 

sage: F = Transducer([[0, 0, 1/4, 0], [0, 1, 3/4, 1], 

....: [1, 0, 1/2, 0], [1, 1, 1/2, 1]]) 

sage: F.is_Markov_chain() 

False 

 

Sum of input labels of the transitions leaving states must be 1:: 

 

sage: F = Transducer([[0, 0, 1/4, 0], [0, 1, 3/4, 1], 

....: [1, 0, 1/2, 0]], 

....: on_duplicate_transition=duplicate_transition_add_input) 

sage: F.is_Markov_chain() 

False 

 

The initial probabilities of all states must be ``None`` or they must 

sum up to 1. The initial probabilities of all states have to be set in the latter case:: 

 

sage: F = Transducer([[0, 0, 1/4, 0], [0, 1, 3/4, 1], 

....: [1, 0, 1, 0]], 

....: on_duplicate_transition=duplicate_transition_add_input) 

sage: F.is_Markov_chain() 

True 

sage: F.state(0).initial_probability = 1/4 

sage: F.is_Markov_chain() 

False 

sage: F.state(1).initial_probability = 7 

sage: F.is_Markov_chain() 

False 

sage: F.state(1).initial_probability = 3/4 

sage: F.is_Markov_chain() 

True 

 

If the probabilities are variables in the symbolic ring, 

:func:`~sage.symbolic.assumptions.assume` will do the trick:: 

 

sage: var('p q') 

(p, q) 

sage: F = Transducer([(0, 0, p, 1), (0, 0, q, 0)], 

....: on_duplicate_transition=duplicate_transition_add_input) 

sage: assume(p + q == 1) 

sage: (p + q - 1).is_zero() 

True 

sage: F.is_Markov_chain() 

True 

sage: forget() 

sage: del(p, q) 

 

If the probabilities are variables in some polynomial ring, 

the parameter ``is_zero`` can be used:: 

 

sage: R.<p, q> = PolynomialRing(QQ) 

sage: def is_zero_polynomial(polynomial): 

....: return polynomial in (p + q - 1)*R 

sage: F = Transducer([(0, 0, p, 1), (0, 0, q, 0)], 

....: on_duplicate_transition=duplicate_transition_add_input) 

sage: F.state(0).initial_probability = p + q 

sage: F.is_Markov_chain() 

False 

sage: F.is_Markov_chain(is_zero_polynomial) 

True 

""" 

def default_is_zero(expression): 

return expression.is_zero() 

 

is_zero_function = default_is_zero 

if is_zero is not None: 

is_zero_function = is_zero 

 

if self.on_duplicate_transition != duplicate_transition_add_input: 

return False 

 

if any(s.initial_probability is not None for s in self.iter_states()) and \ 

any(s.initial_probability is None for s in self.iter_states()): 

return False 

 

if any(s.initial_probability is not None for s in self.iter_states()) and \ 

not is_zero_function(sum(s.initial_probability for s 

in self.iter_states()) - 1): 

return False 

 

return all(is_zero_function(sum(t.word_in[0] for t in state.transitions) - 1) 

for state in self.iter_states()) 

 

 

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

# representations / LaTeX 

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

 

 

def _repr_(self): 

""" 

Represents the finite state machine as "Finite state machine 

with n states" where n is the number of states. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A string. 

 

EXAMPLES:: 

 

sage: FiniteStateMachine()._repr_() 

'Empty finite state machine' 

 

TESTS:: 

 

sage: F = FiniteStateMachine() 

sage: F 

Empty finite state machine 

sage: F.add_state(42) 

42 

sage: F 

Finite state machine with 1 state 

sage: F.add_state(43) 

43 

sage: F 

Finite state machine with 2 states 

 

""" 

if len(self._states_)==0: 

return "Empty finite state machine" 

if len(self._states_)==1: 

return "Finite state machine with 1 state" 

else: 

return "Finite state machine with %s states" % len(self._states_) 

 

default_format_letter = sage.misc.latex.latex 

format_letter = default_format_letter 

 

 

def format_letter_negative(self, letter): 

r""" 

Format negative numbers as overlined numbers, everything 

else by standard LaTeX formatting. 

 

INPUT: 

 

``letter`` -- anything. 

 

OUTPUT: 

 

Overlined absolute value if letter is a negative integer, 

:func:`latex(letter) <sage.misc.latex.latex>` otherwise. 

 

EXAMPLES:: 

 

sage: A = Automaton([(0, 0, -1)]) 

sage: list(map(A.format_letter_negative, [-1, 0, 1, 'a', None])) 

['\\overline{1}', 0, 1, \text{\texttt{a}}, \mathrm{None}] 

sage: A.latex_options(format_letter=A.format_letter_negative) 

sage: print(latex(A)) 

\begin{tikzpicture}[auto, initial text=, >=latex] 

\node[state] (v0) at (3.000000, 0.000000) {$0$}; 

\path[->] (v0) edge[loop above] node {$\overline{1}$} (); 

\end{tikzpicture} 

""" 

from sage.rings.integer_ring import ZZ 

if letter in ZZ and letter < 0: 

return r'\overline{%d}' % -letter 

else: 

return sage.misc.latex.latex(letter) 

 

 

def format_transition_label_reversed(self, word): 

r""" 

Format words in transition labels in reversed order. 

 

INPUT: 

 

``word`` -- list of letters. 

 

OUTPUT: 

 

String representation of ``word`` suitable to be typeset in 

mathematical mode, letters are written in reversed order. 

 

This is the reversed version of 

:meth:`.default_format_transition_label`. 

 

In digit expansions, digits are frequently processed from the 

least significant to the most significant position, but it is 

customary to write the least significant digit at the 

right-most position. Therefore, the labels have to be 

reversed. 

 

EXAMPLES:: 

 

sage: T = Transducer([(0, 0, 0, [1, 2, 3])]) 

sage: T.format_transition_label_reversed([1, 2, 3]) 

'3 2 1' 

sage: T.latex_options(format_transition_label=T.format_transition_label_reversed) 

sage: print(latex(T)) 

\begin{tikzpicture}[auto, initial text=, >=latex] 

\node[state] (v0) at (3.000000, 0.000000) {$0$}; 

\path[->] (v0) edge[loop above] node {$0\mid 3 2 1$} (); 

\end{tikzpicture} 

 

TESTS: 

 

Check that :trac:`16357` is fixed:: 

 

sage: T = Transducer() 

sage: T.format_transition_label_reversed([]) 

'\\varepsilon' 

""" 

return self.default_format_transition_label(reversed(word)) 

 

 

def default_format_transition_label(self, word): 

r""" 

Default formatting of words in transition labels for LaTeX output. 

 

INPUT: 

 

``word`` -- list of letters 

 

OUTPUT: 

 

String representation of ``word`` suitable to be typeset in 

mathematical mode. 

 

- For a non-empty word: Concatenation of the letters, piped through 

``self.format_letter`` and separated by blanks. 

- For an empty word: 

``sage.combinat.finite_state_machine.EmptyWordLaTeX``. 

 

There is also a variant :meth:`.format_transition_label_reversed` 

writing the words in reversed order. 

 

EXAMPLES: 

 

#. Example of a non-empty word:: 

 

sage: T = Transducer() 

sage: print(T.default_format_transition_label( 

....: ['a', 'alpha', 'a_1', '0', 0, (0, 1)])) 

\text{\texttt{a}} \text{\texttt{alpha}} 

\text{\texttt{a{\char`\_}1}} 0 0 \left(0, 1\right) 

 

#. In the example above, ``'a'`` and ``'alpha'`` should perhaps 

be symbols:: 

 

sage: var('a alpha a_1') 

(a, alpha, a_1) 

sage: print(T.default_format_transition_label([a, alpha, a_1])) 

a \alpha a_{1} 

 

#. Example of an empty word:: 

 

sage: print(T.default_format_transition_label([])) 

\varepsilon 

 

We can change this by setting 

``sage.combinat.finite_state_machine.EmptyWordLaTeX``:: 

 

sage: sage.combinat.finite_state_machine.EmptyWordLaTeX = '' 

sage: T.default_format_transition_label([]) 

'' 

 

Finally, we restore the default value:: 

 

sage: sage.combinat.finite_state_machine.EmptyWordLaTeX = r'\varepsilon' 

 

#. This method is the default value for 

``FiniteStateMachine.format_transition_label``. That can be changed to be 

any other function:: 

 

sage: A = Automaton([(0, 1, 0)]) 

sage: def custom_format_transition_label(word): 

....: return "t" 

sage: A.latex_options(format_transition_label=custom_format_transition_label) 

sage: print(latex(A)) 

\begin{tikzpicture}[auto, initial text=, >=latex] 

\node[state] (v0) at (3.000000, 0.000000) {$0$}; 

\node[state] (v1) at (-3.000000, 0.000000) {$1$}; 

\path[->] (v0) edge node[rotate=360.00, anchor=south] {$t$} (v1); 

\end{tikzpicture} 

 

TESTS: 

 

Check that :trac:`16357` is fixed:: 

 

sage: T = Transducer() 

sage: T.default_format_transition_label([]) 

'\\varepsilon' 

sage: T.default_format_transition_label(iter([])) 

'\\varepsilon' 

""" 

result = " ".join(self.format_letter(u) for u in word) 

if result: 

return result 

else: 

return EmptyWordLaTeX 

 

 

format_transition_label = default_format_transition_label 

 

 

def latex_options(self, 

coordinates=None, 

format_state_label=None, 

format_letter=None, 

format_transition_label=None, 

loop_where=None, 

initial_where=None, 

accepting_style=None, 

accepting_distance=None, 

accepting_where=None, 

accepting_show_empty=None): 

r""" 

Set options for LaTeX output via 

:func:`~sage.misc.latex.latex` and therefore 

:func:`~sage.misc.latex.view`. 

 

INPUT: 

 

- ``coordinates`` -- a dictionary or a function mapping labels 

of states to pairs interpreted as coordinates. If no 

coordinates are given, states a placed equidistantly on a 

circle of radius `3`. See also :meth:`.set_coordinates`. 

 

- ``format_state_label`` -- a function mapping labels of 

states to a string suitable for typesetting in LaTeX's 

mathematics mode. If not given, :func:`~sage.misc.latex.latex` 

is used. 

 

- ``format_letter`` -- a function mapping letters of the input 

and output alphabets to a string suitable for typesetting in 

LaTeX's mathematics mode. If not given, 

:meth:`.default_format_transition_label` uses 

:func:`~sage.misc.latex.latex`. 

 

- ``format_transition_label`` -- a function mapping words over 

the input and output alphabets to a string suitable for 

typesetting in LaTeX's mathematics mode. If not given, 

:meth:`.default_format_transition_label` is used. 

 

- ``loop_where`` -- a dictionary or a function mapping labels of 

initial states to one of ``'above'``, ``'left'``, ``'below'``, 

``'right'``. If not given, ``'above'`` is used. 

 

- ``initial_where`` -- a dictionary or a function mapping 

labels of initial states to one of ``'above'``, ``'left'``, 

``'below'``, ``'right'``. If not given, TikZ' default 

(currently ``'left'``) is used. 

 

- ``accepting_style`` -- one of ``'accepting by double'`` and 

``'accepting by arrow'``. If not given, ``'accepting by 

double'`` is used unless there are non-empty final output 

words. 

 

- ``accepting_distance`` -- a string giving a LaTeX length 

used for the length of the arrow leading from a final state. 

If not given, TikZ' default (currently ``'3ex'``) is used 

unless there are non-empty final output words, in which case 

``'7ex'`` is used. 

 

- ``accepting_where`` -- a dictionary or a function mapping 

labels of final states to one of ``'above'``, ``'left'``, 

``'below'``, ``'right'``. If not given, TikZ' default 

(currently ``'right'``) is used. If the final state has a 

final output word, it is also possible to give an angle 

in degrees. 

 

- ``accepting_show_empty`` -- if ``True`` the arrow of an 

empty final output word is labeled as well. Note that this 

implicitly implies ``accepting_style='accepting by 

arrow'``. If not given, the default ``False`` is used. 

 

OUTPUT: 

 

Nothing. 

 

As TikZ (cf. the :wikipedia:`PGF/TikZ`) is used to typeset 

the graphics, the syntax is oriented on TikZ' syntax. 

 

This is a convenience function collecting all options for 

LaTeX output. All of its functionality can also be achieved by 

directly setting the attributes 

 

- ``coordinates``, ``format_label``, ``loop_where``, 

``initial_where``, and ``accepting_where`` of 

:class:`FSMState` (here, ``format_label`` is a callable 

without arguments, everything else is a specific value); 

 

- ``format_label`` of :class:`FSMTransition` (``format_label`` 

is a callable without arguments); 

 

- ``format_state_label``, ``format_letter``, 

``format_transition_label``, ``accepting_style``, 

``accepting_distance``, and ``accepting_show_empty`` 

of :class:`FiniteStateMachine`. 

 

This function, however, also (somewhat) checks its input and 

serves to collect documentation on all these options. 

 

The function can be called several times, only those arguments 

which are not ``None`` are taken into account. By the same 

means, it can be combined with directly setting some 

attributes as outlined above. 

 

EXAMPLES: 

 

See also the section on :ref:`finite_state_machine_LaTeX_output` 

in the introductory examples of this module. 

 

:: 

 

sage: T = Transducer(initial_states=[4], 

....: final_states=[0, 3]) 

sage: for j in srange(4): 

....: T.add_transition(4, j, 0, [0, j]) 

....: T.add_transition(j, 4, 0, [0, -j]) 

....: T.add_transition(j, j, 0, 0) 

Transition from 4 to 0: 0|0,0 

Transition from 0 to 4: 0|0,0 

Transition from 0 to 0: 0|0 

Transition from 4 to 1: 0|0,1 

Transition from 1 to 4: 0|0,-1 

Transition from 1 to 1: 0|0 

Transition from 4 to 2: 0|0,2 

Transition from 2 to 4: 0|0,-2 

Transition from 2 to 2: 0|0 

Transition from 4 to 3: 0|0,3 

Transition from 3 to 4: 0|0,-3 

Transition from 3 to 3: 0|0 

sage: T.add_transition(4, 4, 0, 0) 

Transition from 4 to 4: 0|0 

sage: T.state(3).final_word_out = [0, 0] 

sage: T.latex_options( 

....: coordinates={4: (0, 0), 

....: 0: (-6, 3), 

....: 1: (-2, 3), 

....: 2: (2, 3), 

....: 3: (6, 3)}, 

....: format_state_label=lambda x: r'\mathbf{%s}' % x, 

....: format_letter=lambda x: r'w_{%s}' % x, 

....: format_transition_label=lambda x: 

....: r"{\scriptstyle %s}" % T.default_format_transition_label(x), 

....: loop_where={4: 'below', 0: 'left', 1: 'above', 

....: 2: 'right', 3:'below'}, 

....: initial_where=lambda x: 'above', 

....: accepting_style='accepting by double', 

....: accepting_distance='10ex', 

....: accepting_where={0: 'left', 3: 45} 

....: ) 

sage: T.state(4).format_label=lambda: r'\mathcal{I}' 

sage: latex(T) 

\begin{tikzpicture}[auto, initial text=, >=latex] 

\node[state, initial, initial where=above] (v0) at (0.000000, 0.000000) {$\mathcal{I}$}; 

\node[state, accepting, accepting where=left] (v1) at (-6.000000, 3.000000) {$\mathbf{0}$}; 

\node[state, accepting, accepting where=45] (v2) at (6.000000, 3.000000) {$\mathbf{3}$}; 

\path[->] (v2.45.00) edge node[rotate=45.00, anchor=south] {$\$ \mid {\scriptstyle w_{0} w_{0}}$} ++(45.00:10ex); 

\node[state] (v3) at (-2.000000, 3.000000) {$\mathbf{1}$}; 

\node[state] (v4) at (2.000000, 3.000000) {$\mathbf{2}$}; 

\path[->] (v1) edge[loop left] node[rotate=90, anchor=south] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0}}$} (); 

\path[->] (v1.-21.57) edge node[rotate=-26.57, anchor=south] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{0}}$} (v0.148.43); 

\path[->] (v3) edge[loop above] node {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0}}$} (); 

\path[->] (v3.-51.31) edge node[rotate=-56.31, anchor=south] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{-1}}$} (v0.118.69); 

\path[->] (v4) edge[loop right] node[rotate=90, anchor=north] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0}}$} (); 

\path[->] (v4.-118.69) edge node[rotate=56.31, anchor=north] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{-2}}$} (v0.51.31); 

\path[->] (v2) edge[loop below] node {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0}}$} (); 

\path[->] (v2.-148.43) edge node[rotate=26.57, anchor=north] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{-3}}$} (v0.21.57); 

\path[->] (v0.158.43) edge node[rotate=333.43, anchor=north] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{0}}$} (v1.328.43); 

\path[->] (v0.128.69) edge node[rotate=303.69, anchor=north] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{1}}$} (v3.298.69); 

\path[->] (v0.61.31) edge node[rotate=56.31, anchor=south] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{2}}$} (v4.231.31); 

\path[->] (v0.31.57) edge node[rotate=26.57, anchor=south] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{3}}$} (v2.201.57); 

\path[->] (v0) edge[loop below] node {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0}}$} (); 

\end{tikzpicture} 

sage: view(T) # not tested 

 

To actually see this, use the live documentation in the Sage notebook 

and execute the cells. 

 

By changing some of the options, we get the following output:: 

 

sage: T.latex_options( 

....: format_transition_label=T.default_format_transition_label, 

....: accepting_style='accepting by arrow', 

....: accepting_show_empty=True 

....: ) 

sage: latex(T) 

\begin{tikzpicture}[auto, initial text=, >=latex, accepting text=, accepting/.style=accepting by arrow, accepting distance=10ex] 

\node[state, initial, initial where=above] (v0) at (0.000000, 0.000000) {$\mathcal{I}$}; 

\node[state] (v1) at (-6.000000, 3.000000) {$\mathbf{0}$}; 

\path[->] (v1.180.00) edge node[rotate=360.00, anchor=south] {$\$ \mid \varepsilon$} ++(180.00:10ex); 

\node[state] (v2) at (6.000000, 3.000000) {$\mathbf{3}$}; 

\path[->] (v2.45.00) edge node[rotate=45.00, anchor=south] {$\$ \mid w_{0} w_{0}$} ++(45.00:10ex); 

\node[state] (v3) at (-2.000000, 3.000000) {$\mathbf{1}$}; 

\node[state] (v4) at (2.000000, 3.000000) {$\mathbf{2}$}; 

\path[->] (v1) edge[loop left] node[rotate=90, anchor=south] {$w_{0}\mid w_{0}$} (); 

\path[->] (v1.-21.57) edge node[rotate=-26.57, anchor=south] {$w_{0}\mid w_{0} w_{0}$} (v0.148.43); 

\path[->] (v3) edge[loop above] node {$w_{0}\mid w_{0}$} (); 

\path[->] (v3.-51.31) edge node[rotate=-56.31, anchor=south] {$w_{0}\mid w_{0} w_{-1}$} (v0.118.69); 

\path[->] (v4) edge[loop right] node[rotate=90, anchor=north] {$w_{0}\mid w_{0}$} (); 

\path[->] (v4.-118.69) edge node[rotate=56.31, anchor=north] {$w_{0}\mid w_{0} w_{-2}$} (v0.51.31); 

\path[->] (v2) edge[loop below] node {$w_{0}\mid w_{0}$} (); 

\path[->] (v2.-148.43) edge node[rotate=26.57, anchor=north] {$w_{0}\mid w_{0} w_{-3}$} (v0.21.57); 

\path[->] (v0.158.43) edge node[rotate=333.43, anchor=north] {$w_{0}\mid w_{0} w_{0}$} (v1.328.43); 

\path[->] (v0.128.69) edge node[rotate=303.69, anchor=north] {$w_{0}\mid w_{0} w_{1}$} (v3.298.69); 

\path[->] (v0.61.31) edge node[rotate=56.31, anchor=south] {$w_{0}\mid w_{0} w_{2}$} (v4.231.31); 

\path[->] (v0.31.57) edge node[rotate=26.57, anchor=south] {$w_{0}\mid w_{0} w_{3}$} (v2.201.57); 

\path[->] (v0) edge[loop below] node {$w_{0}\mid w_{0}$} (); 

\end{tikzpicture} 

sage: view(T) # not tested 

 

TESTS:: 

 

sage: T.latex_options(format_state_label='Nothing') 

Traceback (most recent call last): 

... 

TypeError: format_state_label must be callable. 

sage: T.latex_options(format_letter='') 

Traceback (most recent call last): 

... 

TypeError: format_letter must be callable. 

sage: T.latex_options(format_transition_label='') 

Traceback (most recent call last): 

... 

TypeError: format_transition_label must be callable. 

sage: T.latex_options(loop_where=37) 

Traceback (most recent call last): 

... 

TypeError: loop_where must be a callable or a 

dictionary. 

sage: T.latex_options(loop_where=lambda x: 'top') 

Traceback (most recent call last): 

... 

ValueError: loop_where for 4 must be in ['below', 

'right', 'above', 'left']. 

sage: T.latex_options(initial_where=90) 

Traceback (most recent call last): 

... 

TypeError: initial_where must be a callable or a 

dictionary. 

sage: T.latex_options(initial_where=lambda x: 'top') 

Traceback (most recent call last): 

... 

ValueError: initial_where for 4 must be in ['below', 

'right', 'above', 'left']. 

sage: T.latex_options(accepting_style='fancy') 

Traceback (most recent call last): 

... 

ValueError: accepting_style must be in ['accepting by 

double', 'accepting by arrow']. 

sage: T.latex_options(accepting_where=90) 

Traceback (most recent call last): 

... 

TypeError: accepting_where must be a callable or a 

dictionary. 

sage: T.latex_options(accepting_where=lambda x: 'top') 

Traceback (most recent call last): 

... 

ValueError: accepting_where for 0 must be in ['below', 

'right', 'above', 'left']. 

sage: T.latex_options(accepting_where={0: 'above', 3: 'top'}) 

Traceback (most recent call last): 

... 

ValueError: accepting_where for 3 must be a real number or 

be in ['below', 'right', 'above', 'left']. 

""" 

if coordinates is not None: 

self.set_coordinates(coordinates) 

 

if format_state_label is not None: 

if not hasattr(format_state_label, '__call__'): 

raise TypeError('format_state_label must be callable.') 

self.format_state_label = format_state_label 

 

if format_letter is not None: 

if not hasattr(format_letter, '__call__'): 

raise TypeError('format_letter must be callable.') 

self.format_letter = format_letter 

 

if format_transition_label is not None: 

if not hasattr(format_transition_label, '__call__'): 

raise TypeError('format_transition_label must be callable.') 

self.format_transition_label = format_transition_label 

 

if loop_where is not None: 

permissible = list(tikz_automata_where) 

for state in self.states(): 

if hasattr(loop_where, '__call__'): 

where = loop_where(state.label()) 

else: 

try: 

where = loop_where[state.label()] 

except TypeError: 

raise TypeError("loop_where must be a " 

"callable or a dictionary.") 

except KeyError: 

continue 

if where in permissible: 

state.loop_where = where 

else: 

raise ValueError('loop_where for %s must be in %s.' % 

(state.label(), permissible)) 

 

if initial_where is not None: 

permissible = list(tikz_automata_where) 

for state in self.iter_initial_states(): 

if hasattr(initial_where, '__call__'): 

where = initial_where(state.label()) 

else: 

try: 

where = initial_where[state.label()] 

except TypeError: 

raise TypeError("initial_where must be a " 

"callable or a dictionary.") 

except KeyError: 

continue 

if where in permissible: 

state.initial_where = where 

else: 

raise ValueError('initial_where for %s must be in %s.' % 

(state.label(), permissible)) 

 

if accepting_style is not None: 

permissible = ['accepting by double', 

'accepting by arrow'] 

if accepting_style in permissible: 

self.accepting_style = accepting_style 

else: 

raise ValueError('accepting_style must be in %s.' % 

permissible) 

 

if accepting_distance is not None: 

self.accepting_distance = accepting_distance 

 

if accepting_where is not None: 

permissible = list(tikz_automata_where) 

for state in self.iter_final_states(): 

if hasattr(accepting_where, '__call__'): 

where = accepting_where(state.label()) 

else: 

try: 

where = accepting_where[state.label()] 

except TypeError: 

raise TypeError("accepting_where must be a " 

"callable or a dictionary.") 

except KeyError: 

continue 

if where in permissible: 

state.accepting_where = where 

elif hasattr(state, 'final_word_out') \ 

and state.final_word_out: 

if where in sage.rings.real_mpfr.RR: 

state.accepting_where = where 

else: 

raise ValueError('accepting_where for %s must ' 

'be a real number or be in %s.' % 

(state.label(), permissible)) 

 

else: 

raise ValueError('accepting_where for %s must be in %s.' % 

(state.label(), permissible)) 

 

if accepting_show_empty is not None: 

self.accepting_show_empty = accepting_show_empty 

 

 

def _latex_(self): 

r""" 

Returns a LaTeX code for the graph of the finite state machine. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A string. 

 

EXAMPLES:: 

 

sage: F = FiniteStateMachine([('A', 'B', 1, 2)], 

....: initial_states=['A'], 

....: final_states=['B']) 

sage: F.state('A').initial_where='below' 

sage: print(latex(F)) # indirect doctest 

\begin{tikzpicture}[auto, initial text=, >=latex] 

\node[state, initial, initial where=below] (v0) at (3.000000, 0.000000) {$\text{\texttt{A}}$}; 

\node[state, accepting] (v1) at (-3.000000, 0.000000) {$\text{\texttt{B}}$}; 

\path[->] (v0) edge node[rotate=360.00, anchor=south] {$ $} (v1); 

\end{tikzpicture} 

 

TESTS: 

 

Check that :trac:`16943` is fixed:: 

 

sage: latex(Transducer( 

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

\begin{tikzpicture}[auto, initial text=, >=latex] 

\node[state] (v0) at (3.000000, 0.000000) {$0$}; 

\node[state] (v1) at (0.927051, 2.853170) {$1$}; 

\node[state] (v2) at (-2.427051, 1.763356) {$2$}; 

\node[state] (v3) at (-2.427051, -1.763356) {$3$}; 

\node[state] (v4) at (0.927051, -2.853170) {$4$}; 

\path[->] (v0) edge node[rotate=306.00, anchor=south] {$\varepsilon\mid \varepsilon$} (v1); 

\path[->] (v1) edge[loop above] node {$\varepsilon\mid \varepsilon$} (); 

\path[->] (v2) edge[loop above] node {$\varepsilon\mid \varepsilon$} (); 

\path[->] (v3) edge[loop above] node {$\varepsilon\mid \varepsilon$} (); 

\path[->] (v4) edge[loop above] node {$\varepsilon\mid \varepsilon$} (); 

\end{tikzpicture} 

""" 

from sage.functions.trig import sin, cos 

from sage.symbolic.constants import pi 

 

def label_rotation(angle, both_directions): 

""" 

Given an angle of a transition, compute the TikZ string to 

rotate the label. 

""" 

angle_label = angle 

anchor_label = "south" 

if angle > 90 or angle <= -90: 

angle_label = angle + 180 

if both_directions: 

# if transitions in both directions, the transition to the 

# left has its label below the transition, otherwise above 

anchor_label = "north" 

if hasattr(angle_label, 'n'): 

# we may need to convert symbolic expressions to floats, 

# but int does not have .n() 

angle_label = angle_label.n() 

return "rotate=%.2f, anchor=%s" % (angle_label, anchor_label) 

 

setup_latex_preamble() 

 

options = ["auto", "initial text=", ">=latex"] 

 

nonempty_final_word_out = False 

for state in self.iter_final_states(): 

if state.final_word_out: 

nonempty_final_word_out = True 

break 

 

if hasattr(self, "accepting_style"): 

accepting_style = self.accepting_style 

elif nonempty_final_word_out: 

accepting_style = "accepting by arrow" 

else: 

accepting_style = "accepting by double" 

 

if accepting_style == "accepting by arrow": 

options.append("accepting text=") 

options.append("accepting/.style=%s" % accepting_style) 

 

if hasattr(self, "accepting_distance"): 

accepting_distance = self.accepting_distance 

elif nonempty_final_word_out: 

accepting_distance = "7ex" 

else: 

accepting_distance = None 

if accepting_style == "accepting by arrow" and accepting_distance: 

options.append("accepting distance=%s" 

% accepting_distance) 

 

if hasattr(self, "accepting_show_empty"): 

accepting_show_empty = self.accepting_show_empty 

else: 

accepting_show_empty = False 

 

result = "\\begin{tikzpicture}[%s]\n" % ", ".join(options) 

j = 0; 

for vertex in self.iter_states(): 

if not hasattr(vertex, "coordinates"): 

vertex.coordinates = (3*cos(2*pi*j/len(self.states())), 

3*sin(2*pi*j/len(self.states()))) 

options = "" 

if vertex.is_final: 

if not (vertex.final_word_out 

and accepting_style == "accepting by arrow") \ 

and not accepting_show_empty: 

# otherwise, we draw a custom made accepting path 

# with label below 

options += ", accepting" 

if hasattr(vertex, "accepting_where"): 

options += ", accepting where=%s" % ( 

vertex.accepting_where,) 

if vertex.is_initial: 

options += ", initial" 

if hasattr(vertex, "initial_where"): 

options += ", initial where=%s" % vertex.initial_where 

if hasattr(vertex, "format_label"): 

label = vertex.format_label() 

elif hasattr(self, "format_state_label"): 

label = self.format_state_label(vertex) 

else: 

label = sage.misc.latex.latex(vertex.label()) 

result += "\\node[state%s] (v%d) at (%f, %f) {$%s$};\n" % ( 

options, j, vertex.coordinates[0], 

vertex.coordinates[1], label) 

vertex._number_ = j 

if vertex.is_final and (vertex.final_word_out or accepting_show_empty): 

angle = 0 

if hasattr(vertex, "accepting_where"): 

angle = tikz_automata_where.get(vertex.accepting_where, 

vertex.accepting_where) 

result += "\\path[->] (v%d.%.2f) edge node[%s] {$%s \mid %s$} ++(%.2f:%s);\n" % ( 

j, angle, 

label_rotation(angle, False), 

EndOfWordLaTeX, 

self.format_transition_label(vertex.final_word_out), 

angle, accepting_distance) 

 

j += 1 

 

def key_function(s): 

return (s.from_state, s.to_state) 

# We use an OrderedDict instead of a dict in order to have a 

# defined ordering of the transitions in the output. See 

# http://trac.sagemath.org/ticket/16580#comment:3 . As the 

# transitions have to be sorted anyway, the performance 

# penalty should be bearable; nevertheless, this is only 

# required for doctests. 

adjacent = collections.OrderedDict( 

(pair, list(transitions)) 

for pair, transitions in 

itertools.groupby( 

sorted(self.iter_transitions(), 

key=key_function), 

key=key_function 

)) 

 

for ((source, target), transitions) in six.iteritems(adjacent): 

if len(transitions) > 0: 

labels = [] 

for transition in transitions: 

if hasattr(transition, "format_label"): 

labels.append(transition.format_label()) 

else: 

labels.append(self._latex_transition_label_( 

transition, self.format_transition_label)) 

label = ", ".join(labels) 

if source != target: 

angle = sage.functions.trig.atan2( 

target.coordinates[1] - source.coordinates[1], 

target.coordinates[0] - source.coordinates[0]) * 180/pi 

both_directions = (target, source) in adjacent 

if both_directions: 

angle_source = ".%.2f" % ((angle + 5).n(),) 

angle_target = ".%.2f" % ((angle + 175).n(),) 

else: 

angle_source = "" 

angle_target = "" 

result += "\\path[->] (v%d%s) edge node[%s] {$%s$} (v%d%s);\n" % ( 

source._number_, angle_source, 

label_rotation(angle, both_directions), 

label, 

target._number_, angle_target) 

else: 

loop_where = "above" 

if hasattr(source, "loop_where"): 

loop_where = source.loop_where 

rotation = {'left': '[rotate=90, anchor=south]', 

'right': '[rotate=90, anchor=north]'} 

result += "\\path[->] (v%d) edge[loop %s] node%s {$%s$} ();\n" % ( 

source._number_, 

loop_where, rotation.get(loop_where, ''), 

label) 

 

result += "\\end{tikzpicture}" 

return result 

 

 

def _latex_transition_label_(self, transition, 

format_function=sage.misc.latex.latex): 

r""" 

Returns the proper transition label. 

 

INPUT: 

 

- ``transition`` - a transition 

 

- ``format_function`` - a function formatting the labels 

 

OUTPUT: 

 

A string. 

 

TESTS:: 

 

sage: F = FiniteStateMachine([('A', 'B', 0, 1)]) 

sage: t = F.transitions()[0] 

sage: F._latex_transition_label_(t) 

' ' 

""" 

return ' ' 

 

 

def set_coordinates(self, coordinates, default=True): 

""" 

Set coordinates of the states for the LaTeX representation by 

a dictionary or a function mapping labels to coordinates. 

 

INPUT: 

 

- ``coordinates`` -- a dictionary or a function mapping labels 

of states to pairs interpreted as coordinates. 

 

- ``default`` -- If ``True``, then states not given by 

``coordinates`` get a default position on a circle of 

radius 3. 

 

OUTPUT: 

 

Nothing. 

 

EXAMPLES:: 

 

sage: F = Automaton([[0, 1, 1], [1, 2, 2], [2, 0, 0]]) 

sage: F.set_coordinates({0: (0, 0), 1: (2, 0), 2: (1, 1)}) 

sage: F.state(0).coordinates 

(0, 0) 

 

We can also use a function to determine the coordinates:: 

 

sage: F = Automaton([[0, 1, 1], [1, 2, 2], [2, 0, 0]]) 

sage: F.set_coordinates(lambda l: (l, 3/(l+1))) 

sage: F.state(2).coordinates 

(2, 1) 

""" 

from sage.functions.trig import sin, cos 

from sage.symbolic.constants import pi 

 

states_without_coordinates = [] 

for state in self.iter_states(): 

try: 

state.coordinates = coordinates[state.label()] 

continue 

except (KeyError, TypeError): 

pass 

 

try: 

state.coordinates = coordinates(state.label()) 

continue 

except TypeError: 

pass 

 

states_without_coordinates.append(state) 

 

if default: 

n = len(states_without_coordinates) 

for j, state in enumerate(states_without_coordinates): 

state.coordinates = (3*cos(2*pi*j/n), 

3*sin(2*pi*j/n)) 

 

 

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

# other 

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

 

 

def _matrix_(self, R=None): 

""" 

Returns the adjacency matrix of the finite state machine. 

See :meth:`.adjacency_matrix` for more information. 

 

EXAMPLES:: 

 

sage: B = FiniteStateMachine({0: {0: (0, 0), 'a': (1, 0)}, 

....: 'a': {2: (0, 0), 3: (1, 0)}, 

....: 2:{0:(1, 1), 4:(0, 0)}, 

....: 3:{'a':(0, 1), 2:(1, 1)}, 

....: 4:{4:(1, 1), 3:(0, 1)}}, 

....: initial_states=[0]) 

sage: B._matrix_() 

[1 1 0 0 0] 

[0 0 1 1 0] 

[x 0 0 0 1] 

[0 x x 0 0] 

[0 0 0 x x] 

""" 

return self.adjacency_matrix() 

 

 

def adjacency_matrix(self, input=None, 

entry=None): 

""" 

Returns the adjacency matrix of the underlying graph. 

 

INPUT: 

 

- ``input`` -- Only transitions with input label ``input`` are 

respected. 

 

- ``entry`` -- The function ``entry`` takes a transition and the 

return value is written in the matrix as the entry 

``(transition.from_state, transition.to_state)``. The default 

value (``None``) of entry takes the variable ``x`` to the 

power of the sum of the output word of the transition. 

 

OUTPUT: 

 

A matrix. 

 

If any label of a state is not an integer, the finite state 

machine is relabeled at the beginning. If there are more than 

one transitions between two states, then the different return 

values of ``entry`` are added up. 

 

EXAMPLES:: 

 

sage: B = FiniteStateMachine({0:{0:(0, 0), 'a':(1, 0)}, 

....: 'a':{2:(0, 0), 3:(1, 0)}, 

....: 2:{0:(1, 1), 4:(0, 0)}, 

....: 3:{'a':(0, 1), 2:(1, 1)}, 

....: 4:{4:(1, 1), 3:(0, 1)}}, 

....: initial_states=[0]) 

sage: B.adjacency_matrix() 

[1 1 0 0 0] 

[0 0 1 1 0] 

[x 0 0 0 1] 

[0 x x 0 0] 

[0 0 0 x x] 

 

This is equivalent to:: 

 

sage: matrix(B) 

[1 1 0 0 0] 

[0 0 1 1 0] 

[x 0 0 0 1] 

[0 x x 0 0] 

[0 0 0 x x] 

 

It is also possible to use other entries in the adjacency matrix:: 

 

sage: B.adjacency_matrix(entry=(lambda transition: 1)) 

[1 1 0 0 0] 

[0 0 1 1 0] 

[1 0 0 0 1] 

[0 1 1 0 0] 

[0 0 0 1 1] 

sage: B.adjacency_matrix(1, entry=(lambda transition: 

....: exp(I*transition.word_out[0]*var('t')))) 

[ 0 1 0 0 0] 

[ 0 0 0 1 0] 

[e^(I*t) 0 0 0 0] 

[ 0 0 e^(I*t) 0 0] 

[ 0 0 0 0 e^(I*t)] 

sage: a = Automaton([(0, 1, 0), 

....: (1, 2, 0), 

....: (2, 0, 1), 

....: (2, 1, 0)], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: a.adjacency_matrix() 

[0 1 0] 

[0 0 1] 

[1 1 0] 

 

""" 

from sage.rings.integer_ring import ZZ 

 

def default_function(transitions): 

x = sage.symbolic.ring.SR.var('x') 

return x**sum(transition.word_out) 

 

if entry is None: 

entry = default_function 

 

relabeledFSM = self 

l = len(relabeledFSM.states()) 

for state in self.iter_states(): 

if state.label() not in ZZ or state.label() >= l \ 

or state.label() < 0: 

relabeledFSM = self.relabeled() 

break 

dictionary = {} 

for transition in relabeledFSM.iter_transitions(): 

if input is None or transition.word_in == [input]: 

if (transition.from_state.label(), 

transition.to_state.label()) in dictionary: 

dictionary[(transition.from_state.label(), 

transition.to_state.label())] \ 

+= entry(transition) 

else: 

dictionary[(transition.from_state.label(), 

transition.to_state.label())] \ 

= entry(transition) 

return sage.matrix.constructor.matrix( 

len(relabeledFSM.states()), dictionary) 

 

 

def determine_input_alphabet(self, reset=True): 

""" 

Determine the input alphabet according to the transitions 

of this finite state machine. 

 

INPUT: 

 

- ``reset`` -- a boolean (default: ``True``). If ``True``, then 

the existing input alphabet is erased, otherwise new letters are 

appended to the existing alphabet. 

 

OUTPUT: 

 

Nothing. 

 

After this operation the input alphabet of this finite state machine 

is a list of letters. 

 

.. TODO:: 

 

At the moment, the letters of the alphabet need to be hashable. 

 

EXAMPLES:: 

 

sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1), 

....: (2, 2, 1, 1), (2, 2, 0, 0)], 

....: final_states=[1], 

....: determine_alphabets=False) 

sage: (T.input_alphabet, T.output_alphabet) 

(None, None) 

sage: T.determine_input_alphabet() 

sage: (T.input_alphabet, T.output_alphabet) 

([0, 1, 2], None) 

 

.. SEEALSO:: 

 

:meth:`determine_output_alphabet`, 

:meth:`determine_alphabets`. 

""" 

if reset: 

ain = set() 

else: 

ain = set(self.input_alphabet) 

 

for t in self.iter_transitions(): 

for letter in t.word_in: 

ain.add(letter) 

self.input_alphabet = list(ain) 

 

 

def determine_output_alphabet(self, reset=True): 

""" 

Determine the output alphabet according to the transitions 

of this finite state machine. 

 

INPUT: 

 

- ``reset`` -- a boolean (default: ``True``). If ``True``, then 

the existing output alphabet is erased, otherwise new letters are 

appended to the existing alphabet. 

 

OUTPUT: 

 

Nothing. 

 

After this operation the output alphabet of this finite state machine 

is a list of letters. 

 

.. TODO:: 

 

At the moment, the letters of the alphabet need to be hashable. 

 

EXAMPLES:: 

 

sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1), 

....: (2, 2, 1, 1), (2, 2, 0, 0)], 

....: final_states=[1], 

....: determine_alphabets=False) 

sage: T.state(1).final_word_out = [1, 4] 

sage: (T.input_alphabet, T.output_alphabet) 

(None, None) 

sage: T.determine_output_alphabet() 

sage: (T.input_alphabet, T.output_alphabet) 

(None, [0, 1, 4]) 

 

.. SEEALSO:: 

 

:meth:`determine_input_alphabet`, 

:meth:`determine_alphabets`. 

""" 

if reset: 

aout = set() 

else: 

aout = set(self.output_alphabet) 

 

for t in self.iter_transitions(): 

for letter in t.word_out: 

aout.add(letter) 

for s in self.iter_final_states(): 

for letter in s.final_word_out: 

aout.add(letter) 

self.output_alphabet = list(aout) 

 

 

def determine_alphabets(self, reset=True): 

""" 

Determine the input and output alphabet according to the 

transitions in this finite state machine. 

 

INPUT: 

 

- ``reset`` -- If reset is ``True``, then the existing input 

and output alphabets are erased, otherwise new letters are 

appended to the existing alphabets. 

 

OUTPUT: 

 

Nothing. 

 

After this operation the input alphabet and the output 

alphabet of this finite state machine are a list of letters. 

 

.. TODO:: 

 

At the moment, the letters of the alphabets need to be hashable. 

 

EXAMPLES:: 

 

sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1), 

....: (2, 2, 1, 1), (2, 2, 0, 0)], 

....: final_states=[1], 

....: determine_alphabets=False) 

sage: T.state(1).final_word_out = [1, 4] 

sage: (T.input_alphabet, T.output_alphabet) 

(None, None) 

sage: T.determine_alphabets() 

sage: (T.input_alphabet, T.output_alphabet) 

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

 

.. SEEALSO:: 

 

:meth:`determine_input_alphabet`, 

:meth:`determine_output_alphabet`. 

""" 

self.determine_input_alphabet(reset) 

self.determine_output_alphabet(reset) 

 

 

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

# get states and transitions 

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

 

 

def states(self): 

""" 

Returns the states of the finite state machine. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

The states of the finite state machine as list. 

 

EXAMPLES:: 

 

sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)]) 

sage: FSM.states() 

['1', '2'] 

 

""" 

from copy import copy 

return copy(self._states_) 

 

 

def iter_states(self): 

""" 

Returns an iterator of the states. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

An iterator of the states of the finite state machine. 

 

EXAMPLES:: 

 

sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)]) 

sage: [s.label() for s in FSM.iter_states()] 

['1', '2'] 

""" 

return iter(self._states_) 

 

 

def transitions(self, from_state=None): 

""" 

Returns a list of all transitions. 

 

INPUT: 

 

- ``from_state`` -- (default: ``None``) If ``from_state`` is 

given, then a list of transitions starting there is given. 

 

OUTPUT: 

 

A list of all transitions. 

 

EXAMPLES:: 

 

sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)]) 

sage: FSM.transitions() 

[Transition from '1' to '2': 1|-, 

Transition from '2' to '2': 0|-] 

""" 

return list(self.iter_transitions(from_state)) 

 

 

def iter_transitions(self, from_state=None): 

""" 

Returns an iterator of all transitions. 

 

INPUT: 

 

- ``from_state`` -- (default: ``None``) If ``from_state`` is 

given, then a list of transitions starting there is given. 

 

OUTPUT: 

 

An iterator of all transitions. 

 

EXAMPLES:: 

 

sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)]) 

sage: [(t.from_state.label(), t.to_state.label()) 

....: for t in FSM.iter_transitions('1')] 

[('1', '2')] 

sage: [(t.from_state.label(), t.to_state.label()) 

....: for t in FSM.iter_transitions('2')] 

[('2', '2')] 

sage: [(t.from_state.label(), t.to_state.label()) 

....: for t in FSM.iter_transitions()] 

[('1', '2'), ('2', '2')] 

""" 

if from_state is None: 

return self._iter_transitions_all_() 

else: 

return iter(self.state(from_state).transitions) 

 

 

def _iter_transitions_all_(self): 

""" 

Returns an iterator over all transitions. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

An iterator over all transitions. 

 

EXAMPLES:: 

 

sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)]) 

sage: [(t.from_state.label(), t.to_state.label()) 

....: for t in FSM._iter_transitions_all_()] 

[('1', '2'), ('2', '2')] 

""" 

for state in self.iter_states(): 

for t in state.transitions: 

yield t 

 

 

def initial_states(self): 

""" 

Returns a list of all initial states. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A list of all initial states. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A', is_initial=True) 

sage: B = FSMState('B') 

sage: F = FiniteStateMachine([(A, B, 1, 0)]) 

sage: F.initial_states() 

['A'] 

""" 

return list(self.iter_initial_states()) 

 

 

def iter_initial_states(self): 

""" 

Returns an iterator of the initial states. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

An iterator over all initial states. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A', is_initial=True) 

sage: B = FSMState('B') 

sage: F = FiniteStateMachine([(A, B, 1, 0)]) 

sage: [s.label() for s in F.iter_initial_states()] 

['A'] 

""" 

from six.moves import filter 

return filter(lambda s:s.is_initial, self.iter_states()) 

 

 

def final_states(self): 

""" 

Returns a list of all final states. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A list of all final states. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A', is_final=True) 

sage: B = FSMState('B', is_initial=True) 

sage: C = FSMState('C', is_final=True) 

sage: F = FiniteStateMachine([(A, B), (A, C)]) 

sage: F.final_states() 

['A', 'C'] 

""" 

return list(self.iter_final_states()) 

 

 

def iter_final_states(self): 

""" 

Returns an iterator of the final states. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

An iterator over all initial states. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A', is_final=True) 

sage: B = FSMState('B', is_initial=True) 

sage: C = FSMState('C', is_final=True) 

sage: F = FiniteStateMachine([(A, B), (A, C)]) 

sage: [s.label() for s in F.iter_final_states()] 

['A', 'C'] 

""" 

from six.moves import filter 

return filter(lambda s:s.is_final, self.iter_states()) 

 

def state(self, state): 

""" 

Returns the state of the finite state machine. 

 

INPUT: 

 

- ``state`` -- If ``state`` is not an instance of 

:class:`FSMState`, then it is assumed that it is the label 

of a state. 

 

OUTPUT: 

 

Returns the state of the finite state machine corresponding to 

``state``. 

 

If no state is found, then a ``LookupError`` is thrown. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A') 

sage: FSM = FiniteStateMachine([(A, 'B'), ('C', A)]) 

sage: FSM.state('A') == A 

True 

sage: FSM.state('xyz') 

Traceback (most recent call last): 

... 

LookupError: No state with label xyz found. 

""" 

def what(s, switch): 

if switch: 

return s.label() 

else: 

return s 

switch = is_FSMState(state) 

 

try: 

return self._states_dict_[what(state, switch)] 

except AttributeError: 

for s in self.iter_states(): 

if what(s, not switch) == state: 

return s 

except KeyError: 

pass 

raise LookupError("No state with label %s found." % (what(state, switch),)) 

 

 

def transition(self, transition): 

""" 

Returns the transition of the finite state machine. 

 

INPUT: 

 

- ``transition`` -- If ``transition`` is not an instance of 

:class:`FSMTransition`, then it is assumed that it is a 

tuple ``(from_state, to_state, word_in, word_out)``. 

 

OUTPUT: 

 

Returns the transition of the finite state machine 

corresponding to ``transition``. 

 

If no transition is found, then a ``LookupError`` is thrown. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: t = FSMTransition('A', 'B', 0) 

sage: F = FiniteStateMachine([t]) 

sage: F.transition(('A', 'B', 0)) 

Transition from 'A' to 'B': 0|- 

sage: id(t) == id(F.transition(('A', 'B', 0))) 

True 

""" 

if not is_FSMTransition(transition): 

transition = FSMTransition(*transition) 

for s in self.iter_transitions(transition.from_state): 

if s == transition: 

return s 

raise LookupError("No transition found.") 

 

 

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

# properties (state and transitions) 

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

 

 

def has_state(self, state): 

""" 

Returns whether ``state`` is one of the states of the finite 

state machine. 

 

INPUT: 

 

- ``state`` can be a :class:`FSMState` or a label of a state. 

 

OUTPUT: 

 

True or False. 

 

EXAMPLES:: 

 

sage: FiniteStateMachine().has_state('A') 

False 

""" 

try: 

self.state(state) 

return True 

except LookupError: 

return False 

 

 

def has_transition(self, transition): 

""" 

Returns whether ``transition`` is one of the transitions of 

the finite state machine. 

 

INPUT: 

 

- ``transition`` has to be a :class:`FSMTransition`. 

 

OUTPUT: 

 

True or False. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: t = FSMTransition('A', 'A', 0, 1) 

sage: FiniteStateMachine().has_transition(t) 

False 

sage: FiniteStateMachine().has_transition(('A', 'A', 0, 1)) 

Traceback (most recent call last): 

... 

TypeError: Transition is not an instance of FSMTransition. 

""" 

if is_FSMTransition(transition): 

return transition in self.iter_transitions() 

raise TypeError("Transition is not an instance of FSMTransition.") 

 

 

def has_initial_state(self, state): 

""" 

Returns whether ``state`` is one of the initial states of the 

finite state machine. 

 

INPUT: 

 

- ``state`` can be a :class:`FSMState` or a label. 

 

OUTPUT: 

 

True or False. 

 

EXAMPLES:: 

 

sage: F = FiniteStateMachine([('A', 'A')], initial_states=['A']) 

sage: F.has_initial_state('A') 

True 

""" 

try: 

return self.state(state).is_initial 

except LookupError: 

return False 

 

 

def has_initial_states(self): 

""" 

Returns whether the finite state machine has an initial state. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

True or False. 

 

EXAMPLES:: 

 

sage: FiniteStateMachine().has_initial_states() 

False 

""" 

return len(self.initial_states()) > 0 

 

 

def has_final_state(self, state): 

""" 

Returns whether ``state`` is one of the final states of the 

finite state machine. 

 

INPUT: 

 

- ``state`` can be a :class:`FSMState` or a label. 

 

OUTPUT: 

 

True or False. 

 

EXAMPLES:: 

 

sage: FiniteStateMachine(final_states=['A']).has_final_state('A') 

True 

""" 

try: 

return self.state(state).is_final 

except LookupError: 

return False 

 

 

def has_final_states(self): 

""" 

Returns whether the finite state machine has a final state. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

True or False. 

 

EXAMPLES:: 

 

sage: FiniteStateMachine().has_final_states() 

False 

""" 

return len(self.final_states()) > 0 

 

 

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

# properties 

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

 

 

def is_deterministic(self): 

""" 

Return whether the finite finite state machine is deterministic. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

A finite state machine is considered to be deterministic if 

each transition has input label of length one and for each 

pair `(q,a)` where `q` is a state and `a` is an element of the 

input alphabet, there is at most one transition from `q` with 

input label `a`. Furthermore, the finite state may not have 

more than one initial state. 

 

EXAMPLES:: 

 

sage: fsm = FiniteStateMachine() 

sage: fsm.add_transition(('A', 'B', 0, [])) 

Transition from 'A' to 'B': 0|- 

sage: fsm.is_deterministic() 

True 

sage: fsm.add_transition(('A', 'C', 0, [])) 

Transition from 'A' to 'C': 0|- 

sage: fsm.is_deterministic() 

False 

sage: fsm.add_transition(('A', 'B', [0,1], [])) 

Transition from 'A' to 'B': 0,1|- 

sage: fsm.is_deterministic() 

False 

 

Check that :trac:`18556` is fixed:: 

 

sage: Automaton().is_deterministic() 

True 

sage: Automaton(initial_states=[0]).is_deterministic() 

True 

sage: Automaton(initial_states=[0, 1]).is_deterministic() 

False 

""" 

if len(self.initial_states())>1: 

return False 

for state in self.iter_states(): 

for transition in state.transitions: 

if len(transition.word_in) != 1: 

return False 

 

transition_classes_by_word_in = full_group_by( 

state.transitions, 

key=lambda t: t.word_in) 

 

for key,transition_class in transition_classes_by_word_in: 

if len(transition_class) > 1: 

return False 

return True 

 

 

def is_complete(self): 

""" 

Returns whether the finite state machine is complete. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

A finite state machine is considered to be complete if 

each transition has an input label of length one and for each 

pair `(q, a)` where `q` is a state and `a` is an element of the 

input alphabet, there is exactly one transition from `q` with 

input label `a`. 

 

EXAMPLES:: 

 

sage: fsm = FiniteStateMachine([(0, 0, 0, 0), 

....: (0, 1, 1, 1), 

....: (1, 1, 0, 0)], 

....: determine_alphabets=False) 

sage: fsm.is_complete() 

Traceback (most recent call last): 

... 

ValueError: No input alphabet is given. Try calling determine_alphabets(). 

sage: fsm.input_alphabet = [0, 1] 

sage: fsm.is_complete() 

False 

sage: fsm.add_transition((1, 1, 1, 1)) 

Transition from 1 to 1: 1|1 

sage: fsm.is_complete() 

True 

sage: fsm.add_transition((0, 0, 1, 0)) 

Transition from 0 to 0: 1|0 

sage: fsm.is_complete() 

False 

""" 

if self.input_alphabet is None: 

raise ValueError("No input alphabet is given. " 

"Try calling determine_alphabets().") 

 

for state in self.iter_states(): 

for transition in state.transitions: 

if len(transition.word_in) != 1: 

return False 

 

transition_classes_by_word_in = full_group_by( 

state.transitions, 

key=lambda t: t.word_in) 

 

for key, transition_class in transition_classes_by_word_in: 

if len(transition_class) > 1: 

return False 

 

# all input labels are lists, extract the only element 

outgoing_alphabet = [key[0] for key, transition_class in 

transition_classes_by_word_in] 

if not sorted(self.input_alphabet) == sorted(outgoing_alphabet): 

return False 

 

return True 

 

 

def is_connected(self): 

""" 

TESTS:: 

 

sage: FiniteStateMachine().is_connected() 

Traceback (most recent call last): 

... 

NotImplementedError 

""" 

raise NotImplementedError 

 

 

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

# let the finite state machine work 

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

 

_process_default_options_ = {'full_output': True, 

'list_of_outputs': None, 

'only_accepted': False, 

'always_include_output': False, 

'automatic_output_type': False} 

 

 

def process(self, *args, **kwargs): 

""" 

Returns whether the finite state machine accepts the input, the state 

where the computation stops and which output is generated. 

 

INPUT: 

 

- ``input_tape`` -- the input tape can be a list or an 

iterable with entries from the input alphabet. If we are 

working with a multi-tape machine (see parameter 

``use_multitape_input`` and notes below), then the tape is a 

list or tuple of tracks, each of which can be a list or an 

iterable with entries from the input alphabet. 

 

- ``initial_state`` or ``initial_states`` -- the initial 

state(s) in which the machine starts. Either specify a 

single one with ``initial_state`` or a list of them with 

``initial_states``. If both are given, ``initial_state`` 

will be appended to ``initial_states``. If neither is 

specified, the initial states of the finite state machine 

are taken. 

 

- ``list_of_outputs`` -- (default: ``None``) a boolean or 

``None``. If ``True``, then the outputs are given in list form 

(even if we have no or only one single output). If 

``False``, then the result is never a list (an exception is 

raised if the result cannot be returned). If 

``list_of_outputs=None``, the method determines automatically 

what to do (e.g. if a non-deterministic machine returns more 

than one path, then the output is returned in list form). 

 

- ``only_accepted`` -- (default: ``False``) a boolean. If set, 

then the first argument in the output is guaranteed to be 

``True`` (if the output is a list, then the first argument 

of each element will be ``True``). 

 

- ``always_include_output`` -- if set (not by default), always 

include the output. This is inconsequential for a 

:class:`FiniteStateMachine`, but can be used in derived 

classes where the output is suppressed by default, 

cf. :meth:`Automaton.process`. 

 

- ``format_output`` -- a function that translates the written 

output (which is in form of a list) to something more 

readable. By default (``None``) identity is used here. 

 

- ``check_epsilon_transitions`` -- (default: ``True``) a 

boolean. If ``False``, then epsilon transitions are not 

taken into consideration during process. 

 

- ``write_final_word_out`` -- (default: ``True``) a boolean 

specifying whether the final output words should be written 

or not. 

 

- ``use_multitape_input`` -- (default: ``False``) a 

boolean. If ``True``, then the multi-tape mode of the 

process iterator is activated. See also the notes below for 

multi-tape machines. 

 

- ``process_all_prefixes_of_input`` -- (default: ``False``) a 

boolean. If ``True``, then each prefix of the input word is 

processed (instead of processing the whole input word at 

once). Consequently, there is an output generated for each 

of these prefixes. 

 

- ``process_iterator_class`` -- (default: ``None``) a class 

inherited from :class:`FSMProcessIterator`. If ``None``, 

then :class:`FSMProcessIterator` is taken. An instance of this 

class is created and is used during the processing. 

 

- ``automatic_output_type`` -- (default: ``False``) a boolean. 

If set and the input has a parent, then the 

output will have the same parent. If the input does not have 

a parent, then the output will be of the same type as the 

input. 

 

OUTPUT: 

 

A triple (or a list of triples, 

cf. parameter ``list_of_outputs``), where 

 

- the first entry is ``True`` if the input string is accepted, 

 

- the second gives the reached state after processing the 

input tape (This is a state with label ``None`` if the input 

could not be processed, i.e., if at one point no 

transition to go on could be found.), and 

 

- the third gives a list of the output labels written during 

processing (in the case the finite state machine runs as 

transducer). 

 

Note that in the case the finite state machine is not 

deterministic, all possible paths are taken into account. 

 

This function uses an iterator which, in its simplest form, goes 

from one state to another in each step. To decide which way to 

go, it uses the input words of the outgoing transitions and 

compares them to the input tape. More precisely, in each step, 

the iterator takes an outgoing transition of the current state, 

whose input label equals the input letter of the tape. The 

output label of the transition, if present, is written on the 

output tape. 

 

If the choice of the outgoing transition is not unique (i.e., 

we have a non-deterministic finite state machine), all 

possibilites are followed. This is done by splitting the 

process into several branches, one for each of the possible 

outgoing transitions. 

 

The process (iteration) stops if all branches are finished, 

i.e., for no branch, there is any transition whose input word 

coincides with the processed input tape. This can simply 

happen when the entire tape was read. 

 

Also see :meth:`~FiniteStateMachine.__call__` for a version of 

:meth:`.process` with shortened output. 

 

Internally this function creates and works with an instance of 

:class:`FSMProcessIterator`. This iterator can also be obtained 

with :meth:`iter_process`. 

 

If working with multi-tape finite state machines, all input 

words of transitions are words of `k`-tuples of letters. 

Moreover, the input tape has to consist of `k` tracks, i.e., 

be a list or tuple of `k` iterators, one for each track. 

 

.. WARNING:: 

 

Working with multi-tape finite state machines is still 

experimental and can lead to wrong outputs. 

 

EXAMPLES:: 

 

sage: binary_inverter = FiniteStateMachine({'A': [('A', 0, 1), ('A', 1, 0)]}, 

....: initial_states=['A'], final_states=['A']) 

sage: binary_inverter.process([0, 1, 0, 0, 1, 1]) 

(True, 'A', [1, 0, 1, 1, 0, 0]) 

 

Alternatively, we can invoke this function by:: 

 

sage: binary_inverter([0, 1, 0, 0, 1, 1]) 

(True, 'A', [1, 0, 1, 1, 0, 0]) 

 

Below we construct a finite state machine which tests if an input 

is a non-adjacent form, i.e., no two neighboring letters are 

both nonzero (see also the example on 

:ref:`non-adjacent forms <finite_state_machine_recognizing_NAFs_example>` 

in the documentation of the module 

:doc:`finite_state_machine`):: 

 

sage: NAF = FiniteStateMachine( 

....: {'_': [('_', 0), (1, 1)], 1: [('_', 0)]}, 

....: initial_states=['_'], final_states=['_', 1]) 

sage: [NAF.process(w)[0] for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1], 

....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] 

[True, True, False, True, False, False] 

 

Working only with the first component (i.e., returning whether 

accepted or not) usually corresponds to using the more 

specialized class :class:`Automaton`. 

 

Non-deterministic finite state machines can be handeled as well. 

 

:: 

 

sage: T = Transducer([(0, 1, 0, 0), (0, 2, 0, 0)], 

....: initial_states=[0]) 

sage: T.process([0]) 

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

 

Here is another non-deterministic finite state machine. Note 

that we use ``format_output`` (see 

:class:`FSMProcessIterator`) to convert the written outputs 

(all characters) to strings. 

 

:: 

 

sage: T = Transducer([(0, 1, [0, 0], 'a'), (0, 2, [0, 0, 1], 'b'), 

....: (0, 1, 1, 'c'), (1, 0, [], 'd'), 

....: (1, 1, 1, 'e')], 

....: initial_states=[0], final_states=[0, 1]) 

sage: T.process([0], format_output=lambda o: ''.join(o)) 

(False, None, None) 

sage: T.process([0, 0], format_output=lambda o: ''.join(o)) 

[(True, 0, 'ad'), (True, 1, 'a')] 

sage: T.process([1], format_output=lambda o: ''.join(o)) 

[(True, 0, 'cd'), (True, 1, 'c')] 

sage: T.process([1, 1], format_output=lambda o: ''.join(o)) 

[(True, 0, 'cdcd'), (True, 0, 'ced'), 

(True, 1, 'cdc'), (True, 1, 'ce')] 

sage: T.process([0, 0, 1], format_output=lambda o: ''.join(o)) 

[(True, 0, 'adcd'), (True, 0, 'aed'), 

(True, 1, 'adc'), (True, 1, 'ae'), (False, 2, 'b')] 

sage: T.process([0, 0, 1], format_output=lambda o: ''.join(o), 

....: only_accepted=True) 

[(True, 0, 'adcd'), (True, 0, 'aed'), 

(True, 1, 'adc'), (True, 1, 'ae')] 

 

A simple example of a multi-tape finite state machine is the 

following: It writes the length of the first tape many letters 

``a`` and then the length of the second tape many letters 

``b``:: 

 

sage: M = FiniteStateMachine([(0, 0, (1, None), 'a'), 

....: (0, 1, [], []), 

....: (1, 1, (None, 1), 'b')], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: M.process(([1, 1], [1]), use_multitape_input=True) 

(True, 1, ['a', 'a', 'b']) 

 

.. SEEALSO:: 

 

:meth:`Automaton.process`, 

:meth:`Transducer.process`, 

:meth:`~FiniteStateMachine.iter_process`, 

:meth:`~FiniteStateMachine.__call__`, 

:class:`FSMProcessIterator`. 

 

TESTS:: 

 

sage: T = Transducer([(0, 1, [0, 0], 0), (0, 2, [0, 0, 1], 0), 

....: (0, 1, 1, 2), (1, 0, [], 1), (1, 1, 1, 3)], 

....: initial_states=[0], final_states=[0, 1]) 

sage: T.process([0]) 

(False, None, None) 

sage: T.process([0, 0]) 

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

sage: T.process([1]) 

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

sage: T.process([1, 1]) 

[(True, 0, [2, 1, 2, 1]), (True, 0, [2, 3, 1]), 

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

 

:: 

 

sage: F = FiniteStateMachine([(0, 0, 0, 0)], 

....: initial_states=[0]) 

sage: F.process([0], only_accepted=True) 

[] 

sage: F.process([0], only_accepted=True, list_of_outputs=False) 

Traceback (most recent call last): 

... 

ValueError: No accepting output was found but according to the 

given options, an accepting output should be returned. Change 

only_accepted and/or list_of_outputs options. 

sage: F.process([0], only_accepted=True, list_of_outputs=True) 

[] 

sage: F.process([0], only_accepted=False) 

(False, 0, [0]) 

sage: F.process([0], only_accepted=False, list_of_outputs=False) 

(False, 0, [0]) 

sage: F.process([0], only_accepted=False, list_of_outputs=True) 

[(False, 0, [0])] 

sage: F.process([1], only_accepted=True) 

[] 

sage: F.process([1], only_accepted=True, list_of_outputs=False) 

Traceback (most recent call last): 

... 

ValueError: No accepting output was found but according to the 

given options, an accepting output should be returned. Change 

only_accepted and/or list_of_outputs options. 

sage: F.process([1], only_accepted=True, list_of_outputs=True) 

[] 

sage: F.process([1], only_accepted=False) 

(False, None, None) 

sage: F.process([1], only_accepted=False, list_of_outputs=False) 

(False, None, None) 

sage: F.process([1], only_accepted=False, list_of_outputs=True) 

[] 

 

:: 

 

sage: F = FiniteStateMachine([(0, 1, 1, 'a'), (0, 2, 2, 'b')], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: A = Automaton([(0, 1, 1), (0, 2, 2)], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: T = Transducer([(0, 1, 1, 'a'), (0, 2, 2, 'b')], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: F.process([1]) 

(True, 1, ['a']) 

sage: A.process([1]) 

(True, 1) 

sage: T.process([1]) 

(True, 1, ['a']) 

sage: F.process([2]) 

(False, 2, ['b']) 

sage: A.process([2]) 

(False, 2) 

sage: T.process([2]) 

(False, 2, ['b']) 

sage: F.process([3]) 

(False, None, None) 

sage: A.process([3]) 

(False, None) 

sage: T.process([3]) 

(False, None, None) 

""" 

from copy import copy 

 

# set default values 

options = copy(self._process_default_options_) 

options.update(kwargs) 

 

# perform iteration 

it = self.iter_process(*args, **options) 

for _ in it: 

pass 

 

# process output: filtering accepting results 

only_accepted = options['only_accepted'] 

it_output = [result for result in it.result() 

if not only_accepted or result[0]] 

 

# process output: returning a list output 

if (len(it_output) > 1 and options['list_of_outputs'] is None or 

options['list_of_outputs']): 

return [self._process_convert_output_(out, **options) 

for out in it_output] 

 

# process output: cannot return output to due input parameters 

if options['list_of_outputs'] is False: 

if not it_output and only_accepted: 

raise ValueError('No accepting output was found but according ' 

'to the given options, an accepting output ' 

'should be returned. Change only_accepted ' 

'and/or list_of_outputs options.') 

elif len(it_output) > 1: 

raise ValueError('Got more than one output, but only allowed ' 

'to show one. Change list_of_outputs option.') 

# At this point it_output has length 0 or 1. 

 

# process output: create non-accepting output if needed 

if not it_output: 

if only_accepted: 

return [] 

NoneState = FSMState(None, allow_label_None=True) 

it_output = [(False, NoneState, None)] 

 

return self._process_convert_output_(it_output[0], **options) 

 

 

def _process_convert_output_(self, output_data, **kwargs): 

""" 

Helper function which converts the output of 

:meth:`FiniteStateMachine.process`. This is the identity. 

 

INPUT: 

 

- ``output_data`` -- a triple. 

 

- ``full_output`` -- a boolean. 

 

OUTPUT: 

 

The converted output. 

 

This function is overridden in :class:`Automaton` and 

:class:`Transducer`. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: F = FiniteStateMachine() 

sage: F._process_convert_output_((True, FSMState('a'), [1, 0, 1]), 

....: full_output=False) 

(True, 'a', [1, 0, 1]) 

sage: F._process_convert_output_((True, FSMState('a'), [1, 0, 1]), 

....: full_output=True) 

(True, 'a', [1, 0, 1]) 

""" 

accept_input, current_state, output = output_data 

return (accept_input, current_state, output) 

 

 

def iter_process(self, input_tape=None, initial_state=None, 

process_iterator_class=None, 

iterator_type=None, 

automatic_output_type=False, **kwargs): 

r""" 

This function returns an iterator for processing the input. 

See :meth:`.process` (which runs this iterator until the end) 

for more information. 

 

INPUT: 

 

- ``iterator_type`` -- If ``None`` (default), then 

an instance of :class:`FSMProcessIterator` is returned. If 

this is ``'simple'`` only an iterator over one output is 

returned (an exception is raised if this is not the case, i.e., 

if the process has branched). 

 

See :meth:`process` for a description of the other parameters. 

 

OUTPUT: 

 

An iterator. 

 

EXAMPLES: 

 

We can use :meth:`iter_process` to deal with infinite words:: 

 

sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, 

....: initial_states=['A'], final_states=['A']) 

sage: words.FibonacciWord() 

word: 0100101001001010010100100101001001010010... 

sage: it = inverter.iter_process( 

....: words.FibonacciWord(), iterator_type='simple') 

sage: Words([0,1])(it) 

word: 1011010110110101101011011010110110101101... 

 

This can also be done by:: 

 

sage: inverter.iter_process(words.FibonacciWord(), 

....: iterator_type='simple', 

....: automatic_output_type=True) 

word: 1011010110110101101011011010110110101101... 

 

or even simpler by:: 

 

sage: inverter(words.FibonacciWord()) 

word: 1011010110110101101011011010110110101101... 

 

To see what is going on, we use :meth:`iter_process` without 

arguments:: 

 

sage: from itertools import islice 

sage: it = inverter.iter_process(words.FibonacciWord()) 

sage: for current in islice(it, 4): 

....: print(current) 

process (1 branch) 

+ at state 'A' 

+-- tape at 1, [[1]] 

process (1 branch) 

+ at state 'A' 

+-- tape at 2, [[1, 0]] 

process (1 branch) 

+ at state 'A' 

+-- tape at 3, [[1, 0, 1]] 

process (1 branch) 

+ at state 'A' 

+-- tape at 4, [[1, 0, 1, 1]] 

 

The following show the difference between using the ``'simple'``-option 

and not using it. With this option, we have 

:: 

 

sage: it = inverter.iter_process(input_tape=[0, 1, 1], 

....: iterator_type='simple') 

sage: for i, o in enumerate(it): 

....: print('step %s: output %s' % (i, o)) 

step 0: output 1 

step 1: output 0 

step 2: output 0 

 

So :meth:`iter_process` is a generator expression which gives 

a new output letter in each step (and not more). In many cases 

this is sufficient. 

 

Doing the same without the ``'simple'``-option does not give 

the output directly; it has to be extracted first. On the 

other hand, additional information is presented:: 

 

sage: it = inverter.iter_process(input_tape=[0, 1, 1]) 

sage: for current in it: 

....: print(current) 

process (1 branch) 

+ at state 'A' 

+-- tape at 1, [[1]] 

process (1 branch) 

+ at state 'A' 

+-- tape at 2, [[1, 0]] 

process (1 branch) 

+ at state 'A' 

+-- tape at 3, [[1, 0, 0]] 

process (0 branches) 

sage: it.result() 

[Branch(accept=True, state='A', output=[1, 0, 0])] 

 

One can see the growing of the output (the list of lists at 

the end of each entry). 

 

Even if the transducer has transitions with empty or multiletter 

output, the simple iterator returns one new output letter in 

each step:: 

 

sage: T = Transducer([(0, 0, 0, []), 

....: (0, 0, 1, [1]), 

....: (0, 0, 2, [2, 2])], 

....: initial_states=[0]) 

sage: it = T.iter_process(input_tape=[0, 1, 2, 0, 1, 2], 

....: iterator_type='simple') 

sage: for i, o in enumerate(it): 

....: print('step %s: output %s' % (i, o)) 

step 0: output 1 

step 1: output 2 

step 2: output 2 

step 3: output 1 

step 4: output 2 

step 5: output 2 

 

.. SEEALSO:: 

 

:meth:`FiniteStateMachine.process`, 

:meth:`Automaton.process`, 

:meth:`Transducer.process`, 

:meth:`~FiniteStateMachine.__call__`, 

:class:`FSMProcessIterator`. 

""" 

if automatic_output_type and 'format_output' in kwargs: 

raise ValueError("Parameter 'automatic_output_type' set, but " 

"'format_output' specified as well.") 

if automatic_output_type: 

try: 

kwargs['format_output'] = input_tape.parent() 

except AttributeError: 

kwargs['format_output'] = type(input_tape) 

 

if process_iterator_class is None: 

process_iterator_class = FSMProcessIterator 

it = process_iterator_class(self, 

input_tape=input_tape, 

initial_state=initial_state, 

**kwargs) 

if iterator_type is None: 

return it 

elif iterator_type == 'simple': 

simple_it = self._iter_process_simple_(it) 

try: 

return kwargs['format_output'](simple_it) 

except KeyError: 

return simple_it 

else: 

raise ValueError('Iterator type %s unknown.' % (iterator_type,)) 

 

 

def _iter_process_simple_(self, iterator): 

r""" 

Converts a :class:`process iterator <FSMProcessIterator>` to a simpler 

iterator, which only outputs the written letters. 

 

INPUT: 

 

- ``iterator`` -- in instance of :class:`FSMProcessIterator`. 

 

OUTPUT: 

 

A generator. 

 

An exception is raised if the process branches. 

 

EXAMPLES:: 

 

sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, 

....: initial_states=['A'], final_states=['A']) 

sage: it = inverter.iter_process(words.FibonacciWord()[:10]) 

sage: it_simple = inverter._iter_process_simple_(it) 

sage: list(it_simple) 

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

 

.. SEEALSO:: 

 

:meth:`iter_process`, 

:meth:`FiniteStateMachine.process`, 

:meth:`Automaton.process`, 

:meth:`Transducer.process`, 

:meth:`~FiniteStateMachine.__call__`, 

:class:`FSMProcessIterator`. 

 

TESTS:: 

 

sage: T = Transducer([(0, 0, [0, 0], 0), (0, 1, 0, 0)], 

....: initial_states=[0], final_states=[0]) 

sage: list(T.iter_process([0, 0], iterator_type='simple')) 

Traceback (most recent call last): 

... 

RuntimeError: Process has branched (2 branches exist). 

The 'simple' iterator cannot be used here. 

sage: T = Transducer([(0, 0, 0, 0), (0, 1, 0, 0)], 

....: initial_states=[0], final_states=[0]) 

sage: list(T.iter_process([0], iterator_type='simple')) 

Traceback (most recent call last): 

... 

RuntimeError: Process has branched (visiting 2 states in branch). 

The 'simple' iterator cannot be used here. 

sage: T = Transducer([(0, 1, 0, 1), (0, 1, 0, 2)], 

....: initial_states=[0], final_states=[0]) 

sage: list(T.iter_process([0], iterator_type='simple')) 

Traceback (most recent call last): 

... 

RuntimeError: Process has branched. (2 different outputs in branch). 

The 'simple' iterator cannot be used here. 

""" 

for current in iterator: 

if not current: 

return 

 

if len(current) > 1: 

raise RuntimeError("Process has branched " 

"(%s branches exist). The " 

"'simple' iterator cannot be used " 

"here." % 

(len(current),)) 

pos, states = next(six.iteritems(current)) 

if len(states) > 1: 

raise RuntimeError("Process has branched " 

"(visiting %s states in branch). The " 

"'simple' iterator cannot be used " 

"here." % 

(len(states),)) 

state, branch = next(six.iteritems(states)) 

if len(branch.outputs) > 1: 

raise RuntimeError("Process has branched. " 

"(%s different outputs in branch). The " 

"'simple' iterator cannot be used " 

"here." % 

(len(branch.outputs),)) 

 

for o in branch.outputs[0]: 

yield o 

branch.outputs[0] = [] # Reset output so that in the next round 

# (of "for current in iterator") only new 

# output is returned (by the yield). 

 

 

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

# change finite state machine (add/remove state/transitions) 

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

 

 

def add_state(self, state): 

""" 

Adds a state to the finite state machine and returns the new 

state. If the state already exists, that existing state is 

returned. 

 

INPUT: 

 

- ``state`` is either an instance of 

:class:`FSMState` or, 

otherwise, a label of a state. 

 

OUTPUT: 

 

The new or existing state. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: F = FiniteStateMachine() 

sage: A = FSMState('A', is_initial=True) 

sage: F.add_state(A) 

'A' 

""" 

try: 

return self.state(state) 

except LookupError: 

pass 

# at this point we know that we have a new state 

if is_FSMState(state): 

s = state 

else: 

s = FSMState(state) 

s.transitions = list() 

self._states_.append(s) 

try: 

self._states_dict_[s.label()] = s 

except AttributeError: 

pass 

return s 

 

 

def add_states(self, states): 

""" 

Adds several states. See add_state for more information. 

 

INPUT: 

 

- ``states`` -- a list of states or iterator over states. 

 

OUTPUT: 

 

Nothing. 

 

EXAMPLES:: 

 

sage: F = FiniteStateMachine() 

sage: F.add_states(['A', 'B']) 

sage: F.states() 

['A', 'B'] 

""" 

for state in states: 

self.add_state(state) 

 

 

def add_transition(self, *args, **kwargs): 

""" 

Adds a transition to the finite state machine and returns the 

new transition. 

 

If the transition already exists, the return value of 

``self.on_duplicate_transition`` is returned. See the 

documentation of :class:`FiniteStateMachine`. 

 

INPUT: 

 

The following forms are all accepted: 

 

:: 

 

sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition 

sage: A = FSMState('A') 

sage: B = FSMState('B') 

 

sage: FSM = FiniteStateMachine() 

sage: FSM.add_transition(FSMTransition(A, B, 0, 1)) 

Transition from 'A' to 'B': 0|1 

 

sage: FSM = FiniteStateMachine() 

sage: FSM.add_transition(A, B, 0, 1) 

Transition from 'A' to 'B': 0|1 

 

sage: FSM = FiniteStateMachine() 

sage: FSM.add_transition(A, B, word_in=0, word_out=1) 

Transition from 'A' to 'B': 0|1 

 

sage: FSM = FiniteStateMachine() 

sage: FSM.add_transition('A', 'B', {'word_in': 0, 'word_out': 1}) 

Transition from 'A' to 'B': {'word_in': 0, 'word_out': 1}|- 

 

sage: FSM = FiniteStateMachine() 

sage: FSM.add_transition(from_state=A, to_state=B, 

....: word_in=0, word_out=1) 

Transition from 'A' to 'B': 0|1 

 

sage: FSM = FiniteStateMachine() 

sage: FSM.add_transition({'from_state': A, 'to_state': B, 

....: 'word_in': 0, 'word_out': 1}) 

Transition from 'A' to 'B': 0|1 

 

sage: FSM = FiniteStateMachine() 

sage: FSM.add_transition((A, B, 0, 1)) 

Transition from 'A' to 'B': 0|1 

 

sage: FSM = FiniteStateMachine() 

sage: FSM.add_transition([A, B, 0, 1]) 

Transition from 'A' to 'B': 0|1 

 

If the states ``A`` and ``B`` are not instances of 

:class:`FSMState`, then it is assumed that they are labels of 

states. 

 

OUTPUT: 

 

The new transition. 

""" 

if len(args) + len(kwargs) == 0: 

return 

if len(args) + len(kwargs) == 1: 

if len(args) == 1: 

d = args[0] 

if is_FSMTransition(d): 

return self._add_fsm_transition_(d) 

else: 

d = next(itervalues(kwargs)) 

if hasattr(d, 'iteritems'): 

args = [] 

kwargs = d 

elif hasattr(d, '__iter__'): 

args = d 

kwargs = {} 

else: 

raise TypeError("Cannot decide what to do with input.") 

 

data = dict(zip( 

('from_state', 'to_state', 'word_in', 'word_out', 'hook'), 

args)) 

data.update(kwargs) 

 

data['from_state'] = self.add_state(data['from_state']) 

data['to_state'] = self.add_state(data['to_state']) 

 

return self._add_fsm_transition_(FSMTransition(**data)) 

 

 

def _add_fsm_transition_(self, t): 

""" 

Adds a transition. 

 

INPUT: 

 

- ``t`` -- an instance of :class:`FSMTransition`. 

 

OUTPUT: 

 

The new transition. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: F = FiniteStateMachine() 

sage: F._add_fsm_transition_(FSMTransition('A', 'B')) 

Transition from 'A' to 'B': -|- 

""" 

try: 

existing_transition = self.transition(t) 

except LookupError: 

pass 

else: 

return self.on_duplicate_transition(existing_transition, t) 

from_state = self.add_state(t.from_state) 

self.add_state(t.to_state) 

from_state.transitions.append(t) 

return t 

 

 

def add_from_transition_function(self, function, initial_states=None, 

explore_existing_states=True): 

""" 

Constructs a finite state machine from a transition function. 

 

INPUT: 

 

- ``function`` may return a tuple (new_state, output_word) or a 

list of such tuples. 

 

- ``initial_states`` -- If no initial states are given, the 

already existing initial states of self are taken. 

 

- If ``explore_existing_states`` is True (default), then 

already existing states in self (e.g. already given final 

states) will also be processed if they are reachable from 

the initial states. 

 

OUTPUT: 

 

Nothing. 

 

EXAMPLES:: 

 

sage: F = FiniteStateMachine(initial_states=['A'], 

....: input_alphabet=[0, 1]) 

sage: def f(state, input): 

....: return [('A', input), ('B', 1-input)] 

sage: F.add_from_transition_function(f) 

sage: F.transitions() 

[Transition from 'A' to 'A': 0|0, 

Transition from 'A' to 'B': 0|1, 

Transition from 'A' to 'A': 1|1, 

Transition from 'A' to 'B': 1|0, 

Transition from 'B' to 'A': 0|0, 

Transition from 'B' to 'B': 0|1, 

Transition from 'B' to 'A': 1|1, 

Transition from 'B' to 'B': 1|0] 

 

Initial states can also be given as a parameter:: 

 

sage: F = FiniteStateMachine(input_alphabet=[0,1]) 

sage: def f(state, input): 

....: return [('A', input), ('B', 1-input)] 

sage: F.add_from_transition_function(f,initial_states=['A']) 

sage: F.initial_states() 

['A'] 

 

Already existing states in the finite state machine (the final 

states in the example below) are also explored:: 

 

sage: F = FiniteStateMachine(initial_states=[0], 

....: final_states=[1], 

....: input_alphabet=[0]) 

sage: def transition_function(state, letter): 

....: return(1-state, []) 

sage: F.add_from_transition_function(transition_function) 

sage: F.transitions() 

[Transition from 0 to 1: 0|-, 

Transition from 1 to 0: 0|-] 

 

If ``explore_existing_states=False``, however, this behavior 

is turned off, i.e., already existing states are not 

explored:: 

 

sage: F = FiniteStateMachine(initial_states=[0], 

....: final_states=[1], 

....: input_alphabet=[0]) 

sage: def transition_function(state, letter): 

....: return(1-state, []) 

sage: F.add_from_transition_function(transition_function, 

....: explore_existing_states=False) 

sage: F.transitions() 

[Transition from 0 to 1: 0|-] 

 

TESTS:: 

 

sage: F = FiniteStateMachine(initial_states=['A']) 

sage: def f(state, input): 

....: return [('A', input), ('B', 1-input)] 

sage: F.add_from_transition_function(f) 

Traceback (most recent call last): 

... 

ValueError: No input alphabet is given. 

Try calling determine_alphabets(). 

 

:: 

 

sage: def transition(state, where): 

....: return (vector([0, 0]), 1) 

sage: Transducer(transition, input_alphabet=[0], initial_states=[0]) 

Traceback (most recent call last): 

... 

TypeError: mutable vectors are unhashable 

""" 

if self.input_alphabet is None: 

raise ValueError("No input alphabet is given. " 

"Try calling determine_alphabets().") 

 

if initial_states is None: 

not_done = self.initial_states() 

elif hasattr(initial_states, '__iter__'): 

not_done = [] 

for s in initial_states: 

state = self.add_state(s) 

state.is_initial = True 

not_done.append(state) 

else: 

raise TypeError('Initial states must be iterable ' \ 

'(e.g. a list of states).') 

if len(not_done) == 0: 

raise ValueError("No state is initial.") 

if explore_existing_states: 

ignore_done = self.states() 

for s in not_done: 

try: 

ignore_done.remove(s) 

except ValueError: 

pass 

else: 

ignore_done = [] 

while len(not_done) > 0: 

s = not_done.pop(0) 

for letter in self.input_alphabet: 

try: 

return_value = function(s.label(), letter) 

except LookupError: 

continue 

if not hasattr(return_value, "pop"): 

return_value = [return_value] 

try: 

for (st_label, word) in return_value: 

pass 

except TypeError: 

raise ValueError("The callback function for " 

"add_from_transition is expected " 

"to return a pair (new_state, " 

"output_label) or a list of such pairs. " 

"For the state %s and the input " 

"letter %s, it however returned %s, " 

"which is not acceptable." 

% (s.label(), letter, return_value)) 

for (st_label, word) in return_value: 

if not self.has_state(st_label): 

not_done.append(self.add_state(st_label)) 

elif len(ignore_done) > 0: 

u = self.state(st_label) 

if u in ignore_done: 

not_done.append(u) 

ignore_done.remove(u) 

self.add_transition(s, st_label, 

word_in=letter, word_out=word) 

 

 

def add_transitions_from_function(self, function, labels_as_input=True): 

""" 

Adds one or more transitions if ``function(state, state)`` 

says that there are some. 

 

INPUT: 

 

- ``function`` -- a transition function. Given two states 

``from_state`` and ``to_state`` (or their labels if 

``label_as_input`` is true), this function shall return a 

tuple ``(word_in, word_out)`` to add a transition from 

``from_state`` to ``to_state`` with input and output labels 

``word_in`` and ``word_out``, respectively. If no such 

addition is to be added, the transition function shall 

return ``None``. The transition function may also return 

a list of such tuples in order to add multiple transitions 

between the pair of states. 

 

- ``label_as_input`` -- (default: ``True``) 

 

OUTPUT: 

 

Nothing. 

 

EXAMPLES:: 

 

sage: F = FiniteStateMachine() 

sage: F.add_states(['A', 'B', 'C']) 

sage: def f(state1, state2): 

....: if state1 == 'C': 

....: return None 

....: return (0, 1) 

sage: F.add_transitions_from_function(f) 

sage: len(F.transitions()) 

6 

 

Multiple transitions are also possible:: 

 

sage: F = FiniteStateMachine() 

sage: F.add_states([0, 1]) 

sage: def f(state1, state2): 

....: if state1 != state2: 

....: return [(0, 1), (1, 0)] 

....: else: 

....: return None 

sage: F.add_transitions_from_function(f) 

sage: F.transitions() 

[Transition from 0 to 1: 0|1, 

Transition from 0 to 1: 1|0, 

Transition from 1 to 0: 0|1, 

Transition from 1 to 0: 1|0] 

 

TESTS:: 

 

sage: F = FiniteStateMachine() 

sage: F.add_state(0) 

0 

sage: def f(state1, state2): 

....: return 1 

sage: F.add_transitions_from_function(f) 

Traceback (most recent call last): 

... 

ValueError: The callback function for add_transitions_from_function 

is expected to return a pair (word_in, word_out) or a list of such 

pairs. For states 0 and 0 however, it returned 1, 

which is not acceptable. 

 

""" 

for s_from in self.iter_states(): 

for s_to in self.iter_states(): 

try: 

if labels_as_input: 

return_value = function(s_from.label(), s_to.label()) 

else: 

return_value = function(s_from, s_to) 

except LookupError: 

continue 

if return_value is None: 

continue 

if not hasattr(return_value, "pop"): 

transitions = [return_value] 

else: 

transitions = return_value 

for t in transitions: 

if not hasattr(t, '__getitem__'): 

raise ValueError("The callback function for " 

"add_transitions_from_function " 

"is expected to return a " 

"pair (word_in, word_out) or a " 

"list of such pairs. For " 

"states %s and %s however, it " 

"returned %s, which is not " 

"acceptable." % (s_from, s_to, return_value)) 

label_in = t[0] 

try: 

label_out = t[1] 

except LookupError: 

label_out = None 

self.add_transition(s_from, s_to, label_in, label_out) 

 

 

def delete_transition(self, t): 

""" 

Deletes a transition by removing it from the list of transitions of 

the state, where the transition starts. 

 

INPUT: 

 

- ``t`` -- a transition. 

 

OUTPUT: 

 

Nothing. 

 

EXAMPLES:: 

 

sage: F = FiniteStateMachine([('A', 'B', 0), ('B', 'A', 1)]) 

sage: F.delete_transition(('A', 'B', 0)) 

sage: F.transitions() 

[Transition from 'B' to 'A': 1|-] 

""" 

transition = self.transition(t) 

transition.from_state.transitions.remove(transition) 

 

 

def delete_state(self, s): 

""" 

Deletes a state and all transitions coming or going to this state. 

 

INPUT: 

 

- ``s`` -- a label of a state or an :class:`FSMState`. 

 

OUTPUT: 

 

Nothing. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMTransition 

sage: t1 = FSMTransition('A', 'B', 0) 

sage: t2 = FSMTransition('B', 'B', 1) 

sage: F = FiniteStateMachine([t1, t2]) 

sage: F.delete_state('A') 

sage: F.transitions() 

[Transition from 'B' to 'B': 1|-] 

 

TESTS: 

 

This shows that :trac:`16024` is fixed. :: 

 

sage: F._states_ 

['B'] 

sage: F._states_dict_ 

{'B': 'B'} 

""" 

state = self.state(s) 

for transition in self.transitions(): 

if transition.to_state == state: 

self.delete_transition(transition) 

self._states_.remove(state) 

try: 

del self._states_dict_[state.label()] 

except AttributeError: 

pass 

 

 

def remove_epsilon_transitions(self): 

""" 

TESTS:: 

 

sage: FiniteStateMachine().remove_epsilon_transitions() 

Traceback (most recent call last): 

... 

NotImplementedError 

""" 

raise NotImplementedError 

 

 

def epsilon_successors(self, state): 

""" 

Returns the dictionary with states reachable from ``state`` 

without reading anything from an input tape as keys. The 

values are lists of outputs. 

 

INPUT: 

 

- ``state`` -- the state whose epsilon successors should be 

determined. 

 

OUTPUT: 

 

A dictionary mapping states to a list of output words. 

 

The states in the output are the epsilon successors of 

``state``. Each word of the list of output words is a word 

written when taking a path from ``state`` to the corresponding 

state. 

 

EXAMPLES:: 

 

sage: T = Transducer([(0, 1, None, 'a'), (1, 2, None, 'b')]) 

sage: T.epsilon_successors(0) 

{1: [['a']], 2: [['a', 'b']]} 

sage: T.epsilon_successors(1) 

{2: [['b']]} 

sage: T.epsilon_successors(2) 

{} 

 

If there is a cycle with only epsilon transitions, then this 

cycle is only processed once and there is no infinite loop:: 

 

sage: S = Transducer([(0, 1, None, 'a'), (1, 0, None, 'b')]) 

sage: S.epsilon_successors(0) 

{0: [['a', 'b']], 1: [['a']]} 

sage: S.epsilon_successors(1) 

{0: [['b']], 1: [['b', 'a']]} 

""" 

return self.state(state)._epsilon_successors_(self) 

 

 

def accessible_components(self): 

""" 

Return a new finite state machine with the accessible states 

of self and all transitions between those states. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A finite state machine with the accessible states of self and 

all transitions between those states. 

 

A state is accessible if there is a directed path from an 

initial state to the state. If self has no initial states then 

a copy of the finite state machine self is returned. 

 

EXAMPLES:: 

 

sage: F = Automaton([(0, 0, 0), (0, 1, 1), (1, 1, 0), (1, 0, 1)], 

....: initial_states=[0]) 

sage: F.accessible_components() 

Automaton with 2 states 

 

:: 

 

sage: F = Automaton([(0, 0, 1), (0, 0, 1), (1, 1, 0), (1, 0, 1)], 

....: initial_states=[0]) 

sage: F.accessible_components() 

Automaton with 1 state 

 

.. SEEALSO:: 

:meth:`coaccessible_components` 

 

TESTS: 

 

Check whether input of length > 1 works:: 

 

sage: F = Automaton([(0, 1, [0, 1]), (0, 2, 0)], 

....: initial_states=[0]) 

sage: F.accessible_components() 

Automaton with 3 states 

""" 

from copy import deepcopy 

if len(self.initial_states()) == 0: 

return deepcopy(self) 

 

memo = {} 

def accessible(from_state, read): 

return [(deepcopy(x.to_state, memo), x.word_out) 

for x in self.iter_transitions(from_state) 

if x.word_in[0] == read] 

 

new_initial_states=[deepcopy(x, memo) for x in self.initial_states()] 

result = self.empty_copy() 

result.add_from_transition_function(accessible, 

initial_states=new_initial_states) 

for final_state in self.iter_final_states(): 

try: 

new_final_state=result.state(final_state.label) 

new_final_state.is_final=True 

except LookupError: 

pass 

return result 

 

 

def coaccessible_components(self): 

r""" 

Return the sub-machine induced by the coaccessible states of this 

finite state machine. 

 

OUTPUT: 

 

A finite state machine of the same type as this finite state 

machine. 

 

EXAMPLES:: 

 

sage: A = automata.ContainsWord([1, 1], 

....: input_alphabet=[0, 1]).complement().minimization().relabeled() 

sage: A.transitions() 

[Transition from 0 to 0: 0|-, 

Transition from 0 to 0: 1|-, 

Transition from 1 to 1: 0|-, 

Transition from 1 to 2: 1|-, 

Transition from 2 to 1: 0|-, 

Transition from 2 to 0: 1|-] 

sage: A.initial_states() 

[1] 

sage: A.final_states() 

[1, 2] 

sage: C = A.coaccessible_components() 

sage: C.transitions() 

[Transition from 1 to 1: 0|-, 

Transition from 1 to 2: 1|-, 

Transition from 2 to 1: 0|-] 

 

.. SEEALSO:: 

:meth:`accessible_components`, 

:meth:`induced_sub_finite_state_machine` 

""" 

DG = self.digraph().reverse() 

coaccessible_states = DG.breadth_first_search( 

[_.label() for _ in self.iter_final_states()]) 

return self.induced_sub_finite_state_machine( 

[self.state(_) for _ in coaccessible_states]) 

 

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

# creating new finite state machines 

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

 

 

def disjoint_union(self, other): 

""" 

Return the disjoint union of this and another finite state 

machine. 

 

INPUT: 

 

- ``other`` -- a :class:`FiniteStateMachine`. 

 

OUTPUT: 

 

A finite state machine of the same type as this finite state 

machine. 

 

In general, the disjoint union of two finite state machines is 

non-deterministic. In the case of a automata, the language 

accepted by the disjoint union is the union of the languages 

accepted by the constituent automata. In the case of 

transducer, for each successful path in one of the constituent 

transducers, there will be one successful path with the same input 

and output labels in the disjoint union. 

 

The labels of the states of the disjoint union are pairs ``(i, 

s)``: for each state ``s`` of this finite state machine, there 

is a state ``(0, s)`` in the disjoint union; for each state 

``s`` of the other finite state machine, there is a state ``(1, 

s)`` in the disjoint union. 

 

The input alphabet is the union of the input alphabets (if 

possible) and ``None`` otherwise. In the latter case, try 

calling :meth:`.determine_alphabets`. 

 

The disjoint union can also be written as ``A + B`` or ``A | B``. 

 

EXAMPLES:: 

 

sage: A = Automaton([(0, 1, 0), (1, 0, 1)], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: A([0, 1, 0, 1]) 

True 

sage: B = Automaton([(0, 1, 0), (1, 2, 0), (2, 0, 1)], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: B([0, 0, 1]) 

True 

sage: C = A.disjoint_union(B) 

sage: C 

Automaton with 5 states 

sage: C.transitions() 

[Transition from (0, 0) to (0, 1): 0|-, 

Transition from (0, 1) to (0, 0): 1|-, 

Transition from (1, 0) to (1, 1): 0|-, 

Transition from (1, 1) to (1, 2): 0|-, 

Transition from (1, 2) to (1, 0): 1|-] 

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

True 

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

True 

sage: C([1]) 

False 

sage: C.initial_states() 

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

 

Instead of ``.disjoint_union``, alternative notations are 

available:: 

 

sage: C1 = A + B 

sage: C1 == C 

True 

sage: C2 = A | B 

sage: C2 == C 

True 

 

In general, the disjoint union is not deterministic.:: 

 

sage: C.is_deterministic() 

False 

sage: D = C.determinisation().minimization() 

sage: D.is_equivalent(Automaton([(0, 0, 0), (0, 0, 1), 

....: (1, 7, 0), (1, 0, 1), (2, 6, 0), (2, 0, 1), 

....: (3, 5, 0), (3, 0, 1), (4, 0, 0), (4, 2, 1), 

....: (5, 0, 0), (5, 3, 1), (6, 4, 0), (6, 0, 1), 

....: (7, 4, 0), (7, 3, 1)], 

....: initial_states=[1], 

....: final_states=[1, 2, 3])) 

True 

 

Disjoint union of transducers:: 

 

sage: T1 = Transducer([(0, 0, 0, 1)], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: T2 = Transducer([(0, 0, 0, 2)], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: T1([0]) 

[1] 

sage: T2([0]) 

[2] 

sage: T = T1.disjoint_union(T2) 

sage: T([0]) 

Traceback (most recent call last): 

... 

ValueError: Found more than one accepting path. 

sage: T.process([0]) 

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

 

Handling of the input alphabet (see :trac:`18989`):: 

 

sage: A = Automaton([(0, 0, 0)]) 

sage: B = Automaton([(0, 0, 1)], input_alphabet=[1, 2]) 

sage: C = Automaton([(0, 0, 2)], determine_alphabets=False) 

sage: D = Automaton([(0, 0, [[0, 0]])], input_alphabet=[[0, 0]]) 

sage: A.input_alphabet 

[0] 

sage: B.input_alphabet 

[1, 2] 

sage: C.input_alphabet is None 

True 

sage: D.input_alphabet 

[[0, 0]] 

sage: (A + B).input_alphabet 

[0, 1, 2] 

sage: (A + C).input_alphabet is None 

True 

sage: (A + D).input_alphabet is None 

True 

 

.. SEEALSO:: 

 

:meth:`Automaton.intersection`, 

:meth:`Transducer.intersection`, 

:meth:`.determine_alphabets`. 

""" 

result = self.empty_copy() 

for s in self.iter_states(): 

result.add_state(s.relabeled((0, s))) 

for s in other.iter_states(): 

result.add_state(s.relabeled((1, s))) 

for t in self.iter_transitions(): 

result.add_transition((0, t.from_state), 

(0, t.to_state), 

t.word_in, 

t.word_out) 

for t in other.iter_transitions(): 

result.add_transition((1, t.from_state), 

(1, t.to_state), 

t.word_in, 

t.word_out) 

try: 

result.input_alphabet = list(set(self.input_alphabet) 

| set(other.input_alphabet)) 

except TypeError: 

# e.g. None or unhashable letters 

result.input_alphabet = None 

 

return result 

 

 

def concatenation(self, other): 

""" 

Concatenate this finite state machine with another finite 

state machine. 

 

INPUT: 

 

- ``other`` -- a :class:`FiniteStateMachine`. 

 

OUTPUT: 

 

A :class:`FiniteStateMachine` of the same type as this finite 

state machine. 

 

Assume that both finite state machines are automata. If 

`\mathcal{L}_1` is the language accepted by this automaton and 

`\mathcal{L}_2` is the language accepted by the other automaton, 

then the language accepted by the concatenated automaton is 

`\{ w_1w_2 \mid w_1\in\mathcal{L}_1, w_2\in\mathcal{L}_2\}` where 

`w_1w_2` denotes the concatenation of the words `w_1` and `w_2`. 

 

Assume that both finite state machines are transducers and that 

this transducer maps words `w_1\in\mathcal{L}_1` to words 

`f_1(w_1)` and that the other transducer maps words 

`w_2\in\mathcal{L}_2` to words `f_2(w_2)`. Then the concatenated 

transducer maps words `w_1w_2` with `w_1\in\mathcal{L}_1` and 

`w_2\in\mathcal{L}_2` to `f_1(w_1)f_2(w_2)`. Here, `w_1w_2` and 

`f_1(w_1)f_2(w_2)` again denote concatenation of words. 

 

The input alphabet is the union of the input alphabets (if 

possible) and ``None`` otherwise. In the latter case, try 

calling :meth:`.determine_alphabets`. 

 

Instead of ``A.concatenation(B)``, the notation ``A * B`` can be 

used. 

 

EXAMPLES: 

 

Concatenation of two automata:: 

 

sage: A = automata.Word([0]) 

sage: B = automata.Word([1]) 

sage: C = A.concatenation(B) 

sage: C.transitions() 

[Transition from (0, 0) to (0, 1): 0|-, 

Transition from (0, 1) to (1, 0): -|-, 

Transition from (1, 0) to (1, 1): 1|-] 

sage: [w 

....: for w in ([0, 0], [0, 1], [1, 0], [1, 1]) 

....: if C(w)] 

[[0, 1]] 

sage: from sage.combinat.finite_state_machine import ( 

....: is_Automaton, is_Transducer) 

sage: is_Automaton(C) 

True 

 

Concatenation of two transducers:: 

 

sage: A = Transducer([(0, 1, 0, 1), (0, 1, 1, 2)], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: B = Transducer([(0, 1, 0, 1), (0, 1, 1, 0)], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: C = A.concatenation(B) 

sage: C.transitions() 

[Transition from (0, 0) to (0, 1): 0|1, 

Transition from (0, 0) to (0, 1): 1|2, 

Transition from (0, 1) to (1, 0): -|-, 

Transition from (1, 0) to (1, 1): 0|1, 

Transition from (1, 0) to (1, 1): 1|0] 

sage: [(w, C(w)) for w in ([0, 0], [0, 1], [1, 0], [1, 1])] 

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

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

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

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

sage: is_Transducer(C) 

True 

 

 

Alternative notation as multiplication:: 

 

sage: C == A * B 

True 

 

Final output words are taken into account:: 

 

sage: A = Transducer([(0, 1, 0, 1)], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: A.state(1).final_word_out = 2 

sage: B = Transducer([(0, 1, 0, 3)], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: B.state(1).final_word_out = 4 

sage: C = A * B 

sage: C([0, 0]) 

[1, 2, 3, 4] 

 

Handling of the input alphabet:: 

 

sage: A = Automaton([(0, 0, 0)]) 

sage: B = Automaton([(0, 0, 1)], input_alphabet=[1, 2]) 

sage: C = Automaton([(0, 0, 2)], determine_alphabets=False) 

sage: D = Automaton([(0, 0, [[0, 0]])], input_alphabet=[[0, 0]]) 

sage: A.input_alphabet 

[0] 

sage: B.input_alphabet 

[1, 2] 

sage: C.input_alphabet is None 

True 

sage: D.input_alphabet 

[[0, 0]] 

sage: (A * B).input_alphabet 

[0, 1, 2] 

sage: (A * C).input_alphabet is None 

True 

sage: (A * D).input_alphabet is None 

True 

 

.. SEEALSO:: 

 

:meth:`~.disjoint_union`, 

:meth:`.determine_alphabets`. 

 

TESTS:: 

 

sage: A = Automaton() 

sage: F = FiniteStateMachine() 

sage: A * F 

Traceback (most recent call last): 

... 

TypeError: Cannot concatenate finite state machines of 

different types. 

sage: F * A 

Traceback (most recent call last): 

... 

TypeError: Cannot concatenate finite state machines of 

different types. 

sage: F * 5 

Traceback (most recent call last): 

... 

TypeError: A finite state machine can only be concatenated 

with a another finite state machine. 

""" 

if not is_FiniteStateMachine(other): 

raise TypeError('A finite state machine can only be concatenated ' 

'with a another finite state machine.') 

if is_Automaton(other) != is_Automaton(self): 

raise TypeError('Cannot concatenate finite state machines of ' 

'different types.') 

 

result = self.empty_copy() 

first_states = {} 

second_states = {} 

for s in self.iter_states(): 

new_state = s.relabeled((0, s.label())) 

new_state.final_word_out = None 

new_state.is_final = False 

first_states[s] = new_state 

result.add_state(new_state) 

 

for s in other.iter_states(): 

new_state = s.relabeled((1, s.label())) 

new_state.is_initial = False 

second_states[s] = new_state 

result.add_state(new_state) 

 

for t in self.iter_transitions(): 

result.add_transition(first_states[t.from_state], 

first_states[t.to_state], 

t.word_in, 

t.word_out) 

 

for t in other.iter_transitions(): 

result.add_transition(second_states[t.from_state], 

second_states[t.to_state], 

t.word_in, 

t.word_out) 

 

for s in self.iter_final_states(): 

first_state = first_states[s] 

for t in other.iter_initial_states(): 

second_state = second_states[t] 

result.add_transition(first_state, 

second_state, 

[], 

s.final_word_out) 

 

try: 

result.input_alphabet = list(set(self.input_alphabet) 

| set(other.input_alphabet)) 

except TypeError: 

# e.g. None or unhashable letters 

result.input_alphabet = None 

 

return result 

 

 

__mul__ = concatenation 

 

 

def kleene_star(self): 

r""" 

Compute the Kleene closure of this finite state machine. 

 

OUTPUT: 

 

A :class:`FiniteStateMachine` of the same type as this finite 

state machine. 

 

Assume that this finite state machine is an automaton 

recognizing the language `\mathcal{L}`. Then the Kleene star 

recognizes the language `\mathcal{L}^*=\{ w_1\ldots w_n \mid 

n\ge 0, w_j\in\mathcal{L} \text{ for all } j\}`. 

 

Assume that this finite state machine is a transducer realizing 

a function `f` on some alphabet `\mathcal{L}`. Then the Kleene 

star realizes a function `g` on `\mathcal{L}^*` with 

`g(w_1\ldots w_n)=f(w_1)\ldots f(w_n)`. 

 

EXAMPLES: 

 

Kleene star of an automaton:: 

 

sage: A = automata.Word([0, 1]) 

sage: B = A.kleene_star() 

sage: B.transitions() 

[Transition from 0 to 1: 0|-, 

Transition from 2 to 0: -|-, 

Transition from 1 to 2: 1|-] 

sage: from sage.combinat.finite_state_machine import ( 

....: is_Automaton, is_Transducer) 

sage: is_Automaton(B) 

True 

sage: [w for w in ([], [0, 1], [0, 1, 0], [0, 1, 0, 1], [0, 1, 1, 1]) 

....: if B(w)] 

[[], 

[0, 1], 

[0, 1, 0, 1]] 

 

Kleene star of a transducer:: 

 

sage: T = Transducer([(0, 1, 0, 1), (0, 1, 1, 0)], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: S = T.kleene_star() 

sage: S.transitions() 

[Transition from 0 to 1: 0|1, 

Transition from 0 to 1: 1|0, 

Transition from 1 to 0: -|-] 

sage: is_Transducer(S) 

True 

sage: for w in ([], [0], [1], [0, 0], [0, 1]): 

....: print("{} {}".format(w, S.process(w))) 

[] (True, 0, []) 

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

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

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

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

 

Final output words are taken into account:: 

 

sage: T = Transducer([(0, 1, 0, 1)], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: T.state(1).final_word_out = 2 

sage: S = T.kleene_star() 

sage: S.process([0, 0]) 

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

 

Final output words may lead to undesirable situations if initial 

states and final states coincide:: 

 

sage: T = Transducer(initial_states=[0], final_states=[0]) 

sage: T.state(0).final_word_out = 1 

sage: T([]) 

[1] 

sage: S = T.kleene_star() 

sage: S([]) 

Traceback (most recent call last): 

... 

RuntimeError: State 0 is in an epsilon cycle (no input), but 

output is written. 

""" 

from copy import deepcopy 

result = deepcopy(self) 

for initial in result.iter_initial_states(): 

for final in result.iter_final_states(): 

result.add_transition(final, initial, [], final.final_word_out) 

 

for initial in result.iter_initial_states(): 

initial.is_final = True 

 

return result 

 

 

def intersection(self, other): 

""" 

TESTS:: 

 

sage: FiniteStateMachine().intersection(FiniteStateMachine()) 

Traceback (most recent call last): 

... 

NotImplementedError 

""" 

raise NotImplementedError 

 

 

def product_FiniteStateMachine(self, other, function, 

new_input_alphabet=None, 

only_accessible_components=True, 

final_function=None, 

new_class=None): 

r""" 

Returns a new finite state machine whose states are 

`d`-tuples of states of the original finite state machines. 

 

INPUT: 

 

- ``other`` -- a finite state machine (for `d=2`) or a list 

(or iterable) of `d-1` finite state machines. 

 

- ``function`` has to accept `d` transitions from `A_j` to `B_j` 

for `j\in\{1, \ldots, d\}` and returns a pair ``(word_in, word_out)`` 

which is the label of the transition `A=(A_1, \ldots, A_d)` to `B=(B_1, 

\ldots, B_d)`. If there is no transition from `A` to `B`, 

then ``function`` should raise a ``LookupError``. 

 

- ``new_input_alphabet`` (optional) -- the new input alphabet 

as a list. 

 

- ``only_accessible_components`` -- If ``True`` (default), then 

the result is piped through :meth:`.accessible_components`. If no 

``new_input_alphabet`` is given, it is determined by 

:meth:`.determine_alphabets`. 

 

- ``final_function`` -- A function mapping `d` final states of 

the original finite state machines to the final output of 

the corresponding state in the new finite state machine. By 

default, the final output is the empty word if both final 

outputs of the constituent states are empty; otherwise, a 

``ValueError`` is raised. 

 

- ``new_class`` -- Class of the new finite state machine. By 

default (``None``), the class of ``self`` is used. 

 

OUTPUT: 

 

A finite state machine whose states are `d`-tuples of states of the 

original finite state machines. A state is initial or 

final if all constituent states are initial or final, 

respectively. 

 

The labels of the transitions are defined by ``function``. 

 

The final output of a final state is determined by calling 

``final_function`` on the constituent states. 

 

The color of a new state is the tuple of colors of the 

constituent states of ``self`` and ``other``. However, 

if all constituent states have color ``None``, then 

the state has color ``None``, too. 

 

EXAMPLES:: 

 

sage: F = Automaton([('A', 'B', 1), ('A', 'A', 0), ('B', 'A', 2)], 

....: initial_states=['A'], final_states=['B'], 

....: determine_alphabets=True) 

sage: G = Automaton([(1, 1, 1)], initial_states=[1], final_states=[1]) 

sage: def addition(transition1, transition2): 

....: return (transition1.word_in[0] + transition2.word_in[0], 

....: None) 

sage: H = F.product_FiniteStateMachine(G, addition, [0, 1, 2, 3], only_accessible_components=False) 

sage: H.transitions() 

[Transition from ('A', 1) to ('B', 1): 2|-, 

Transition from ('A', 1) to ('A', 1): 1|-, 

Transition from ('B', 1) to ('A', 1): 3|-] 

sage: [s.color for s in H.iter_states()] 

[None, None] 

sage: H1 = F.product_FiniteStateMachine(G, addition, [0, 1, 2, 3], only_accessible_components=False) 

sage: H1.states()[0].label()[0] is F.states()[0] 

True 

sage: H1.states()[0].label()[1] is G.states()[0] 

True 

 

:: 

 

sage: F = Automaton([(0,1,1/4), (0,0,3/4), (1,1,3/4), (1,0,1/4)], 

....: initial_states=[0] ) 

sage: G = Automaton([(0,0,1), (1,1,3/4), (1,0,1/4)], 

....: initial_states=[0] ) 

sage: H = F.product_FiniteStateMachine( 

....: G, lambda t1,t2: (t1.word_in[0]*t2.word_in[0], None)) 

sage: H.states() 

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

 

:: 

 

sage: F = Automaton([(0,1,1/4), (0,0,3/4), (1,1,3/4), (1,0,1/4)], 

....: initial_states=[0] ) 

sage: G = Automaton([(0,0,1), (1,1,3/4), (1,0,1/4)], 

....: initial_states=[0] ) 

sage: H = F.product_FiniteStateMachine(G, 

....: lambda t1,t2: (t1.word_in[0]*t2.word_in[0], None), 

....: only_accessible_components=False) 

sage: H.states() 

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

 

Also final output words are considered according to the function 

``final_function``:: 

 

sage: F = Transducer([(0, 1, 0, 1), (1, 1, 1, 1), (1, 1, 0, 1)], 

....: final_states=[1]) 

sage: F.state(1).final_word_out = 1 

sage: G = Transducer([(0, 0, 0, 1), (0, 0, 1, 0)], final_states=[0]) 

sage: G.state(0).final_word_out = 1 

sage: def minus(t1, t2): 

....: return (t1.word_in[0] - t2.word_in[0], 

....: t1.word_out[0] - t2.word_out[0]) 

sage: H = F.product_FiniteStateMachine(G, minus) 

Traceback (most recent call last): 

... 

ValueError: A final function must be given. 

sage: def plus(s1, s2): 

....: return s1.final_word_out[0] + s2.final_word_out[0] 

sage: H = F.product_FiniteStateMachine(G, minus, 

....: final_function=plus) 

sage: H.final_states() 

[(1, 0)] 

sage: H.final_states()[0].final_word_out 

[2] 

 

Products of more than two finite state machines are also possible:: 

 

sage: def plus(s1, s2, s3): 

....: if s1.word_in == s2.word_in == s3.word_in: 

....: return (s1.word_in, 

....: sum(s.word_out[0] for s in (s1, s2, s3))) 

....: else: 

....: raise LookupError 

sage: T0 = transducers.CountSubblockOccurrences([0, 0], [0, 1, 2]) 

sage: T1 = transducers.CountSubblockOccurrences([1, 1], [0, 1, 2]) 

sage: T2 = transducers.CountSubblockOccurrences([2, 2], [0, 1, 2]) 

sage: T = T0.product_FiniteStateMachine([T1, T2], plus) 

sage: T.transitions() 

[Transition from ((), (), ()) to ((0,), (), ()): 0|0, 

Transition from ((), (), ()) to ((), (1,), ()): 1|0, 

Transition from ((), (), ()) to ((), (), (2,)): 2|0, 

Transition from ((0,), (), ()) to ((0,), (), ()): 0|1, 

Transition from ((0,), (), ()) to ((), (1,), ()): 1|0, 

Transition from ((0,), (), ()) to ((), (), (2,)): 2|0, 

Transition from ((), (1,), ()) to ((0,), (), ()): 0|0, 

Transition from ((), (1,), ()) to ((), (1,), ()): 1|1, 

Transition from ((), (1,), ()) to ((), (), (2,)): 2|0, 

Transition from ((), (), (2,)) to ((0,), (), ()): 0|0, 

Transition from ((), (), (2,)) to ((), (1,), ()): 1|0, 

Transition from ((), (), (2,)) to ((), (), (2,)): 2|1] 

sage: T([0, 0, 1, 1, 2, 2, 0, 1, 2, 2]) 

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

 

``other`` can also be an iterable:: 

 

sage: T == T0.product_FiniteStateMachine(iter([T1, T2]), plus) 

True 

 

TESTS: 

 

Check that colors are correctly dealt with. In particular, the 

new colors have to be hashable such that 

:meth:`Automaton.determinisation` does not fail:: 

 

sage: A = Automaton([[0, 0, 0]], initial_states=[0]) 

sage: B = A.product_FiniteStateMachine(A, 

....: lambda t1, t2: (0, None)) 

sage: B.states()[0].color is None 

True 

sage: B.determinisation() 

Automaton with 1 state 

 

Check handling of the parameter ``other``:: 

 

sage: A.product_FiniteStateMachine(None, plus) 

Traceback (most recent call last): 

... 

ValueError: other must be a finite state machine or a list 

of finite state machines. 

sage: A.product_FiniteStateMachine([None], plus) 

Traceback (most recent call last): 

... 

ValueError: other must be a finite state machine or a list 

of finite state machines. 

 

Test whether ``new_class`` works:: 

 

sage: T = Transducer() 

sage: type(T.product_FiniteStateMachine(T, None)) 

<class 'sage.combinat.finite_state_machine.Transducer'> 

sage: type(T.product_FiniteStateMachine(T, None, 

....: new_class=Automaton)) 

<class 'sage.combinat.finite_state_machine.Automaton'> 

 

Check that isolated vertices are kept (:trac:`16762`):: 

 

sage: F = Transducer(initial_states=[0]) 

sage: F.add_state(1) 

1 

sage: G = Transducer(initial_states=['A']) 

sage: F.product_FiniteStateMachine(G, None).states() 

[(0, 'A')] 

sage: F.product_FiniteStateMachine( 

....: G, None, only_accessible_components=False).states() 

[(0, 'A'), (1, 'A')] 

""" 

def default_final_function(*args): 

if any(s.final_word_out for s in args): 

raise ValueError("A final function must be given.") 

return [] 

 

if final_function is None: 

final_function = default_final_function 

 

result = self.empty_copy(new_class=new_class) 

if new_input_alphabet is not None: 

result.input_alphabet = new_input_alphabet 

else: 

result.input_alphabet = None 

 

if hasattr(other, '__iter__'): 

machines = [self] 

machines.extend(other) 

if not all(is_FiniteStateMachine(m) for m in machines): 

raise ValueError("other must be a finite state machine " 

"or a list of finite state machines.") 

elif is_FiniteStateMachine(other): 

machines = [self, other] 

else: 

raise ValueError("other must be a finite state machine or " 

"a list of finite state machines.") 

 

for transitions in itertools.product( 

*(m.iter_transitions() for m in machines)): 

try: 

word = function(*transitions) 

except LookupError: 

continue 

result.add_transition(tuple(t.from_state for t in transitions), 

tuple(t.to_state for t in transitions), 

word[0], word[1]) 

 

if only_accessible_components: 

state_iterator = itertools.product( 

*(m.iter_initial_states() for m in machines)) 

else: 

state_iterator = itertools.product( 

*(m.iter_states() for m in machines)) 

 

for state in state_iterator: 

result.add_state(state) 

 

for state in result.states(): 

if all(s.is_initial for s in state.label()): 

state.is_initial = True 

if all(s.is_final for s in state.label()): 

state.is_final = True 

state.final_word_out = final_function(*state.label()) 

if all(s.color is None for s in state.label()): 

state.color = None 

else: 

state.color = tuple(s.color for s in state.label()) 

 

if only_accessible_components: 

if result.input_alphabet is None: 

result.determine_alphabets() 

return result.accessible_components() 

else: 

return result 

 

 

def composition(self, other, algorithm=None, 

only_accessible_components=True): 

""" 

Returns a new transducer which is the composition of ``self`` 

and ``other``. 

 

INPUT: 

 

- ``other`` -- a transducer 

 

- ``algorithm`` -- can be one of the following 

 

- ``direct`` -- The composition is calculated directly. 

 

There can be arbitrarily many initial and final states, 

but the input and output labels must have length `1`. 

 

.. WARNING:: 

 

The output of ``other`` is fed into ``self``. 

 

- ``explorative`` -- An explorative algorithm is used. 

 

The input alphabet of self has to be specified. 

 

.. WARNING:: 

 

The output of ``other`` is fed into ``self``. 

 

If algorithm is ``None``, then the algorithm is chosen 

automatically (at the moment always ``direct``, except when 

there are output words of ``other`` or input words of ``self`` 

of length greater than `1`). 

 

OUTPUT: 

 

A new transducer. 

 

The labels of the new finite state machine are pairs of states 

of the original finite state machines. The color of a new 

state is the tuple of colors of the constituent states. 

 

 

EXAMPLES:: 

 

sage: F = Transducer([('A', 'B', 1, 0), ('B', 'A', 0, 1)], 

....: initial_states=['A', 'B'], final_states=['B'], 

....: determine_alphabets=True) 

sage: G = Transducer([(1, 1, 1, 0), (1, 2, 0, 1), 

....: (2, 2, 1, 1), (2, 2, 0, 0)], 

....: initial_states=[1], final_states=[2], 

....: determine_alphabets=True) 

sage: Hd = F.composition(G, algorithm='direct') 

sage: Hd.initial_states() 

[(1, 'B'), (1, 'A')] 

sage: Hd.transitions() 

[Transition from (1, 'B') to (1, 'A'): 1|1, 

Transition from (1, 'A') to (2, 'B'): 0|0, 

Transition from (2, 'B') to (2, 'A'): 0|1, 

Transition from (2, 'A') to (2, 'B'): 1|0] 

sage: He = F.composition(G, algorithm='explorative') 

sage: He.initial_states() 

[(1, 'A'), (1, 'B')] 

sage: He.transitions() 

[Transition from (1, 'A') to (2, 'B'): 0|0, 

Transition from (1, 'B') to (1, 'A'): 1|1, 

Transition from (2, 'B') to (2, 'A'): 0|1, 

Transition from (2, 'A') to (2, 'B'): 1|0] 

sage: Hd == He 

True 

 

The following example has output of length `> 1`, so the 

explorative algorithm has to be used (and is selected 

automatically). 

 

:: 

 

sage: F = Transducer([('A', 'B', 1, [1, 0]), ('B', 'B', 1, 1), 

....: ('B', 'B', 0, 0)], 

....: initial_states=['A'], final_states=['B']) 

sage: G = Transducer([(1, 1, 0, 0), (1, 2, 1, 0), 

....: (2, 2, 0, 1), (2, 1, 1, 1)], 

....: initial_states=[1], final_states=[1]) 

sage: He = G.composition(F, algorithm='explorative') 

sage: He.transitions() 

[Transition from ('A', 1) to ('B', 2): 1|0,1, 

Transition from ('B', 2) to ('B', 2): 0|1, 

Transition from ('B', 2) to ('B', 1): 1|1, 

Transition from ('B', 1) to ('B', 1): 0|0, 

Transition from ('B', 1) to ('B', 2): 1|0] 

sage: Ha = G.composition(F) 

sage: Ha == He 

True 

 

Final output words are also considered:: 

 

sage: F = Transducer([('A', 'B', 1, 0), ('B', 'A', 0, 1)], 

....: initial_states=['A', 'B'], 

....: final_states=['A', 'B']) 

sage: F.state('A').final_word_out = 0 

sage: F.state('B').final_word_out = 1 

sage: G = Transducer([(1, 1, 1, 0), (1, 2, 0, 1), 

....: (2, 2, 1, 1), (2, 2, 0, 0)], 

....: initial_states=[1], final_states=[2]) 

sage: G.state(2).final_word_out = 0 

sage: Hd = F.composition(G, algorithm='direct') 

sage: Hd.final_states() 

[(2, 'B')] 

sage: He = F.composition(G, algorithm='explorative') 

sage: He.final_states() 

[(2, 'B')] 

 

Note that ``(2, 'A')`` is not final, as the final output `0` 

of state `2` of `G` cannot be processed in state ``'A'`` of 

`F`. 

 

:: 

 

sage: [s.final_word_out for s in Hd.final_states()] 

[[1, 0]] 

sage: [s.final_word_out for s in He.final_states()] 

[[1, 0]] 

sage: Hd == He 

True 

 

Here is a non-deterministic example with intermediate output 

length `>1`. 

 

:: 

 

sage: F = Transducer([(1, 1, 1, ['a', 'a']), (1, 2, 1, 'b'), 

....: (2, 1, 2, 'a'), (2, 2, 2, 'b')], 

....: initial_states=[1, 2]) 

sage: G = Transducer([('A', 'A', 'a', 'i'), 

....: ('A', 'B', 'a', 'l'), 

....: ('B', 'B', 'b', 'e')], 

....: initial_states=['A', 'B']) 

sage: G(F).transitions() 

[Transition from (1, 'A') to (1, 'A'): 1|'i','i', 

Transition from (1, 'A') to (1, 'B'): 1|'i','l', 

Transition from (1, 'B') to (2, 'B'): 1|'e', 

Transition from (2, 'A') to (1, 'A'): 2|'i', 

Transition from (2, 'A') to (1, 'B'): 2|'l', 

Transition from (2, 'B') to (2, 'B'): 2|'e'] 

 

Be aware that after composition, different transitions may 

share the same output label (same python object):: 

 

sage: F = Transducer([ ('A','B',0,0), ('B','A',0,0)], 

....: initial_states=['A'], 

....: final_states=['A']) 

sage: F.transitions()[0].word_out is F.transitions()[1].word_out 

False 

sage: G = Transducer([('C','C',0,1)],) 

....: initial_states=['C'], 

....: final_states=['C']) 

sage: H = G.composition(F) 

sage: H.transitions()[0].word_out is H.transitions()[1].word_out 

True 

 

TESTS: 

 

In the explorative algorithm, transducers with non-empty final 

output words are implemented in :trac:`16548`:: 

 

sage: A = transducers.GrayCode() 

sage: B = transducers.abs([0, 1]) 

sage: A.composition(B, algorithm='explorative').transitions() 

[Transition from (0, 0) to (0, 1): 0|-, 

Transition from (0, 0) to (0, 2): 1|-, 

Transition from (0, 1) to (0, 1): 0|0, 

Transition from (0, 1) to (0, 2): 1|1, 

Transition from (0, 2) to (0, 1): 0|1, 

Transition from (0, 2) to (0, 2): 1|0] 

 

Similarly, the explorative algorithm can handle 

non-deterministic finite state machines as of :trac:`16548`:: 

 

sage: A = Transducer([(0, 0, 0, 0), (0, 1, 0, 0)], 

....: initial_states=[0]) 

sage: B = transducers.Identity([0]) 

sage: A.composition(B, algorithm='explorative').transitions() 

[Transition from (0, 0) to (0, 0): 0|0, 

Transition from (0, 0) to (0, 1): 0|0] 

sage: B.composition(A, algorithm='explorative').transitions() 

[Transition from (0, 0) to (0, 0): 0|0, 

Transition from (0, 0) to (1, 0): 0|0] 

 

In the following example, ``algorithm='direct'`` is inappropriate 

as there are edges with output labels of length greater than 1:: 

 

sage: F = Transducer([('A', 'B', 1, [1, 0]), ('B', 'B', 1, 1), 

....: ('B', 'B', 0, 0)], 

....: initial_states=['A'], final_states=['B']) 

sage: G = Transducer([(1, 1, 0, 0), (1, 2, 1, 0), 

....: (2, 2, 0, 1), (2, 1, 1, 1)], 

....: initial_states=[1], final_states=[1]) 

sage: Hd = G.composition(F, algorithm='direct') 

 

In the following examples, we compose transducers and automata 

and check whether the types are correct. 

 

:: 

 

sage: from sage.combinat.finite_state_machine import ( 

....: is_Automaton, is_Transducer) 

sage: T = Transducer([(0, 0, 0, 0)], initial_states=[0]) 

sage: A = Automaton([(0, 0, 0)], initial_states=[0]) 

sage: is_Transducer(T.composition(T, algorithm='direct')) 

True 

sage: is_Transducer(T.composition(T, algorithm='explorative')) 

True 

sage: T.composition(A, algorithm='direct') 

Traceback (most recent call last): 

... 

TypeError: Composition with automaton is not possible. 

sage: T.composition(A, algorithm='explorative') 

Traceback (most recent call last): 

... 

TypeError: Composition with automaton is not possible. 

sage: A.composition(A, algorithm='direct') 

Traceback (most recent call last): 

... 

TypeError: Composition with automaton is not possible. 

sage: A.composition(A, algorithm='explorative') 

Traceback (most recent call last): 

... 

TypeError: Composition with automaton is not possible. 

sage: is_Automaton(A.composition(T, algorithm='direct')) 

True 

sage: is_Automaton(A.composition(T, algorithm='explorative')) 

True 

 

Non-deterministic final output cannot be handeled:: 

 

sage: F = Transducer([('I', 'A', 0, 42), ('I', 'B', 0, 42)], 

....: initial_states=['I'], 

....: final_states=['A', 'B']) 

sage: G = Transducer(initial_states=[0], 

....: final_states=[0], 

....: input_alphabet=[0]) 

sage: G.state(0).final_word_out = 0 

sage: H = F.composition(G, algorithm='explorative') 

sage: for s in H.final_states(): 

....: print("{} {}".format(s, s.final_word_out)) 

(0, 'I') [42] 

sage: F.state('A').final_word_out = 'a' 

sage: F.state('B').final_word_out = 'b' 

sage: F.composition(G, algorithm='explorative') 

Traceback (most recent call last): 

... 

NotImplementedError: Stopping in state (0, 'I') leads to 

non-deterministic final output. 

 

Check that the output and input alphabets are set correctly:: 

 

sage: F = Transducer([(0, 0, 1, 'A')], 

....: initial_states=[0], 

....: determine_alphabets=False) 

sage: G = Transducer([(2, 2, 'A', 'a')], 

....: initial_states=[2], 

....: determine_alphabets=False) 

sage: Hd = G(F, algorithm='direct') 

sage: Hd.input_alphabet, Hd.output_alphabet 

([1], ['a']) 

sage: He = G(F, algorithm='explorative') 

Traceback (most recent call last): 

... 

ValueError: No input alphabet is given. Try calling 

determine_alphabets(). 

sage: F.input_alphabet = [1] 

sage: Hd = G(F, algorithm='direct') 

sage: Hd.input_alphabet, Hd.output_alphabet 

([1], ['a']) 

sage: He = G(F, algorithm='explorative') 

sage: He.input_alphabet, He.output_alphabet 

([1], None) 

sage: G.output_alphabet = ['a'] 

sage: Hd = G(F, algorithm='direct') 

sage: Hd.input_alphabet, Hd.output_alphabet 

([1], ['a']) 

sage: He = G(F, algorithm='explorative') 

sage: He.input_alphabet, He.output_alphabet 

([1], ['a']) 

sage: Hd == He 

True 

sage: F.input_alphabet = None 

sage: Hd = G(F, algorithm='direct') 

sage: Hd.input_alphabet, Hd.output_alphabet 

([1], ['a']) 

sage: He = G(F, algorithm='explorative') 

Traceback (most recent call last): 

... 

ValueError: No input alphabet is given. Try calling 

determine_alphabets(). 

""" 

if not other._allow_composition_: 

raise TypeError("Composition with automaton is not " 

"possible.") 

 

if algorithm is None: 

if (any(len(t.word_out) > 1 

for t in other.iter_transitions()) 

or 

any(len(t.word_in) != 1 

for t in self.iter_transitions()) 

#this might be used for multi-tape mode. 

#or 

#any(isinstance(t.word_in[0], tuple) and None in t.word_in[0] 

# for t in self.iter_transitions()) 

): 

algorithm = 'explorative' 

else: 

algorithm = 'direct' 

if algorithm == 'direct': 

return self._composition_direct_(other, only_accessible_components) 

elif algorithm == 'explorative': 

return self._composition_explorative_(other) 

else: 

raise ValueError("Unknown algorithm %s." % (algorithm,)) 

 

 

def _composition_direct_(self, other, only_accessible_components=True): 

""" 

See :meth:`.composition` for details. 

 

TESTS:: 

 

sage: F = Transducer([('A', 'B', 1, 0), ('B', 'A', 0, 1)], 

....: initial_states=['A', 'B'], final_states=['B'], 

....: determine_alphabets=True) 

sage: G = Transducer([(1, 1, 1, 0), (1, 2, 0, 1), 

....: (2, 2, 1, 1), (2, 2, 0, 0)], 

....: initial_states=[1], final_states=[2], 

....: determine_alphabets=True) 

sage: Hd = F._composition_direct_(G) 

sage: Hd.initial_states() 

[(1, 'B'), (1, 'A')] 

sage: Hd.transitions() 

[Transition from (1, 'B') to (1, 'A'): 1|1, 

Transition from (1, 'A') to (2, 'B'): 0|0, 

Transition from (2, 'B') to (2, 'A'): 0|1, 

Transition from (2, 'A') to (2, 'B'): 1|0] 

""" 

def function(transition1, transition2): 

if transition1.word_out == transition2.word_in: 

return (transition1.word_in, transition2.word_out) 

else: 

raise LookupError 

 

result = other.product_FiniteStateMachine( 

self, function, 

only_accessible_components=only_accessible_components, 

final_function=lambda s1, s2: [], 

new_class=self.__class__) 

 

for state_result in result.iter_states(): 

state = state_result.label()[0] 

if state.is_final: 

accept, state_to, output = self.process( 

state.final_word_out, 

initial_state=self.state(state_result.label()[1])) 

if not accept: 

state_result.is_final = False 

else: 

state_result.is_final = True 

state_result.final_word_out = output 

 

return result 

 

 

def _composition_explorative_(self, other): 

""" 

See :meth:`.composition` for details. 

 

TESTS:: 

 

sage: F = Transducer([('A', 'B', 1, [1, 0]), ('B', 'B', 1, 1), 

....: ('B', 'B', 0, 0)], 

....: initial_states=['A'], final_states=['B']) 

sage: G = Transducer([(1, 1, 0, 0), (1, 2, 1, 0), 

....: (2, 2, 0, 1), (2, 1, 1, 1)], 

....: initial_states=[1], final_states=[1]) 

sage: He = G._composition_explorative_(F) 

sage: He.transitions() 

[Transition from ('A', 1) to ('B', 2): 1|0,1, 

Transition from ('B', 2) to ('B', 2): 0|1, 

Transition from ('B', 2) to ('B', 1): 1|1, 

Transition from ('B', 1) to ('B', 1): 0|0, 

Transition from ('B', 1) to ('B', 2): 1|0] 

 

Check that colors are correctly dealt with, cf. :trac:`19199`. 

In particular, the new colors have to be hashable such that 

:meth:`Automaton.determinisation` does not fail:: 

 

sage: T = Transducer([[0, 0, 0, 0]], initial_states=[0]) 

sage: A = T.input_projection() 

sage: B = A.composition(T, algorithm='explorative') 

sage: B.states()[0].color is None 

True 

sage: A.state(0).color = 0 

sage: B = A.composition(T, algorithm='explorative') 

sage: B.states()[0].color 

(None, 0) 

sage: B.determinisation() 

Automaton with 1 state 

""" 

def composition_transition(states, input): 

(state1, state2) = states 

return [((new_state1, new_state2), output_second) 

for _, new_state1, output_first in 

first.process([input], 

list_of_outputs=True, 

initial_state=state1, 

write_final_word_out=False) 

for _, new_state2, output_second in 

second.process(output_first, 

list_of_outputs=True, 

initial_state=state2, 

write_final_word_out=False, 

always_include_output=True)] 

 

 

first = other 

if any(len(t.word_in) > 1 

for t in first.iter_transitions()): 

first = first.split_transitions() 

 

second = self 

if any(len(t.word_in) > 1 

for t in second.iter_transitions()): 

second = second.split_transitions() 

 

F = first.empty_copy(new_class=second.__class__) 

new_initial_states = itertools.product( 

first.iter_initial_states(), 

second.iter_initial_states()) 

F.add_from_transition_function(composition_transition, 

initial_states=new_initial_states) 

 

for state in F.iter_states(): 

(state1, state2) = state.label() 

if state1.is_final: 

final_output_second = second.process( 

state1.final_word_out, 

list_of_outputs=True, 

initial_state=state2, 

only_accepted=True, 

always_include_output=True) 

if (len(final_output_second) > 1 and 

not equal(r[2] for r in final_output_second)): 

raise NotImplementedError("Stopping in state %s " 

"leads to " 

"non-deterministic final " 

"output." % state) 

if final_output_second: 

state.is_final = True 

state.final_word_out = final_output_second[0][2] 

 

if all(s.color is None for s in state.label()): 

state.color = None 

else: 

state.color = tuple(s.color for s in state.label()) 

 

F.output_alphabet = second.output_alphabet 

return F 

 

 

def input_projection(self): 

""" 

Returns an automaton where the output of each transition of 

self is deleted. 

 

INPUT: 

 

Nothing 

 

OUTPUT: 

 

An automaton. 

 

EXAMPLES:: 

 

sage: F = FiniteStateMachine([('A', 'B', 0, 1), ('A', 'A', 1, 1), 

....: ('B', 'B', 1, 0)]) 

sage: G = F.input_projection() 

sage: G.transitions() 

[Transition from 'A' to 'B': 0|-, 

Transition from 'A' to 'A': 1|-, 

Transition from 'B' to 'B': 1|-] 

""" 

return self.projection(what='input') 

 

 

def output_projection(self): 

""" 

Returns a automaton where the input of each transition of self 

is deleted and the new input is the original output. 

 

INPUT: 

 

Nothing 

 

OUTPUT: 

 

An automaton. 

 

EXAMPLES:: 

 

sage: F = FiniteStateMachine([('A', 'B', 0, 1), ('A', 'A', 1, 1), 

....: ('B', 'B', 1, 0)]) 

sage: G = F.output_projection() 

sage: G.transitions() 

[Transition from 'A' to 'B': 1|-, 

Transition from 'A' to 'A': 1|-, 

Transition from 'B' to 'B': 0|-] 

 

Final output words are also considered correctly:: 

 

sage: H = Transducer([('A', 'B', 0, 1), ('A', 'A', 1, 1), 

....: ('B', 'B', 1, 0), ('A', ('final', 0), 0, 0)], 

....: final_states=['A', 'B']) 

sage: H.state('B').final_word_out = 2 

sage: J = H.output_projection() 

sage: J.states() 

['A', 'B', ('final', 0), ('final', 1)] 

sage: J.transitions() 

[Transition from 'A' to 'B': 1|-, 

Transition from 'A' to 'A': 1|-, 

Transition from 'A' to ('final', 0): 0|-, 

Transition from 'B' to 'B': 0|-, 

Transition from 'B' to ('final', 1): 2|-] 

sage: J.final_states() 

['A', ('final', 1)] 

""" 

return self.projection(what='output') 

 

 

def projection(self, what='input'): 

""" 

Returns an Automaton which transition labels are the projection 

of the transition labels of the input. 

 

INPUT: 

 

- ``what`` -- (default: ``input``) either ``input`` or ``output``. 

 

OUTPUT: 

 

An automaton. 

 

EXAMPLES:: 

 

sage: F = FiniteStateMachine([('A', 'B', 0, 1), ('A', 'A', 1, 1), 

....: ('B', 'B', 1, 0)]) 

sage: G = F.projection(what='output') 

sage: G.transitions() 

[Transition from 'A' to 'B': 1|-, 

Transition from 'A' to 'A': 1|-, 

Transition from 'B' to 'B': 0|-] 

""" 

from copy import copy, deepcopy 

 

new = Automaton() 

# TODO: use empty_copy() in order to 

# preserve on_duplicate_transition and future extensions. 

# for this, empty_copy would need a new optional argument 

# use_class=None ? 

 

if what == 'input': 

new.input_alphabet = copy(self.input_alphabet) 

elif what == 'output': 

new.input_alphabet = copy(self.output_alphabet) 

else: 

raise NotImplementedError 

 

state_mapping = {} 

for state in self.iter_states(): 

state_mapping[state] = new.add_state(deepcopy(state)) 

for transition in self.iter_transitions(): 

if what == 'input': 

new_word_in = transition.word_in 

elif what == 'output': 

new_word_in = transition.word_out 

else: 

raise NotImplementedError 

new.add_transition((state_mapping[transition.from_state], 

state_mapping[transition.to_state], 

new_word_in, None)) 

 

if what == 'output': 

states = [s for s in self.iter_final_states() if s.final_word_out] 

if not states: 

return new 

number = 0 

while new.has_state(('final', number)): 

number += 1 

final = new.add_state(('final', number)) 

final.is_final = True 

for state in states: 

output = state.final_word_out 

new.state(state_mapping[state]).final_word_out = [] 

new.state(state_mapping[state]).is_final = False 

new.add_transition((state_mapping[state], final, output, None)) 

 

return new 

 

 

def transposition(self, reverse_output_labels=True): 

""" 

Returns a new finite state machine, where all transitions of the 

input finite state machine are reversed. 

 

INPUT: 

 

- ``reverse_output_labels`` -- a boolean (default: ``True``): whether to reverse 

output labels. 

 

OUTPUT: 

 

A new finite state machine. 

 

EXAMPLES:: 

 

sage: aut = Automaton([('A', 'A', 0), ('A', 'A', 1), ('A', 'B', 0)], 

....: initial_states=['A'], final_states=['B']) 

sage: aut.transposition().transitions('B') 

[Transition from 'B' to 'A': 0|-] 

 

:: 

 

sage: aut = Automaton([('1', '1', 1), ('1', '2', 0), ('2', '2', 0)], 

....: initial_states=['1'], final_states=['1', '2']) 

sage: aut.transposition().initial_states() 

['1', '2'] 

 

:: 

 

sage: A = Automaton([(0, 1, [1, 0])], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: A([1, 0]) 

True 

sage: A.transposition()([0, 1]) 

True 

 

:: 

 

sage: T = Transducer([(0, 1, [1, 0], [1, 0])], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: T([1, 0]) 

[1, 0] 

sage: T.transposition()([0, 1]) 

[0, 1] 

sage: T.transposition(reverse_output_labels=False)([0, 1]) 

[1, 0] 

 

 

TESTS: 

 

If a final state of ``self`` has a non-empty final output word, 

transposition is not implemented:: 

 

sage: T = Transducer([('1', '1', 1, 0), ('1', '2', 0, 1), 

....: ('2', '2', 0, 2)], 

....: initial_states=['1'], 

....: final_states=['1', '2']) 

sage: T.state('1').final_word_out = [2, 5] 

sage: T.transposition() 

Traceback (most recent call last): 

... 

NotImplementedError: Transposition for transducers with 

final output words is not implemented. 

""" 

from copy import deepcopy 

 

if reverse_output_labels: 

rewrite_output = lambda word: list(reversed(word)) 

else: 

rewrite_output = lambda word: word 

 

transposition = self.empty_copy() 

 

for state in self.iter_states(): 

transposition.add_state(deepcopy(state)) 

 

for transition in self.iter_transitions(): 

transposition.add_transition( 

transition.to_state.label(), transition.from_state.label(), 

list(reversed(transition.word_in)), 

rewrite_output(transition.word_out)) 

 

for initial in self.iter_initial_states(): 

state = transposition.state(initial.label()) 

if not initial.is_final: 

state.is_final = True 

state.is_initial = False 

 

for final in self.iter_final_states(): 

state = transposition.state(final.label()) 

if final.final_word_out: 

raise NotImplementedError("Transposition for transducers " 

"with final output words is not " 

"implemented.") 

if not final.is_initial: 

state.is_final = False 

state.is_initial = True 

 

return transposition 

 

 

def split_transitions(self): 

""" 

Returns a new transducer, where all transitions in self with input 

labels consisting of more than one letter 

are replaced by a path of the corresponding length. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A new transducer. 

 

EXAMPLES:: 

 

sage: A = Transducer([('A', 'B', [1, 2, 3], 0)], 

....: initial_states=['A'], final_states=['B']) 

sage: A.split_transitions().states() 

[('A', ()), ('B', ()), 

('A', (1,)), ('A', (1, 2))] 

""" 

new = self.empty_copy() 

for state in self.states(): 

new.add_state(FSMState((state, ()), is_initial=state.is_initial, 

is_final=state.is_final)) 

for transition in self.transitions(): 

for j in range(len(transition.word_in)-1): 

new.add_transition(( 

(transition.from_state, tuple(transition.word_in[:j])), 

(transition.from_state, tuple(transition.word_in[:j+1])), 

transition.word_in[j], 

[])) 

new.add_transition(( 

(transition.from_state, tuple(transition.word_in[:-1])), 

(transition.to_state, ()), 

transition.word_in[-1:], 

transition.word_out)) 

return new 

 

 

def final_components(self): 

""" 

Returns the final components of a finite state machine as finite 

state machines. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A list of finite state machines, each representing a final 

component of ``self``. 

 

A final component of a transducer ``T`` is a strongly connected 

component ``C`` such that there are no transitions of ``T`` 

leaving ``C``. 

 

The final components are the only parts of a transducer which 

influence the main terms of the asymptotic behaviour of the sum 

of output labels of a transducer, see [HKP2015]_ and [HKW2015]_. 

 

EXAMPLES:: 

 

sage: T = Transducer([['A', 'B', 0, 0], ['B', 'C', 0, 1], 

....: ['C', 'B', 0, 1], ['A', 'D', 1, 0], 

....: ['D', 'D', 0, 0], ['D', 'B', 1, 0], 

....: ['A', 'E', 2, 0], ['E', 'E', 0, 0]]) 

sage: FC = T.final_components() 

sage: sorted(FC[0].transitions()) 

[Transition from 'B' to 'C': 0|1, 

Transition from 'C' to 'B': 0|1] 

sage: FC[1].transitions() 

[Transition from 'E' to 'E': 0|0] 

 

Another example (cycle of length 2):: 

 

sage: T = Automaton([[0, 1, 0], [1, 0, 0]]) 

sage: len(T.final_components()) == 1 

True 

sage: T.final_components()[0].transitions() 

[Transition from 0 to 1: 0|-, 

Transition from 1 to 0: 0|-] 

""" 

DG = self.digraph() 

condensation = DG.strongly_connected_components_digraph() 

return [self.induced_sub_finite_state_machine([self.state(_) for _ in component]) 

for component in condensation.vertices() 

if condensation.out_degree(component) == 0] 

 

 

def completion(self, sink=None): 

""" 

Return a completion of this finite state machine. 

 

INPUT: 

 

- ``sink`` -- either an instance of :class:`FSMState` or a label 

for the sink (default: ``None``). If ``None``, the least 

available non-zero integer is used. 

 

OUTPUT: 

 

A :class:`FiniteStateMachine` of the same type as this finite 

state machine. 

 

The resulting finite state machine is a complete version of this 

finite state machine. A finite state machine is considered to 

be complete if each transition has an input label of length one 

and for each pair `(q, a)` where `q` is a state and `a` is an 

element of the input alphabet, there is exactly one transition 

from `q` with input label `a`. 

 

If this finite state machine is already complete, a deep copy is 

returned. Otherwise, a new non-final state (usually called a 

sink) is created and transitions to this sink are introduced as 

appropriate. 

 

EXAMPLES:: 

 

sage: F = FiniteStateMachine([(0, 0, 0, 0), 

....: (0, 1, 1, 1), 

....: (1, 1, 0, 0)]) 

sage: F.is_complete() 

False 

sage: G1 = F.completion() 

sage: G1.is_complete() 

True 

sage: G1.transitions() 

[Transition from 0 to 0: 0|0, 

Transition from 0 to 1: 1|1, 

Transition from 1 to 1: 0|0, 

Transition from 1 to 2: 1|-, 

Transition from 2 to 2: 0|-, 

Transition from 2 to 2: 1|-] 

sage: G2 = F.completion('Sink') 

sage: G2.is_complete() 

True 

sage: G2.transitions() 

[Transition from 0 to 0: 0|0, 

Transition from 0 to 1: 1|1, 

Transition from 1 to 1: 0|0, 

Transition from 1 to 'Sink': 1|-, 

Transition from 'Sink' to 'Sink': 0|-, 

Transition from 'Sink' to 'Sink': 1|-] 

sage: F.completion(1) 

Traceback (most recent call last): 

... 

ValueError: The finite state machine already contains a state 

'1'. 

 

An input alphabet must be given:: 

 

sage: F = FiniteStateMachine([(0, 0, 0, 0), 

....: (0, 1, 1, 1), 

....: (1, 1, 0, 0)], 

....: determine_alphabets=False) 

sage: F.is_complete() 

Traceback (most recent call last): 

... 

ValueError: No input alphabet is given. Try calling 

determine_alphabets(). 

 

Non-deterministic machines are not allowed. :: 

 

sage: F = FiniteStateMachine([(0, 0, 0, 0), (0, 1, 0, 0)]) 

sage: F.is_complete() 

False 

sage: F.completion() 

Traceback (most recent call last): 

... 

ValueError: The finite state machine must be deterministic. 

sage: F = FiniteStateMachine([(0, 0, [0, 0], 0)]) 

sage: F.is_complete() 

False 

sage: F.completion() 

Traceback (most recent call last): 

... 

ValueError: The finite state machine must be deterministic. 

 

.. SEEALSO:: 

 

:meth:`is_complete`, 

:meth:`split_transitions`, 

:meth:`determine_alphabets`, 

:meth:`is_deterministic`. 

 

TESTS: 

 

Test the use of an :class:`FSMState` as sink:: 

 

sage: F = FiniteStateMachine([(0, 0, 0, 0), 

....: (0, 1, 1, 1), 

....: (1, 1, 0, 0)]) 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: F.completion(FSMState(1)) 

Traceback (most recent call last): 

... 

ValueError: The finite state machine already contains a state 

'1'. 

sage: s = FSMState(2) 

sage: G = F.completion(s) 

sage: G.state(2) is s 

True 

""" 

from copy import deepcopy 

result = deepcopy(self) 

if result.is_complete(): 

return result 

if not result.is_deterministic(): 

raise ValueError( 

"The finite state machine must be deterministic.") 

 

if sink is not None: 

try: 

s = result.state(sink) 

raise ValueError("The finite state machine already " 

"contains a state '%s'." % s.label()) 

except LookupError: 

pass 

else: 

from sage.rings.integer_ring import ZZ 

sink = 1 + max(itertools.chain( 

[-1], 

(s.label() for s in result.iter_states() 

if s.label() in ZZ))) 

 

sink_state = result.add_state(sink) 

 

for state in result.iter_states(): 

for transition in state.transitions: 

if len(transition.word_in) != 1: 

raise ValueError( 

"Transitions with input labels of length greater " 

"than one are not allowed. Try calling " 

"split_transitions().") 

 

existing = set(transition.word_in[0] 

for transition in state.transitions) 

for missing in set(result.input_alphabet) - existing: 

result.add_transition(state, sink_state, missing) 

 

return result 

 

 

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

# simplifications 

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

 

 

def prepone_output(self): 

""" 

For all paths, shift the output of the path from one 

transition to the earliest possible preceeding transition of 

the path. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

Nothing. 

 

Apply the following to each state `s` (except initial states) of the 

finite state machine as often as possible: 

 

If the letter `a` is a prefix of the output label of all transitions from 

`s` (including the final output of `s`), then remove it from all these 

labels and append it to all output labels of all transitions leading 

to `s`. 

 

We assume that the states have no output labels, but final outputs are 

allowed. 

 

EXAMPLES:: 

 

sage: A = Transducer([('A', 'B', 1, 1), 

....: ('B', 'B', 0, 0), 

....: ('B', 'C', 1, 0)], 

....: initial_states=['A'], 

....: final_states=['C']) 

sage: A.prepone_output() 

sage: A.transitions() 

[Transition from 'A' to 'B': 1|1,0, 

Transition from 'B' to 'B': 0|0, 

Transition from 'B' to 'C': 1|-] 

 

:: 

 

sage: B = Transducer([('A', 'B', 0, 1), 

....: ('B', 'C', 1, [1, 1]), 

....: ('B', 'C', 0, 1)], 

....: initial_states=['A'], 

....: final_states=['C']) 

sage: B.prepone_output() 

sage: B.transitions() 

[Transition from 'A' to 'B': 0|1,1, 

Transition from 'B' to 'C': 1|1, 

Transition from 'B' to 'C': 0|-] 

 

If initial states are not labeled as such, unexpected results may be 

obtained:: 

 

sage: C = Transducer([(0,1,0,0)]) 

sage: C.prepone_output() 

verbose 0 (...: finite_state_machine.py, prepone_output) 

All transitions leaving state 0 have an output label with 

prefix 0. However, there is no inbound transition and it 

is not an initial state. This routine (possibly called by 

simplification) therefore erased this prefix from all 

outbound transitions. 

sage: C.transitions() 

[Transition from 0 to 1: 0|-] 

 

Also the final output of final states can be changed:: 

 

sage: T = Transducer([('A', 'B', 0, 1), 

....: ('B', 'C', 1, [1, 1]), 

....: ('B', 'C', 0, 1)], 

....: initial_states=['A'], 

....: final_states=['B']) 

sage: T.state('B').final_word_out = [1] 

sage: T.prepone_output() 

sage: T.transitions() 

[Transition from 'A' to 'B': 0|1,1, 

Transition from 'B' to 'C': 1|1, 

Transition from 'B' to 'C': 0|-] 

sage: T.state('B').final_word_out 

[] 

 

:: 

 

sage: S = Transducer([('A', 'B', 0, 1), 

....: ('B', 'C', 1, [1, 1]), 

....: ('B', 'C', 0, 1)], 

....: initial_states=['A'], 

....: final_states=['B']) 

sage: S.state('B').final_word_out = [0] 

sage: S.prepone_output() 

sage: S.transitions() 

[Transition from 'A' to 'B': 0|1, 

Transition from 'B' to 'C': 1|1,1, 

Transition from 'B' to 'C': 0|1] 

sage: S.state('B').final_word_out 

[0] 

 

Output labels do not have to be hashable:: 

 

sage: C = Transducer([(0, 1, 0, []), 

....: (1, 0, 0, [vector([0, 0]), 0]), 

....: (1, 1, 1, [vector([0, 0]), 1]), 

....: (0, 0, 1, 0)], 

....: determine_alphabets=False, 

....: initial_states=[0]) 

sage: C.prepone_output() 

sage: sorted(C.transitions()) 

[Transition from 0 to 1: 0|(0, 0), 

Transition from 0 to 0: 1|0, 

Transition from 1 to 0: 0|0, 

Transition from 1 to 1: 1|1,(0, 0)] 

""" 

from six.moves import filter 

 

def find_common_output(state): 

if any(filter( 

lambda transition: not transition.word_out, 

self.transitions(state))) \ 

or state.is_final and not state.final_word_out: 

return tuple() 

first_letters = [transition.word_out[0] for transition in self.transitions(state)] 

if state.is_final: 

first_letters = first_letters + [state.final_word_out[0]] 

if not first_letters: 

return tuple() 

first_item = first_letters.pop() 

if all(item == first_item for item in first_letters): 

return (first_item,) 

return tuple() 

 

changed = 1 

iteration = 0 

while changed > 0: 

changed = 0 

iteration += 1 

for state in self.iter_states(): 

if state.is_initial: 

continue 

if state.word_out: 

raise NotImplementedError( 

"prepone_output assumes that all states have " 

"empty output word, but state %s has output " 

"word %s" % (state, state.word_out)) 

common_output = find_common_output(state) 

if common_output: 

changed += 1 

if state.is_final: 

assert state.final_word_out[0] == common_output[0] 

state.final_word_out = state.final_word_out[1:] 

for transition in self.transitions(state): 

assert transition.word_out[0] == common_output[0] 

transition.word_out = transition.word_out[1:] 

found_inbound_transition = False 

for transition in self.iter_transitions(): 

if transition.to_state == state: 

transition.word_out = transition.word_out \ 

+ [common_output[0]] 

found_inbound_transition = True 

if not found_inbound_transition: 

sage.misc.misc.verbose( 

"All transitions leaving state %s have an " 

"output label with prefix %s. However, " 

"there is no inbound transition and it is " 

"not an initial state. This routine " 

"(possibly called by simplification) " 

"therefore erased this prefix from all " 

"outbound transitions." % 

(state, common_output[0]), 

level=0) 

 

 

def equivalence_classes(self): 

r""" 

Returns a list of equivalence classes of states. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A list of equivalence classes of states. 

 

Two states `a` and `b` are equivalent if and only if there is 

a bijection `\varphi` between paths starting at `a` and paths 

starting at `b` with the following properties: Let `p_a` be a 

path from `a` to `a'` and `p_b` a path from `b` to `b'` such 

that `\varphi(p_a)=p_b`, then 

 

- `p_a.\mathit{word}_\mathit{in}=p_b.\mathit{word}_\mathit{in}`, 

- `p_a.\mathit{word}_\mathit{out}=p_b.\mathit{word}_\mathit{out}`, 

- `a'` and `b'` have the same output label, and 

- `a'` and `b'` are both final or both non-final and have the 

same final output word. 

 

The function :meth:`.equivalence_classes` returns a list of 

the equivalence classes to this equivalence relation. 

 

This is one step of Moore's minimization algorithm. 

 

.. SEEALSO:: 

 

:meth:`.minimization` 

 

EXAMPLES:: 

 

sage: fsm = FiniteStateMachine([("A", "B", 0, 1), ("A", "B", 1, 0), 

....: ("B", "C", 0, 0), ("B", "C", 1, 1), 

....: ("C", "D", 0, 1), ("C", "D", 1, 0), 

....: ("D", "A", 0, 0), ("D", "A", 1, 1)]) 

sage: sorted(fsm.equivalence_classes()) 

[['A', 'C'], ['B', 'D']] 

sage: fsm.state("A").is_final = True 

sage: sorted(fsm.equivalence_classes()) 

[['A'], ['B'], ['C'], ['D']] 

sage: fsm.state("C").is_final = True 

sage: sorted(fsm.equivalence_classes()) 

[['A', 'C'], ['B', 'D']] 

sage: fsm.state("A").final_word_out = 1 

sage: sorted(fsm.equivalence_classes()) 

[['A'], ['B'], ['C'], ['D']] 

sage: fsm.state("C").final_word_out = 1 

sage: sorted(fsm.equivalence_classes()) 

[['A', 'C'], ['B', 'D']] 

""" 

 

# Two states `a` and `b` are j-equivalent if and only if there 

# is a bijection `\varphi` between paths of length <= j 

# starting at `a` and paths starting at `b` with the following 

# properties: Let `p_a` be a path from `a` to `a'` and `p_b` a 

# path from `b` to `b'` such that `\varphi(p_a)=p_b`, then 

# 

# - `p_a.\mathit{word}_{in}=p_b.\mathit{word}_{in}`, 

# - `p_a.\mathit{word}_{out}=p_b.\mathit{word}_{out}`, 

# - `a'` and `b'` have the same output label, and 

# - `a'` and `b'` are both final or both non-final. 

 

# If for some j the relations j-1 equivalent and j-equivalent 

# coincide, then they are equal to the equivalence relation 

# described in the docstring. 

 

# classes_current holds the equivalence classes of 

# j-equivalence, classes_previous holds the equivalence 

# classes of j-1 equivalence. 

 

# initialize with 0-equivalence 

classes_previous = [] 

key_0 = lambda state: (state.is_final, state.color, state.word_out, 

state.final_word_out) 

states_grouped = full_group_by(self.states(), key=key_0) 

classes_current = [equivalence_class for 

(key,equivalence_class) in states_grouped] 

 

while len(classes_current) != len(classes_previous): 

class_of = {} 

classes_previous = classes_current 

classes_current = [] 

 

for k in range(len(classes_previous)): 

for state in classes_previous[k]: 

class_of[state] = k 

 

key_current = lambda state: sorted( 

[(transition.word_in, 

transition.word_out, 

class_of[transition.to_state]) 

for transition in state.transitions]) 

 

for class_previous in classes_previous: 

states_grouped = full_group_by(class_previous, key=key_current) 

classes_current.extend([equivalence_class for 

(key,equivalence_class) in states_grouped]) 

 

return classes_current 

 

 

def quotient(self, classes): 

r""" 

Constructs the quotient with respect to the equivalence 

classes. 

 

INPUT: 

 

- ``classes`` is a list of equivalence classes of states. 

 

OUTPUT: 

 

A finite state machine. 

 

The labels of the new states are tuples of states of the 

``self``, corresponding to ``classes``. 

 

Assume that `c` is a class, and `a` and `b` are states in 

`c`. Then there is a bijection `\varphi` between the 

transitions from `a` and the transitions from `b` with the 

following properties: if `\varphi(t_a)=t_b`, then 

 

- `t_a.\mathit{word}_\mathit{in}=t_b.\mathit{word}_\mathit{in}`, 

- `t_a.\mathit{word}_\mathit{out}=t_b.\mathit{word}_\mathit{out}`, and 

- `t_a` and `t_b` lead to some equivalent states `a'` and `b'`. 

 

Non-initial states may be merged with initial states, the 

resulting state is an initial state. 

 

All states in a class must have the same ``is_final``, 

``final_word_out`` and ``word_out`` values. 

 

EXAMPLES:: 

 

sage: fsm = FiniteStateMachine([("A", "B", 0, 1), ("A", "B", 1, 0), 

....: ("B", "C", 0, 0), ("B", "C", 1, 1), 

....: ("C", "D", 0, 1), ("C", "D", 1, 0), 

....: ("D", "A", 0, 0), ("D", "A", 1, 1)]) 

sage: fsmq = fsm.quotient([[fsm.state("A"), fsm.state("C")], 

....: [fsm.state("B"), fsm.state("D")]]) 

sage: fsmq.transitions() 

[Transition from ('A', 'C') 

to ('B', 'D'): 0|1, 

Transition from ('A', 'C') 

to ('B', 'D'): 1|0, 

Transition from ('B', 'D') 

to ('A', 'C'): 0|0, 

Transition from ('B', 'D') 

to ('A', 'C'): 1|1] 

sage: fsmq.relabeled().transitions() 

[Transition from 0 to 1: 0|1, 

Transition from 0 to 1: 1|0, 

Transition from 1 to 0: 0|0, 

Transition from 1 to 0: 1|1] 

sage: fsmq1 = fsm.quotient(fsm.equivalence_classes()) 

sage: fsmq1 == fsmq 

True 

sage: fsm.quotient([[fsm.state("A"), fsm.state("B"), fsm.state("C"), fsm.state("D")]]) 

Traceback (most recent call last): 

... 

AssertionError: Transitions of state 'A' and 'B' are incompatible. 

 

TESTS:: 

 

sage: fsm = FiniteStateMachine([("A", "B", 0, 1), ("A", "B", 1, 0), 

....: ("B", "C", 0, 0), ("B", "C", 1, 1), 

....: ("C", "D", 0, 1), ("C", "D", 1, 0), 

....: ("D", "A", 0, 0), ("D", "A", 1, 1)], 

....: final_states=["A", "C"]) 

sage: fsm.state("A").final_word_out = 1 

sage: fsm.state("C").final_word_out = 2 

sage: fsmq = fsm.quotient([[fsm.state("A"), fsm.state("C")], 

....: [fsm.state("B"), fsm.state("D")]]) 

Traceback (most recent call last): 

... 

AssertionError: Class ['A', 'C'] mixes 

final states with different final output words. 

""" 

new = self.empty_copy() 

state_mapping = {} 

 

# Create new states and build state_mapping 

for c in classes: 

new_label = tuple(c) 

new_state = c[0].relabeled(new_label) 

new.add_state(new_state) 

for state in c: 

state_mapping[state] = new_state 

 

# Copy data from old transducer 

for c in classes: 

new_state = state_mapping[c[0]] 

sorted_transitions = sorted( 

[(state_mapping[t.to_state], t.word_in, t.word_out) 

for t in c[0].transitions]) 

for transition in self.iter_transitions(c[0]): 

new.add_transition( 

from_state = new_state, 

to_state = state_mapping[transition.to_state], 

word_in = transition.word_in, 

word_out = transition.word_out) 

 

# check that all class members have the same information (modulo classes) 

for state in c: 

new_state.is_initial = new_state.is_initial or state.is_initial 

assert new_state.is_final == state.is_final, \ 

"Class %s mixes final and non-final states" % (c,) 

assert new_state.word_out == state.word_out, \ 

"Class %s mixes different word_out" % (c,) 

assert new_state.color == state.color, \ 

"Class %s mixes different colors" % (c,) 

assert sorted_transitions == sorted( 

[(state_mapping[t.to_state], t.word_in, t.word_out) 

for t in state.transitions]), \ 

"Transitions of state %s and %s are incompatible." % (c[0], state) 

assert new_state.final_word_out == state.final_word_out, \ 

"Class %s mixes final states with different " \ 

"final output words." % (c,) 

return new 

 

 

def merged_transitions(self): 

""" 

Merges transitions which have the same ``from_state``, 

``to_state`` and ``word_out`` while adding their ``word_in``. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A finite state machine with merged transitions. If no mergers occur, 

return ``self``. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input 

sage: T = Transducer([[1, 2, 1/4, 1], [1, -2, 1/4, 1], [1, -2, 1/2, 1], 

....: [2, 2, 1/4, 1], [2, -2, 1/4, 1], [-2, -2, 1/4, 1], 

....: [-2, 2, 1/4, 1], [2, 3, 1/2, 1], [-2, 3, 1/2, 1]], 

....: on_duplicate_transition=duplicate_transition_add_input) 

sage: T1 = T.merged_transitions() 

sage: T1 is T 

False 

sage: sorted(T1.transitions()) 

[Transition from -2 to -2: 1/4|1, 

Transition from -2 to 2: 1/4|1, 

Transition from -2 to 3: 1/2|1, 

Transition from 1 to 2: 1/4|1, 

Transition from 1 to -2: 3/4|1, 

Transition from 2 to -2: 1/4|1, 

Transition from 2 to 2: 1/4|1, 

Transition from 2 to 3: 1/2|1] 

 

Applying the function again does not change the result:: 

 

sage: T2 = T1.merged_transitions() 

sage: T2 is T1 

True 

""" 

from copy import deepcopy 

def key(transition): 

return (transition.to_state, transition.word_out) 

 

new = self.empty_copy() 

changed = False 

state_dict = {} 

memo = {} 

 

for state in self.states(): 

new_state = deepcopy(state,memo) 

state_dict[state] = new_state 

new.add_state(new_state) 

 

for state in self.states(): 

grouped_transitions = itertools.groupby(sorted(state.transitions, key=key), key=key) 

for (to_state, word_out), transitions in grouped_transitions: 

transition_list = list(transitions) 

changed = changed or len(transition_list) > 1 

word_in = 0 

for transition in transition_list: 

if hasattr(transition.word_in, '__iter__') and len(transition.word_in) == 1: 

word_in += transition.word_in[0] 

else: 

raise TypeError('%s does not have a list of length 1 as word_in' % transition) 

new.add_transition((state, to_state, word_in, word_out)) 

 

if changed: 

return new 

else: 

return self 

 

 

def markov_chain_simplification(self): 

""" 

Consider ``self`` as Markov chain with probabilities as input labels 

and simplify it. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

Simplified version of ``self``. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input 

sage: T = Transducer([[1, 2, 1/4, 0], [1, -2, 1/4, 0], [1, -2, 1/2, 0], 

....: [2, 2, 1/4, 1], [2, -2, 1/4, 1], [-2, -2, 1/4, 1], 

....: [-2, 2, 1/4, 1], [2, 3, 1/2, 2], [-2, 3, 1/2, 2]], 

....: initial_states=[1], 

....: final_states=[3], 

....: on_duplicate_transition=duplicate_transition_add_input) 

sage: T1 = T.markov_chain_simplification() 

sage: sorted(T1.transitions()) 

[Transition from ((1,),) to ((2, -2),): 1|0, 

Transition from ((2, -2),) to ((2, -2),): 1/2|1, 

Transition from ((2, -2),) to ((3,),): 1/2|2] 

""" 

current = self.merged_transitions() 

number_states = len(current.states()) 

 

while True: 

current = current.simplification() 

new_number_states = len(current.states()) 

new = current.merged_transitions() 

if new is current and number_states == new_number_states: 

return new 

current = new 

number_states = new_number_states 

 

 

def with_final_word_out(self, letters, allow_non_final=True): 

""" 

Constructs a new finite state machine with final output words 

for all states by implicitly reading trailing letters until a 

final state is reached. 

 

INPUT: 

 

- ``letters`` -- either an element of the input alphabet or a 

list of such elements. This is repeated cyclically when 

needed. 

 

- ``allow_non_final`` -- a boolean (default: ``True``) which 

indicates whether we allow that some states may be non-final 

in the resulting finite state machine. I.e., if ``False`` then 

each state has to have a path to a final state with input 

label matching ``letters``. 

 

OUTPUT: 

 

A finite state machine. 

 

The inplace version of this function is 

:meth:`.construct_final_word_out`. 

 

Suppose for the moment a single element ``letter`` as input 

for ``letters``. This is equivalent to ``letters = [letter]``. 

We will discuss the general case below. 

 

Let ``word_in`` be a word over the input alphabet and assume 

that the original finite state machine transforms ``word_in`` to 

``word_out`` reaching a possibly non-final state ``s``. Let 

further `k` be the minimum number of letters ``letter`` such 

that there is a path from ``s`` to some final state ``f`` whose 

input label consists of `k` copies of ``letter`` and whose 

output label is ``path_word_out``. Then the state ``s`` of the 

resulting finite state machine is a final state with final 

output ``path_word_out + f.final_word_out``. Therefore, the new 

finite state machine transforms ``word_in`` to ``word_out + 

path_word_out + f.final_word_out``. 

 

This is e.g. useful for finite state machines operating on digit 

expansions: there, it is sometimes required to read a sufficient 

number of trailing zeros (at the most significant positions) in 

order to reach a final state and to flush all carries. In this 

case, this method constructs an essentially equivalent finite 

state machine in the sense that it not longer requires adding 

sufficiently many trailing zeros. However, it is the 

responsibility of the user to make sure that if adding trailing 

zeros to the input anyway, the output is equivalent. 

 

If ``letters`` consists of more than one letter, then it is 

assumed that (not necessarily complete) cycles of ``letters`` 

are appended as trailing input. 

 

.. SEEALSO:: 

 

:ref:`example on Gray code <finite_state_machine_gray_code_example>` 

 

EXAMPLES: 

 

#. A simple transducer transforming `00` blocks to `01` 

blocks:: 

 

sage: T = Transducer([(0, 1, 0, 0), (1, 0, 0, 1)], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: T.process([0, 0, 0]) 

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

sage: T.process([0, 0, 0, 0]) 

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

sage: F = T.with_final_word_out(0) 

sage: for f in F.iter_final_states(): 

....: print("{} {}".format(f, f.final_word_out)) 

0 [] 

1 [1] 

sage: F.process([0, 0, 0]) 

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

sage: F.process([0, 0, 0, 0]) 

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

 

#. A more realistic example: Addition of `1` in binary. We 

construct a transition function transforming the input 

to its binary expansion:: 

 

sage: def binary_transition(carry, input): 

....: value = carry + input 

....: if value.mod(2) == 0: 

....: return (value/2, 0) 

....: else: 

....: return ((value-1)/2, 1) 

 

Now, we only have to start with a carry of `1` to 

get the required transducer:: 

 

sage: T = Transducer(binary_transition, 

....: input_alphabet=[0, 1], 

....: initial_states=[1], 

....: final_states=[0]) 

 

We test this for the binary expansion of `7`:: 

 

sage: T.process([1, 1, 1]) 

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

 

The final carry `1` has not be flushed yet, we have to add a 

trailing zero:: 

 

sage: T.process([1, 1, 1, 0]) 

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

 

We check that with this trailing zero, the transducer 

performs as advertised:: 

 

sage: all(ZZ(T(k.bits()+[0]), base=2) == k + 1 

....: for k in srange(16)) 

True 

 

However, most of the time, we produce superfluous trailing 

zeros:: 

 

sage: T(11.bits()+[0]) 

[0, 0, 1, 1, 0] 

 

We now use this method:: 

 

sage: F = T.with_final_word_out(0) 

sage: for f in F.iter_final_states(): 

....: print("{} {}".format(f, f.final_word_out)) 

1 [1] 

0 [] 

 

The same tests as above, but we do not have to pad with 

trailing zeros anymore:: 

 

sage: F.process([1, 1, 1]) 

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

sage: all(ZZ(F(k.bits()), base=2) == k + 1 

....: for k in srange(16)) 

True 

 

No more trailing zero in the output:: 

 

sage: F(11.bits()) 

[0, 0, 1, 1] 

sage: all(F(k.bits())[-1] == 1 

....: for k in srange(16)) 

True 

 

#. Here is an example, where we allow trailing repeated `10`:: 

 

sage: T = Transducer([(0, 1, 0, 'a'), 

....: (1, 2, 1, 'b'), 

....: (2, 0, 0, 'c')], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: F = T.with_final_word_out([1, 0]) 

sage: for f in F.iter_final_states(): 

....: print(str(f) + ' ' + ''.join(f.final_word_out)) 

0 

1 bc 

 

Trying this with trailing repeated `01` does not produce 

a ``final_word_out`` for state ``1``, but for state ``2``:: 

 

sage: F = T.with_final_word_out([0, 1]) 

sage: for f in F.iter_final_states(): 

....: print(str(f) + ' ' + ''.join(f.final_word_out)) 

0 

2 c 

 

#. Here another example with a more-letter trailing input:: 

 

sage: T = Transducer([(0, 1, 0, 'a'), 

....: (1, 2, 0, 'b'), (1, 2, 1, 'b'), 

....: (2, 3, 0, 'c'), (2, 0, 1, 'e'), 

....: (3, 1, 0, 'd'), (3, 1, 1, 'd')], 

....: initial_states=[0], 

....: final_states=[0], 

....: with_final_word_out=[0, 0, 1, 1]) 

sage: for f in T.iter_final_states(): 

....: print(str(f) + ' ' + ''.join(f.final_word_out)) 

0 

1 bcdbcdbe 

2 cdbe 

3 dbe 

 

TESTS: 

 

#. Reading copies of ``letter`` may result in a cycle. In 

this simple example, we have no final state at all:: 

 

sage: T = Transducer([(0, 1, 0, 0), (1, 0, 0, 0)], 

....: initial_states=[0]) 

sage: T.with_final_word_out(0) 

Traceback (most recent call last): 

... 

ValueError: The finite state machine contains 

a cycle starting at state 0 with input label 0 

and no final state. 

 

#. A unique transition with input word ``letter`` is 

required:: 

 

sage: T = Transducer([(0, 1, 0, 0), (0, 2, 0, 0)]) 

sage: T.with_final_word_out(0) 

Traceback (most recent call last): 

... 

ValueError: No unique transition leaving state 0 

with input label 0. 

 

It is not a problem if there is no transition starting 

at state ``1`` with input word ``letter``:: 

 

sage: T = Transducer([(0, 1, 0, 0)]) 

sage: F = T.with_final_word_out(0) 

sage: for f in F.iter_final_states(): 

....: print(f, f.final_word_out) 

 

Anyhow, you can override this by:: 

 

sage: T = Transducer([(0, 1, 0, 0)]) 

sage: T.with_final_word_out(0, allow_non_final=False) 

Traceback (most recent call last): 

... 

ValueError: No unique transition leaving state 1 

with input label 0. 

 

#. All transitions must have input labels of length `1`:: 

 

sage: T = Transducer([(0, 0, [], 0)]) 

sage: T.with_final_word_out(0) 

Traceback (most recent call last): 

... 

NotImplementedError: All transitions must have input 

labels of length 1. Consider calling split_transitions(). 

sage: T = Transducer([(0, 0, [0, 1], 0)]) 

sage: T.with_final_word_out(0) 

Traceback (most recent call last): 

... 

NotImplementedError: All transitions must have input 

labels of length 1. Consider calling split_transitions(). 

 

#. An empty list as input is not allowed:: 

 

sage: T = Transducer([(0, 0, [], 0)]) 

sage: T.with_final_word_out([]) 

Traceback (most recent call last): 

... 

ValueError: letters is not allowed to be an empty list. 

""" 

from copy import deepcopy 

 

new = deepcopy(self) 

new.construct_final_word_out(letters, allow_non_final) 

return new 

 

 

def construct_final_word_out(self, letters, allow_non_final=True): 

""" 

This is an inplace version of :meth:`.with_final_word_out`. See 

:meth:`.with_final_word_out` for documentation and examples. 

 

TESTS:: 

 

sage: T = Transducer([(0, 1, 0, 0), (1, 0, 0, 1)], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: F = T.with_final_word_out(0) 

sage: T.construct_final_word_out(0) 

sage: T == F # indirect doctest 

True 

sage: T = Transducer([(0, 1, 0, None)], 

....: final_states=[1]) 

sage: F = T.with_final_word_out(0) 

sage: F.state(0).final_word_out 

[] 

""" 

from itertools import cycle 

 

if not isinstance(letters, list): 

letters = [letters] 

elif not letters: 

raise ValueError( 

"letters is not allowed to be an empty list.") 

 

in_progress = set() 

cache = {} 

 

def find_final_word_out(state): 

# The return value is the output which is produced when 

# reading the given letters until a final state is reached. 

# If no final state can be reached, then None is returned. 

# For final states, the final word out is returned. 

# For final states with empty final output, that is []. 

position, letter = next(trailing_letters) 

if state.is_final: 

return state.final_word_out 

 

if (state, position) in cache: 

return cache[state, position] 

 

if (state, position) in in_progress: 

raise ValueError( 

"The finite state machine contains a cycle " 

"starting at state %s with input label %s " 

"and no final state." % (state, letter)) 

 

if any(len(t.word_in) != 1 for t in state.transitions): 

raise NotImplementedError( 

"All transitions must have input labels of length " 

"1. Consider calling split_transitions().") 

 

transitions = [t for t in state.transitions 

if t.word_in == [letter]] 

if allow_non_final and not transitions: 

final_word_out = None 

elif len(transitions) != 1: 

raise ValueError( 

"No unique transition leaving state %s with input " 

"label %s." % (state, letter)) 

else: 

in_progress.add((state, position)) 

next_word = find_final_word_out(transitions[0].to_state) 

if next_word is not None: 

final_word_out = transitions[0].word_out + next_word 

else: 

final_word_out = None 

in_progress.remove((state, position)) 

 

cache[state, position] = final_word_out 

return final_word_out 

 

for state in self.iter_states(): 

assert(not in_progress) 

# trailing_letters is an infinite iterator additionally 

# marking positions 

trailing_letters = cycle(enumerate(letters)) 

find_final_word_out(state) 

 

# actual modifications can only be carried out after all final words 

# have been computed as it may not be permissible to stop at a 

# formerly non-final state unless a cycle has been completed. 

 

for (state, position), final_word_out in six.iteritems(cache): 

if position == 0 and final_word_out is not None: 

state.is_final = True 

state.final_word_out = final_word_out 

 

 

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

# other 

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

 

 

def graph(self, edge_labels='words_in_out'): 

""" 

Returns the graph of the finite state machine with labeled 

vertices and labeled edges. 

 

INPUT: 

 

- ``edge_label``: (default: ``'words_in_out'``) can be 

- ``'words_in_out'`` (labels will be strings ``'i|o'``) 

- a function with which takes as input a transition 

and outputs (returns) the label 

 

OUTPUT: 

 

A :class:`directed graph <DiGraph>`. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = FSMState('A') 

sage: T = Transducer() 

sage: T.graph() 

Looped multi-digraph on 0 vertices 

sage: T.add_state(A) 

'A' 

sage: T.graph() 

Looped multi-digraph on 1 vertex 

sage: T.add_transition(('A', 'A', 0, 1)) 

Transition from 'A' to 'A': 0|1 

sage: T.graph() 

Looped multi-digraph on 1 vertex 

 

.. SEEALSO:: 

 

:class:`DiGraph` 

""" 

if edge_labels == 'words_in_out': 

label_fct = lambda t:t._in_out_label_() 

elif hasattr(edge_labels, '__call__'): 

label_fct = edge_labels 

else: 

raise TypeError('Wrong argument for edge_labels.') 

 

graph_data = [] 

isolated_vertices = [] 

for state in self.iter_states(): 

transitions = state.transitions 

if len(transitions) == 0: 

isolated_vertices.append(state.label()) 

for t in transitions: 

graph_data.append((t.from_state.label(), t.to_state.label(), 

label_fct(t))) 

 

G = sage.graphs.digraph.DiGraph(graph_data, multiedges=True, loops=True) 

G.add_vertices(isolated_vertices) 

return G 

 

 

digraph = graph 

 

 

def plot(self): 

""" 

Plots a graph of the finite state machine with labeled 

vertices and labeled edges. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A plot of the graph of the finite state machine. 

 

TESTS:: 

 

sage: FiniteStateMachine([('A', 'A', 0)]).plot() 

Graphics object consisting of 3 graphics primitives 

""" 

return self.graph(edge_labels='words_in_out').plot() 

 

 

def predecessors(self, state, valid_input=None): 

""" 

Lists all predecessors of a state. 

 

INPUT: 

 

- ``state`` -- the state from which the predecessors should be 

listed. 

 

- ``valid_input`` -- If ``valid_input`` is a list, then we 

only consider transitions whose input labels are contained 

in ``valid_input``. ``state`` has to be a :class:`FSMState` 

(not a label of a state). If input labels of length larger 

than `1` are used, then ``valid_input`` has to be a list of 

lists. 

 

OUTPUT: 

 

A list of states. 

 

EXAMPLES:: 

 

sage: A = Transducer([('I', 'A', 'a', 'b'), ('I', 'B', 'b', 'c'), 

....: ('I', 'C', 'c', 'a'), ('A', 'F', 'b', 'a'), 

....: ('B', 'F', ['c', 'b'], 'b'), ('C', 'F', 'a', 'c')], 

....: initial_states=['I'], final_states=['F']) 

sage: A.predecessors(A.state('A')) 

['A', 'I'] 

sage: A.predecessors(A.state('F'), valid_input=['b', 'a']) 

['F', 'C', 'A', 'I'] 

sage: A.predecessors(A.state('F'), valid_input=[['c', 'b'], 'a']) 

['F', 'C', 'B'] 

""" 

if valid_input is not None: 

valid_list = list() 

for input in valid_input: 

input_list = input 

if not isinstance(input_list, list): 

input_list = [input] 

valid_list.append(input_list) 

valid_input = valid_list 

 

unhandeled_direct_predecessors = {s:[] for s in self.states() } 

for t in self.transitions(): 

if valid_input is None or t.word_in in valid_input: 

unhandeled_direct_predecessors[t.to_state].append(t.from_state) 

done = [] 

open = [state] 

while len(open) > 0: 

s = open.pop() 

candidates = unhandeled_direct_predecessors[s] 

if candidates is not None: 

open.extend(candidates) 

unhandeled_direct_predecessors[s] = None 

done.append(s) 

return(done) 

 

 

def number_of_words(self, variable=sage.symbolic.ring.SR.var('n'), 

base_ring=sage.rings.qqbar.QQbar): 

r""" 

Return the number of successful input words of given length. 

 

INPUT: 

 

- ``variable`` -- a symbol denoting the length of the words, 

by default `n`. 

 

- ``base_ring`` -- Ring (default: ``QQbar``) in which to 

compute the eigenvalues. 

 

OUTPUT: 

 

A symbolic expression. 

 

EXAMPLES:: 

 

sage: NAFpm = Automaton([(0, 0, 0), (0, 1, 1), 

....: (0, 1, -1), (1, 0, 0)], 

....: initial_states=[0], 

....: final_states=[0, 1]) 

sage: N = NAFpm.number_of_words(); N 

4/3*2^n - 1/3*(-1)^n 

sage: all(len(list(NAFpm.language(s))) 

....: - len(list(NAFpm.language(s-1))) == N.subs(n=s) 

....: for s in srange(1, 6)) 

True 

 

An example with non-rational eigenvalues. By default, 

eigenvalues are elements of the 

:mod:`field of algebraic numbers <sage.rings.qqbar>`. :: 

 

sage: NAFp = Automaton([(0, 0, 0), (0, 1, 1), (1, 0, 0)], 

....: initial_states=[0], 

....: final_states=[0, 1]) 

sage: N = NAFp.number_of_words(); N 

1.170820393249937?*1.618033988749895?^n 

- 0.1708203932499369?*(-0.618033988749895?)^n 

sage: all(len(list(NAFp.language(s))) 

....: - len(list(NAFp.language(s-1))) == N.subs(n=s) 

....: for s in srange(1, 6)) 

True 

 

We specify a suitable ``base_ring`` to obtain a radical 

expression. To do so, we first compute the characteristic 

polynomial and then construct a number field generated by its 

roots. :: 

 

sage: M = NAFp.adjacency_matrix(entry=lambda t: 1) 

sage: M.characteristic_polynomial() 

x^2 - x - 1 

sage: R.<phi> = NumberField(x^2-x-1, embedding=1.6) 

sage: N = NAFp.number_of_words(base_ring=R); N 

1/2*(1/2*sqrt(5) + 1/2)^n*(3*sqrt(1/5) + 1) 

- 1/2*(-1/2*sqrt(5) + 1/2)^n*(3*sqrt(1/5) - 1) 

sage: all(len(list(NAFp.language(s))) 

....: - len(list(NAFp.language(s-1))) == N.subs(n=s) 

....: for s in srange(1, 6)) 

True 

 

In this special case, we might also use the constant 

:class:`golden_ratio <sage.symbolic.constants.GoldenRatio>`:: 

 

sage: R.<phi> = NumberField(x^2-x-1, embedding=golden_ratio) 

sage: N = NAFp.number_of_words(base_ring=R); N 

1/5*(3*golden_ratio + 1)*golden_ratio^n 

- 1/5*(3*golden_ratio - 4)*(-golden_ratio + 1)^n 

sage: all(len(list(NAFp.language(s))) 

....: - len(list(NAFp.language(s-1))) == N.subs(n=s) 

....: for s in srange(1, 6)) 

True 

 

The adjacency matrix of the following example is a Jordan 

matrix of size 3 to the eigenvalue 4:: 

 

sage: J3 = Automaton([(0, 1, -1), (1, 2, -1)], 

....: initial_states=[0], 

....: final_states=[0, 1, 2]) 

sage: for i in range(3): 

....: for j in range(4): 

....: new_transition = J3.add_transition(i, i, j) 

sage: J3.adjacency_matrix(entry=lambda t: 1) 

[4 1 0] 

[0 4 1] 

[0 0 4] 

sage: N = J3.number_of_words(); N 

1/2*4^(n - 2)*(n - 1)*n + 4^(n - 1)*n + 4^n 

sage: all(len(list(J3.language(s))) 

....: - len(list(J3.language(s-1))) == N.subs(n=s) 

....: for s in range(1, 6)) 

True 

 

Here is an automaton without cycles, so with eigenvalue `0`. :: 

 

sage: A = Automaton([(j, j+1, 0) for j in range(3)], 

....: initial_states=[0], 

....: final_states=list(range(3))) 

sage: A.number_of_words() 

1/2*0^(n - 2)*(n - 1)*n + 0^(n - 1)*n + 0^n 

 

TESTS:: 

 

sage: A = Automaton([(0, 0, 0), (0, 1, 0)], 

....: initial_states=[0]) 

sage: A.number_of_words() 

Traceback (most recent call last): 

... 

NotImplementedError: Finite State Machine must be deterministic. 

""" 

from sage.matrix.constructor import matrix 

from sage.modules.free_module_element import vector 

from sage.arith.all import falling_factorial 

from sage.rings.integer_ring import ZZ 

from sage.symbolic.ring import SR 

 

def jordan_block_power(block, exponent): 

eigenvalue = SR(block[0, 0]) 

return matrix(block.nrows(), 

block.nrows(), 

lambda i, j: eigenvalue**(exponent-(j-i)) * 

falling_factorial(exponent, j-i) / ZZ(j-i).factorial() 

if j >= i else 0) 

 

if not self.is_deterministic(): 

raise NotImplementedError("Finite State Machine must be deterministic.") 

 

left = vector(ZZ(s.is_initial) for s in self.iter_states()) 

right = vector(ZZ(s.is_final) for s in self.iter_states()) 

A = self.adjacency_matrix(entry=lambda t: 1) 

J, T = A.jordan_form(base_ring, transformation=True) 

Jpower = matrix.block_diagonal( 

[jordan_block_power(J.subdivision(j, j), variable) 

for j in range(len(J.subdivisions()[0]) + 1)]) 

T_inv_right = T.solve_right(right).change_ring(SR) 

left_T = (left * T).change_ring(SR) 

return left_T * Jpower * T_inv_right 

 

 

def asymptotic_moments(self, variable=sage.symbolic.ring.SR.var('n')): 

r""" 

Returns the main terms of expectation and variance of the sum 

of output labels and its covariance with the sum of input 

labels. 

 

INPUT: 

 

- ``variable`` -- a symbol denoting the length of the input, 

by default `n`. 

 

OUTPUT: 

 

A dictionary consisting of 

 

- ``expectation`` -- `e n + \operatorname{Order}(1)`, 

- ``variance`` -- `v n + \operatorname{Order}(1)`, 

- ``covariance`` -- `c n + \operatorname{Order}(1)` 

 

for suitable constants `e`, `v` and `c`. 

 

Assume that all input and output labels are numbers and that 

``self`` is complete and has only one final component. Assume 

further that this final component is aperiodic. Furthermore, 

assume that there is exactly one initial state and that all 

states are final. 

 

Denote by `X_n` the sum of output labels written by the 

finite state machine when reading a random input word of 

length `n` over the input alphabet (assuming 

equidistribution). 

 

Then the expectation of `X_n` is `en+O(1)`, the variance 

of `X_n` is `vn+O(1)` and the covariance of `X_n` and 

the sum of input labels is `cn+O(1)`, cf. [HKW2015]_, 

Theorem 3.9. 

 

In the case of non-integer input or output labels, performance 

degrades significantly. For rational input and output labels, 

consider rescaling to integers. This limitation comes from the 

fact that determinants over polynomial rings can be computed 

much more efficiently than over the symbolic ring. In fact, we 

compute (parts) of a trivariate generating function where the 

input and output labels are exponents of some indeterminates, 

see [HKW2015]_, Theorem 3.9 for details. If those exponents are 

integers, we can use a polynomial ring. 

 

EXAMPLES: 

 

#. A trivial example: write the negative of the input:: 

 

sage: T = Transducer([(0, 0, 0, 0), (0, 0, 1, -1)], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: T([0, 1, 1]) 

[0, -1, -1] 

sage: moments = T.asymptotic_moments() 

sage: moments['expectation'] 

-1/2*n + Order(1) 

sage: moments['variance'] 

1/4*n + Order(1) 

sage: moments['covariance'] 

-1/4*n + Order(1) 

 

#. For the case of the Hamming weight of the non-adjacent-form 

(NAF) of integers, cf. the :wikipedia:`Non-adjacent_form` 

and the :ref:`example on recognizing NAFs 

<finite_state_machine_recognizing_NAFs_example>`, the 

following agrees with the results in [HP2007]_. 

 

We first use the transducer to convert the standard binary 

expansion to the NAF given in [HP2007]_. We use the parameter 

``with_final_word_out`` such that we do not have to add 

sufficiently many trailing zeros:: 

 

sage: NAF = Transducer([(0, 0, 0, 0), 

....: (0, '.1', 1, None), 

....: ('.1', 0, 0, [1, 0]), 

....: ('.1', 1, 1, [-1, 0]), 

....: (1, 1, 1, 0), 

....: (1, '.1', 0, None)], 

....: initial_states=[0], 

....: final_states=[0], 

....: with_final_word_out=[0]) 

 

As an example, we compute the NAF of `27` by this 

transducer. 

 

:: 

 

sage: binary_27 = 27.bits() 

sage: binary_27 

[1, 1, 0, 1, 1] 

sage: NAF_27 = NAF(binary_27) 

sage: NAF_27 

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

sage: ZZ(NAF_27, base=2) 

27 

 

Next, we are only interested in the Hamming weight:: 

 

sage: def weight(state, input): 

....: if input is None: 

....: result = 0 

....: else: 

....: result = ZZ(input != 0) 

....: return (0, result) 

sage: weight_transducer = Transducer(weight, 

....: input_alphabet=[-1, 0, 1], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: NAFweight = weight_transducer.composition(NAF) 

sage: NAFweight.transitions() 

[Transition from (0, 0) to (0, 0): 0|0, 

Transition from (0, 0) to ('.1', 0): 1|-, 

Transition from ('.1', 0) to (0, 0): 0|1,0, 

Transition from ('.1', 0) to (1, 0): 1|1,0, 

Transition from (1, 0) to ('.1', 0): 0|-, 

Transition from (1, 0) to (1, 0): 1|0] 

sage: NAFweight(binary_27) 

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

 

Now, we actually compute the asymptotic moments:: 

 

sage: moments = NAFweight.asymptotic_moments() 

sage: moments['expectation'] 

1/3*n + Order(1) 

sage: moments['variance'] 

2/27*n + Order(1) 

sage: moments['covariance'] 

Order(1) 

 

#. This is Example 3.16 in [HKW2015]_, where a transducer with 

variable output labels is given. There, the aim was to 

choose the output labels of this very simple transducer such 

that the input and output sum are asymptotically 

independent, i.e., the constant `c` vanishes. 

 

:: 

 

sage: var('a_1, a_2, a_3, a_4') 

(a_1, a_2, a_3, a_4) 

sage: T = Transducer([[0, 0, 0, a_1], [0, 1, 1, a_3], 

....: [1, 0, 0, a_4], [1, 1, 1, a_2]], 

....: initial_states=[0], final_states=[0, 1]) 

sage: moments = T.asymptotic_moments() 

verbose 0 (...) Non-integer output weights lead to 

significant performance degradation. 

sage: moments['expectation'] 

1/4*(a_1 + a_2 + a_3 + a_4)*n + Order(1) 

sage: moments['covariance'] 

-1/4*(a_1 - a_2)*n + Order(1) 

 

Therefore, the asymptotic covariance vanishes if and only if 

`a_2=a_1`. 

 

#. This is Example 4.3 in [HKW2015]_, dealing with the 

transducer converting the binary expansion of an integer 

into Gray code (cf. the :wikipedia:`Gray_code` and the 

:ref:`example on Gray code 

<finite_state_machine_gray_code_example>`):: 

 

sage: moments = transducers.GrayCode().asymptotic_moments() 

sage: moments['expectation'] 

1/2*n + Order(1) 

sage: moments['variance'] 

1/4*n + Order(1) 

sage: moments['covariance'] 

Order(1) 

 

#. This is the first part of Example 4.4 in [HKW2015]_, 

counting the number of 10 blocks in the standard binary 

expansion. The least significant digit is at the left-most 

position:: 

 

sage: block10 = transducers.CountSubblockOccurrences( 

....: [1, 0], 

....: input_alphabet=[0, 1]) 

sage: sorted(block10.transitions()) 

[Transition from () to (): 0|0, 

Transition from () to (1,): 1|0, 

Transition from (1,) to (): 0|1, 

Transition from (1,) to (1,): 1|0] 

sage: moments = block10.asymptotic_moments() 

sage: moments['expectation'] 

1/4*n + Order(1) 

sage: moments['variance'] 

1/16*n + Order(1) 

sage: moments['covariance'] 

Order(1) 

 

#. This is the second part of Example 4.4 in [HKW2015]_, 

counting the number of 11 blocks in the standard binary 

expansion. The least significant digit is at the left-most 

position:: 

 

sage: block11 = transducers.CountSubblockOccurrences( 

....: [1, 1], 

....: input_alphabet=[0, 1]) 

sage: sorted(block11.transitions()) 

[Transition from () to (): 0|0, 

Transition from () to (1,): 1|0, 

Transition from (1,) to (): 0|0, 

Transition from (1,) to (1,): 1|1] 

sage: var('N') 

N 

sage: moments = block11.asymptotic_moments(N) 

sage: moments['expectation'] 

1/4*N + Order(1) 

sage: moments['variance'] 

5/16*N + Order(1) 

sage: correlation = (moments['covariance'].coefficient(N) / 

....: (1/2 * sqrt(moments['variance'].coefficient(N)))) 

sage: correlation 

2/5*sqrt(5) 

 

#. This is Example 4.5 in [HKW2015]_, counting the number of 

01 blocks minus the number of 10 blocks in the standard binary 

expansion. The least significant digit is at the left-most 

position:: 

 

sage: block01 = transducers.CountSubblockOccurrences( 

....: [0, 1], 

....: input_alphabet=[0, 1]) 

sage: product_01x10 = block01.cartesian_product(block10) 

sage: block_difference = transducers.sub([0, 1])(product_01x10) 

sage: T = block_difference.simplification().relabeled() 

sage: T.transitions() 

[Transition from 0 to 1: 0|-1, 

Transition from 0 to 0: 1|0, 

Transition from 1 to 1: 0|0, 

Transition from 1 to 0: 1|1, 

Transition from 2 to 1: 0|0, 

Transition from 2 to 0: 1|0] 

sage: moments = T.asymptotic_moments() 

sage: moments['expectation'] 

Order(1) 

sage: moments['variance'] 

Order(1) 

sage: moments['covariance'] 

Order(1) 

 

#. The finite state machine must have a unique final component:: 

 

sage: T = Transducer([(0, -1, -1, -1), (0, 1, 1, 1), 

....: (-1, -1, -1, -1), (-1, -1, 1, -1), 

....: (1, 1, -1, 1), (1, 1, 1, 1)], 

....: initial_states=[0], 

....: final_states=[0, 1, -1]) 

sage: T.asymptotic_moments() 

Traceback (most recent call last): 

... 

NotImplementedError: asymptotic_moments is only 

implemented for finite state machines with one final 

component. 

 

In this particular example, the first letter of the input 

decides whether we reach the loop at `-1` or the loop at 

`1`. In the first case, we have `X_n = -n`, while we have 

`X_n = n` in the second case. Therefore, the expectation 

`E(X_n)` of `X_n` is `E(X_n) = 0`. We get `(X_n-E(X_n))^2 = 

n^2` in all cases, which results in a variance of `n^2`. 

 

So this example shows that the variance may be non-linear if 

there is more than one final component. 

 

TESTS: 

 

#. An input alphabet must be given:: 

 

sage: T = Transducer([[0, 0, 0, 0]], 

....: initial_states=[0], final_states=[0], 

....: determine_alphabets=False) 

sage: T.asymptotic_moments() 

Traceback (most recent call last): 

... 

ValueError: No input alphabet is given. 

Try calling determine_alphabets(). 

 

#. The finite state machine must have a unique initial state:: 

 

sage: T = Transducer([(0, 0, 0, 0)]) 

sage: T.asymptotic_moments() 

Traceback (most recent call last): 

... 

ValueError: A unique initial state is required. 

 

#. The finite state machine must be complete:: 

 

sage: T = Transducer([[0, 0, 0, 0]], 

....: initial_states=[0], final_states=[0], 

....: input_alphabet=[0, 1]) 

sage: T.asymptotic_moments() 

Traceback (most recent call last): 

... 

NotImplementedError: This finite state machine is 

not complete. 

 

#. The final component of the finite state machine must be 

aperiodic:: 

 

sage: T = Transducer([(0, 1, 0, 0), (1, 0, 0, 0)], 

....: initial_states=[0], final_states=[0, 1]) 

sage: T.asymptotic_moments() 

Traceback (most recent call last): 

... 

NotImplementedError: asymptotic_moments is only 

implemented for finite state machines whose unique final 

component is aperiodic. 

 

#. Non-integer input or output labels lead to a warning:: 

 

sage: T = Transducer([[0, 0, 0, 0], [0, 0, 1, -1/2]], 

....: initial_states=[0], final_states=[0]) 

sage: moments = T.asymptotic_moments() 

verbose 0 (...) Non-integer output weights lead to 

significant performance degradation. 

sage: moments['expectation'] 

-1/4*n + Order(1) 

sage: moments['variance'] 

1/16*n + Order(1) 

sage: moments['covariance'] 

-1/8*n + Order(1) 

 

This warning can be silenced by :func:`~sage.misc.misc.set_verbose`:: 

 

sage: set_verbose(-1, "finite_state_machine.py") 

sage: moments = T.asymptotic_moments() 

sage: moments['expectation'] 

-1/4*n + Order(1) 

sage: moments['variance'] 

1/16*n + Order(1) 

sage: moments['covariance'] 

-1/8*n + Order(1) 

sage: set_verbose(0, "finite_state_machine.py") 

 

#. Check whether ``word_out`` of ``FSMState`` are correctly 

dealt with:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: s = FSMState(0, word_out=2, 

....: is_initial=True, 

....: is_final=True) 

sage: T = Transducer([(s, s, 0, 1)], 

....: initial_states=[s], final_states=[s]) 

sage: T([0, 0]) 

[2, 1, 2, 1, 2] 

sage: T.asymptotic_moments()['expectation'] 

3*n + Order(1) 

 

The same test for non-integer output:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: s = FSMState(0, word_out=2/3) 

sage: T = Transducer([(s, s, 0, 1/2)], 

....: initial_states=[s], final_states=[s]) 

sage: T.asymptotic_moments()['expectation'] 

verbose 0 (...) Non-integer output weights lead to 

significant performance degradation. 

7/6*n + Order(1) 

 

#. All states of ``self`` have to be final:: 

 

sage: T = Transducer([(0, 1, 1, 4)], initial_states=[0]) 

sage: T.asymptotic_moments() 

Traceback (most recent call last): 

... 

ValueError: Not all states are final. 

 

ALGORITHM: 

 

See [HKW2015]_, Theorem 3.9. 

 

REFERENCES: 

 

.. [HP2007] Clemens Heuberger and Helmut Prodinger, *The Hamming 

Weight of the Non-Adjacent-Form under Various Input Statistics*, 

Periodica Mathematica Hungarica Vol. 55 (1), 2007, pp. 81--96, 

:doi:`10.1007/s10998-007-3081-z`. 

""" 

from sage.calculus.functional import derivative 

from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing 

from sage.rings.rational_field import QQ 

from sage.symbolic.ring import SR 

 

if self.input_alphabet is None: 

raise ValueError("No input alphabet is given. " 

"Try calling determine_alphabets().") 

 

if len(self.initial_states()) != 1: 

raise ValueError("A unique initial state is required.") 

 

if not all(state.is_final for state in self.iter_states()): 

raise ValueError("Not all states are final.") 

 

if not self.is_complete(): 

raise NotImplementedError("This finite state machine is " 

"not complete.") 

 

final_components = self.final_components() 

if len(final_components) != 1: 

raise NotImplementedError("asymptotic_moments is only " 

"implemented for finite state machines " 

"with one final component.") 

final_component = final_components[0] 

 

if not final_component.digraph().is_aperiodic(): 

raise NotImplementedError("asymptotic_moments is only " 

"implemented for finite state machines " 

"whose unique final component is " 

"aperiodic.") 

 

def get_matrix(fsm, x, y): 

return fsm.adjacency_matrix( 

entry=lambda transition: x**sum(transition.word_in) * 

y**(sum(transition.word_out) + 

sum(transition.from_state.word_out))) 

 

K = len(self.input_alphabet) 

R = PolynomialRing(QQ, ("x", "y", "z")) 

(x, y, z) = R.gens() 

try: 

M = get_matrix(self, x, y) 

except (TypeError, ValueError): 

sage.misc.misc.verbose( 

"Non-integer output weights lead to " 

"significant performance degradation.", level=0) 

# fall back to symbolic ring 

R = SR 

x = R.symbol() 

y = R.symbol() 

z = R.symbol() 

M = get_matrix(self, x, y) 

def substitute_one(g): 

return g.subs({x: 1, y: 1, z: 1}) 

else: 

def substitute_one(g): 

# the result of the substitution shall live in QQ, 

# not in the polynomial ring R, so the method 

# subs does not achieve the result. 

# Therefore, we need this helper function. 

return g(1, 1, 1) 

 

f = (M.parent().identity_matrix() - z/K*M).det() 

f_x = substitute_one(derivative(f, x)) 

f_y = substitute_one(derivative(f, y)) 

f_z = substitute_one(derivative(f, z)) 

f_xy = substitute_one(derivative(f, x, y)) 

f_xz = substitute_one(derivative(f, x, z)) 

f_yz = substitute_one(derivative(f, y, z)) 

f_yy = substitute_one(derivative(f, y, y)) 

f_zz = substitute_one(derivative(f, z, z)) 

 

e_2 = f_y / f_z 

v_2 = (f_y**2 * (f_zz+f_z) + f_z**2 * (f_yy+f_y) 

- 2*f_y*f_z*f_yz) / f_z**3 

c = (f_x * f_y * (f_zz+f_z) + f_z**2 * f_xy - f_y*f_z*f_xz 

- f_x*f_z*f_yz) / f_z**3 

 

return {'expectation': e_2*variable + SR(1).Order(), 

'variance': v_2*variable + SR(1).Order(), 

'covariance': c*variable + SR(1).Order()} 

 

 

def moments_waiting_time(self, test=bool, is_zero=None, 

expectation_only=False): 

r""" 

If this finite state machine acts as a Markov chain, return 

the expectation and variance of the number of steps until 

first writing ``True``. 

 

INPUT: 

 

- ``test`` -- (default: ``bool``) a callable deciding whether 

an output label is to be considered ``True``. By default, the 

standard conversion to boolean is used. 

 

- ``is_zero`` -- (default: ``None``) a callable deciding 

whether an expression for a probability is zero. By default, 

checking for zero is simply done by 

:meth:`~sage.structure.element.Element.is_zero`. This 

parameter can be used to provide a more sophisticated check 

for zero, e.g. in the case of symbolic probabilities, see 

the examples below. This parameter is passed on to 

:meth:`is_Markov_chain`. This parameter only affects the 

input of the Markov chain. 

 

- ``expectation_only`` -- (default: ``False``) if set, the 

variance is not computed (in order to save time). By default, 

the variance is computed. 

 

OUTPUT: 

 

A dictionary (if ``expectation_only=False``) consisting of 

 

- ``expectation``, 

- ``variance``. 

 

Otherwise, just the expectation is returned (no dictionary for 

``expectation_only=True``). 

 

Expectation and variance of the number of steps until first 

writing ``True`` (as determined by the parameter ``test``). 

 

ALGORITHM: 

 

Relies on a (classical and easy) probabilistic argument, 

cf. [FGT1992]_, Eqns. (6) and (7). 

 

For the variance, see [FHP2015]_, Section 2. 

 

EXAMPLES: 

 

#. The simplest example is to wait for the first `1` in a 

`0`-`1`-string where both digits appear with probability 

`1/2`. In fact, the waiting time equals `k` if and only if 

the string starts with `0^{k-1}1`. This event occurs with 

probability `2^{-k}`. Therefore, the expected waiting time 

and the variance are `\sum_{k\ge 1} k2^{-k}=2` and 

`\sum_{k\ge 1} (k-2)^2 2^{-k}=2`:: 

 

sage: var('k') 

k 

sage: sum(k * 2^(-k), k, 1, infinity) 

2 

sage: sum((k-2)^2 * 2^(-k), k, 1, infinity) 

2 

 

We now compute the same expectation and variance by using a 

Markov chain:: 

 

sage: from sage.combinat.finite_state_machine import ( 

....: duplicate_transition_add_input) 

sage: T = Transducer( 

....: [(0, 0, 1/2, 0), (0, 0, 1/2, 1)], 

....: on_duplicate_transition=\ 

....: duplicate_transition_add_input, 

....: initial_states=[0], 

....: final_states=[0]) 

sage: T.moments_waiting_time() 

{'expectation': 2, 'variance': 2} 

sage: T.moments_waiting_time(expectation_only=True) 

2 

 

In the following, we replace the output ``0`` by ``-1`` and 

demonstrate the use of the parameter ``test``:: 

 

sage: T.delete_transition((0, 0, 1/2, 0)) 

sage: T.add_transition((0, 0, 1/2, -1)) 

Transition from 0 to 0: 1/2|-1 

sage: T.moments_waiting_time(test=lambda x: x<0) 

{'expectation': 2, 'variance': 2} 

 

#. Make sure that the transducer is actually a Markov 

chain. Although this is checked by the code, unexpected 

behaviour may still occur if the transducer looks like a 

Markov chain. In the following example, we 'forget' to 

assign probabilities, but due to a coincidence, all 

'probabilities' add up to one. Nevertheless, `0` is never 

written, so the expectation is `1`. 

 

:: 

 

sage: T = Transducer([(0, 0, 0, 0), (0, 0, 1, 1)], 

....: on_duplicate_transition=\ 

....: duplicate_transition_add_input, 

....: initial_states=[0], 

....: final_states=[0]) 

sage: T.moments_waiting_time() 

{'expectation': 1, 'variance': 0} 

 

#. If ``True`` is never written, the moments are 

``+Infinity``:: 

 

sage: T = Transducer([(0, 0, 1, 0)], 

....: on_duplicate_transition=\ 

....: duplicate_transition_add_input, 

....: initial_states=[0], 

....: final_states=[0]) 

sage: T.moments_waiting_time() 

{'expectation': +Infinity, 'variance': +Infinity} 

 

#. Let `h` and `r` be positive integers. We consider random 

strings of letters `1`, `\ldots`, `r` where the letter `j` 

occurs with probability `p_j`. Let `B` be the random 

variable giving the first position of a block of `h` 

consecutive identical letters. Then 

 

.. MATH:: 

 

\begin{aligned} 

\mathbb{E}(B)&=\frac1{\displaystyle\sum_{i=1}^r 

\frac1{p_i^{-1}+\cdots+p_i^{-h}}},\\ 

\mathbb{V}(B)&=\frac{\displaystyle\sum_{i=1}^r\biggl( 

\frac{p_i +p_i^h}{1-p_i^h} 

- 2h\frac{ p_i^h(1-p_i)}{(1-p_i^h)^2}\biggr)} 

{\displaystyle\biggl(\sum_{i=1}^r 

\frac1{p_i^{-1}+\cdots+p_i^{-h}}\biggr)^2} 

\end{aligned} 

 

cf. [S1986]_, p. 62, or [FHP2015]_, Theorem 1. We now 

verify this with a transducer approach. 

 

:: 

 

sage: def test(h, r): 

....: R = PolynomialRing( 

....: QQ, 

....: names=['p_%d' % j for j in range(r)]) 

....: p = R.gens() 

....: def is_zero(polynomial): 

....: return polynomial in (sum(p) - 1) * R 

....: theory_expectation = 1/(sum(1/sum(p[j]^(-i) 

....: for i in range(1, h+1)) 

....: for j in range(r))) 

....: theory_variance = sum( 

....: (p[i] + p[i]^h)/(1 - p[i]^h) 

....: - 2*h*p[i]^h * (1 - p[i])/(1 - p[i]^h)^2 

....: for i in range(r) 

....: ) * theory_expectation^2 

....: alphabet = list(range(r)) 

....: counters = [ 

....: transducers.CountSubblockOccurrences([j]*h, 

....: alphabet) 

....: for j in alphabet] 

....: all_counter = counters[0].cartesian_product( 

....: counters[1:]) 

....: adder = transducers.add(input_alphabet=[0, 1], 

....: number_of_operands=r) 

....: probabilities = Transducer( 

....: [(0, 0, p[j], j) for j in alphabet], 

....: initial_states=[0], 

....: final_states=[0], 

....: on_duplicate_transition=\ 

....: duplicate_transition_add_input) 

....: chain = adder(all_counter(probabilities)) 

....: result = chain.moments_waiting_time( 

....: is_zero=is_zero) 

....: return is_zero((result['expectation'] - 

....: theory_expectation).numerator()) \ 

....: and \ 

....: is_zero((result['variance'] - 

....: theory_variance).numerator()) 

sage: test(2, 2) 

True 

sage: test(2, 3) 

True 

sage: test(3, 3) 

True 

 

#. Consider the alphabet `\{0, \ldots, r-1\}`, some `1\le j\le 

r` and some `h\ge 1`. For some probabilities `p_0`, 

`\ldots`, `p_{r-1}`, we consider infinite words where the 

letters occur independently with the given probabilities. 

The random variable `B_j` is the first position `n` such 

that there exist `j` of the `r` letters having an `h`-run. 

The expectation of `B_j` is given in [FHP2015]_, Theorem 2. 

Here, we verify this result by using transducers:: 

 

sage: def test(h, r, j): 

....: R = PolynomialRing( 

....: QQ, 

....: names=['p_%d' % i for i in range(r)]) 

....: p = R.gens() 

....: def is_zero(polynomial): 

....: return polynomial in (sum(p) - 1) * R 

....: alphabet = list(range(r)) 

....: counters = [ 

....: transducers.Wait([0, 1])( 

....: transducers.CountSubblockOccurrences( 

....: [i]*h, 

....: alphabet)) 

....: for i in alphabet] 

....: all_counter = counters[0].cartesian_product( 

....: counters[1:]) 

....: adder = transducers.add(input_alphabet=[0, 1], 

....: number_of_operands=r) 

....: threshold = transducers.map( 

....: f=lambda x: x >= j, 

....: input_alphabet=srange(r+1)) 

....: probabilities = Transducer( 

....: [(0, 0, p[i], i) for i in alphabet], 

....: initial_states=[0], 

....: final_states=[0], 

....: on_duplicate_transition=\ 

....: duplicate_transition_add_input) 

....: chain = threshold(adder(all_counter( 

....: probabilities))) 

....: result = chain.moments_waiting_time( 

....: is_zero=is_zero, 

....: expectation_only=True) 

....: 

....: R_v = PolynomialRing( 

....: QQ, 

....: names=['p_%d' % i for i in range(r)]) 

....: v = R_v.gens() 

....: S = 1/(1 - sum(v[i]/(1+v[i]) 

....: for i in range(r))) 

....: alpha = [(p[i] - p[i]^h)/(1 - p[i]) 

....: for i in range(r)] 

....: gamma = [p[i]/(1 - p[i]) for i in range(r)] 

....: alphabet_set = set(alphabet) 

....: expectation = 0 

....: for q in range(j): 

....: for M in Subsets(alphabet_set, q): 

....: summand = S 

....: for i in M: 

....: summand = summand.subs( 

....: {v[i]: gamma[i]}) -\ 

....: summand.subs({v[i]: alpha[i]}) 

....: for i in alphabet_set - set(M): 

....: summand = summand.subs( 

....: {v[i]: alpha[i]}) 

....: expectation += summand 

....: return is_zero((result - expectation).\ 

....: numerator()) 

sage: test(2, 3, 2) 

True 

 

REFERENCES: 

 

.. [FGT1992] Philippe Flajolet, Danièle Gardy, Loÿs Thimonier, 

*Birthday paradox, coupon collectors, caching algorithms and 

self-organizing search*, Discrete Appl. Math. 39 (1992), 

207--229, :doi:`10.1016/0166-218X(92)90177-C`. 

 

.. [FHP2015] Uta Freiberg, Clemens Heuberger, Helmut Prodinger, 

*Application of Smirnov Words to Waiting Time Distributions 

of Runs*, :arxiv:`1503.08096`. 

 

.. [S1986] Gábor J. Székely, *Paradoxes in Probability Theory 

and Mathematical Statistics*, D. Reidel Publishing Company. 

 

TESTS: 

 

Only Markov chains are acceptable:: 

 

sage: T = transducers.Identity([0, 1, 2]) 

sage: T.moments_waiting_time() 

Traceback (most recent call last): 

... 

ValueError: Only Markov chains can compute 

moments_waiting_time. 

 

There must be a unique initial state:: 

 

sage: T = Transducer([(0, 1, 1, 1), (1, 0, 1, 0)], 

....: on_duplicate_transition=\ 

....: duplicate_transition_add_input) 

sage: T.moments_waiting_time() 

Traceback (most recent call last): 

... 

ValueError: Unique initial state is required. 

 

Using `0` as initial state in this example, a `1` is written in 

the first step with probability `1`, so the waiting time is 

always `1`:: 

 

sage: T.state(0).is_initial = True 

sage: T.moments_waiting_time() 

{'expectation': 1, 'variance': 0} 

 

Using both `0` and `1` as initial states again yields an error 

message:: 

 

sage: T.state(1).is_initial = True 

sage: T.moments_waiting_time() 

Traceback (most recent call last): 

... 

ValueError: Unique initial state is required. 

 

Detection of infinite waiting time for symbolic probabilities:: 

 

sage: R.<p, q> = PolynomialRing(QQ) 

sage: T = Transducer([(0, 0, p, 0), (0, 0, q, 0)], 

....: initial_states=[0], 

....: on_duplicate_transition=\ 

....: duplicate_transition_add_input) 

sage: T.moments_waiting_time( 

....: is_zero=lambda e: e in (p + q - 1)*R) 

{'expectation': +Infinity, 'variance': +Infinity} 

""" 

from sage.modules.free_module_element import vector 

from sage.matrix.constructor import identity_matrix 

from sage.rings.polynomial.polynomial_ring_constructor import\ 

PolynomialRing 

 

def default_is_zero(expression): 

return expression.is_zero() 

 

is_zero_function = default_is_zero 

if is_zero is not None: 

is_zero_function = is_zero 

 

if not self.is_Markov_chain(is_zero): 

raise ValueError("Only Markov chains can compute " 

"moments_waiting_time.") 

 

if len(self.initial_states()) != 1: 

raise ValueError("Unique initial state is required.") 

 

def entry(transition): 

word_out = transition.word_out 

if len(word_out) == 0 or ( 

len(word_out) == 1 and not test(word_out[0])): 

return transition.word_in[0] 

else: 

return 0 

 

relabeled = self.relabeled() 

n = len(relabeled.states()) 

assert [s.label() for s in relabeled.states()] == list(range(n)) 

from sage.rings.integer_ring import ZZ 

entry_vector = vector(ZZ(s.is_initial) 

for s in relabeled.states()) 

exit_vector = vector([1] * n) 

transition_matrix = relabeled.adjacency_matrix(entry=entry) 

# transition_matrix is the probability transition matrix 

# of the part of the transducer before the occurrence of true 

# output. 

# We cannot use the input parameter of adjacency_matrix 

# because we want to check for "true" input in the sense 

# of python's boolean conversion. So we cannot give 

# input=[False] as this might lead to strange phenomena. 

if all(map(is_zero_function, 

transition_matrix * exit_vector - exit_vector)): 

import sage.rings.infinity 

expectation = sage.rings.infinity.PlusInfinity() 

variance = sage.rings.infinity.PlusInfinity() 

else: 

if expectation_only: 

system_matrix = identity_matrix(n) - transition_matrix 

expectation = entry_vector * \ 

system_matrix.solve_right(exit_vector) 

else: 

base_ring = transition_matrix.parent().base_ring() 

from sage.rings.polynomial.multi_polynomial_ring \ 

import is_MPolynomialRing 

if is_MPolynomialRing(base_ring): 

# if base_ring is already a multivariate polynomial 

# ring, extend it instead of creating a univariate 

# polynomial ring over a polynomial ring. This 

# should improve performance. 

R = PolynomialRing( 

base_ring.base_ring(), 

base_ring.variable_names() 

+ ('Z_waiting_time',)) 

else: 

R = PolynomialRing(base_ring, 'Z_waiting_time') 

Z = R.gens()[-1] 

system_matrix = identity_matrix(n) - Z * \ 

transition_matrix 

G = entry_vector * system_matrix.solve_right( 

exit_vector) 

expectation = G.subs({Z: 1}) 

variance = 2 * G.derivative(Z).subs({Z: 1}) \ 

+ expectation \ 

- expectation**2 

 

if expectation_only: 

return expectation 

else: 

return {'expectation': expectation, 

'variance': variance} 

 

 

def is_monochromatic(self): 

""" 

Checks whether the colors of all states are equal. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

EXAMPLES:: 

 

sage: G = transducers.GrayCode() 

sage: [s.color for s in G.iter_states()] 

[None, None, None] 

sage: G.is_monochromatic() 

True 

sage: G.state(1).color = 'blue' 

sage: G.is_monochromatic() 

False 

""" 

return equal(s.color for s in self.iter_states()) 

 

 

def language(self, max_length=None, **kwargs): 

r""" 

Return all words that can be written by this transducer. 

 

INPUT: 

 

- ``max_length`` -- an integer or ``None`` (default). Only 

output words which come from inputs of length at most 

``max_length`` will be considered. If ``None``, then this 

iterates over all possible words without length restrictions. 

 

- ``kwargs`` -- will be passed on to the :class:`process 

iterator <FSMProcessIterator>`. See :meth:`process` for a 

description. 

 

OUTPUT: 

 

An iterator. 

 

EXAMPLES:: 

 

sage: NAF = Transducer([('I', 0, 0, None), ('I', 1, 1, None), 

....: (0, 0, 0, 0), (0, 1, 1, 0), 

....: (1, 0, 0, 1), (1, 2, 1, -1), 

....: (2, 1, 0, 0), (2, 2, 1, 0)], 

....: initial_states=['I'], final_states=[0], 

....: input_alphabet=[0, 1]) 

sage: sorted(NAF.language(4), 

....: key=lambda o: (ZZ(o, base=2), len(o))) 

[[], [0], [0, 0], [0, 0, 0], 

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

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

[-1, 0, 1], 

[0, 0, 1], 

[1, 0, 1]] 

 

:: 

 

sage: iterator = NAF.language() 

sage: next(iterator) 

[] 

sage: next(iterator) 

[0] 

sage: next(iterator) 

[1] 

sage: next(iterator) 

[0, 0] 

sage: next(iterator) 

[0, 1] 

 

.. SEEALSO:: 

 

:meth:`Automaton.language`, 

:meth:`process`. 

 

TESTS:: 

 

sage: T = Transducer([(0, 1, 0, 'a'), (1, 2, 1, 'b')], 

....: initial_states=[0], final_states=[0, 1, 2]) 

sage: T.determine_alphabets() 

sage: list(T.language(2)) 

[[], ['a'], ['a', 'b']] 

sage: list(T.language(3)) 

[[], ['a'], ['a', 'b']] 

sage: from sage.combinat.finite_state_machine import _FSMProcessIteratorAll_ 

sage: it = T.iter_process( 

....: process_iterator_class=_FSMProcessIteratorAll_, 

....: max_length=3, 

....: process_all_prefixes_of_input=True) 

sage: for current in it: 

....: print(current) 

....: print("finished: {}".format([branch.output for branch in it._finished_])) 

process (1 branch) 

+ at state 1 

+-- tape at 1, [['a']] 

finished: [[]] 

process (1 branch) 

+ at state 2 

+-- tape at 2, [['a', 'b']] 

finished: [[], ['a']] 

process (0 branches) 

finished: [[], ['a'], ['a', 'b']] 

""" 

kwargs['process_iterator_class'] = _FSMProcessIteratorAll_ 

kwargs['max_length'] = max_length 

kwargs['process_all_prefixes_of_input'] = True 

it = self.iter_process(**kwargs) 

for _ in it: 

for branch in it._finished_: 

if branch.accept: 

yield branch.output 

it._finished_ = [] 

 

 

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

 

 

def is_Automaton(FSM): 

""" 

Tests whether or not ``FSM`` inherits from :class:`Automaton`. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import is_FiniteStateMachine, is_Automaton 

sage: is_Automaton(FiniteStateMachine()) 

False 

sage: is_Automaton(Automaton()) 

True 

sage: is_FiniteStateMachine(Automaton()) 

True 

""" 

return isinstance(FSM, Automaton) 

 

 

class Automaton(FiniteStateMachine): 

""" 

This creates an automaton, which is a finite state machine, whose 

transitions have input labels. 

 

An automaton has additional features like creating a deterministic 

and a minimized automaton. 

 

See class :class:`FiniteStateMachine` for more information. 

 

EXAMPLES: 

 

We can create an automaton recognizing even numbers (given in 

binary and read from left to right) in the following way:: 

 

sage: A = Automaton([('P', 'Q', 0), ('P', 'P', 1), 

....: ('Q', 'P', 1), ('Q', 'Q', 0)], 

....: initial_states=['P'], final_states=['Q']) 

sage: A 

Automaton with 2 states 

sage: A([0]) 

True 

sage: A([1, 1, 0]) 

True 

sage: A([1, 0, 1]) 

False 

 

Note that the full output of the commands can be obtained by 

calling :meth:`.process` and looks like this:: 

 

sage: A.process([1, 0, 1]) 

(False, 'P') 

 

TESTS:: 

 

sage: Automaton() 

Empty automaton 

""" 

 

def __init__(self, *args, **kwargs): 

""" 

Initialize an automaton. See :class:`Automaton` and its parent 

:class:`FiniteStateMachine` for more information. 

 

TESTS:: 

 

sage: Transducer()._allow_composition_ 

True 

sage: Automaton()._allow_composition_ 

False 

 

""" 

super(Automaton, self).__init__(*args, **kwargs) 

self._allow_composition_ = False 

 

 

def _repr_(self): 

""" 

Represents the finite state machine as "Automaton with n 

states" where n is the number of states. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A string. 

 

EXAMPLES:: 

 

sage: Automaton()._repr_() 

'Empty automaton' 

 

 

TESTS:: 

 

sage: A = Automaton() 

sage: A 

Empty automaton 

sage: A.add_state(42) 

42 

sage: A 

Automaton with 1 state 

sage: A.add_state(43) 

43 

sage: A 

Automaton with 2 states 

 

""" 

if len(self._states_)==0: 

return "Empty automaton" 

if len(self._states_)==1: 

return "Automaton with 1 state" 

else: 

return "Automaton with %s states" % len(self._states_) 

 

 

def _latex_transition_label_(self, transition, 

format_function=sage.misc.latex.latex): 

r""" 

Returns the proper transition label. 

 

INPUT: 

 

- ``transition`` - a transition 

 

- ``format_function`` - a function formatting the labels 

 

OUTPUT: 

 

A string. 

 

EXAMPLES:: 

 

sage: F = Automaton([('A', 'B', 1)]) 

sage: print(latex(F)) # indirect doctest 

\begin{tikzpicture}[auto, initial text=, >=latex] 

\node[state] (v0) at (3.000000, 0.000000) {$\text{\texttt{A}}$}; 

\node[state] (v1) at (-3.000000, 0.000000) {$\text{\texttt{B}}$}; 

\path[->] (v0) edge node[rotate=360.00, anchor=south] {$1$} (v1); 

\end{tikzpicture} 

 

TESTS:: 

 

sage: F = Automaton([('A', 'B', 0, 1)]) 

sage: t = F.transitions()[0] 

sage: F._latex_transition_label_(t) 

\left[0\right] 

""" 

return format_function(transition.word_in) 

 

 

def intersection(self, other, only_accessible_components=True): 

""" 

Returns a new automaton which accepts an input if it is 

accepted by both given automata. 

 

INPUT: 

 

- ``other`` -- an automaton 

 

- ``only_accessible_components`` -- If ``True`` (default), then 

the result is piped through :meth:`.accessible_components`. If no 

``new_input_alphabet`` is given, it is determined by 

:meth:`.determine_alphabets`. 

 

OUTPUT: 

 

A new automaton which computes the intersection 

(see below) of the languages of ``self`` and ``other``. 

 

The set of states of the new automaton is the Cartesian product of the 

set of states of both given automata. There is a transition `((A, B), 

(C, D), a)` in the new automaton if there are transitions `(A, C, a)` 

and `(B, D, a)` in the old automata. 

 

The methods :meth:`.intersection` and 

:meth:`.cartesian_product` are the same (for automata). 

 

EXAMPLES:: 

 

sage: aut1 = Automaton([('1', '2', 1), 

....: ('2', '2', 1), 

....: ('2', '2', 0)], 

....: initial_states=['1'], 

....: final_states=['2'], 

....: determine_alphabets=True) 

sage: aut2 = Automaton([('A', 'A', 1), 

....: ('A', 'B', 0), 

....: ('B', 'B', 0), 

....: ('B', 'A', 1)], 

....: initial_states=['A'], 

....: final_states=['B'], 

....: determine_alphabets=True) 

sage: res = aut1.intersection(aut2) 

sage: (aut1([1, 1]), aut2([1, 1]), res([1, 1])) 

(True, False, False) 

sage: (aut1([1, 0]), aut2([1, 0]), res([1, 0])) 

(True, True, True) 

sage: res.transitions() 

[Transition from ('1', 'A') to ('2', 'A'): 1|-, 

Transition from ('2', 'A') to ('2', 'B'): 0|-, 

Transition from ('2', 'A') to ('2', 'A'): 1|-, 

Transition from ('2', 'B') to ('2', 'B'): 0|-, 

Transition from ('2', 'B') to ('2', 'A'): 1|-] 

 

For automata with epsilon-transitions, intersection is not well 

defined. But for any finite state machine, epsilon-transitions can be 

removed by :meth:`.remove_epsilon_transitions`. 

 

:: 

 

sage: a1 = Automaton([(0, 0, 0), 

....: (0, 1, None), 

....: (1, 1, 1), 

....: (1, 2, 1)], 

....: initial_states=[0], 

....: final_states=[1], 

....: determine_alphabets=True) 

sage: a2 = Automaton([(0, 0, 0), (0, 1, 1), (1, 1, 1)], 

....: initial_states=[0], 

....: final_states=[1], 

....: determine_alphabets=True) 

sage: a1.intersection(a2) 

Traceback (most recent call last): 

... 

ValueError: An epsilon-transition (with empty input) 

was found. 

sage: a1.remove_epsilon_transitions() # not tested (since not implemented yet) 

sage: a1.intersection(a2) # not tested 

""" 

if not is_Automaton(other): 

raise TypeError( 

"Only an automaton can be intersected with an automaton.") 

 

def function(transition1, transition2): 

if not transition1.word_in or not transition2.word_in: 

raise ValueError( 

"An epsilon-transition (with empty input) was found.") 

if transition1.word_in == transition2.word_in: 

return (transition1.word_in, None) 

else: 

raise LookupError 

 

return self.product_FiniteStateMachine( 

other, 

function, 

only_accessible_components=only_accessible_components) 

 

 

cartesian_product = intersection 

 

 

def determinisation(self): 

""" 

Returns a deterministic automaton which accepts the same input 

words as the original one. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A new automaton, which is deterministic. 

 

The labels of the states of the new automaton are frozensets 

of states of ``self``. The color of a new state is the 

frozenset of colors of the constituent states of ``self``. 

Therefore, the colors of the constituent states have to be 

hashable. However, if all constituent states have color 

``None``, then the resulting color is ``None``, too. 

 

The input alphabet must be specified. 

 

EXAMPLES:: 

 

sage: aut = Automaton([('A', 'A', 0), ('A', 'B', 1), ('B', 'B', 1)], 

....: initial_states=['A'], final_states=['B']) 

sage: aut.determinisation().transitions() 

[Transition from frozenset(['A']) 

to frozenset(['A']): 0|-, 

Transition from frozenset(['A']) 

to frozenset(['B']): 1|-, 

Transition from frozenset(['B']) 

to frozenset([]): 0|-, 

Transition from frozenset(['B']) 

to frozenset(['B']): 1|-, 

Transition from frozenset([]) 

to frozenset([]): 0|-, 

Transition from frozenset([]) 

to frozenset([]): 1|-] 

 

:: 

 

sage: A = Automaton([('A', 'A', 1), ('A', 'A', 0), ('A', 'B', 1), 

....: ('B', 'C', 0), ('C', 'C', 1), ('C', 'C', 0)], 

....: initial_states=['A'], final_states=['C']) 

sage: A.determinisation().states() 

[frozenset(['A']), frozenset(['A', 'B']), 

frozenset(['A', 'C']), frozenset(['A', 'C', 'B'])] 

 

:: 

 

sage: A = Automaton([(0, 1, 1), (0, 2, [1, 1]), (0, 3, [1, 1, 1]), 

....: (1, 0, -1), (2, 0, -2), (3, 0, -3)], 

....: initial_states=[0], final_states=[0, 1, 2, 3]) 

sage: B = A.determinisation().relabeled().coaccessible_components() 

sage: sorted(B.transitions()) 

[Transition from 0 to 1: 1|-, 

Transition from 1 to 0: -1|-, 

Transition from 1 to 3: 1|-, 

Transition from 3 to 0: -2|-, 

Transition from 3 to 4: 1|-, 

Transition from 4 to 0: -3|-] 

 

Note that colors of states have to be hashable:: 

 

sage: A = Automaton([[0, 0, 0]], initial_states=[0]) 

sage: A.state(0).color = [] 

sage: A.determinisation() 

Traceback (most recent call last): 

... 

TypeError: unhashable type: 'list' 

sage: A.state(0).color = () 

sage: A.determinisation() 

Automaton with 1 state 

 

If the colors of all constituent states are ``None``, 

the resulting color is ``None``, too (:trac:`19199`):: 

 

sage: A = Automaton([(0, 0, 0)], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: [s.color for s in A.determinisation().iter_states()] 

[None] 

 

TESTS: 

 

This is from :trac:`15078`, comment 13. 

 

:: 

 

sage: D = {'A': [('A', 'a'), ('B', 'a'), ('A', 'b')], 

....: 'C': [], 'B': [('C', 'b')]} 

sage: auto = Automaton(D, initial_states=['A'], final_states=['C']) 

sage: auto.is_deterministic() 

False 

sage: auto.process(list('aaab')) 

[(False, 'A'), (True, 'C')] 

sage: auto.states() 

['A', 'C', 'B'] 

sage: Ddet = auto.determinisation() 

sage: Ddet 

Automaton with 3 states 

sage: Ddet.is_deterministic() 

True 

sage: sorted(Ddet.transitions()) 

[Transition from frozenset(['A']) to frozenset(['A', 'B']): 'a'|-, 

Transition from frozenset(['A']) to frozenset(['A']): 'b'|-, 

Transition from frozenset(['A', 'B']) to frozenset(['A', 'B']): 'a'|-, 

Transition from frozenset(['A', 'B']) to frozenset(['A', 'C']): 'b'|-, 

Transition from frozenset(['A', 'C']) to frozenset(['A', 'B']): 'a'|-, 

Transition from frozenset(['A', 'C']) to frozenset(['A']): 'b'|-] 

sage: Ddet.initial_states() 

[frozenset(['A'])] 

sage: Ddet.final_states() 

[frozenset(['A', 'C'])] 

sage: Ddet.process(list('aaab')) 

(True, frozenset(['A', 'C'])) 

 

Test that :trac:`18992` is fixed:: 

 

sage: A = Automaton([(0, 1, []), (1, 1, 0)], 

....: initial_states=[0], final_states=[1]) 

sage: B = A.determinisation() 

sage: B.initial_states() 

[frozenset([0, 1])] 

sage: B.final_states() 

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

sage: B.transitions() 

[Transition from frozenset([0, 1]) to frozenset([1]): 0|-, 

Transition from frozenset([1]) to frozenset([1]): 0|-] 

sage: C = B.minimization().relabeled() 

sage: C.initial_states() 

[0] 

sage: C.final_states() 

[0] 

sage: C.transitions() 

[Transition from 0 to 0: 0|-] 

""" 

if any(len(t.word_in) > 1 for t in self.iter_transitions()): 

return self.split_transitions().determinisation() 

 

epsilon_successors = {} 

direct_epsilon_successors = {} 

for state in self.iter_states(): 

direct_epsilon_successors[state] = set( 

t.to_state 

for t in self.iter_transitions(state) 

if not t.word_in) 

epsilon_successors[state] = set([state]) 

 

old_count_epsilon_successors = 0 

count_epsilon_successors = len(epsilon_successors) 

 

while old_count_epsilon_successors < count_epsilon_successors: 

old_count_epsilon_successors = count_epsilon_successors 

count_epsilon_successors = 0 

for state in self.iter_states(): 

for direct_successor in direct_epsilon_successors[state]: 

epsilon_successors[state] = epsilon_successors[state].union(epsilon_successors[direct_successor]) 

count_epsilon_successors += len(epsilon_successors[state]) 

 

def set_transition(states, letter): 

result = set() 

for state in states: 

for transition in self.iter_transitions(state): 

if transition.word_in == [letter]: 

result.add(transition.to_state) 

result = result.union(*(epsilon_successors[s] for s in result)) 

return (frozenset(result), []) 

 

result = self.empty_copy() 

new_initial_states = [frozenset(set().union( 

*(epsilon_successors[s] 

for s in self.iter_initial_states() 

)))] 

result.add_from_transition_function(set_transition, 

initial_states=new_initial_states) 

 

for state in result.iter_states(): 

state.is_final = any(s.is_final for s in state.label()) 

if all(s.color is None for s in state.label()): 

state.color = None 

else: 

state.color = frozenset(s.color for s in state.label()) 

 

return result 

 

 

def minimization(self, algorithm=None): 

""" 

Returns the minimization of the input automaton as a new automaton. 

 

INPUT: 

 

- ``algorithm`` -- Either Moore's algorithm (by 

``algorithm='Moore'`` or as default for deterministic 

automata) or Brzozowski's algorithm (when 

``algorithm='Brzozowski'`` or when the automaton is not 

deterministic) is used. 

 

OUTPUT: 

 

A new automaton. 

 

The resulting automaton is deterministic and has a minimal 

number of states. 

 

EXAMPLES:: 

 

sage: A = Automaton([('A', 'A', 1), ('A', 'A', 0), ('A', 'B', 1), 

....: ('B', 'C', 0), ('C', 'C', 1), ('C', 'C', 0)], 

....: initial_states=['A'], final_states=['C']) 

sage: B = A.minimization(algorithm='Brzozowski') 

sage: B.transitions(B.states()[1]) 

[Transition from frozenset([frozenset(['A', 'C', 'B']), 

frozenset(['C', 'B']), frozenset(['A', 'C'])]) to 

frozenset([frozenset(['A', 'C', 'B']), frozenset(['C', 'B']), 

frozenset(['A', 'C']), frozenset(['C'])]): 0|-, 

Transition from frozenset([frozenset(['A', 'C', 'B']), 

frozenset(['C', 'B']), frozenset(['A', 'C'])]) to 

frozenset([frozenset(['A', 'C', 'B']), frozenset(['C', 'B']), 

frozenset(['A', 'C'])]): 1|-] 

sage: len(B.states()) 

3 

sage: C = A.minimization(algorithm='Brzozowski') 

sage: C.transitions(C.states()[1]) 

[Transition from frozenset([frozenset(['A', 'C', 'B']), 

frozenset(['C', 'B']), frozenset(['A', 'C'])]) to 

frozenset([frozenset(['A', 'C', 'B']), frozenset(['C', 'B']), 

frozenset(['A', 'C']), frozenset(['C'])]): 0|-, 

Transition from frozenset([frozenset(['A', 'C', 'B']), 

frozenset(['C', 'B']), frozenset(['A', 'C'])]) to 

frozenset([frozenset(['A', 'C', 'B']), frozenset(['C', 'B']), 

frozenset(['A', 'C'])]): 1|-] 

sage: len(C.states()) 

3 

 

:: 

 

sage: aut = Automaton([('1', '2', 'a'), ('2', '3', 'b'), 

....: ('3', '2', 'a'), ('2', '1', 'b'), 

....: ('3', '4', 'a'), ('4', '3', 'b')], 

....: initial_states=['1'], final_states=['1']) 

sage: min = aut.minimization(algorithm='Brzozowski') 

sage: [len(min.states()), len(aut.states())] 

[3, 4] 

sage: min = aut.minimization(algorithm='Moore') 

Traceback (most recent call last): 

... 

NotImplementedError: Minimization via Moore's Algorithm is only 

implemented for deterministic finite state machines 

""" 

deterministic = self.is_deterministic() 

 

if algorithm == "Moore" or (algorithm is None and deterministic): 

return self._minimization_Moore_() 

elif algorithm == "Brzozowski" or (algorithm is None and not deterministic): 

return self._minimization_Brzozowski_() 

else: 

raise NotImplementedError("Algorithm '%s' is not implemented. Choose 'Moore' or 'Brzozowski'" % algorithm) 

 

 

def _minimization_Brzozowski_(self): 

""" 

Returns a minimized automaton by using Brzozowski's algorithm. 

 

See also :meth:`.minimization`. 

 

TESTS:: 

 

sage: A = Automaton([('A', 'A', 1), ('A', 'A', 0), ('A', 'B', 1), 

....: ('B', 'C', 0), ('C', 'C', 1), ('C', 'C', 0)], 

....: initial_states=['A'], final_states=['C']) 

sage: B = A._minimization_Brzozowski_() 

sage: len(B.states()) 

3 

""" 

return self.transposition().determinisation().transposition().determinisation() 

 

 

def _minimization_Moore_(self): 

""" 

Returns a minimized automaton by using Moore's algorithm. 

 

See also :meth:`.minimization`. 

 

TESTS:: 

 

sage: aut = Automaton([('1', '2', 'a'), ('2', '3', 'b'), 

....: ('3', '2', 'a'), ('2', '1', 'b'), 

....: ('3', '4', 'a'), ('4', '3', 'b')], 

....: initial_states=['1'], final_states=['1']) 

sage: min = aut._minimization_Moore_() 

Traceback (most recent call last): 

... 

NotImplementedError: Minimization via Moore's Algorithm is only 

implemented for deterministic finite state machines 

""" 

if self.is_deterministic(): 

return self.quotient(self.equivalence_classes()) 

else: 

raise NotImplementedError("Minimization via Moore's Algorithm is only " \ 

"implemented for deterministic finite state machines") 

 

 

def complement(self): 

r""" 

Return the complement of this automaton. 

 

OUTPUT: 

 

An :class:`Automaton`. 

 

If this automaton recognizes language `\mathcal{L}` over an 

input alphabet `\mathcal{A}`, then the complement recognizes 

`\mathcal{A}\setminus\mathcal{L}`. 

 

EXAMPLES:: 

 

sage: A = automata.Word([0, 1]) 

sage: [w for w in 

....: [], [0], [1], [0, 0], [0, 1], [1, 0], [1, 1] 

....: if A(w)] 

[[0, 1]] 

sage: Ac = A.complement() 

sage: Ac.transitions() 

[Transition from 0 to 1: 0|-, 

Transition from 0 to 3: 1|-, 

Transition from 2 to 3: 0|-, 

Transition from 2 to 3: 1|-, 

Transition from 1 to 2: 1|-, 

Transition from 1 to 3: 0|-, 

Transition from 3 to 3: 0|-, 

Transition from 3 to 3: 1|-] 

sage: [w for w in 

....: [], [0], [1], [0, 0], [0, 1], [1, 0], [1, 1] 

....: if Ac(w)] 

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

 

The automaton must be deterministic:: 

 

sage: A = automata.Word([0]) * automata.Word([1]) 

sage: A.complement() 

Traceback (most recent call last): 

... 

ValueError: The finite state machine must be deterministic. 

sage: Ac = A.determinisation().complement() 

sage: [w for w in 

....: [], [0], [1], [0, 0], [0, 1], [1, 0], [1, 1] 

....: if Ac(w)] 

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

""" 

result = self.completion() 

for state in result.iter_states(): 

state.is_final = not state.is_final 

 

return result 

 

def is_equivalent(self, other): 

""" 

Test whether two automata are equivalent, i.e., accept the same 

language. 

 

INPUT: 

 

- ``other`` -- an :class:`Automaton`. 

 

EXAMPLES:: 

 

sage: A = Automaton([(0, 0, 0), (0, 1, 1), (1, 0, 1)], 

....: initial_states=[0], 

....: final_states=[0]) 

sage: B = Automaton([('a', 'a', 0), ('a', 'b', 1), ('b', 'a', 1)], 

....: initial_states=['a'], 

....: final_states=['a']) 

sage: A.is_equivalent(B) 

True 

sage: B.add_transition('b', 'a', 0) 

Transition from 'b' to 'a': 0|- 

sage: A.is_equivalent(B) 

False 

""" 

A = self.minimization().relabeled() 

[initial] = A.initial_states() 

address = {initial: ()} 

for v in A.digraph().breadth_first_search(initial.label()): 

state = A.state(v) 

state_address = address[state] 

for t in A.iter_transitions(state): 

if t.to_state not in address: 

address[t.to_state] = state_address + tuple(t.word_in) 

 

B = other.minimization().relabeled() 

labels = {B.process(path)[1].label(): state.label() 

for (state, path) in six.iteritems(address)} 

try: 

return A == B.relabeled(labels=labels) 

except KeyError: 

return False 

 

 

def process(self, *args, **kwargs): 

""" 

Return whether the automaton accepts the input and the state 

where the computation stops. 

 

INPUT: 

 

- ``input_tape`` -- the input tape can be a list or an 

iterable with entries from the input alphabet. If we are 

working with a multi-tape machine (see parameter 

``use_multitape_input`` and notes below), then the tape is a 

list or tuple of tracks, each of which can be a list or an 

iterable with entries from the input alphabet. 

 

- ``initial_state`` or ``initial_states`` -- the initial 

state(s) in which the machine starts. Either specify a 

single one with ``initial_state`` or a list of them with 

``initial_states``. If both are given, ``initial_state`` 

will be appended to ``initial_states``. If neither is 

specified, the initial states of the finite state machine 

are taken. 

 

- ``list_of_outputs`` -- (default: ``None``) a boolean or 

``None``. If ``True``, then the outputs are given in list form 

(even if we have no or only one single output). If 

``False``, then the result is never a list (an exception is 

raised if the result cannot be returned). If 

``list_of_outputs=None`` the method determines automatically 

what to do (e.g. if a non-deterministic machine returns more 

than one path, then the output is returned in list form). 

 

- ``only_accepted`` -- (default: ``False``) a boolean. If set, 

then the first argument in the output is guaranteed to be 

``True`` (if the output is a list, then the first argument 

of each element will be ``True``). 

 

- ``full_output`` -- (default: ``True``) a boolean. If set, 

then the full output is given, otherwise only whether the 

sequence is accepted or not (the first entry below only). 

 

- ``always_include_output`` -- if set (not by default), always 

return a triple containing the (non-existing) output. This 

is in order to obtain output compatible with that of 

:meth:`FiniteStateMachine.process`. If this parameter is set, 

``full_output`` has no effect. 

 

- ``format_output`` -- a function that translates the written 

output (which is in form of a list) to something more 

readable. By default (``None``) identity is used here. 

 

- ``check_epsilon_transitions`` -- (default: ``True``) a 

boolean. If ``False``, then epsilon transitions are not 

taken into consideration during process. 

 

- ``write_final_word_out`` -- (default: ``True``) a boolean 

specifying whether the final output words should be written 

or not. 

 

- ``use_multitape_input`` -- (default: ``False``) a 

boolean. If ``True``, then the multi-tape mode of the 

process iterator is activated. See also the notes below for 

multi-tape machines. 

 

- ``process_all_prefixes_of_input`` -- (default: ``False``) a 

boolean. If ``True``, then each prefix of the input word is 

processed (instead of processing the whole input word at 

once). Consequently, there is an output generated for each 

of these prefixes. 

 

- ``process_iterator_class`` -- (default: ``None``) a class 

inherited from :class:`FSMProcessIterator`. If ``None``, 

then :class:`FSMProcessIterator` is taken. An instance of this 

class is created and is used during the processing. 

 

OUTPUT: 

 

The full output is a pair (or a list of pairs, 

cf. parameter ``list_of_outputs``), where 

 

- the first entry is ``True`` if the input string is accepted and 

 

- the second gives the state reached after processing the 

input tape (This is a state with label ``None`` if the input 

could not be processed, i.e., if at one point no 

transition to go on could be found.). 

 

If ``full_output`` is ``False``, then only the first entry 

is returned. 

 

If ``always_include_output`` is set, an additional third entry 

``[]`` is included. 

 

Note that in the case the automaton is not 

deterministic, all possible paths are taken into account. 

You can use :meth:`.determinisation` to get a deterministic 

automaton machine. 

 

This function uses an iterator which, in its simplest form, goes 

from one state to another in each step. To decide which way to 

go, it uses the input words of the outgoing transitions and 

compares them to the input tape. More precisely, in each step, 

the iterator takes an outgoing transition of the current state, 

whose input label equals the input letter of the tape. 

 

If the choice of the outgoing transition is not unique (i.e., 

we have a non-deterministic finite state machine), all 

possibilites are followed. This is done by splitting the 

process into several branches, one for each of the possible 

outgoing transitions. 

 

The process (iteration) stops if all branches are finished, 

i.e., for no branch, there is any transition whose input word 

coincides with the processed input tape. This can simply 

happen when the entire tape was read. 

 

Also see :meth:`~FiniteStateMachine.__call__` for a 

version of :meth:`.process` with shortened output. 

 

Internally this function creates and works with an instance of 

:class:`FSMProcessIterator`. This iterator can also be obtained 

with :meth:`~FiniteStateMachine.iter_process`. 

 

If working with multi-tape finite state machines, all input 

words of transitions are words of `k`-tuples of letters. 

Moreover, the input tape has to consist of `k` tracks, i.e., 

be a list or tuple of `k` iterators, one for each track. 

 

.. WARNING:: 

 

Working with multi-tape finite state machines is still 

experimental and can lead to wrong outputs. 

 

EXAMPLES: 

 

In the following examples, we construct an automaton which 

accepts non-adjacent forms (see also the example on 

:ref:`non-adjacent forms <finite_state_machine_recognizing_NAFs_example>` 

in the documentation of the module 

:doc:`finite_state_machine`) 

and then test it by feeding it with several binary digit 

expansions. 

 

:: 

 

sage: NAF = Automaton( 

....: {'_': [('_', 0), ('1', 1)], '1': [('_', 0)]}, 

....: initial_states=['_'], final_states=['_', '1']) 

sage: [NAF.process(w) for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1], 

....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] 

[(True, '_'), (True, '1'), (False, None), 

(True, '1'), (False, None), (False, None)] 

 

If we just want a condensed output, we use:: 

 

sage: [NAF.process(w, full_output=False) 

....: for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1], 

....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] 

[True, True, False, True, False, False] 

 

It is equivalent to:: 

 

sage: [NAF(w) for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1], 

....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] 

[True, True, False, True, False, False] 

 

The following example illustrates the difference between 

non-existing paths and reaching a non-final state:: 

 

sage: NAF.process([2]) 

(False, None) 

sage: NAF.add_transition(('_', 's', 2)) 

Transition from '_' to 's': 2|- 

sage: NAF.process([2]) 

(False, 's') 

 

A simple example of a (non-deterministic) multi-tape automaton is the 

following: It checks whether the two input tapes have the same number 

of ones:: 

 

sage: M = Automaton([('=', '=', (1, 1)), 

....: ('=', '=', (None, 0)), 

....: ('=', '=', (0, None)), 

....: ('=', '<', (None, 1)), 

....: ('<', '<', (None, 1)), 

....: ('<', '<', (None, 0)), 

....: ('=', '>', (1, None)), 

....: ('>', '>', (1, None)), 

....: ('>', '>', (0, None))], 

....: initial_states=['='], 

....: final_states=['=']) 

sage: M.process(([1, 0, 1], [1, 0]), use_multitape_input=True) 

(False, '>') 

sage: M.process(([0, 1, 0], [0, 1, 1]), use_multitape_input=True) 

(False, '<') 

sage: M.process(([1, 1, 0, 1], [0, 0, 1, 0, 1, 1]), 

....: use_multitape_input=True) 

(True, '=') 

 

Alternatively, we can use the following (non-deterministic) 

multi-tape automaton for the same check:: 

 

sage: N = Automaton([('=', '=', (0, 0)), 

....: ('=', '<', (None, 1)), 

....: ('<', '<', (0, None)), 

....: ('<', '=', (1, None)), 

....: ('=', '>', (1, None)), 

....: ('>', '>', (None, 0)), 

....: ('>', '=', (None, 1))], 

....: initial_states=['='], 

....: final_states=['=']) 

sage: N.process(([1, 0, 1], [1, 0]), use_multitape_input=True) 

(False, '>') 

sage: N.process(([0, 1, 0], [0, 1, 1]), use_multitape_input=True) 

(False, '<') 

sage: N.process(([1, 1, 0, 1], [0, 0, 1, 0, 1, 1]), 

....: use_multitape_input=True) 

(True, '=') 

 

.. SEEALSO:: 

 

:meth:`FiniteStateMachine.process`, 

:meth:`Transducer.process`, 

:meth:`~FiniteStateMachine.iter_process`, 

:meth:`~FiniteStateMachine.__call__`, 

:class:`FSMProcessIterator`. 

""" 

from copy import copy 

 

# set default values 

options = copy(self._process_default_options_) 

options.update(kwargs) 

 

condensed_output = (options['list_of_outputs'] is False and 

not options['full_output']) 

 

if condensed_output: 

options['list_of_outputs'] = True 

options['only_accepted'] = True 

 

result = super(Automaton, self).process(*args, **options) 

 

if condensed_output: 

return any(result) 

return result 

 

 

def _process_convert_output_(self, output_data, **kwargs): 

""" 

Helper function which converts the output of 

:meth:`FiniteStateMachine.process` to one suitable for 

automata. 

 

INPUT: 

 

- ``output_data`` -- a triple. 

 

- ``full_output`` -- a boolean. 

 

- ``always_include_output`` -- if set (not by default), always 

return a triple containing the (non-existing) output. This 

is for compatibility with transducers. 

 

OUTPUT: 

 

The converted output. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: A = Automaton() 

sage: A._process_convert_output_((True, FSMState('a'), [1, 0, 1]), 

....: full_output=False, 

....: always_include_output=False) 

True 

sage: A._process_convert_output_((True, FSMState('a'), [1, 0, 1]), 

....: full_output=True, 

....: always_include_output=False) 

(True, 'a') 

sage: A._process_convert_output_((True, FSMState('a'), [1, 0, 1]), 

....: full_output=False, 

....: always_include_output=True) 

(True, 'a', [1, 0, 1]) 

""" 

if kwargs['always_include_output']: 

return super(Automaton, self)._process_convert_output_( 

output_data, **kwargs) 

accept_input, current_state, output = output_data 

if kwargs['full_output']: 

return (accept_input, current_state) 

else: 

return accept_input 

 

 

def shannon_parry_markov_chain(self): 

""" 

Compute a time homogeneous Markov chain such that all words of a 

given length recognized by the original automaton occur as the 

output with the same weight; the transition probabilities 

correspond to the Parry measure. 

 

OUTPUT: 

 

A Markov chain. Its input labels are the transition probabilities, the 

output labels the labels of the original automaton. In order to obtain 

equal weight for all words of the same length, an "exit weight" is 

needed. It is stored in the attribute ``color`` of the states of the 

Markov chain. The weights of the words of the same length sum up to one 

up to an exponentially small error. 

 

The stationary distribution of this Markov chain is 

saved as the initial probabilities of the states. 

 

The transition probabilities correspond to the Parry measure 

(see [S1948]_ and [P1964]_). 

 

The automaton is assumed to be deterministic, irreducible and 

aperiodic. All states must be final. 

 

EXAMPLES:: 

 

sage: NAF = Automaton([(0, 0, 0), (0, 1, 1), (0, 1, -1), 

....: (1, 0, 0)], initial_states=[0], 

....: final_states=[0, 1]) 

sage: P_NAF = NAF.shannon_parry_markov_chain() 

sage: P_NAF.transitions() 

[Transition from 0 to 0: 1/2|0, 

Transition from 0 to 1: 1/4|1, 

Transition from 0 to 1: 1/4|-1, 

Transition from 1 to 0: 1|0] 

sage: for s in P_NAF.iter_states(): 

....: print(s.color) 

3/4 

3/2 

 

The stationary distribution is also computed and saved as the 

initial probabilities of the returned Markov chain:: 

 

sage: for s in P_NAF.states(): 

....: print("{} {}".format(s, s.initial_probability)) 

0 2/3 

1 1/3 

 

The automaton is assumed to be deterministic, irreducible and aperiodic:: 

 

sage: A = Automaton([(0, 0, 0), (0, 1, 1), (1, 1, 1), (1, 1, 0)], 

....: initial_states=[0]) 

sage: A.shannon_parry_markov_chain() 

Traceback (most recent call last): 

... 

NotImplementedError: Automaton must be strongly connected. 

sage: A = Automaton([(0, 0, 0), (0, 1, 0)], 

....: initial_states=[0]) 

sage: A.shannon_parry_markov_chain() 

Traceback (most recent call last): 

... 

NotImplementedError: Automaton must be deterministic. 

sage: A = Automaton([(0, 1, 0), (1, 0, 0)], 

....: initial_states=[0]) 

sage: A.shannon_parry_markov_chain() 

Traceback (most recent call last): 

... 

NotImplementedError: Automaton must be aperiodic. 

 

All states must be final:: 

 

sage: A = Automaton([(0, 1, 0), (0, 0, 1), (1, 0, 0)], 

....: initial_states=[0]) 

sage: A.shannon_parry_markov_chain() 

Traceback (most recent call last): 

... 

NotImplementedError: All states must be final. 

 

ALGORITHM: 

 

See [HKP2015a]_, Lemma 4.1. 

 

REFERENCES: 

 

.. [HKP2015a] Clemens Heuberger, Sara Kropf, and Helmut 

Prodinger, *Analysis of Carries in Signed Digit Expansions*, 

:arxiv:`1503.08816`. 

.. [P1964] William Parry, *Intrinsic Markov chains*, Transactions 

of the American Mathematical Society 112, 1964, pp. 55-66. 

:doi:`10.1090/S0002-9947-1964-0161372-1`. 

.. [S1948] Claude E. Shannon, *A mathematical theory of communication*, 

The Bell System Technical Journal 27, 1948, 379-423, 

:doi:`10.1002/j.1538-7305.1948.tb01338.x`. 

""" 

from sage.modules.free_module_element import vector 

if not self.is_deterministic(): 

raise NotImplementedError("Automaton must be deterministic.") 

if not self.digraph().is_aperiodic(): 

raise NotImplementedError("Automaton must be aperiodic.") 

if not self.digraph().is_strongly_connected(): 

raise NotImplementedError("Automaton must be strongly connected.") 

if not all(s.is_final for s in self.iter_states()): 

raise NotImplementedError("All states must be final.") 

from sage.rings.integer_ring import ZZ 

M = self.adjacency_matrix().change_ring(ZZ) 

states = {state: i for i, state in enumerate(self.iter_states())} 

w_all = sorted(M.eigenvectors_right(), 

key=lambda x: abs(x[0]), 

reverse=True) 

w = w_all[0][1][0] 

mu = w_all[0][0] 

u_all = sorted(M.eigenvectors_left(), 

key=lambda x: abs(x[0]), 

reverse=True) 

u = u_all[0][1][0] 

u = 1/(u*w) * u 

final = vector(int(s.is_final) for s in self.iter_states()) 

ff = u*final 

 

assert u*w == 1 

P = Transducer(initial_states=[s.label() for s in self.iter_initial_states()], 

final_states=[s.label() for s in self.iter_final_states()], 

on_duplicate_transition=duplicate_transition_add_input) 

for t in self.iter_transitions(): 

P.add_transition(t.from_state.label(), 

t.to_state.label(), 

w[states[t.to_state]]/w[states[t.from_state]]/mu, 

t.word_in) 

for s in self.iter_states(): 

P.state(s.label()).color = 1/(w[states[s]] * ff) 

P.state(s.label()).initial_probability = w[states[s]] * u[states[s]] 

return P 

 

 

def with_output(self, word_out_function=None): 

r""" 

Construct a transducer out of this automaton. 

 

INPUT: 

 

- ``word_out_function`` -- (default: ``None``) a function. It 

transforms a :class:`transition <FSMTransition>` to the 

output word for this transition. 

 

If this is ``None``, then the output word will be equal to 

the input word of each transition. 

 

OUTPUT: 

 

A transducer. 

 

EXAMPLES:: 

 

sage: A = Automaton([(0, 0, 'A'), (0, 1, 'B'), (1, 2, 'C')]) 

sage: T = A.with_output(); T 

Transducer with 3 states 

sage: T.transitions() 

[Transition from 0 to 0: 'A'|'A', 

Transition from 0 to 1: 'B'|'B', 

Transition from 1 to 2: 'C'|'C'] 

 

This result is in contrast to:: 

 

sage: Transducer(A).transitions() 

[Transition from 0 to 0: 'A'|-, 

Transition from 0 to 1: 'B'|-, 

Transition from 1 to 2: 'C'|-] 

 

where no output labels are created. 

 

Here is another example:: 

 

sage: T2 = A.with_output(lambda t: [c.lower() for c in t.word_in]) 

sage: T2.transitions() 

[Transition from 0 to 0: 'A'|'a', 

Transition from 0 to 1: 'B'|'b', 

Transition from 1 to 2: 'C'|'c'] 

 

We can obtain the same result by composing two transducers. As inner 

transducer of the composition, we use :meth:`.with_output` 

without the optional argument 

``word_out_function`` (which makes the output of each 

transition equal to its input); as outer transducer we use a 

:meth:`map-transducer 

<sage.combinat.finite_state_machine_generators.TransducerGenerators.map>` 

(for converting to lower case). 

This gives 

 

:: 

 

sage: L = transducers.map(lambda x: x.lower(), ['A', 'B', 'C']) 

sage: L.composition(A.with_output()).relabeled().transitions() 

[Transition from 0 to 0: 'A'|'a', 

Transition from 0 to 1: 'B'|'b', 

Transition from 1 to 2: 'C'|'c'] 

 

.. SEEALSO:: 

 

:meth:`.input_projection`, 

:meth:`.output_projection`, 

:class:`Transducer`, 

:meth:`transducers.map() 

<sage.combinat.finite_state_machine_generators.TransducerGenerators.map>`. 

 

TESTS:: 

 

sage: A.with_output().input_projection() == A 

True 

sage: NAF = Automaton( 

....: {'A': [('A', 0), ('B', 1), ('B', -1)], 'B': [('A', 0)]}) 

sage: NAF.with_output().input_projection() == NAF 

True 

sage: B = Automaton( 

....: {0: [(0, 'a'), (1, ['b', 'c']), (2, ['d', 'e'])], 

....: 1: [(0, ['f', 'g']), (1, 'h'), (2, None)], 

....: 2: [(0, None), (1, None), (2, ['i', 'j'])]}, 

....: initial_states=[1, 2], final_states=[0]) 

sage: B.with_output(lambda t: [c.upper() for c in t.word_in]).input_projection() == B 

True 

""" 

from copy import copy 

 

if word_out_function is None: 

word_out_function = lambda transition: copy(transition.word_in) 

new = Transducer() 

memo = dict() 

new._copy_from_other_(self, memo=memo) 

for t in new.iter_transitions(): 

t.word_out = word_out_function(t) 

return new 

 

 

def language(self, max_length=None, **kwargs): 

r""" 

Return all words accepted by this automaton. 

 

INPUT: 

 

- ``max_length`` -- an integer or ``None`` (default). Only 

inputs of length at most ``max_length`` will be 

considered. If ``None``, then this iterates over all 

possible words without length restrictions. 

 

- ``kwargs`` -- will be passed on to the :class:`process 

iterator <FSMProcessIterator>`. See :meth:`process` for a 

description. 

 

OUTPUT: 

 

An iterator. 

 

EXAMPLES:: 

 

sage: NAF = Automaton( 

....: {'A': [('A', 0), ('B', 1), ('B', -1)], 

....: 'B': [('A', 0)]}, 

....: initial_states=['A'], final_states=['A', 'B']) 

sage: list(NAF.language(3)) 

[[], 

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

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

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

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

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

 

.. SEEALSO:: 

 

:meth:`FiniteStateMachine.language`, 

:meth:`process`. 

 

TESTS:: 

 

sage: def R(ell): 

....: return (2^(ell+2)-(-1)^ell)/3 

sage: import itertools 

sage: all(len(list(NAFs)) == R(ell) for ell, NAFs in 

....: itertools.groupby(NAF.language(5), key=len)) 

True 

""" 

T = self.with_output() 

return T.language(max_length) 

 

 

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

 

 

def is_Transducer(FSM): 

""" 

Tests whether or not ``FSM`` inherits from :class:`Transducer`. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import is_FiniteStateMachine, is_Transducer 

sage: is_Transducer(FiniteStateMachine()) 

False 

sage: is_Transducer(Transducer()) 

True 

sage: is_FiniteStateMachine(Transducer()) 

True 

""" 

return isinstance(FSM, Transducer) 

 

 

class Transducer(FiniteStateMachine): 

""" 

This creates a transducer, which is a finite state machine, whose 

transitions have input and output labels. 

 

An transducer has additional features like creating a simplified 

transducer. 

 

See class :class:`FiniteStateMachine` for more information. 

 

EXAMPLES: 

 

We can create a transducer performing the addition of 1 (for 

numbers given in binary and read from right to left) in the 

following way:: 

 

sage: T = Transducer([('C', 'C', 1, 0), ('C', 'N', 0, 1), 

....: ('N', 'N', 0, 0), ('N', 'N', 1, 1)], 

....: initial_states=['C'], final_states=['N']) 

sage: T 

Transducer with 2 states 

sage: T([0]) 

[1] 

sage: T([1,1,0]) 

[0, 0, 1] 

sage: ZZ(T(15.digits(base=2)+[0]), base=2) 

16 

 

Note that we have padded the binary input sequence by a `0` so 

that the transducer can reach its final state. 

 

TESTS:: 

 

sage: Transducer() 

Empty transducer 

""" 

 

def _repr_(self): 

""" 

Represents the transducer as "Transducer with n states" where 

n is the number of states. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A string. 

 

EXAMPLES:: 

 

sage: Transducer()._repr_() 

'Empty transducer' 

 

TESTS:: 

 

sage: T = Transducer() 

sage: T 

Empty transducer 

sage: T.add_state(42) 

42 

sage: T 

Transducer with 1 state 

sage: T.add_state(43) 

43 

sage: T 

Transducer with 2 states 

 

""" 

if len(self._states_)==0: 

return "Empty transducer" 

if len(self._states_)==1: 

return "Transducer with 1 state" 

else: 

return "Transducer with %s states" % len(self._states_) 

 

 

def _latex_transition_label_(self, transition, 

format_function=sage.misc.latex.latex): 

r""" 

Returns the proper transition label. 

 

INPUT: 

 

- ``transition`` - a transition 

 

- ``format_function`` - a function formatting the labels 

 

OUTPUT: 

 

A string. 

 

EXAMPLES:: 

 

sage: F = Transducer([('A', 'B', 1, 2)]) 

sage: print(latex(F)) # indirect doctest 

\begin{tikzpicture}[auto, initial text=, >=latex] 

\node[state] (v0) at (3.000000, 0.000000) {$\text{\texttt{A}}$}; 

\node[state] (v1) at (-3.000000, 0.000000) {$\text{\texttt{B}}$}; 

\path[->] (v0) edge node[rotate=360.00, anchor=south] {$1\mid 2$} (v1); 

\end{tikzpicture} 

 

TESTS:: 

 

sage: F = Transducer([('A', 'B', 0, 1)]) 

sage: t = F.transitions()[0] 

sage: F._latex_transition_label_(t) 

\left[0\right] \mid \left[1\right] 

""" 

return (format_function(transition.word_in) + "\\mid " 

+ format_function(transition.word_out)) 

 

 

def intersection(self, other, only_accessible_components=True): 

""" 

Returns a new transducer which accepts an input if it is accepted by 

both given finite state machines producing the same output. 

 

INPUT: 

 

- ``other`` -- a transducer 

 

- ``only_accessible_components`` -- If ``True`` (default), then 

the result is piped through :meth:`.accessible_components`. If no 

``new_input_alphabet`` is given, it is determined by 

:meth:`.determine_alphabets`. 

 

OUTPUT: 

 

A new transducer which computes the intersection 

(see below) of the languages of ``self`` and ``other``. 

 

The set of states of the transducer is the Cartesian product of the 

set of states of both given transducer. There is a transition `((A, 

B), (C, D), a, b)` in the new transducer if there are 

transitions `(A, C, a, b)` and `(B, D, a, b)` in the old transducers. 

 

EXAMPLES:: 

 

sage: transducer1 = Transducer([('1', '2', 1, 0), 

....: ('2', '2', 1, 0), 

....: ('2', '2', 0, 1)], 

....: initial_states=['1'], 

....: final_states=['2']) 

sage: transducer2 = Transducer([('A', 'A', 1, 0), 

....: ('A', 'B', 0, 0), 

....: ('B', 'B', 0, 1), 

....: ('B', 'A', 1, 1)], 

....: initial_states=['A'], 

....: final_states=['B']) 

sage: res = transducer1.intersection(transducer2) 

sage: res.transitions() 

[Transition from ('1', 'A') to ('2', 'A'): 1|0, 

Transition from ('2', 'A') to ('2', 'A'): 1|0] 

 

In general, transducers are not closed under intersection. But 

for transducer which do not have epsilon-transitions, the 

intersection is well defined (cf. [BaWo2012]_). However, in 

the next example the intersection of the two transducers is 

not well defined. The intersection of the languages consists 

of `(a^n, b^n c^n)`. This set is not recognizable by a 

*finite* transducer. 

 

:: 

 

sage: t1 = Transducer([(0, 0, 'a', 'b'), 

....: (0, 1, None, 'c'), 

....: (1, 1, None, 'c')], 

....: initial_states=[0], 

....: final_states=[0, 1]) 

sage: t2 = Transducer([('A', 'A', None, 'b'), 

....: ('A', 'B', 'a', 'c'), 

....: ('B', 'B', 'a', 'c')], 

....: initial_states=['A'], 

....: final_states=['A', 'B']) 

sage: t2.intersection(t1) 

Traceback (most recent call last): 

... 

ValueError: An epsilon-transition (with empty input or output) 

was found. 

 

TESTS:: 

 

sage: transducer1 = Transducer([('1', '2', 1, 0)], 

....: initial_states=['1'], 

....: final_states=['2']) 

sage: transducer2 = Transducer([('A', 'B', 1, 0)], 

....: initial_states=['A'], 

....: final_states=['B']) 

sage: res = transducer1.intersection(transducer2) 

sage: res.final_states() 

[('2', 'B')] 

sage: transducer1.state('2').final_word_out = 1 

sage: transducer2.state('B').final_word_out = 2 

sage: res = transducer1.intersection(transducer2) 

sage: res.final_states() 

[] 

 

REFERENCES: 

 

.. [BaWo2012] Javier Baliosian and Dina Wonsever, *Finite State 

Transducers*, chapter in *Handbook of Finite State Based Models and 

Applications*, edited by Jiacun Wang, Chapman and Hall/CRC, 2012. 

""" 

if not is_Transducer(other): 

raise TypeError( 

"Only a transducer can be intersected with a transducer.") 

 

def function(transition1, transition2): 

if not transition1.word_in or not transition2.word_in \ 

or not transition1.word_out or not transition2.word_out: 

raise ValueError("An epsilon-transition " 

"(with empty input or output) was found.") 

if transition1.word_in == transition2.word_in \ 

and transition1.word_out == transition2.word_out: 

return (transition1.word_in, transition1.word_out) 

else: 

raise LookupError 

 

new = self.product_FiniteStateMachine( 

other, 

function, 

only_accessible_components=only_accessible_components, 

final_function=lambda s1, s2: s1.final_word_out) 

 

for state in new.iter_final_states(): 

state0 = self.state(state.label()[0]) 

state1 = other.state(state.label()[1]) 

if state0.final_word_out != state1.final_word_out: 

state.final_word_out = None 

state.is_final = False 

 

return new 

 

 

def cartesian_product(self, other, only_accessible_components=True): 

""" 

Return a new transducer which can simultaneously process an 

input with the machines ``self`` and ``other`` where the 

output labels are `d`-tuples of the original output labels. 

 

INPUT: 

 

- ``other`` - a finite state machine (if `d=2`) or a list (or 

other iterable) of `d-1` finite state machines 

 

- ``only_accessible_components`` -- If ``True`` (default), then 

the result is piped through :meth:`.accessible_components`. If no 

``new_input_alphabet`` is given, it is determined by 

:meth:`.determine_alphabets`. 

 

OUTPUT: 

 

A transducer which can simultaneously process an input with ``self`` 

and the machine(s) in ``other``. 

 

The set of states of the new transducer is the Cartesian product of 

the set of states of ``self`` and ``other``. 

 

Let `(A_j, B_j, a_j, b_j)` for `j\in\{1, \ldots, d\}` be 

transitions in the machines ``self`` and in ``other``. Then 

there is a transition `((A_1, \ldots, A_d), (B_1, \ldots, 

B_d), a, (b_1, \ldots, b_d))` in the new transducer if `a_1 = 

\cdots = a_d =: a`. 

 

EXAMPLES:: 

 

sage: transducer1 = Transducer([('A', 'A', 0, 0), 

....: ('A', 'A', 1, 1)], 

....: initial_states=['A'], 

....: final_states=['A'], 

....: determine_alphabets=True) 

sage: transducer2 = Transducer([(0, 1, 0, ['b', 'c']), 

....: (0, 0, 1, 'b'), 

....: (1, 1, 0, 'a')], 

....: initial_states=[0], 

....: final_states=[1], 

....: determine_alphabets=True) 

sage: result = transducer1.cartesian_product(transducer2) 

sage: result 

Transducer with 2 states 

sage: result.transitions() 

[Transition from ('A', 0) to ('A', 1): 0|(0, 'b'),(None, 'c'), 

Transition from ('A', 0) to ('A', 0): 1|(1, 'b'), 

Transition from ('A', 1) to ('A', 1): 0|(0, 'a')] 

sage: result([1, 0, 0]) 

[(1, 'b'), (0, 'b'), (None, 'c'), (0, 'a')] 

sage: (transducer1([1, 0, 0]), transducer2([1, 0, 0])) 

([1, 0, 0], ['b', 'b', 'c', 'a']) 

 

Also final output words are correctly processed:: 

 

sage: transducer1.state('A').final_word_out = 2 

sage: result = transducer1.cartesian_product(transducer2) 

sage: result.final_states()[0].final_word_out 

[(2, None)] 

 

The following transducer counts the number of 11 blocks minus 

the number of 10 blocks over the alphabet ``[0, 1]``. 

 

:: 

 

sage: count_11 = transducers.CountSubblockOccurrences( 

....: [1, 1], 

....: input_alphabet=[0, 1]) 

sage: count_10 = transducers.CountSubblockOccurrences( 

....: [1, 0], 

....: input_alphabet=[0, 1]) 

sage: count_11x10 = count_11.cartesian_product(count_10) 

sage: difference = transducers.sub([0, 1])(count_11x10) 

sage: T = difference.simplification().relabeled() 

sage: T.initial_states() 

[1] 

sage: sorted(T.transitions()) 

[Transition from 0 to 1: 0|-1, 

Transition from 0 to 0: 1|1, 

Transition from 1 to 1: 0|0, 

Transition from 1 to 0: 1|0] 

sage: input = [0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0] 

sage: output = [0, 0, 1, -1, 0, -1, 0, 0, 0, 1, 1, -1] 

sage: T(input) == output 

True 

 

If ``other`` is an automaton, then :meth:`.cartesian_product` returns 

``self`` where the input is restricted to the input accepted by 

``other``. 

 

For example, if the transducer transforms the standard 

binary expansion into the non-adjacent form and the automaton 

recognizes the binary expansion without adjacent ones, then the 

Cartesian product of these two is a transducer which does not change 

the input (except for changing ``a`` to ``(a, None)`` and ignoring a 

leading `0`). 

 

:: 

 

sage: NAF = Transducer([(0, 1, 0, None), 

....: (0, 2, 1, None), 

....: (1, 1, 0, 0), 

....: (1, 2, 1, 0), 

....: (2, 1, 0, 1), 

....: (2, 3, 1, -1), 

....: (3, 2, 0, 0), 

....: (3, 3, 1, 0)], 

....: initial_states=[0], 

....: final_states=[1], 

....: determine_alphabets=True) 

sage: aut11 = Automaton([(0, 0, 0), (0, 1, 1), (1, 0, 0)], 

....: initial_states=[0], 

....: final_states=[0, 1], 

....: determine_alphabets=True) 

sage: res = NAF.cartesian_product(aut11) 

sage: res([1, 0, 0, 1, 0, 1, 0]) 

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

 

This is obvious because if the standard binary expansion does not have 

adjacent ones, then it is the same as the non-adjacent form. 

 

Be aware that :meth:`.cartesian_product` is not commutative. 

 

:: 

 

sage: aut11.cartesian_product(NAF) 

Traceback (most recent call last): 

... 

TypeError: Only an automaton can be intersected with an automaton. 

 

The Cartesian product of more than two finite state machines can also 

be computed:: 

 

sage: T0 = transducers.CountSubblockOccurrences([0, 0], [0, 1, 2]) 

sage: T1 = transducers.CountSubblockOccurrences([1, 1], [0, 1, 2]) 

sage: T2 = transducers.CountSubblockOccurrences([2, 2], [0, 1, 2]) 

sage: T = T0.cartesian_product([T1, T2]) 

sage: T.transitions() 

[Transition from ((), (), ()) to ((0,), (), ()): 0|(0, 0, 0), 

Transition from ((), (), ()) to ((), (1,), ()): 1|(0, 0, 0), 

Transition from ((), (), ()) to ((), (), (2,)): 2|(0, 0, 0), 

Transition from ((0,), (), ()) to ((0,), (), ()): 0|(1, 0, 0), 

Transition from ((0,), (), ()) to ((), (1,), ()): 1|(0, 0, 0), 

Transition from ((0,), (), ()) to ((), (), (2,)): 2|(0, 0, 0), 

Transition from ((), (1,), ()) to ((0,), (), ()): 0|(0, 0, 0), 

Transition from ((), (1,), ()) to ((), (1,), ()): 1|(0, 1, 0), 

Transition from ((), (1,), ()) to ((), (), (2,)): 2|(0, 0, 0), 

Transition from ((), (), (2,)) to ((0,), (), ()): 0|(0, 0, 0), 

Transition from ((), (), (2,)) to ((), (1,), ()): 1|(0, 0, 0), 

Transition from ((), (), (2,)) to ((), (), (2,)): 2|(0, 0, 1)] 

sage: T([0, 0, 1, 1, 2, 2, 0, 1, 2, 2]) 

[(0, 0, 0), 

(1, 0, 0), 

(0, 0, 0), 

(0, 1, 0), 

(0, 0, 0), 

(0, 0, 1), 

(0, 0, 0), 

(0, 0, 0), 

(0, 0, 0), 

(0, 0, 1)] 

""" 

def function(*transitions): 

if equal(t.word_in for t in transitions): 

return (transitions[0].word_in, 

list(zip_longest( 

*(t.word_out for t in transitions) 

))) 

else: 

raise LookupError 

 

def final_function(*states): 

return list(zip_longest(*(s.final_word_out 

for s in states))) 

 

return self.product_FiniteStateMachine( 

other, 

function, 

final_function=final_function, 

only_accessible_components=only_accessible_components) 

 

 

def simplification(self): 

""" 

Returns a simplified transducer. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A new transducer. 

 

This function simplifies a transducer by Moore's algorithm, 

first moving common output labels of transitions leaving a 

state to output labels of transitions entering the state 

(cf. :meth:`.prepone_output`). 

 

The resulting transducer implements the same function as the 

original transducer. 

 

EXAMPLES:: 

 

sage: fsm = Transducer([("A", "B", 0, 1), ("A", "B", 1, 0), 

....: ("B", "C", 0, 0), ("B", "C", 1, 1), 

....: ("C", "D", 0, 1), ("C", "D", 1, 0), 

....: ("D", "A", 0, 0), ("D", "A", 1, 1)]) 

sage: fsms = fsm.simplification() 

sage: fsms 

Transducer with 2 states 

sage: fsms.transitions() 

[Transition from ('A', 'C') 

to ('B', 'D'): 0|1, 

Transition from ('A', 'C') 

to ('B', 'D'): 1|0, 

Transition from ('B', 'D') 

to ('A', 'C'): 0|0, 

Transition from ('B', 'D') 

to ('A', 'C'): 1|1] 

sage: fsms.relabeled().transitions() 

[Transition from 0 to 1: 0|1, 

Transition from 0 to 1: 1|0, 

Transition from 1 to 0: 0|0, 

Transition from 1 to 0: 1|1] 

 

:: 

 

sage: fsm = Transducer([("A", "A", 0, 0), 

....: ("A", "B", 1, 1), 

....: ("A", "C", 1, -1), 

....: ("B", "A", 2, 0), 

....: ("C", "A", 2, 0)]) 

sage: fsm_simplified = fsm.simplification() 

sage: fsm_simplified 

Transducer with 2 states 

sage: fsm_simplified.transitions() 

[Transition from ('A',) to ('A',): 0|0, 

Transition from ('A',) to ('B', 'C'): 1|1,0, 

Transition from ('A',) to ('B', 'C'): 1|-1,0, 

Transition from ('B', 'C') to ('A',): 2|-] 

 

:: 

 

sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input 

sage: T = Transducer([('A', 'A', 1/2, 0), 

....: ('A', 'B', 1/4, 1), 

....: ('A', 'C', 1/4, 1), 

....: ('B', 'A', 1, 0), 

....: ('C', 'A', 1, 0)], 

....: initial_states=[0], 

....: final_states=['A', 'B', 'C'], 

....: on_duplicate_transition=duplicate_transition_add_input) 

sage: sorted(T.simplification().transitions()) 

[Transition from ('A',) to ('A',): 1/2|0, 

Transition from ('A',) to ('B', 'C'): 1/2|1, 

Transition from ('B', 'C') to ('A',): 1|0] 

 

Illustrating the use of colors in order to avoid identification of states:: 

 

sage: T = Transducer( [[0,0,0,0], [0,1,1,1], 

....: [1,0,0,0], [1,1,1,1]], 

....: initial_states=[0], 

....: final_states=[0,1]) 

sage: sorted(T.simplification().transitions()) 

[Transition from (0, 1) to (0, 1): 0|0, 

Transition from (0, 1) to (0, 1): 1|1] 

sage: T.state(0).color = 0 

sage: T.state(0).color = 1 

sage: sorted(T.simplification().transitions()) 

[Transition from (0,) to (0,): 0|0, 

Transition from (0,) to (1,): 1|1, 

Transition from (1,) to (0,): 0|0, 

Transition from (1,) to (1,): 1|1] 

""" 

from copy import deepcopy 

 

fsm = deepcopy(self) 

fsm.prepone_output() 

return fsm.quotient(fsm.equivalence_classes()) 

 

 

def process(self, *args, **kwargs): 

""" 

Return whether the transducer accepts the input, the state 

where the computation stops and which output is generated. 

 

INPUT: 

 

- ``input_tape`` -- the input tape can be a list or an 

iterable with entries from the input alphabet. If we are 

working with a multi-tape machine (see parameter 

``use_multitape_input`` and notes below), then the tape is a 

list or tuple of tracks, each of which can be a list or an 

iterable with entries from the input alphabet. 

 

- ``initial_state`` or ``initial_states`` -- the initial 

state(s) in which the machine starts. Either specify a 

single one with ``initial_state`` or a list of them with 

``initial_states``. If both are given, ``initial_state`` 

will be appended to ``initial_states``. If neither is 

specified, the initial states of the finite state machine 

are taken. 

 

- ``list_of_outputs`` -- (default: ``None``) a boolean or 

``None``. If ``True``, then the outputs are given in list form 

(even if we have no or only one single output). If 

``False``, then the result is never a list (an exception is 

raised if the result cannot be returned). If 

``list_of_outputs=None`` the method determines automatically 

what to do (e.g. if a non-deterministic machine returns more 

than one path, then the output is returned in list form). 

 

- ``only_accepted`` -- (default: ``False``) a boolean. If set, 

then the first argument in the output is guaranteed to be 

``True`` (if the output is a list, then the first argument 

of each element will be ``True``). 

 

- ``full_output`` -- (default: ``True``) a boolean. If set, 

then the full output is given, otherwise only the generated 

output (the third entry below only). If the input is not 

accepted, a ``ValueError`` is raised. 

 

- ``always_include_output`` -- if set (not by default), always 

include the output. This is inconsequential for a 

:class:`Transducer`, but can be used in other classes 

derived from :class:`FiniteStateMachine` where the output is 

suppressed by default, cf. :meth:`Automaton.process`. 

 

- ``format_output`` -- a function that translates the written 

output (which is in form of a list) to something more 

readable. By default (``None``) identity is used here. 

 

- ``check_epsilon_transitions`` -- (default: ``True``) a 

boolean. If ``False``, then epsilon transitions are not 

taken into consideration during process. 

 

- ``write_final_word_out`` -- (default: ``True``) a boolean 

specifying whether the final output words should be written 

or not. 

 

- ``use_multitape_input`` -- (default: ``False``) a 

boolean. If ``True``, then the multi-tape mode of the 

process iterator is activated. See also the notes below for 

multi-tape machines. 

 

- ``process_all_prefixes_of_input`` -- (default: ``False``) a 

boolean. If ``True``, then each prefix of the input word is 

processed (instead of processing the whole input word at 

once). Consequently, there is an output generated for each 

of these prefixes. 

 

- ``process_iterator_class`` -- (default: ``None``) a class 

inherited from :class:`FSMProcessIterator`. If ``None``, 

then :class:`FSMProcessIterator` is taken. An instance of this 

class is created and is used during the processing. 

 

- ``automatic_output_type`` -- (default: ``False``) a boolean 

If set and the input has a parent, then the 

output will have the same parent. If the input does not have 

a parent, then the output will be of the same type as the 

input. 

 

OUTPUT: 

 

The full output is a triple (or a list of triples, 

cf. parameter ``list_of_outputs``), where 

 

- the first entry is ``True`` if the input string is accepted, 

 

- the second gives the reached state after processing the 

input tape (This is a state with label ``None`` if the input 

could not be processed, i.e., if at one point no 

transition to go on could be found.), and 

 

- the third gives a list of the output labels written during 

processing. 

 

If ``full_output`` is ``False``, then only the third entry 

is returned. 

 

Note that in the case the transducer is not 

deterministic, all possible paths are taken into account. 

 

This function uses an iterator which, in its simplest form, goes 

from one state to another in each step. To decide which way to 

go, it uses the input words of the outgoing transitions and 

compares them to the input tape. More precisely, in each step, 

the iterator takes an outgoing transition of the current state, 

whose input label equals the input letter of the tape. The 

output label of the transition, if present, is written on the 

output tape. 

 

If the choice of the outgoing transition is not unique (i.e., 

we have a non-deterministic finite state machine), all 

possibilites are followed. This is done by splitting the 

process into several branches, one for each of the possible 

outgoing transitions. 

 

The process (iteration) stops if all branches are finished, 

i.e., for no branch, there is any transition whose input word 

coincides with the processed input tape. This can simply 

happen when the entire tape was read. 

 

Also see :meth:`~FiniteStateMachine.__call__` for a version of 

:meth:`.process` with shortened output. 

 

Internally this function creates and works with an instance of 

:class:`FSMProcessIterator`. This iterator can also be obtained 

with :meth:`~FiniteStateMachine.iter_process`. 

 

If working with multi-tape finite state machines, all input 

words of transitions are words of `k`-tuples of letters. 

Moreover, the input tape has to consist of `k` tracks, i.e., 

be a list or tuple of `k` iterators, one for each track. 

 

.. WARNING:: 

 

Working with multi-tape finite state machines is still 

experimental and can lead to wrong outputs. 

 

EXAMPLES:: 

 

sage: binary_inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, 

....: initial_states=['A'], final_states=['A']) 

sage: binary_inverter.process([0, 1, 0, 0, 1, 1]) 

(True, 'A', [1, 0, 1, 1, 0, 0]) 

 

If we are only interested in the output, we can also use:: 

 

sage: binary_inverter([0, 1, 0, 0, 1, 1]) 

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

 

This can also be used with words as input:: 

 

sage: W = Words([0, 1]); W 

Finite and infinite words over {0, 1} 

sage: w = W([0, 1, 0, 0, 1, 1]); w 

word: 010011 

sage: binary_inverter(w) 

word: 101100 

 

In this case it is automatically determined that the output is 

a word. The call above is equivalent to:: 

 

sage: binary_inverter.process(w, 

....: full_output=False, 

....: list_of_outputs=False, 

....: automatic_output_type=True) 

word: 101100 

 

The following transducer transforms `0^n 1` to `1^n 2`:: 

 

sage: T = Transducer([(0, 0, 0, 1), (0, 1, 1, 2)]) 

sage: T.state(0).is_initial = True 

sage: T.state(1).is_final = True 

 

We can see the different possibilites of the output by:: 

 

sage: [T.process(w) for w in [[1], [0, 1], [0, 0, 1], [0, 1, 1], 

....: [0], [0, 0], [2, 0], [0, 1, 2]]] 

[(True, 1, [2]), (True, 1, [1, 2]), 

(True, 1, [1, 1, 2]), (False, None, None), 

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

(False, None, None), (False, None, None)] 

 

If we just want a condensed output, we use:: 

 

sage: [T.process(w, full_output=False) 

....: for w in [[1], [0, 1], [0, 0, 1]]] 

[[2], [1, 2], [1, 1, 2]] 

sage: T.process([0], full_output=False) 

Traceback (most recent call last): 

... 

ValueError: Invalid input sequence. 

sage: T.process([0, 1, 2], full_output=False) 

Traceback (most recent call last): 

... 

ValueError: Invalid input sequence. 

 

It is equivalent to:: 

 

sage: [T(w) for w in [[1], [0, 1], [0, 0, 1]]] 

[[2], [1, 2], [1, 1, 2]] 

sage: T([0]) 

Traceback (most recent call last): 

... 

ValueError: Invalid input sequence. 

sage: T([0, 1, 2]) 

Traceback (most recent call last): 

... 

ValueError: Invalid input sequence. 

 

A cycle with empty input and empty output is correctly processed:: 

 

sage: T = Transducer([(0, 1, None, None), (1, 0, None, None)], 

....: initial_states=[0], final_states=[1]) 

sage: T.process([]) 

[(False, 0, []), (True, 1, [])] 

sage: _ = T.add_transition(-1, 0, 0, 'r') 

sage: T.state(-1).is_initial = True 

sage: T.state(0).is_initial = False 

sage: T.process([0]) 

[(False, 0, ['r']), (True, 1, ['r'])] 

 

If there is a cycle with empty input but non-empty output, the 

possible outputs would be an infinite set:: 

 

sage: T = Transducer([(0, 1, None, 'z'), (1, 0, None, None)], 

....: initial_states=[0], final_states=[1]) 

sage: T.process([]) 

Traceback (most recent call last): 

... 

RuntimeError: State 0 is in an epsilon cycle (no input), 

but output is written. 

 

But if this cycle with empty input and non-empty output is not 

reached, the correct output is produced:: 

 

sage: _ = T.add_transition(-1, 0, 0, 'r') 

sage: T.state(-1).is_initial = True 

sage: T.state(0).is_initial = False 

sage: T.process([]) 

(False, -1, []) 

sage: T.process([0]) 

Traceback (most recent call last): 

... 

RuntimeError: State 0 is in an epsilon cycle (no input), 

but output is written. 

 

If we set ``check_epsilon_transitions=False``, then no 

transitions with empty input are considered 

anymore. Thus cycles with empty input are no problem anymore:: 

 

sage: T.process([0], check_epsilon_transitions=False) 

(False, 0, ['r']) 

 

A simple example of a multi-tape transducer is the 

following: It writes the length of the first tape many letters ``a`` 

and then the length of the second tape many letters ``b``:: 

 

sage: M = Transducer([(0, 0, (1, None), 'a'), 

....: (0, 1, [], []), 

....: (1, 1, (None, 1), 'b')], 

....: initial_states=[0], 

....: final_states=[1]) 

sage: M.process(([1, 1], [1]), use_multitape_input=True) 

(True, 1, ['a', 'a', 'b']) 

 

.. SEEALSO:: 

 

:meth:`FiniteStateMachine.process`, 

:meth:`Automaton.process`, 

:meth:`~FiniteStateMachine.iter_process`, 

:meth:`~FiniteStateMachine.__call__`, 

:class:`FSMProcessIterator`. 

 

TESTS:: 

 

sage: T = Transducer([(0, 1, 1, 'a'), (1, 0, 1, 'b')], 

....: initial_states=[0, 1], final_states=[1]) 

sage: T.process([1, 1]) 

[(False, 0, ['a', 'b']), (True, 1, ['b', 'a'])] 

sage: T.process([1, 1], T.state(0)) 

(False, 0, ['a', 'b']) 

sage: T.state(1).final_word_out = 'c' 

sage: T.process([1, 1], T.state(1)) 

(True, 1, ['b', 'a', 'c']) 

sage: T.process([1, 1], T.state(1), write_final_word_out=False) 

(True, 1, ['b', 'a']) 

 

The parameter ``input_tape`` is required:: 

 

sage: T.process() 

Traceback (most recent call last): 

... 

TypeError: No input tape given. 

""" 

from copy import copy 

 

# set default values 

options = copy(self._process_default_options_) 

options.update(kwargs) 

 

condensed_output = (options['list_of_outputs'] is False and 

not options['full_output']) 

 

if condensed_output: 

options['list_of_outputs'] = True 

options['only_accepted'] = True 

 

result = super(Transducer, self).process(*args, **options) 

 

if (condensed_output and not result or 

not options['full_output'] and result is None): 

raise ValueError("Invalid input sequence.") 

if condensed_output and len(result) >= 2: 

raise ValueError("Found more than one accepting path.") 

 

if condensed_output: 

return result[0] 

return result 

 

def _process_convert_output_(self, output_data, **kwargs): 

""" 

Helper function which converts the output of 

:meth:`FiniteStateMachine.process` to one suitable for 

transducers. 

 

INPUT: 

 

- ``output_data`` -- a triple. 

 

- ``full_output`` -- a boolean. 

 

OUTPUT: 

 

The converted output. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import FSMState 

sage: T = Transducer() 

sage: T._process_convert_output_((True, FSMState('a'), [1, 0, 1]), 

....: full_output=False) 

[1, 0, 1] 

sage: T._process_convert_output_((True, FSMState('a'), [1, 0, 1]), 

....: full_output=True) 

(True, 'a', [1, 0, 1]) 

""" 

accept_input, current_state, output = output_data 

if kwargs['full_output']: 

if current_state.label() is None: 

return (accept_input, current_state, None) 

else: 

return (accept_input, current_state, output) 

else: 

if not accept_input: 

return None 

return output 

 

 

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

 

 

class _FSMTapeCache_(sage.structure.sage_object.SageObject): 

""" 

This is a class for caching an input tape. It is used in 

:class:`FSMProcessIterator`. 

 

INPUT: 

 

- ``tape_cache_manager`` -- a list of the existing instances of 

:class:`_FSMTapeCache_`. ``self`` will be appended to this list. 

 

- ``tape`` -- a tuple or list of the input tracks (iterables). 

 

- ``tape_ended`` -- a list of booleans (one for each track of the 

tape), which indicate whether the track iterator has already raised 

a ``StopIteration`` exception. 

 

- ``position`` -- a tuple of pairs `(p, t)` marking the current 

positions of each of the input tracks. There `p` is the number 

of letter read from track `t`. The pairs of ``position`` are 

sorted first by `p` (smallest first) and then by `t`, i.e., 

lexicographically. 

 

- ``is_multitape`` -- If ``True`` each entry of the 

input-word-tuple of a transition is interpreted as word for the 

corresponding input track. If ``False`` input-words are 

interpreted as an iterable of letters. 

 

OUTPUT: 

 

A tape-cache. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import _FSMTapeCache_ 

sage: TC1 = _FSMTapeCache_([], (xsrange(37, 42),), 

....: [False], ((0, 0),), False) 

sage: TC2 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0), (0, 1)), True) 

sage: TC1 

tape at 0 

sage: TC1.tape_cache_manager 

[tape at 0] 

sage: TC2 

multi-tape at (0, 0) 

sage: TC2.tape_cache_manager 

[multi-tape at (0, 0)] 

""" 

def __init__(self, tape_cache_manager, tape, tape_ended, 

position, is_multitape): 

""" 

See :class:`_FSMTapeCache_` for more details. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import _FSMTapeCache_ 

sage: TC1 = _FSMTapeCache_([], (xsrange(37, 42),), 

....: [False], ((0, 0),), False) 

sage: TC2 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0), (0, 1)), True) 

sage: TC1m = _FSMTapeCache_([], (xsrange(37, 42),), 

....: [False], ((0, 0),), True) 

sage: TC3 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False], ((0, 0),), False) 

Traceback (most recent call last): 

... 

TypeError: The lengths of the inputs do not match 

sage: TC4 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0),), False) 

Traceback (most recent call last): 

... 

TypeError: The lengths of the inputs do not match 

sage: TC5 = _FSMTapeCache_([], (xsrange(37, 42),), 

....: [False, False], ((0, 0), (0, 1)), True) 

Traceback (most recent call last): 

... 

TypeError: The lengths of the inputs do not match 

sage: _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 2), (0, 1)), True) 

Traceback (most recent call last): 

... 

TypeError: Tape position ((0, 2), (0, 1)) wrong. 

""" 

if not len(tape) == len(position) == len(tape_ended): 

raise TypeError('The lengths of the inputs do not match') 

if sorted(p[1] for p in position) != list(range(len(tape))): 

raise TypeError('Tape position %s wrong.' % (position,)) 

self.position = position 

self.tape = tape 

self.tape_ended = tape_ended 

self.is_multitape = is_multitape 

 

self.tape_cache_manager = tape_cache_manager 

self.tape_cache_manager.append(self) 

self.cache = tuple(collections.deque() for _ in self.tape) 

 

 

def _repr_(self): 

""" 

Returns a string representation of ``self``. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A string. 

 

Note that this representation depends on the parameter 

``is_multitape`` of ``self``. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import _FSMTapeCache_ 

sage: TC1 = _FSMTapeCache_([], (xsrange(37, 42),), 

....: [False], ((0, 0),), False) 

sage: TC2 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0), (0, 1)), True) 

sage: TC1m = _FSMTapeCache_([], (xsrange(37, 42),), 

....: [False], ((0, 0),), True) 

sage: repr(TC1) # indirect doctest 

'tape at 0' 

sage: repr(TC1m) # indirect doctest 

'multi-tape at (0,)' 

sage: repr(TC2) # indirect doctest 

'multi-tape at (0, 0)' 

""" 

if self.is_multitape: 

pos = tuple(p for p, t in sorted(self.position, key=lambda x: x[1])) 

return 'multi-tape at %s' % (pos,) 

else: 

return 'tape at %s' % (self.position[0][0],) 

 

 

def __deepcopy__(self, memo): 

""" 

See :meth:`.deepcopy` for details. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import _FSMTapeCache_ 

sage: TC2 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0), (0, 1)), True) 

sage: TC3 = deepcopy(TC2) # indirect doctest 

sage: TC3 

multi-tape at (0, 0) 

sage: TC2.tape_cache_manager 

[multi-tape at (0, 0), multi-tape at (0, 0)] 

sage: TC2.tape_cache_manager is TC3.tape_cache_manager 

True 

""" 

from copy import deepcopy 

 

new = type(self)(self.tape_cache_manager, 

self.tape, self.tape_ended, 

self.position, self.is_multitape) 

new.cache = deepcopy(self.cache, memo) 

return new 

 

 

def deepcopy(self, memo=None): 

""" 

Returns a deepcopy of ``self``. 

 

INPUT: 

 

- ``memo`` -- a dictionary. 

 

OUTPUT: 

 

An instance of ``_FSMCacheTape_``. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import _FSMTapeCache_ 

sage: TC2 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0), (0, 1)), True) 

sage: TC3 = deepcopy(TC2) # indirect doctest 

sage: TC2 

multi-tape at (0, 0) 

sage: TC3 

multi-tape at (0, 0) 

sage: TC2.read(0), TC2.read(1), TC2.read(1) 

((True, 37), (True, 11), (True, 12)) 

sage: TC2.preview_word() 

(37, 11) 

sage: TC2.cache is TC3.cache 

False 

""" 

from copy import deepcopy 

return deepcopy(self, memo) 

 

 

def read(self, track_number): 

""" 

Reads one letter from the given track of the input tape into 

the cache. 

 

INPUT: 

 

- ``track_number`` -- an integer. 

 

OUTPUT: 

 

``(True, letter)`` if reading was successful (``letter`` was 

read), otherwise ``(False, None)``. 

 

Note that this updates the cache of all tapes in 

``self.tape_cache_manager``. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import _FSMTapeCache_ 

sage: TC2 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0), (0, 1)), True) 

sage: TC2.read(0), TC2.read(1), TC2.read(1) 

((True, 37), (True, 11), (True, 12)) 

sage: TC2.preview_word() 

(37, 11) 

sage: TC3 = deepcopy(TC2) 

sage: TC2.cache, TC3.cache 

((deque([37]), deque([11, 12])), (deque([37]), deque([11, 12]))) 

sage: TC3.read(1) 

(True, 13) 

sage: TC2.cache, TC3.cache 

((deque([37]), deque([11, 12, 13])), 

(deque([37]), deque([11, 12, 13]))) 

sage: TC2.read(1), TC2.read(1) 

((True, 14), (False, None)) 

sage: TC2.cache 

(deque([37]), deque([11, 12, 13, 14])) 

sage: TC2.tape_ended 

[False, True] 

sage: TC2.read(1) 

(False, None) 

""" 

try: 

newval = next(self.tape[track_number]) 

except StopIteration: 

self.tape_ended[track_number] = True 

return (False, None) 

 

# update all tapes 

for tape in self.tape_cache_manager: 

tape.cache[track_number].append(newval) 

 

return (True, newval) 

 

 

def finished(self, track_number=None): 

r""" 

Returns whether the tape (or a particular track) has reached an 

end, i.e., there are no more letters in the cache and nothing 

more to read on the original tape. 

 

INPUT: 

 

- ``track_number`` -- an integer or ``None``. If ``None``, 

then ``True`` is returned if all tracks are finished. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import ( 

....: _FSMTapeCache_, FSMTransition) 

sage: TC2 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0), (0, 1)), True) 

sage: while True: 

....: try: 

....: word = TC2.preview_word(return_word=True) 

....: except StopIteration: 

....: print('stop') 

....: break 

....: print('cache: {} {}'.format(TC2.cache, TC2)) 

....: print('finished: {} {} {}'.format(TC2.finished(), 

....: TC2.finished(0), TC2.finished(1))) 

....: TC2.forward( 

....: FSMTransition(0, 0, word)) 

cache: (deque([37]), deque([11])) multi-tape at (0, 0) 

finished: False False False 

cache: (deque([38]), deque([12])) multi-tape at (1, 1) 

finished: False False False 

cache: (deque([39]), deque([13])) multi-tape at (2, 2) 

finished: False False False 

cache: (deque([40]), deque([14])) multi-tape at (3, 3) 

finished: False False False 

stop 

sage: print('cache: {} {}'.format(TC2.cache, TC2)) 

cache: (deque([41]), deque([])) multi-tape at (4, 4) 

sage: print('finished: {} {} {}'.format(TC2.finished(), 

....: TC2.finished(0), TC2.finished(1))) 

finished: False False True 

sage: TC2.preview_word() 

Traceback (most recent call last): 

... 

StopIteration 

sage: print('cache: {} {}'.format(TC2.cache, TC2)) 

cache: (deque([41]), deque([])) multi-tape at (4, 4) 

sage: TC2.read(0) 

(False, None) 

sage: TC2.forward(FSMTransition(0, 0, [(0, None)])) 

sage: print('finished: {} {} {}'.format(TC2.finished(), 

....: TC2.finished(0), TC2.finished(1))) 

finished: True True True 

""" 

if track_number is None: 

return all(self.finished(n) for n, _ in enumerate(self.cache)) 

if not self.cache[track_number]: 

self.read(track_number) # to make sure tape_ended is correct 

return self.tape_ended[track_number] and not self.cache[track_number] 

 

 

def preview_word(self, track_number=None, length=1, return_word=False): 

""" 

Reads a word from the input tape. 

 

INPUT: 

 

- ``track_number`` -- an integer or ``None``. If ``None``, 

then a tuple of words (one from each track) is returned. 

 

- ``length`` -- (default: ``1``) the length of the word(s). 

 

- ``return_word`` -- (default: ``False``) a boolean. If set, 

then a word is returned, otherwise a single letter (in which 

case ``length`` has to be ``1``). 

 

OUTPUT: 

 

A single letter or a word. 

 

An exception ``StopIteration`` is thrown if the tape (at least 

one track) has reached its end. 

 

Typically, this method is called from a hook-function of a 

state. 

 

The attribute ``position`` is not changed. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import ( 

....: _FSMTapeCache_, FSMTransition) 

sage: TC2 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0), (0, 1)), True) 

sage: TC2.preview_word(), TC2.preview_word() 

((37, 11), (37, 11)) 

sage: while True: 

....: try: 

....: word = TC2.preview_word(return_word=True) 

....: except StopIteration: 

....: print('stop') 

....: break 

....: print('read: {}'.format(word)) 

....: print('cache: {} {}'.format(TC2.cache, TC2)) 

....: TC2.forward( 

....: FSMTransition(0, 0, word)) 

....: print('cache: {} {}'.format(TC2.cache, TC2)) 

read: [(37, 11)] 

cache: (deque([37]), deque([11])) multi-tape at (0, 0) 

cache: (deque([]), deque([])) multi-tape at (1, 1) 

read: [(38, 12)] 

cache: (deque([38]), deque([12])) multi-tape at (1, 1) 

cache: (deque([]), deque([])) multi-tape at (2, 2) 

read: [(39, 13)] 

cache: (deque([39]), deque([13])) multi-tape at (2, 2) 

cache: (deque([]), deque([])) multi-tape at (3, 3) 

read: [(40, 14)] 

cache: (deque([40]), deque([14])) multi-tape at (3, 3) 

cache: (deque([]), deque([])) multi-tape at (4, 4) 

stop 

sage: print('cache: {} {}'.format(TC2.cache, TC2)) 

cache: (deque([41]), deque([])) multi-tape at (4, 4) 

sage: TC2.preview_word() 

Traceback (most recent call last): 

... 

StopIteration 

sage: print('cache: {} {}'.format(TC2.cache, TC2)) 

cache: (deque([41]), deque([])) multi-tape at (4, 4) 

sage: TC2.preview_word(0) 

41 

sage: print('cache: {} {}'.format(TC2.cache, TC2)) 

cache: (deque([41]), deque([])) multi-tape at (4, 4) 

sage: TC2.forward(FSMTransition(0, 0, [(41, None)])) 

sage: print('cache: {} {}'.format(TC2.cache, TC2)) 

cache: (deque([]), deque([])) multi-tape at (5, 4) 

""" 

if not return_word and length != 1: 

raise ValueError("Should return a letter, but parameter " 

"length is not 1.") 

if track_number is None: 

if self.is_multitape: 

result = tuple(self.preview_word(n, length, return_word) 

for n, _ in enumerate(self.cache)) 

if len(result) != len(self.cache): 

raise StopIteration 

if return_word: 

return tupleofwords_to_wordoftuples(result) 

else: 

return result 

else: 

return self.preview_word(0, length, return_word) 

 

track_cache = self.cache[track_number] 

while len(track_cache) < length: 

if not self.read(track_number)[0]: 

raise StopIteration 

if return_word: 

return list(itertools.islice(track_cache, 0, length)) 

else: 

return track_cache[0] 

 

 

def compare_to_tape(self, track_number, word): 

""" 

Returns whether it is possible to read ``word`` from the given 

track successfully. 

 

INPUT: 

 

- ``track_number`` -- an integer. 

 

- ``word`` -- a tuple or list of letters. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import _FSMTapeCache_ 

sage: TC2 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0), (0, 1)), True) 

sage: TC2.compare_to_tape(0, [37]) 

True 

sage: TC2.compare_to_tape(1, [37]) 

False 

sage: TC2.compare_to_tape(0, [37, 38]) 

True 

sage: TC2.compare_to_tape(1, srange(11,15)) 

True 

sage: TC2.compare_to_tape(1, srange(11,16)) 

False 

sage: TC2.compare_to_tape(1, []) 

True 

""" 

track_cache = self.cache[track_number] 

it_word = iter(word) 

 

# check letters in cache 

if any(letter_on_track != next(it_word) 

for letter_on_track in track_cache): 

return False 

 

# check letters not already cached 

for letter_in_word in it_word: 

successful, letter_on_track = self.read(track_number) 

if not successful: 

return False 

if letter_in_word != letter_on_track: 

return False 

return True 

 

 

def forward(self, transition): 

""" 

Forwards the tape according to the given transition. 

 

INPUT: 

 

- ``transition`` -- a transition of a finite state machine. 

 

OUTPUT: 

 

Nothing. 

 

If ``self.is_multitape`` is ``False``, then this function 

forwards ``self`` (track `0`) by the number of entries of 

``transition.word_in`` different from ``None``. 

Otherwise (if ``self.is_multitape`` is 

``True``), this function forwards each track of ``self`` by 

the length of each entry of ``transition.word_in``. Note that 

the actual values in the input word do not play a role 

(just the length). 

 

This function changes the attribute ``position``. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import ( 

....: _FSMTapeCache_, FSMTransition, 

....: tupleofwords_to_wordoftuples) 

sage: TC2 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0), (0, 1)), True) 

sage: TC2, TC2.cache 

(multi-tape at (0, 0), (deque([]), deque([]))) 

sage: letter = TC2.preview_word(); letter 

(37, 11) 

sage: TC2, TC2.cache 

(multi-tape at (0, 0), (deque([37]), deque([11]))) 

sage: TC2.forward(FSMTransition(0, 0, [letter])) 

sage: TC2, TC2.cache 

(multi-tape at (1, 1), (deque([]), deque([]))) 

sage: TC2.forward(FSMTransition(0, 0, [(0, 0), (None, 0)])) 

sage: TC2, TC2.cache 

(multi-tape at (2, 3), (deque([]), deque([]))) 

sage: letter = TC2.preview_word(); letter 

(39, 14) 

sage: TC2, TC2.cache 

(multi-tape at (2, 3), (deque([39]), deque([14]))) 

sage: word_in = tupleofwords_to_wordoftuples([[None], [None, None]]) 

sage: TC2.forward(FSMTransition(0, 0, word_in)) 

sage: TC2, TC2.cache 

(multi-tape at (2, 3), (deque([39]), deque([14]))) 

sage: TC2.forward(FSMTransition(0, 0, [[0, None], [None, 0]])) 

sage: TC2, TC2.cache 

(multi-tape at (3, 4), (deque([]), deque([]))) 

sage: TC2.forward(FSMTransition(0, 0, [(0, 0)])) 

Traceback (most recent call last): 

... 

ValueError: forwarding tape is not possible 

""" 

def length(word): 

return len(tuple(letter for letter in word if letter is not None)) 

 

if self.is_multitape: 

increments = tuple(length(word) for word in 

zip(*transition.word_in)) 

else: 

increments = (length(transition.word_in),) 

 

for track_number, (track_cache, inc) in \ 

enumerate(zip(self.cache, increments)): 

for _ in range(inc): 

if not track_cache: 

if not self.read(track_number)[0]: 

raise ValueError('forwarding tape is not possible') 

track_cache.popleft() 

position = [(p + increments[t], t) 

for p, t in self.position] 

self.position = tuple(sorted(position)) 

 

 

def transition_possible(self, transition): 

""" 

Tests whether the input word of ``transition`` can be read 

from the tape. 

 

INPUT: 

 

- ``transition`` -- a transition of a finite state machine. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import ( 

....: _FSMTapeCache_, FSMTransition) 

sage: TC2 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0), (0, 1)), True) 

sage: TC2, TC2.cache 

(multi-tape at (0, 0), (deque([]), deque([]))) 

sage: TC2.transition_possible( 

....: FSMTransition(0, 0, [(37, 11), (38, 12), (None, 13)])) 

True 

sage: TC2.transition_possible( 

....: FSMTransition(0, 0, [(37, 11), (38, 13)])) 

False 

sage: TC2.transition_possible( 

....: FSMTransition(0, 0, [(37,), (38,)])) 

Traceback (most recent call last): 

... 

TypeError: Transition from 0 to 0: (37,),(38,)|- has bad 

input word (entries should be tuples of size 2). 

""" 

if self.is_multitape: 

word_in = transition.word_in 

else: 

word_in = tupleofwords_to_wordoftuples((transition.word_in,)) 

if any(len(t) != len(self.cache) for t in word_in): 

raise TypeError('%s has bad input word (entries should be ' 

'tuples of size %s).' % (transition, 

len(self.cache))) 

return self._transition_possible_test_(word_in) 

 

 

def _transition_possible_epsilon_(self, word_in): 

""" 

This helper function tests whether ``word_in`` equals ``epsilon``, 

i.e., whether it is the empty word or consists only of letters ``None``. 

 

INPUT: 

 

- ``word_in`` -- an input word of a transition. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import ( 

....: _FSMTapeCache_) 

sage: TC2 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0), (0, 1)), True) 

sage: TC2._transition_possible_epsilon_([]) 

True 

sage: TC2._transition_possible_epsilon_([(None, None)]) 

True 

sage: TC2._transition_possible_epsilon_( 

....: [(None, None), (None, None)]) 

True 

""" 

# Note that this function does not need self, but it is given 

# to be consistent with the other _transition_possible_*_ 

# functions. 

return all(letter is None for t in word_in for letter in t) 

 

 

def _transition_possible_test_(self, word_in): 

""" 

This helper function tests whether ``word_in`` can be read 

from the tape. 

 

INPUT: 

 

- ``word_in`` -- an input word of a transition. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

This method is usually overridden in inherited classes, 

cf. :class:`_FSMTapeCacheDetectEpsilon_` and 

:class:`_FSMTapeCacheDetectAll_`. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import ( 

....: _FSMTapeCache_, tupleofwords_to_wordoftuples) 

sage: TC2 = _FSMTapeCache_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0), (0, 1)), True) 

sage: TC2, TC2.cache 

(multi-tape at (0, 0), (deque([]), deque([]))) 

sage: word_in = tupleofwords_to_wordoftuples( 

....: [(37, 38), (11, 12, 13)]) 

sage: TC2._transition_possible_test_(word_in) 

True 

sage: word_in = tupleofwords_to_wordoftuples( 

....: [(37, 38), (11, 13)]) 

sage: TC2._transition_possible_test_(word_in) 

False 

 

Note that this function does not perform a check whether the 

input word is correct or not. This is done by the higher-level 

method :meth:`.transition_possible`:: 

 

sage: TC2._transition_possible_test_([(37,), (38,)]) 

True 

 

This function does not accept words of epsilon-transitions:: 

 

sage: TC2._transition_possible_test_([]) 

False 

sage: TC2._transition_possible_test_([(None, None)]) 

False 

""" 

if self._transition_possible_epsilon_(word_in): 

return False 

word_in_transposed = wordoftuples_to_tupleofwords(word_in) 

return all(self.compare_to_tape(track_number, word) 

for track_number, word in enumerate(word_in_transposed)) 

 

 

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

 

 

class _FSMTapeCacheDetectEpsilon_(_FSMTapeCache_): 

""" 

This is a class is similar to :class:`_FSMTapeCache_` but accepts 

only epsilon transitions. 

""" 

def __init__(self, *args, **kwargs): 

""" 

See :class:`_FSMTapeCache_` for more details. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import _FSMTapeCacheDetectEpsilon_ 

sage: _FSMTapeCacheDetectEpsilon_([], (xsrange(37, 42),), 

....: [False], ((0, 0),), False) 

tape at 0 

""" 

super(_FSMTapeCacheDetectEpsilon_, self).__init__(*args, **kwargs) 

self._visited_states_ = set() 

 

 

def __deepcopy__(self, memo): 

""" 

See :meth:`_FSMTapeCache_.deepcopy` for details. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import _FSMTapeCacheDetectEpsilon_ 

sage: TC2 = _FSMTapeCacheDetectEpsilon_([], (xsrange(37, 42),), 

....: [False], ((0, 0),), True) 

sage: TC2._visited_states_.add(1) 

sage: TC3 = deepcopy(TC2) # indirect doctest 

sage: TC3._visited_states_ 

{1} 

""" 

from copy import copy 

 

new = super(_FSMTapeCacheDetectEpsilon_, self).__deepcopy__(memo) 

new._visited_states_ = copy(self._visited_states_) 

return new 

 

 

def _transition_possible_test_(self, word_in): 

""" 

This helper function tests whether ``word_in`` equals ``epsilon``, 

i.e., whether it is the empty word or consists only of letters ``None``. 

 

INPUT: 

 

- ``word_in`` -- an input word of a transition. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import ( 

....: _FSMTapeCacheDetectEpsilon_) 

sage: TCE = _FSMTapeCacheDetectEpsilon_([], (xsrange(37, 42), xsrange(11,15)), 

....: [False, False], ((0, 0), (0, 1)), True) 

sage: TCE._transition_possible_test_([]) 

True 

sage: TCE._transition_possible_test_([(None, None)]) 

True 

sage: TCE._transition_possible_test_([(37, 11), (38, 12)]) 

False 

sage: TCE._transition_possible_test_([(37, 11), (38, 13)]) 

False 

""" 

return self._transition_possible_epsilon_(word_in) 

 

 

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

 

 

class _FSMTapeCacheDetectAll_(_FSMTapeCache_): 

""" 

This is a class is similar to :class:`_FSMTapeCache_` but accepts 

each transition. 

""" 

def compare_to_tape(self, track_number, word): 

""" 

Return whether it is possible to read a word of the same length 

as ``word`` (but ignoring its actual content) 

from the given track successfully. 

 

INPUT: 

 

- ``track_number`` -- an integer. 

 

- ``word`` -- a tuple or list of letters. Only its length is used. 

 

OUTPUT: 

 

``True`` or ``False``. 

 

Note that this method usually returns ``True``. ``False`` can 

only be returned at the end of the input tape. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import _FSMTapeCacheDetectAll_ 

sage: TC = _FSMTapeCacheDetectAll_( 

....: [], (iter((11, 12)),), 

....: [False], ((0, 0),), False) 

sage: TC.compare_to_tape(0, []) 

True 

sage: TC.compare_to_tape(0, [37]) 

True 

sage: TC.compare_to_tape(0, [37, 38]) 

True 

sage: TC.compare_to_tape(0, [37, 38, 39]) 

False 

sage: TC.compare_to_tape(0, [1, 2]) 

True 

""" 

track_cache = self.cache[track_number] 

it_word = iter(word) 

 

# process letters in cache 

for _ in track_cache: 

next(it_word) 

 

# check letters not already cached 

for letter_in_word in it_word: 

successful, letter_on_track = self.read(track_number) 

if not successful: 

return False 

return True 

 

 

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

 

 

def tupleofwords_to_wordoftuples(tupleofwords): 

""" 

Transposes a tuple of words over the alphabet to a word of tuples. 

 

INPUT: 

 

- ``tupleofwords`` -- a tuple of a list of letters. 

 

OUTPUT: 

 

A list of tuples. 

 

Missing letters in the words are padded with the letter ``None`` 

(from the empty word). 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import ( 

....: tupleofwords_to_wordoftuples) 

sage: tupleofwords_to_wordoftuples( 

....: ([1, 2], [3, 4, 5, 6], [7])) 

[(1, 3, 7), (2, 4, None), (None, 5, None), (None, 6, None)] 

""" 

return list(zip_longest(*tupleofwords, fillvalue=None)) 

 

 

def wordoftuples_to_tupleofwords(wordoftuples): 

""" 

Transposes a word of tuples to a tuple of words over the alphabet. 

 

INPUT: 

 

- ``wordoftuples`` -- a list of tuples of letters. 

 

OUTPUT: 

 

A tuple of lists. 

 

Letters ``None`` (empty word) are removed from each word in the output. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import ( 

....: wordoftuples_to_tupleofwords) 

sage: wordoftuples_to_tupleofwords( 

....: [(1, 2), (1, None), (1, None), (1, 2), (None, 2)]) 

([1, 1, 1, 1], [2, 2, 2]) 

""" 

if not equal(len(t) for t in wordoftuples): 

raise ValueError("Not all entries of input have the same length.") 

def remove_empty_letters(word): 

return [letter for letter in word if letter is not None] 

return tuple(remove_empty_letters(word) 

for word in zip(*wordoftuples)) 

 

 

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

 

 

def is_FSMProcessIterator(PI): 

""" 

Tests whether or not ``PI`` inherits from :class:`FSMProcessIterator`. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import is_FSMProcessIterator, FSMProcessIterator 

sage: is_FSMProcessIterator(FSMProcessIterator(FiniteStateMachine([[0, 0, 0, 0]], initial_states=[0]), [])) 

True 

""" 

return isinstance(PI, FSMProcessIterator) 

 

 

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

 

 

class FSMProcessIterator(sage.structure.sage_object.SageObject, 

collections.Iterator): 

""" 

This class takes an input, feeds it into a finite state machine 

(automaton or transducer, in particular), tests whether this was 

successful and calculates the written output. 

 

INPUT: 

 

- ``fsm`` -- the finite state machine on which the input should be 

processed. 

 

- ``input_tape`` -- the input tape can be a list or an 

iterable with entries from the input alphabet. If we are 

working with a multi-tape machine (see parameter 

``use_multitape_input`` and notes below), then the tape is a 

list or tuple of tracks, each of which can be a list or an 

iterable with entries from the input alphabet. 

 

- ``initial_state`` or ``initial_states`` -- the initial 

state(s) in which the machine starts. Either specify a 

single one with ``initial_state`` or a list of them with 

``initial_states``. If both are given, ``initial_state`` 

will be appended to ``initial_states``. If neither is 

specified, the initial states of the finite state machine 

are taken. 

 

- ``format_output`` -- a function that translates the written 

output (which is in form of a list) to something more 

readable. By default (``None``) identity is used here. 

 

- ``check_epsilon_transitions`` -- (default: ``True``) a 

boolean. If ``False``, then epsilon transitions are not 

taken into consideration during process. 

 

- ``write_final_word_out`` -- (default: ``True``) a boolean 

specifying whether the final output words should be written 

or not. 

 

- ``use_multitape_input`` -- (default: ``False``) a 

boolean. If ``True``, then the multi-tape mode of the 

process iterator is activated. See also the notes below for 

multi-tape machines. 

 

- ``process_all_prefixes_of_input`` -- (default: ``False``) a 

boolean. If ``True``, then each prefix of the input word is 

processed (instead of processing the whole input word at 

once). Consequently, there is an output generated for each 

of these prefixes. 

 

OUTPUT: 

 

An iterator. 

 

In its simplest form, it behaves like an iterator which, in 

each step, goes from one state to another. To decide which way 

to go, it uses the input words of the outgoing transitions and 

compares them to the input tape. More precisely, in each step, 

the process iterator takes an outgoing transition of the 

current state, whose input label equals the input letter of 

the tape. The output label of the transition, if present, is 

written on the output tape. 

 

If the choice of the outgoing transition is not unique (i.e., 

we have a non-deterministic finite state machine), all 

possibilites are followed. This is done by splitting the 

process into several branches, one for each of the possible 

outgoing transitions. 

 

The process (iteration) stops if all branches are finished, 

i.e., for no branch, there is any transition whose input word 

coincides with the processed input tape. This can simply 

happen when the entire tape was read. 

When the process stops, a ``StopIteration`` exception is thrown. 

 

.. WARNING:: 

 

Processing an input tape of length `n` usually takes at least `n+1` 

iterations, since there will be `n+1` states visited (in the 

case the taken transitions have input words consisting of single 

letters). 

 

An instance of this class is generated when 

:meth:`FiniteStateMachine.process` or 

:meth:`FiniteStateMachine.iter_process` of a finite state machine, 

an automaton, or a transducer is invoked. 

 

When working with multi-tape finite state machines, all input 

words of transitions are words of `k`-tuples of letters. 

Moreover, the input tape has to consist of `k` tracks, i.e., 

be a list or tuple of `k` iterators, one for each track. 

 

.. WARNING:: 

 

Working with multi-tape finite state machines is still 

experimental and can lead to wrong outputs. 

 

EXAMPLES: 

 

The following transducer reads binary words and outputs a word, 

where blocks of ones are replaced by just a single one. Further 

only words that end with a zero are accepted. 

 

:: 

 

sage: T = Transducer({'A': [('A', 0, 0), ('B', 1, None)], 

....: 'B': [('B', 1, None), ('A', 0, [1, 0])]}, 

....: initial_states=['A'], final_states=['A']) 

sage: input = [1, 1, 0, 0, 1, 0, 1, 1, 1, 0] 

sage: T.process(input) 

(True, 'A', [1, 0, 0, 1, 0, 1, 0]) 

 

The function :meth:`FiniteStateMachine.process` (internally) uses a 

:class:`FSMProcessIterator`. We can do that manually, too, and get full 

access to the iteration process:: 

 

sage: from sage.combinat.finite_state_machine import FSMProcessIterator 

sage: it = FSMProcessIterator(T, input_tape=input) 

sage: for current in it: 

....: print(current) 

process (1 branch) 

+ at state 'B' 

+-- tape at 1, [[]] 

process (1 branch) 

+ at state 'B' 

+-- tape at 2, [[]] 

process (1 branch) 

+ at state 'A' 

+-- tape at 3, [[1, 0]] 

process (1 branch) 

+ at state 'A' 

+-- tape at 4, [[1, 0, 0]] 

process (1 branch) 

+ at state 'B' 

+-- tape at 5, [[1, 0, 0]] 

process (1 branch) 

+ at state 'A' 

+-- tape at 6, [[1, 0, 0, 1, 0]] 

process (1 branch) 

+ at state 'B' 

+-- tape at 7, [[1, 0, 0, 1, 0]] 

process (1 branch) 

+ at state 'B' 

+-- tape at 8, [[1, 0, 0, 1, 0]] 

process (1 branch) 

+ at state 'B' 

+-- tape at 9, [[1, 0, 0, 1, 0]] 

process (1 branch) 

+ at state 'A' 

+-- tape at 10, [[1, 0, 0, 1, 0, 1, 0]] 

process (0 branches) 

sage: it.result() 

[Branch(accept=True, state='A', output=[1, 0, 0, 1, 0, 1, 0])] 

 

:: 

 

sage: T = Transducer([(0, 0, 0, 'a'), (0, 1, 0, 'b'), 

....: (1, 2, 1, 'c'), (2, 0, 0, 'd'), 

....: (2, 1, None, 'd')], 

....: initial_states=[0], final_states=[2]) 

sage: T.process([0, 0, 1], format_output=lambda o: ''.join(o)) 

[(False, 1, 'abcd'), (True, 2, 'abc')] 

sage: it = FSMProcessIterator(T, input_tape=[0, 0, 1], 

....: format_output=lambda o: ''.join(o)) 

sage: for current in it: 

....: print(current) 

process (2 branches) 

+ at state 0 

+-- tape at 1, [['a']] 

+ at state 1 

+-- tape at 1, [['b']] 

process (2 branches) 

+ at state 0 

+-- tape at 2, [['a', 'a']] 

+ at state 1 

+-- tape at 2, [['a', 'b']] 

process (2 branches) 

+ at state 1 

+-- tape at 3, [['a', 'b', 'c', 'd']] 

+ at state 2 

+-- tape at 3, [['a', 'b', 'c']] 

process (0 branches) 

sage: it.result() 

[Branch(accept=False, state=1, output='abcd'), 

Branch(accept=True, state=2, output='abc')] 

 

.. SEEALSO:: 

 

:meth:`FiniteStateMachine.process`, 

:meth:`Automaton.process`, 

:meth:`Transducer.process`, 

:meth:`FiniteStateMachine.iter_process`, 

:meth:`FiniteStateMachine.__call__`, 

:meth:`next`. 

 

TESTS:: 

 

sage: T = Transducer([[0, 0, 0, 0]]) 

sage: T.process([]) 

Traceback (most recent call last): 

... 

ValueError: No state is initial. 

 

:: 

 

sage: T = Transducer([[0, 1, 0, 0]], initial_states=[0, 1]) 

sage: T.process([]) 

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

 

:: 

 

sage: T = Transducer([[0, 0, 0, 0]], 

....: initial_states=[0], final_states=[0]) 

sage: T.state(0).final_word_out = [42] 

sage: T.process([0]) 

(True, 0, [0, 42]) 

sage: T.process([0], write_final_word_out=False) 

(True, 0, [0]) 

""" 

 

class Current(dict): 

""" 

This class stores the branches which have to be processed 

during iteration and provides a nicer formatting of them. 

 

This class is derived from ``dict``. It is returned by the 

``next``-function during iteration. 

 

EXAMPLES: 

 

In the following example you can see the dict directly and 

then the nicer output provided by this class:: 

 

sage: from sage.combinat.finite_state_machine import FSMProcessIterator 

sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, 

....: initial_states=['A'], final_states=['A']) 

sage: it = FSMProcessIterator(inverter, input_tape=[0, 1]) 

sage: for current in it: 

....: print(dict(current)) 

....: print(current) 

{((1, 0),): {'A': Branch(tape_cache=tape at 1, outputs=[[1]])}} 

process (1 branch) 

+ at state 'A' 

+-- tape at 1, [[1]] 

{((2, 0),): {'A': Branch(tape_cache=tape at 2, outputs=[[1, 0]])}} 

process (1 branch) 

+ at state 'A' 

+-- tape at 2, [[1, 0]] 

{} 

process (0 branches) 

""" 

def __repr__(self): 

""" 

Returns a nice representation of ``self``. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

A string. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import FSMProcessIterator 

sage: T = Transducer([(0, 0, 0, 0)], 

....: initial_states=[0], final_states=[0]) 

sage: it = FSMProcessIterator(T, input_tape=[0, 0]) 

sage: for current in it: 

....: print(current) # indirect doctest 

process (1 branch) 

+ at state 0 

+-- tape at 1, [[0]] 

process (1 branch) 

+ at state 0 

+-- tape at 2, [[0, 0]] 

process (0 branches) 

""" 

data = sorted( 

(state, pos, tape_cache, outputs) 

for pos, states in six.iteritems(self) 

for state, (tape_cache, outputs) in six.iteritems(states)) 

branch = "branch" if len(data) == 1 else "branches" 

result = "process (%s %s)" % (len(data), branch) 

for s, sdata in itertools.groupby(data, lambda x: x[0]): 

result += "\n+ at state %s" % (s,) 

for state, pos, tape_cache, outputs in sdata: 

result += "\n+-- %s, %s" % (tape_cache, outputs) 

return result 

 

 

FinishedBranch = collections.namedtuple('Branch', 'accept, state, output') 

r""" 

A :func:`named tuple <collections.namedtuple>` representing the 

attributes of a branch, once 

it is fully processed. 

""" 

 

 

def __init__(self, fsm, 

input_tape=None, 

initial_state=None, initial_states=[], 

use_multitape_input=False, 

check_epsilon_transitions=True, 

write_final_word_out=True, 

format_output=None, 

process_all_prefixes_of_input=False, 

**kwargs): 

""" 

See :class:`FSMProcessIterator` for more information. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMProcessIterator 

sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, 

....: initial_states=['A'], final_states=['A']) 

sage: it = FSMProcessIterator(inverter, input_tape=[0, 1]) 

sage: for current in it: 

....: print(current) 

process (1 branch) 

+ at state 'A' 

+-- tape at 1, [[1]] 

process (1 branch) 

+ at state 'A' 

+-- tape at 2, [[1, 0]] 

process (0 branches) 

sage: it.result() 

[Branch(accept=True, state='A', output=[1, 0])] 

""" 

# FSM 

self.fsm = fsm 

 

# multi-tape flag 

self.is_multitape = use_multitape_input 

 

# initial states 

initial_states = list(initial_states) 

if initial_state is not None: 

initial_states.append(initial_state) 

if not initial_states: 

initial_states = self.fsm.initial_states() 

if not initial_states: 

raise ValueError("No state is initial.") 

 

# input tapes 

tape = [] 

if input_tape is not None: 

if self.is_multitape: 

tape.extend(input_tape) 

else: 

tape.append(input_tape) 

if not tape: 

raise TypeError('No input tape given.') 

if not all(hasattr(track, '__iter__') for track in tape): 

raise TypeError('Given input tape is not iterable.') 

self._input_tape_ = tuple(iter(track) for track in tape) 

self._input_tape_ended_ = [False for _ in tape] 

 

# other options 

if format_output is None: 

self.format_output = list 

else: 

self.format_output = format_output 

 

self.check_epsilon_transitions = check_epsilon_transitions 

self.write_final_word_out = write_final_word_out 

self.process_all_prefixes_of_input = process_all_prefixes_of_input 

 

# init branches 

self._current_ = self.Current() 

self._current_positions_ = [] # a heap queue of the keys of _current_ 

self._tape_cache_manager_ = [] 

position_zero = tuple((0, t) for t, _ in enumerate(self._input_tape_)) 

if not hasattr(self, 'TapeCache'): 

self.TapeCache = _FSMTapeCache_ 

 

for state in initial_states: 

tape_cache = self.TapeCache(self._tape_cache_manager_, 

self._input_tape_, 

self._input_tape_ended_, 

position_zero, 

self.is_multitape) 

self._push_branches_(state, tape_cache, [[]]) 

 

self._finished_ = [] # contains (accept, state, output) 

 

 

_branch_ = collections.namedtuple('Branch', 'tape_cache, outputs') 

r""" 

A :func:`named tuple <collections.namedtuple>` representing the 

attributes of a branch at a particular state during processing. 

""" 

 

 

def _push_branch_(self, state, tape_cache, outputs): 

""" 

This helper function pushes a ``state`` together with 

``tape_cache`` and ``outputs`` (i.e. a branch) to the queue 

``self._current_``. See also :meth:`._push_branches_`. 

 

INPUT: 

 

- ``state`` -- state which has to be processed. 

 

- ``tape_cache`` -- an instance of :class:`_FSMTapeCache_` (storing 

information what to read next). 

 

- ``outputs`` -- a list of output tapes on each of which words 

were written until reaching ``state``. 

 

OUTPUT: 

 

Nothing. 

 

.. NOTE:: 

 

``tape_cache`` is discarded if ``self.__current__`` already 

contains a branch with the same position and state. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import FSMProcessIterator 

sage: A = Automaton({'a': [('a', 0), ('b', 1), ('c', 1)], 

....: 'c': [('b', None)], 'b': [('c', None)]}, 

....: initial_states=['a'], final_states=['b', 'c']) 

sage: it = FSMProcessIterator(A, input_tape=[0, 1, 2]) # indirect doctest 

sage: it._current_ 

process (1 branch) 

+ at state 'a' 

+-- tape at 0, [[]] 

sage: it._push_branch_( 

....: A.state('b'), 

....: deepcopy(it._current_[((0, 0),)][A.state('a')][0]), 

....: [[]]) 

sage: it._current_ 

process (2 branches) 

+ at state 'a' 

+-- tape at 0, [[]] 

+ at state 'b' 

+-- tape at 0, [[]] 

sage: it._push_branches_( 

....: A.state('c'), 

....: deepcopy(it._current_[((0, 0),)][A.state('a')][0]), 

....: [[]]) # indirect doctest 

sage: it._current_ 

process (3 branches) 

+ at state 'a' 

+-- tape at 0, [[]] 

+ at state 'b' 

+-- tape at 0, [[]] 

+ at state 'c' 

+-- tape at 0, [[]] 

 

:: 

 

sage: T = Transducer([(0, 1, 0, 'd'), (0, 1, 0, 'a'), 

....: (0, 1, 0, 'a'), (0, 1, 0, 'a'), 

....: (0, 1, 0, 'n'), (0, 1, 0, 'i'), 

....: (0, 1, 0, 'e'), (0, 1, 0, 'l'), 

....: (0, 1, 0, 'l'), (0, 1, 0, 'l'), 

....: (1, 2, 0, ':'), (2, 3, 0, ')')], 

....: initial_states=[0], final_states=[3]) 

sage: T.process([0, 0, 0], format_output=lambda o: ''.join(o)) 

[(True, 3, 'a:)'), (True, 3, 'd:)'), (True, 3, 'e:)'), 

(True, 3, 'i:)'), (True, 3, 'l:)'), (True, 3, 'n:)')] 

 

""" 

import heapq 

 

if tape_cache.position in self._current_: 

states = self._current_[tape_cache.position] 

else: 

states = self._current_[tape_cache.position] = {} 

heapq.heappush(self._current_positions_, tape_cache.position) 

 

if state in states: 

existing = states[state] 

new_outputs = existing.outputs 

new_outputs.extend(outputs) 

new_outputs = [t for t, _ in 

itertools.groupby(sorted(new_outputs))] 

states[state] = FSMProcessIterator._branch_( 

existing.tape_cache, new_outputs) 

else: 

states[state] = FSMProcessIterator._branch_(tape_cache, outputs) 

 

 

def _push_branches_(self, state, tape_cache, outputs): 

""" 

This function pushes a branch (consisting of a ``state``, an 

input ``tape_cache`` and ``outputs``) and one other branch for 

each epsilon successor of ``state`` to the queue (containing 

branches to process). 

 

INPUT: 

 

- ``state`` -- state which has to be processed (i.e., the 

current state, this branch is in). 

 

- ``tape_cache`` -- an instance of :class:`_FSMTapeCache_` (storing 

information what to read next). 

 

- ``outputs`` -- a list of output tapes on each of which words 

were written until reaching ``state``. 

 

OUTPUT: 

 

Nothing. 

 

When this function is called, a branch is updated, which 

means, stored for further processing. If the state has epsilon 

successors, then a new branch for each epsilon successor is 

created. All these branches start on the same position on the 

tape and get the same (more precisely, a deepcopy of the) list 

of output tapes. 

 

Note that ``self._current_`` contains all states which have to 

be visited in the next steps during processing. The actual 

adding of the data is done in the helper function 

:meth:`._push_branch_`. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import FSMProcessIterator 

sage: A = Automaton({'a': [('a', 0), ('b', 1), ('c', None)], 

....: 'c': [('b', 2)]}, 

....: initial_states=['a'], final_states=['b', 'c']) 

sage: it = FSMProcessIterator(A, input_tape=[0, 1, 2]) # indirect doctest 

sage: it._current_ 

process (2 branches) 

+ at state 'a' 

+-- tape at 0, [[]] 

+ at state 'c' 

+-- tape at 0, [[]] 

sage: it._push_branches_( 

....: A.state('b'), 

....: deepcopy(it._current_[((0, 0),)][A.state('a')][0]), 

....: [[]]) 

sage: it._current_ 

process (3 branches) 

+ at state 'a' 

+-- tape at 0, [[]] 

+ at state 'b' 

+-- tape at 0, [[]] 

+ at state 'c' 

+-- tape at 0, [[]] 

""" 

from copy import deepcopy 

 

self._push_branch_(state, tape_cache, outputs) 

if not self.check_epsilon_transitions: 

return 

if state._in_epsilon_cycle_(self.fsm): 

if not state._epsilon_cycle_output_empty_(self.fsm): 

raise RuntimeError( 

'State %s is in an epsilon cycle (no input), ' 

'but output is written.' % (state,)) 

 

for eps_state, eps_outputs in \ 

six.iteritems(state._epsilon_successors_(self.fsm)): 

if eps_state == state: 

continue 

# "eps_state == state" means epsilon cycle 

# Since we excluded epsilon cycles where 

# output is written, this has to be one 

# which does not write output; therefore 

# skipped. 

for eps_out in eps_outputs: 

new_out = [o + list(eps_out) for o in outputs] 

self._push_branch_(eps_state, deepcopy(tape_cache), new_out) 

 

 

def __next__(self): 

""" 

Makes one step in processing the input tape. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

It returns the current status of the iterator (see below). A 

``StopIteration`` exception is thrown when there is/was 

nothing to do (i.e. all branches ended with previous call 

of :meth:`.next`). 

 

The current status is a dictionary (encapsulated into an instance of 

:class:`~FSMProcessIterator.Current`). 

The keys are positions on 

the tape. The value corresponding to such a position is again 

a dictionary, where each entry represents a branch of the 

process. This dictionary maps the current state of a branch to 

a pair consisting of a tape cache and a list of output words, 

which were written during reaching this current state. 

 

EXAMPLES:: 

 

sage: from sage.combinat.finite_state_machine import FSMProcessIterator 

sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, 

....: initial_states=['A'], final_states=['A']) 

sage: it = FSMProcessIterator(inverter, input_tape=[0, 1]) 

sage: next(it) 

process (1 branch) 

+ at state 'A' 

+-- tape at 1, [[1]] 

sage: next(it) 

process (1 branch) 

+ at state 'A' 

+-- tape at 2, [[1, 0]] 

sage: next(it) 

process (0 branches) 

sage: next(it) 

Traceback (most recent call last): 

... 

StopIteration 

 

.. SEEALSO:: 

 

:meth:`FiniteStateMachine.process`, 

:meth:`Automaton.process`, 

:meth:`Transducer.process`, 

:meth:`FiniteStateMachine.iter_process`, 

:meth:`FiniteStateMachine.__call__`, 

:class:`FSMProcessIterator`. 

 

TESTS:: 

 

sage: Z = Transducer() 

sage: s = Z.add_state(0) 

sage: s.is_initial = True 

sage: s.is_final = True 

sage: s.final_word_out = [1, 2] 

sage: Z.process([]) 

(True, 0, [1, 2]) 

sage: it = FSMProcessIterator(Z, input_tape=[]) 

sage: next(it) 

process (0 branches) 

sage: next(it) 

Traceback (most recent call last): 

... 

StopIteration 

 

:: 

 

sage: N = Transducer([(0, 0, 0, 1)], initial_states=[0]) 

sage: def h_old(state, process): 

....: print("{} {}".format(state, process)) 

sage: N.state(0).hook = h_old 

sage: N.process([0, 0]) 

doctest:...: DeprecationWarning: The hook of state 0 cannot 

be processed: It seems that you are using an old-style hook, 

which is deprecated. 

See http://trac.sagemath.org/16538 for details. 

(False, 0, [1, 1]) 

sage: def h_new(process, state, outputs): 

....: print("{} {}".format(state, outputs)) 

sage: N.state(0).hook = h_new 

sage: N.process([0, 0], check_epsilon_transitions=False) 

0 [[]] 

0 [[1]] 

0 [[1, 1]] 

(False, 0, [1, 1]) 

""" 

from copy import deepcopy 

import heapq 

 

if not self._current_: 

raise StopIteration 

 

def write_word(outputs, word): 

for o in outputs: 

o.extend(word) 

 

def step(current_state, input_tape, outputs): 

# process current state 

next_transitions = None 

state_said_finished = False 

if hasattr(current_state, 'hook'): 

import inspect 

if len(inspect.getargspec(current_state.hook).args) == 2: 

from sage.misc.superseded import deprecation 

deprecation(16538, 'The hook of state %s cannot be ' 

'processed: It seems that you are using an ' 

'old-style hook, which is deprecated. ' 

% (current_state,)) 

else: 

try: 

self._current_branch_input_tape_ = input_tape # for preview_word 

next_transitions = current_state.hook( 

self, current_state, outputs) 

except StopIteration: 

next_transitions = [] 

state_said_finished = True 

if isinstance(next_transitions, FSMTransition): 

next_transitions = [next_transitions] 

if next_transitions is not None and \ 

not hasattr(next_transitions, '__iter__'): 

raise ValueError('hook of state should return a ' 

'transition or ' 

'a list/tuple of transitions.') 

 

# write output word of state 

write_word(outputs, current_state.word_out) 

 

# get next 

if next_transitions is None: 

next_transitions = \ 

[transition for transition in current_state.transitions 

if input_tape.transition_possible(transition)] 

 

if not next_transitions: 

# this branch has to end here... 

if not (input_tape.finished() or 

state_said_finished or 

self.process_all_prefixes_of_input): 

return 

 

if not next_transitions or self.process_all_prefixes_of_input: 

# this branch has to end here... (continued) 

successful = current_state.is_final 

if self.process_all_prefixes_of_input: 

write_outputs = deepcopy(outputs) 

else: 

write_outputs = outputs 

if successful and self.write_final_word_out: 

write_word(write_outputs, current_state.final_word_out) 

for o in write_outputs: 

self._finished_.append( 

FSMProcessIterator.FinishedBranch( 

accept=successful, 

state=current_state, 

output=self.format_output(o))) 

 

if not next_transitions: 

# this branch has to end here... (continued) 

return 

 

# at this point we know that there is at least one 

# outgoing transition to take 

 

new_currents = [(input_tape, outputs)] 

if len(next_transitions) > 1: 

new_currents.extend( 

[deepcopy(new_currents[0]) 

for _ in range(len(next_transitions) - 1)]) 

 

# process transitions 

for transition, (tape, out) in zip(next_transitions, new_currents): 

if hasattr(transition, 'hook'): 

transition.hook(transition, self) 

write_word(out, transition.word_out) 

 

# go to next state 

state = transition.to_state 

tape.forward(transition) 

self._push_branches_(state, tape, out) 

return 

 

states_dict = self._current_.pop(heapq.heappop(self._current_positions_)) 

for state, branch in six.iteritems(states_dict): 

step(state, branch.tape_cache, branch.outputs) 

 

return self._current_ 

 

 

next = __next__ 

 

 

def result(self, format_output=None): 

""" 

Returns the already finished branches during process. 

 

INPUT: 

 

- ``format_output`` -- a function converting the output from 

list form to something more readable (default: output the 

list directly). 

 

OUTPUT: 

 

A list of triples ``(accepted, state, output)``. 

 

See also the parameter ``format_output`` of 

:class:`FSMProcessIterator`. 

 

EXAMPLES:: 

 

sage: inverter = Transducer({'A': [('A', 0, 'one'), ('A', 1, 'zero')]}, 

....: initial_states=['A'], final_states=['A']) 

sage: it = inverter.iter_process(input_tape=[0, 1, 1]) 

sage: for _ in it: 

....: pass 

sage: it.result() 

[Branch(accept=True, state='A', output=['one', 'zero', 'zero'])] 

sage: it.result(lambda L: ', '.join(L)) 

[(True, 'A', 'one, zero, zero')] 

 

Using both the parameter ``format_output`` of 

:class:`FSMProcessIterator` and the parameter ``format_output`` 

of :meth:`.result` leads to concatenation of the two 

functions:: 

 

sage: it = inverter.iter_process(input_tape=[0, 1, 1], 

....: format_output=lambda L: ', '.join(L)) 

sage: for _ in it: 

....: pass 

sage: it.result() 

[Branch(accept=True, state='A', output='one, zero, zero')] 

sage: it.result(lambda L: ', '.join(L)) 

[(True, 'A', 'o, n, e, ,, , z, e, r, o, ,, , z, e, r, o')] 

""" 

if format_output is None: 

return self._finished_ 

return [r[:2] + (format_output(r[2]),) for r in self._finished_] 

 

def preview_word(self, track_number=None, length=1, return_word=False): 

""" 

Reads a word from the input tape. 

 

INPUT: 

 

- ``track_number`` -- an integer or ``None``. If ``None``, 

then a tuple of words (one from each track) is returned. 

 

- ``length`` -- (default: ``1``) the length of the word(s). 

 

- ``return_word`` -- (default: ``False``) a boolean. If set, 

then a word is returned, otherwise a single letter (in which 

case ``length`` has to be ``1``). 

 

OUTPUT: 

 

A single letter or a word. 

 

An exception ``StopIteration`` is thrown if the tape (at least 

one track) has reached its end. 

 

Typically, this method is called from a hook-function of a 

state. 

 

EXAMPLES:: 

 

sage: inverter = Transducer({'A': [('A', 0, 'one'), 

....: ('A', 1, 'zero')]}, 

....: initial_states=['A'], final_states=['A']) 

sage: def state_hook(process, state, output): 

....: print("We are now in state %s." % (state.label(),)) 

....: print("Next on the tape is a %s." % ( 

....: process.preview_word(),)) 

sage: inverter.state('A').hook = state_hook 

sage: it = inverter.iter_process( 

....: input_tape=[0, 1, 1], 

....: check_epsilon_transitions=False) 

sage: for _ in it: 

....: pass 

We are now in state A. 

Next on the tape is a 0. 

We are now in state A. 

Next on the tape is a 1. 

We are now in state A. 

Next on the tape is a 1. 

We are now in state A. 

sage: it.result() 

[Branch(accept=True, state='A', output=['one', 'zero', 'zero'])] 

""" 

return self._current_branch_input_tape_.preview_word( 

track_number, length, return_word) 

 

 

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

 

 

class _FSMProcessIteratorEpsilon_(FSMProcessIterator): 

""" 

This class is similar to :class:`FSMProcessIterator`, but only 

accepts epsilon transitions during process. See 

:class:`FSMProcessIterator` for more information. 

 

EXAMPLES:: 

 

sage: T = Transducer([(0, 1, 0, 'a'), (0, 2, None, 'b'), 

....: (2, 1, None, 'c')]) 

sage: from sage.combinat.finite_state_machine import _FSMProcessIteratorEpsilon_ 

sage: it = _FSMProcessIteratorEpsilon_(T, initial_state=T.state(0), 

....: format_output=lambda o: ''.join(o)) 

 

To see what is going on, we let the transducer run:: 

 

sage: for current in it: 

....: print(current) 

process (1 branch) 

+ at state 2 

+-- tape at 0, [['b']] 

process (1 branch) 

+ at state 1 

+-- tape at 0, [['b', 'c']] 

process (0 branches) 

 

This class has the additional attribute ``visited_states``:: 

 

sage: it.visited_states 

{0: [''], 1: ['bc'], 2: ['b']} 

 

This means the following (let us skip the state `0` for a moment): 

State `1` can be reached by a epsilon path which write ``'bc'`` as 

output. Similarly, state `2` can be reached by writing ``'b'``. We 

started in state `0`, so this is included in visited states as 

well (``''`` means that nothing was written, which is clear, since 

no path had to be taken). 

 

We continue with the other states as initial states:: 

 

sage: it = _FSMProcessIteratorEpsilon_(T, initial_state=T.state(1), 

....: format_output=lambda o: ''.join(o)) 

sage: for current in it: 

....: print(current) 

process (0 branches) 

sage: it.visited_states 

{1: ['']} 

sage: it = _FSMProcessIteratorEpsilon_(T, initial_state=T.state(2), 

....: format_output=lambda o: ''.join(o)) 

sage: for current in it: 

....: print(current) 

process (1 branch) 

+ at state 1 

+-- tape at 0, [['c']] 

process (0 branches) 

sage: it.visited_states 

{1: ['c'], 2: ['']} 

 

TESTS:: 

 

sage: A = Automaton([(0, 1, 0), (1, 2, None), (2, 3, None), 

....: (3, 1, None), (3, 4, None), (1, 4, None)]) 

sage: it = _FSMProcessIteratorEpsilon_(A, initial_state=A.state(0)) 

sage: for current in it: 

....: print(current) 

process (0 branches) 

sage: it.visited_states 

{0: [[]]} 

sage: it = _FSMProcessIteratorEpsilon_(A, initial_state=A.state(1)) 

sage: for current in it: 

....: print(current) 

process (2 branches) 

+ at state 2 

+-- tape at 0, [[]] 

+ at state 4 

+-- tape at 0, [[]] 

process (1 branch) 

+ at state 3 

+-- tape at 0, [[]] 

process (1 branch) 

+ at state 4 

+-- tape at 0, [[]] 

process (0 branches) 

sage: it.visited_states 

{1: [[], []], 2: [[]], 3: [[]], 4: [[], []]} 

 

At this point note that in the previous output, state `1` (from 

which we started) was also reached by a non-trivial 

path. Moreover, there are two different paths from `1` to `4`. 

 

Let us continue with the other initial states:: 

 

sage: it = _FSMProcessIteratorEpsilon_(A, initial_state=A.state(2)) 

sage: for current in it: 

....: print(current) 

process (1 branch) 

+ at state 3 

+-- tape at 0, [[]] 

process (2 branches) 

+ at state 1 

+-- tape at 0, [[]] 

+ at state 4 

+-- tape at 0, [[]] 

process (1 branch) 

+ at state 4 

+-- tape at 0, [[]] 

process (0 branches) 

sage: it.visited_states 

{1: [[]], 2: [[], []], 3: [[]], 4: [[], []]} 

sage: it = _FSMProcessIteratorEpsilon_(A, initial_state=A.state(3)) 

sage: for current in it: 

....: print(current) 

process (2 branches) 

+ at state 1 

+-- tape at 0, [[]] 

+ at state 4 

+-- tape at 0, [[]] 

process (2 branches) 

+ at state 2 

+-- tape at 0, [[]] 

+ at state 4 

+-- tape at 0, [[]] 

process (0 branches) 

sage: it.visited_states 

{1: [[]], 2: [[]], 3: [[], []], 4: [[], []]} 

sage: it = _FSMProcessIteratorEpsilon_(A, initial_state=A.state(4)) 

sage: for current in it: 

....: print(current) 

process (0 branches) 

sage: it.visited_states 

{4: [[]]} 

 

:: 

 

sage: T = Transducer([(0, 1, 0, 'a'), (1, 2, None, 'b'), 

....: (2, 3, None, 'c'), (3, 1, None, 'd'), 

....: (3, 4, None, 'e'), (1, 4, None, 'f')]) 

sage: it = _FSMProcessIteratorEpsilon_(T, initial_state=T.state(0), 

....: format_output=lambda o: ''.join(o)) 

sage: for current in it: 

....: print(current) 

process (0 branches) 

sage: it.visited_states 

{0: ['']} 

sage: it = _FSMProcessIteratorEpsilon_(T, initial_state=T.state(1), 

....: format_output=lambda o: ''.join(o)) 

sage: for current in it: 

....: print(current) 

process (2 branches) 

+ at state 2 

+-- tape at 0, [['b']] 

+ at state 4 

+-- tape at 0, [['f']] 

process (1 branch) 

+ at state 3 

+-- tape at 0, [['b', 'c']] 

process (1 branch) 

+ at state 4 

+-- tape at 0, [['b', 'c', 'e']] 

process (0 branches) 

sage: it.visited_states 

{1: ['', 'bcd'], 2: ['b'], 

3: ['bc'], 4: ['f', 'bce']} 

sage: it = _FSMProcessIteratorEpsilon_(T, initial_state=T.state(2), 

....: format_output=lambda o: ''.join(o)) 

sage: for current in it: 

....: print(current) 

process (1 branch) 

+ at state 3 

+-- tape at 0, [['c']] 

process (2 branches) 

+ at state 1 

+-- tape at 0, [['c', 'd']] 

+ at state 4 

+-- tape at 0, [['c', 'e']] 

process (1 branch) 

+ at state 4 

+-- tape at 0, [['c', 'd', 'f']] 

process (0 branches) 

sage: it.visited_states 

{1: ['cd'], 2: ['', 'cdb'], 

3: ['c'], 4: ['ce', 'cdf']} 

sage: it = _FSMProcessIteratorEpsilon_(T, initial_state=T.state(3), 

....: format_output=lambda o: ''.join(o)) 

sage: for current in it: 

....: print(current) 

process (2 branches) 

+ at state 1 

+-- tape at 0, [['d']] 

+ at state 4 

+-- tape at 0, [['e']] 

process (2 branches) 

+ at state 2 

+-- tape at 0, [['d', 'b']] 

+ at state 4 

+-- tape at 0, [['d', 'f']] 

process (0 branches) 

sage: it.visited_states 

{1: ['d'], 2: ['db'], 

3: ['', 'dbc'], 4: ['e', 'df']} 

sage: it = _FSMProcessIteratorEpsilon_(T, initial_state=T.state(4), 

....: format_output=lambda o: ''.join(o)) 

sage: for current in it: 

....: print(current) 

process (0 branches) 

sage: it.visited_states 

{4: ['']} 

 

:: 

 

sage: T = Transducer([(0, 1, None, 'a'), (0, 2, None, 'b'), 

....: (1, 3, None, 'c'), (2, 3, None, 'd'), 

....: (3, 0, None, 'e')]) 

sage: it = _FSMProcessIteratorEpsilon_(T, initial_state=T.state(0), 

....: format_output=lambda o: ''.join(o)) 

sage: for current in it: 

....: print(current) 

process (2 branches) 

+ at state 1 

+-- tape at 0, [['a']] 

+ at state 2 

+-- tape at 0, [['b']] 

process (1 branch) 

+ at state 3 

+-- tape at 0, [['a', 'c'], ['b', 'd']] 

process (0 branches) 

sage: it.visited_states 

{0: ['', 'ace', 'bde'], 1: ['a'], 2: ['b'], 3: ['ac', 'bd']} 

 

:: 

 

sage: T = Transducer([(0, 1, None, None), (0, 2, None, 'b'), 

....: (1, 3, None, None), (2, 3, None, 'd'), 

....: (3, 0, None, None)]) 

sage: it = _FSMProcessIteratorEpsilon_(T, initial_state=T.state(0), 

....: format_output=lambda o: ''.join(o)) 

sage: for current in it: 

....: print(current) 

process (2 branches) 

+ at state 1 

+-- tape at 0, [[]] 

+ at state 2 

+-- tape at 0, [['b']] 

process (1 branch) 

+ at state 3 

+-- tape at 0, [[], ['b', 'd']] 

process (0 branches) 

sage: it.visited_states 

{0: ['', '', 'bd'], 1: [''], 2: ['b'], 3: ['', 'bd']} 

sage: T.state(0)._epsilon_cycle_output_empty_(T) 

False 

 

:: 

 

sage: T = Transducer([(0, 1, None, 'a'), (1, 2, None, 'b'), 

....: (0, 2, None, 'c'), (2, 3, None, 'd'), 

....: (3, 0, None, 'e')]) 

sage: it = _FSMProcessIteratorEpsilon_(T, initial_state=T.state(0), 

....: format_output=lambda o: ''.join(o)) 

sage: for current in it: 

....: print(current) 

process (2 branches) 

+ at state 1 

+-- tape at 0, [['a']] 

+ at state 2 

+-- tape at 0, [['c']] 

process (2 branches) 

+ at state 2 

+-- tape at 0, [['a', 'b']] 

+ at state 3 

+-- tape at 0, [['c', 'd']] 

process (1 branch) 

+ at state 3 

+-- tape at 0, [['a', 'b', 'd']] 

process (0 branches) 

sage: it.visited_states 

{0: ['', 'cde', 'abde'], 1: ['a'], 2: ['c', 'ab'], 3: ['cd', 'abd']} 

 

:: 

 

sage: T = Transducer([(0, 1, None, 'a'), (0, 2, None, 'b'), 

....: (0, 2, None, 'c'), (2, 3, None, 'd'), 

....: (3, 0, None, 'e')]) 

sage: it = _FSMProcessIteratorEpsilon_(T, initial_state=T.state(0), 

....: format_output=lambda o: ''.join(o)) 

sage: for current in it: 

....: print(current) 

process (2 branches) 

+ at state 1 

+-- tape at 0, [['a']] 

+ at state 2 

+-- tape at 0, [['b'], ['c']] 

process (1 branch) 

+ at state 3 

+-- tape at 0, [['b', 'd'], ['c', 'd']] 

process (0 branches) 

sage: it.visited_states 

{0: ['', 'bde', 'cde'], 1: ['a'], 2: ['b', 'c'], 3: ['bd', 'cd']} 

""" 

def __init__(self, *args, **kwargs): 

""" 

See :class:`_FSMProcessIteratorEpsilon_` and 

:class:`FSMProcessIterator` for more information. 

 

TESTS:: 

 

sage: T = Transducer([(0, 1, None, 'a'), (1, 2, None, 'b')]) 

sage: T.state(0)._epsilon_successors_(T) # indirect doctest 

{1: [['a']], 2: [['a', 'b']]} 

""" 

kwargs['input_tape'] = iter([]) 

self.TapeCache = _FSMTapeCacheDetectEpsilon_ 

self.visited_states = {} 

kwargs['check_epsilon_transitions'] = False 

return super(_FSMProcessIteratorEpsilon_, self).__init__(*args, **kwargs) 

 

 

def _push_branch_(self, state, tape_cache, outputs): 

""" 

This helper function does the actual adding of a ``state`` to 

``self._current_`` (during the update of a branch), but, 

in contrast to :meth:`FSMProcessIterator._push_branch_`, it 

skips adding when the state was already visited in this branch 

(i.e. detects whether ``state`` is in an epsilon cycle). 

 

INPUT: 

 

- ``state`` -- state which has to be processed. 

 

- ``tape_cache`` -- an instance of :class:`_FSMTapeCache_` (storing 

information what to read next). 

 

- ``outputs`` -- a list of output tapes on each of which words 

were written until reaching ``state``. 

 

OUTPUT: 

 

Nothing. 

 

TESTS:: 

 

sage: T = Transducer([(0, 1, None, 'a'), (1, 2, None, 'b'), 

....: (2, 0, None, 'c')]) 

sage: T.state(0)._epsilon_successors_(T) # indirect doctest 

{0: [['a', 'b', 'c']], 1: [['a']], 2: [['a', 'b']]} 

sage: T.state(1)._epsilon_successors_(T) # indirect doctest 

{0: [['b', 'c']], 1: [['b', 'c', 'a']], 2: [['b']]} 

sage: T.state(2)._epsilon_successors_(T) # indirect doctest 

{0: [['c']], 1: [['c', 'a']], 2: [['c', 'a', 'b']]} 

""" 

if state not in self.visited_states: 

self.visited_states[state] = [] 

self.visited_states[state].extend( 

self.format_output(o) for o in outputs) 

 

found = state in tape_cache._visited_states_ 

tape_cache._visited_states_.add(state) 

if found: 

return 

 

super(_FSMProcessIteratorEpsilon_, self)._push_branch_( 

state, tape_cache, outputs) 

 

# As tape_cache may have been discarded because current already 

# contains a branch at the same state, _visited_states_ is 

# updated manually. 

tape_at_state = self._current_[tape_cache.position][state].tape_cache 

tape_at_state._visited_states_.update(tape_cache._visited_states_) 

 

 

class _FSMProcessIteratorAll_(FSMProcessIterator): 

r""" 

This class is similar to :class:`FSMProcessIterator`, but 

accepts all transitions during process. See 

:class:`FSMProcessIterator` for more information. 

 

This is used in :meth:`FiniteStateMachine.language`. 

 

EXAMPLES:: 

 

sage: F = FiniteStateMachine( 

....: {'A': [('A', 0, 'z'), ('B', 1, 'o'), ('B', -1, 'm')], 

....: 'B': [('A', 0, 'z')]}, 

....: initial_states=['A'], final_states=['A', 'B']) 

sage: from sage.combinat.finite_state_machine import _FSMProcessIteratorAll_ 

sage: it = _FSMProcessIteratorAll_(F, max_length=3, 

....: format_output=lambda o: ''.join(o)) 

sage: for current in it: 

....: print(current) 

process (2 branches) 

+ at state 'A' 

+-- tape at 1, [['z']] 

+ at state 'B' 

+-- tape at 1, [['m'], ['o']] 

process (2 branches) 

+ at state 'A' 

+-- tape at 2, [['m', 'z'], ['o', 'z'], ['z', 'z']] 

+ at state 'B' 

+-- tape at 2, [['z', 'm'], ['z', 'o']] 

process (2 branches) 

+ at state 'A' 

+-- tape at 3, [['m', 'z', 'z'], ['o', 'z', 'z'], ['z', 'm', 'z'], 

['z', 'o', 'z'], ['z', 'z', 'z']] 

+ at state 'B' 

+-- tape at 3, [['m', 'z', 'm'], ['m', 'z', 'o'], ['o', 'z', 'm'], 

['o', 'z', 'o'], ['z', 'z', 'm'], ['z', 'z', 'o']] 

process (0 branches) 

sage: it.result() 

[Branch(accept=True, state='A', output='mzz'), 

Branch(accept=True, state='A', output='ozz'), 

Branch(accept=True, state='A', output='zmz'), 

Branch(accept=True, state='A', output='zoz'), 

Branch(accept=True, state='A', output='zzz'), 

Branch(accept=True, state='B', output='mzm'), 

Branch(accept=True, state='B', output='mzo'), 

Branch(accept=True, state='B', output='ozm'), 

Branch(accept=True, state='B', output='ozo'), 

Branch(accept=True, state='B', output='zzm'), 

Branch(accept=True, state='B', output='zzo')] 

""" 

def __init__(self, *args, **kwargs): 

""" 

See :class:`_FSMProcessIteratorAll_` and 

:class:`FSMProcessIterator` for more information. 

 

TESTS:: 

 

sage: T = Transducer([(0, 1, 0, 'a'), (1, 2, 1, 'b')], 

....: initial_states=[0], final_states=[0, 1, 2]) 

sage: T.determine_alphabets() 

sage: list(T.language(2)) # indirect doctest 

[[], ['a'], ['a', 'b']] 

""" 

max_length = kwargs.get('max_length') 

if max_length is None: 

kwargs['input_tape'] = itertools.count() 

else: 

kwargs['input_tape'] = iter(0 for _ in range(max_length)) 

self.TapeCache = _FSMTapeCacheDetectAll_ 

self.visited_states = {} 

kwargs['check_epsilon_transitions'] = False 

return super(_FSMProcessIteratorAll_, self).__init__(*args, **kwargs) 

 

 

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

 

 

@sage.misc.cachefunc.cached_function 

def setup_latex_preamble(): 

r""" 

This function adds the package ``tikz`` with support for automata 

to the preamble of Latex so that the finite state machines can be 

drawn nicely. 

 

INPUT: 

 

Nothing. 

 

OUTPUT: 

 

Nothing. 

 

See the section on :ref:`finite_state_machine_LaTeX_output` 

in the introductory examples of this module. 

 

TESTS:: 

 

sage: from sage.combinat.finite_state_machine import setup_latex_preamble 

sage: setup_latex_preamble() 

sage: ("\usepackage{tikz}" in latex.extra_preamble()) == latex.has_file("tikz.sty") 

True 

""" 

from sage.misc.latex import latex 

latex.add_package_to_preamble_if_available('tikz') 

latex.add_to_mathjax_avoid_list("tikz") 

if latex.has_file("tikz.sty"): 

latex.add_to_preamble(r'\usetikzlibrary{automata}') 

 

 

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