diff --git a/src/sage/combinat/designs/MOLS_handbook_data.py b/src/sage/combinat/designs/MOLS_handbook_data.py new file mode 100644 index 00000000000..3891d20f81b --- /dev/null +++ b/src/sage/combinat/designs/MOLS_handbook_data.py @@ -0,0 +1,571 @@ +r""" +Known lower bounds on the number of Mutually Orthogonal Latin +Squares (MOLS) of a given size. + +This module consists (almost) entirely of an internal, constant tuple +of python integers corresponding to Table 3.87 in the Handbook of +Combinatorial Designs, 2nd edition, by Colbourn and Dinitz. One public +function, :func:`lower_bound`, is provided to access it. + +Make sure we have all of the entries:: + + sage: from sage.combinat.designs import MOLS_handbook_data + sage: len(MOLS_handbook_data._LOWER_BOUNDS) + 10000 + +Jeff Dinitz's website (at UVM) provides the following two updates to +the table as printed in the second edition:: + + sage: from sage.combinat.designs import MOLS_handbook_data + sage: MOLS_handbook_data.lower_bound(60) + 5 + sage: MOLS_handbook_data.lower_bound(7968) + 31 + +""" + +_LOWER_BOUNDS: tuple[int, ...] +_LOWER_BOUNDS = ( + 0,0,1,2,3,4,1,6,7,8,2,10,5,12,3,4,15,16,3,18, # 0 + 4,5,3,22,7,24,4,26,5,28,4,30,31,5,4,5,8,36,4,5, # 20 + 7,40,5,42,5,6,4,46,8,48,6,5,5,52,5,6,7,7,5,58, # 40 + 5,60,5,6,63,7,5,66,5,6,6,70,7,72,5,7,6,6,6,78, # 60 + 9,80,8,82,6,6,6,6,7,88,6,7,6,6,6,6,7,96,6,8, # 80 + 8,100,6,102,7,7,6,106,6,108,6,6,13,112,6,7,6,8,6,6, # 100 + 7,120,6,6,6,124,6,126,127,7,6,130,6,7,6,7,7,136,6,138, # 120 + 6,7,6,10,10,7,6,7,6,148,6,150,7,8,8,7,6,156,7,6, # 140 + 9,7,6,162,6,7,6,166,7,168,6,8,6,172,6,6,14,9,6,178, # 160 + 6,180,6,6,7,9,6,10,6,8,6,190,7,192,6,7,6,196,6,198, # 180 + 7,8,6,7,6,8,6,8,14,11,10,210,6,7,6,7,7,8,6,10, # 200 + 6,12,6,222,13,8,6,226,6,228,6,7,7,232,6,7,6,7,6,238, # 220 + 7,240,6,242,6,7,6,12,7,7,6,250,6,12,9,7,255,256,6,12, # 240 + 6,8,8,262,7,8,7,10,7,268,7,270,15,16,6,13,10,276,6,9, # 260 + 7,280,6,282,6,12,6,7,15,288,6,6,6,292,6,6,7,10,10,12, # 280 + 7,7,7,7,15,15,6,306,7,7,7,310,7,312,7,10,7,316,7,10, # 300 + 15,15,6,16,8,12,6,7,7,9,6,330,7,8,7,6,8,336,6,7, # 320 + 6,10,10,342,7,7,6,346,6,348,8,12,18,352,6,9,7,9,6,358, # 340 + 8,360,6,7,7,10,6,366,15,15,7,15,7,372,7,15,7,13,7,378, # 360 + 7,12,7,382,15,15,7,15,7,388,7,16,7,8,7,7,8,396,7,7, # 380 + 15,400,7,15,11,8,7,15,8,408,7,13,8,12,10,9,18,15,7,418, # 400 + 7,420,7,15,7,16,6,7,7,10,6,430,15,432,6,15,6,18,7,438, # 420 + 7,15,7,442,7,13,7,11,15,448,7,15,7,7,7,15,7,456,7,16, # 440 + 7,460,7,462,15,15,7,466,8,8,7,15,7,15,10,18,7,15,6,478, # 460 + 15,15,6,15,8,7,6,486,7,15,6,490,6,16,6,7,15,15,6,498, # 480 + 7,12,9,502,7,15,6,15,7,508,6,15,511,18,7,15,8,12,8,15, # 500 + 8,520,10,522,12,15,8,16,15,528,7,15,8,12,7,15,8,15,10,15, # 520 + 12,540,7,15,18,7,7,546,7,8,7,18,7,7,7,7,7,556,7,12, # 540 + 15,7,7,562,7,7,6,7,7,568,6,570,7,7,15,22,8,576,7,7, # 560 + 7,8,7,10,7,8,7,586,7,18,17,7,15,592,8,15,7,7,8,598, # 580 + 14,600,12,15,7,15,16,606,18,15,7,15,8,612,8,15,7,616,7,618, # 600 + 8,22,8,15,15,624,7,8,8,16,7,630,7,8,7,8,7,12,7,8, # 620 + 9,640,7,642,7,7,7,646,8,10,7,7,7,652,7,7,15,15,7,658, # 640 + 7,660,7,15,7,15,7,22,7,15,7,15,15,672,7,24,8,676,7,15, # 660 + 7,15,7,682,8,15,7,15,15,15,7,690,8,15,7,15,7,16,7,15, # 680 + 8,700,7,18,15,15,7,15,8,708,7,15,7,22,21,15,7,15,8,718, # 700 + 15,9,8,12,10,24,12,726,7,728,16,16,18,732,7,7,22,10,8,738, # 720 + 7,7,7,742,7,15,7,8,7,10,7,750,15,15,8,15,8,756,8,15, # 740 + 7,760,8,15,8,15,8,15,15,768,8,15,8,772,8,24,23,15,8,18, # 760 + 8,18,7,26,15,15,10,786,12,15,7,15,20,15,18,15,8,796,22,16, # 780 + 24,15,8,15,8,15,8,15,8,808,8,810,8,15,8,15,15,18,8,8, # 800 + 8,820,8,822,8,15,8,826,8,828,8,15,12,16,7,8,7,26,25,838, # 820 + 8,840,8,20,8,10,8,16,15,15,12,22,7,852,16,15,22,856,7,858, # 840 + 22,15,24,862,26,15,7,15,8,15,9,15,7,15,7,15,7,876,8,15, # 860 + 15,880,8,882,8,15,7,886,7,15,8,15,10,18,8,15,13,15,8,28, # 880 + 27,16,8,8,8,22,8,906,8,18,10,910,15,14,8,15,16,10,18,918, # 900 + 24,8,22,12,24,24,26,8,28,928,7,18,7,7,7,14,7,936,7,15, # 920 + 7,940,7,22,15,15,7,946,7,12,12,15,7,952,7,15,7,15,8,15, # 940 + 15,960,29,15,8,15,8,966,8,15,8,970,10,18,12,15,15,976,16,18, # 960 + 18,15,7,982,27,15,24,15,26,22,28,990,31,31,7,15,8,996,25,26, # 980 + 7,15,21,16,19,15,7,18,15,1008,13,18,8,1012,9,22,7,28,7,1018, # 1000 + 7,1020,7,30,1023,24,7,15,9,15,9,1030,7,1032,7,15,8,16,9,1038, # 1020 + 15,15,8,15,8,15,8,15,8,1048,8,1050,8,15,8,15,15,16,8,8, # 1040 + 8,1060,8,1062,8,15,8,15,10,1068,7,15,15,28,7,24,7,15,8,15, # 1060 + 12,22,8,15,8,15,8,1086,16,15,8,1090,8,1092,8,15,8,1096,8,15, # 1080 + 8,15,8,1102,15,15,8,26,8,1108,8,18,8,15,8,15,8,1116,7,15, # 1100 + 16,18,7,1122,7,15,7,22,8,1128,7,15,8,15,10,9,15,15,7,16, # 1120 + 7,8,7,15,7,15,7,30,30,15,7,1150,15,1152,7,15,8,26,12,24, # 1140 + 12,26,7,1162,16,18,18,15,15,15,22,1170,24,15,26,24,28,15,30,30, # 1160 + 8,1180,8,15,31,15,8,1186,8,28,8,15,8,1192,8,15,8,15,8,15, # 1180 + 15,1200,8,15,8,15,8,16,8,15,8,15,8,1212,8,15,18,1216,7,22, # 1200 + 7,15,8,1222,7,24,7,15,7,1228,7,1230,15,9,8,15,7,1236,7,15, # 1220 + 7,16,8,10,8,7,8,28,8,1248,8,8,7,7,7,8,8,8,7,1258, # 1240 + 7,12,23,7,15,15,9,15,9,26,9,30,30,23,8,15,9,1276,9,1278, # 1260 + 15,30,10,1282,12,15,9,24,16,1288,18,1290,8,18,22,15,24,1296,26,15, # 1280 + 28,1300,30,1302,8,15,8,1306,30,15,8,15,31,15,12,15,8,15,8,1318, # 1300 + 8,1320,8,26,8,24,7,1326,15,15,8,1330,8,30,30,15,8,15,9,30, # 1320 + 12,15,8,30,15,30,12,15,9,26,16,24,18,15,9,20,22,22,24,15, # 1340 + 26,1360,28,28,30,30,9,1366,28,1368,30,15,9,1372,30,15,31,16,8,15, # 1360 + 8,1380,8,15,8,15,8,18,8,15,8,15,15,15,8,15,8,10,9,1398, # 1380 + 10,15,8,22,8,8,8,15,10,1408,8,16,7,9,9,22,9,12,7,8, # 1400 + 9,28,7,1422,15,24,9,1426,9,1428,7,26,7,1432,9,15,7,15,7,1438, # 1420 + 15,15,7,15,9,15,9,1446,7,15,7,1450,7,1452,9,15,15,30,30,1458, # 1440 + 8,15,8,30,8,15,8,30,10,30,12,1470,22,30,16,28,18,15,8,24, # 1460 + 22,1480,24,1482,26,18,28,1486,30,1488,13,15,8,1492,30,15,8,15,30,1498, # 1480 + 30,18,9,15,31,15,9,15,9,14,9,1510,9,24,9,9,9,36,9,30, # 1500 + 30,9,9,1522,9,30,9,9,9,30,10,1530,12,9,9,30,16,30,18,18, # 1520 + 8,26,22,1542,24,8,26,20,28,1548,30,30,15,1552,8,15,30,8,8,1558, # 1540 + 30,15,30,15,8,15,30,1566,31,15,8,1570,8,15,12,15,8,18,8,1578, # 1560 + 8,15,8,1582,15,24,8,8,8,15,8,36,7,26,8,15,8,1596,8,15, # 1580 + 24,1600,8,15,8,15,8,1606,8,1608,8,15,8,1612,7,15,15,15,8,1618, # 1600 + 8,1620,7,15,7,15,7,1626,7,15,7,15,24,22,8,15,8,1636,7,15, # 1620 + 7,15,7,30,30,15,7,26,15,30,7,15,11,30,10,30,12,1656,7,30, # 1640 + 16,30,18,1662,15,30,22,1666,24,1668,26,24,28,22,30,30,19,15,7,22, # 1660 + 30,1680,9,15,30,15,30,15,9,15,30,18,30,1692,9,15,31,1696,9,1698, # 1680 + 9,15,8,15,8,15,8,15,8,1708,21,28,15,15,8,15,10,16,7,15, # 1700 + 8,1720,9,1722,9,15,7,15,26,21,8,15,8,1732,7,15,7,15,7,36, # 1720 + 9,1740,8,15,15,15,8,1746,8,15,8,16,9,1752,9,15,9,15,8,1758, # 1740 + 26,15,8,40,9,15,8,15,8,28,8,27,8,15,8,24,15,1776,9,15, # 1760 + 8,15,8,1782,8,15,8,1786,8,1788,8,15,15,15,9,15,8,15,8,15, # 1780 + 8,1800,8,15,9,15,8,30,15,26,8,1810,8,36,7,15,9,22,9,16, # 1800 + 9,15,9,1822,26,24,9,15,9,30,30,1830,9,15,9,30,9,15,9,30, # 1820 + 15,30,12,18,9,30,16,1846,18,1848,9,30,22,16,24,15,28,30,28,28, # 1840 + 30,1860,25,22,8,22,30,1866,8,18,30,1870,30,1872,8,15,30,1876,30,1878, # 1860 + 8,15,30,8,8,8,8,15,31,1888,8,30,30,15,8,15,8,30,8,15, # 1880 + 8,1900,10,30,15,15,8,1906,16,30,18,15,8,1912,22,15,24,26,26,30, # 1900 + 28,30,30,30,27,9,7,40,30,9,8,1930,30,1932,30,8,15,15,30,15, # 1920 + 30,10,8,28,30,15,8,15,8,1948,30,1950,31,15,8,15,8,18,8,15, # 1940 + 8,36,8,15,8,15,8,15,15,15,8,26,8,1972,8,24,9,15,9,1978, # 1960 + 9,15,9,15,30,30,9,1986,9,15,30,15,10,1992,30,15,30,1996,9,1998, # 1980 + 30,16,30,2002,9,9,30,22,9,40,9,2010,30,28,30,30,31,2016,8,15, # 2000 + 27,42,8,15,23,30,21,2026,8,2028,8,30,15,30,13,15,11,30,8,2038, # 2020 + 8,15,8,30,8,30,8,22,2047,15,8,15,8,2052,8,15,8,16,10,28, # 2040 + 8,15,9,2062,15,15,8,15,8,2068,8,18,8,15,9,24,8,30,30,15, # 2060 + 30,2080,8,2082,8,15,8,2086,10,2088,12,15,8,30,16,30,18,15,8,2098, # 2080 + 22,36,24,15,26,30,28,42,30,30,30,2110,15,2112,30,15,9,28,30,24, # 2100 + 30,15,10,15,30,18,30,16,15,2128,30,2130,8,26,9,15,30,2136,30,15, # 2120 + 9,2140,9,2142,31,15,9,18,9,15,9,15,9,2152,10,15,12,15,9,16, # 2140 + 15,2160,9,15,9,14,9,15,9,15,10,14,12,40,9,15,16,15,9,2178, # 2160 + 8,15,9,36,9,15,9,2186,9,15,9,23,15,15,8,15,9,2196,12,15, # 2180 + 9,30,30,2202,8,15,9,2206,15,2208,8,30,10,2212,12,15,8,30,16,30, # 2200 + 18,2220,8,30,22,24,24,16,26,30,28,30,30,30,30,15,10,2236,30,2238, # 2220 + 16,30,30,2242,30,15,8,15,30,22,30,2250,8,18,30,15,15,36,8,15, # 2240 + 30,15,30,30,30,30,9,2266,30,2268,8,15,31,2272,10,30,12,15,8,42, # 2260 + 16,2280,18,15,8,30,22,2286,24,15,26,30,28,2292,30,30,30,2296,9,30, # 2280 + 30,15,9,46,30,30,30,15,9,2308,30,2310,30,22,9,20,30,15,9,15, # 2300 + 15,15,30,22,30,15,9,30,28,16,30,15,9,2332,30,15,31,15,9,2338, # 2320 + 8,2340,8,10,9,15,8,2346,8,28,8,2350,15,12,8,15,9,2356,9,10, # 2340 + 8,15,8,16,8,9,8,10,36,22,10,2370,8,10,8,18,26,2376,8,10, # 2360 + 8,2380,8,2382,15,15,8,15,8,2388,8,15,8,2392,8,42,10,15,13,2398, # 2380 + 15,2400,8,26,8,15,9,28,7,15,7,2410,8,18,17,15,15,2416,7,40, # 2400 + 8,15,8,2422,14,24,12,15,8,15,16,15,18,15,8,15,9,2436,9,15, # 2420 + 9,2440,10,15,10,15,10,2446,15,30,30,15,9,27,9,30,9,15,9,2458, # 2440 + 10,30,12,15,15,30,16,2466,18,15,9,30,22,2472,24,15,26,2476,28,36, # 2460 + 30,30,30,15,12,30,30,15,9,30,30,46,30,15,9,15,30,30,30,28, # 2480 + 8,40,30,2502,8,15,9,22,30,15,30,30,30,30,9,15,30,30,8,15, # 2500 + 30,2520,30,30,12,24,9,30,31,30,18,2530,9,30,22,15,24,42,26,2538, # 2520 + 28,30,30,2542,30,15,9,30,30,2548,9,2550,30,30,30,15,9,2556,30,30, # 2540 + 30,30,9,28,30,15,10,16,9,23,30,15,30,30,30,30,15,15,30,2578, # 2560 + 9,28,30,30,30,30,12,12,12,30,30,2590,31,2592,8,30,22,48,24,22, # 2580 + 26,30,28,30,30,30,30,9,15,2608,30,15,9,30,30,30,30,2616,8,26, # 2600 + 30,2620,30,42,40,30,30,36,8,15,8,24,30,2632,30,15,8,30,8,16, # 2620 + 30,18,8,15,30,15,30,2646,28,15,8,15,30,15,30,15,31,2656,10,2658, # 2640 + 8,15,9,2662,9,15,9,15,7,16,9,2670,15,15,8,24,8,2676,8,15, # 2660 + 9,15,8,2682,9,15,8,2686,15,2688,8,15,10,2692,8,15,8,15,9,2698, # 2680 + 9,36,8,15,15,15,10,2706,8,15,10,2710,9,2712,8,15,10,15,10,2718, # 2700 + 31,15,9,15,9,24,10,26,10,2728,10,2730,9,15,10,15,15,15,8,15, # 2720 + 8,2740,8,15,9,15,8,40,9,2748,8,15,42,2752,9,15,8,15,9,30, # 2740 + 30,15,8,15,9,30,7,2766,15,30,10,30,12,46,8,30,16,2776,18,15, # 2760 + 9,30,22,22,24,15,26,30,28,2788,30,2790,30,15,9,30,30,2796,9,30, # 2780 + 30,2800,30,2802,9,15,30,30,30,2808,8,30,30,28,9,15,15,30,30,2818, # 2800 + 30,15,8,30,9,24,30,15,8,18,30,18,30,2832,9,15,9,2836,30,23, # 2820 + 30,15,30,2842,8,15,8,15,31,15,10,2850,8,15,9,15,8,2856,8,15, # 2840 + 8,2860,29,13,29,15,9,46,29,18,29,15,8,16,29,22,8,15,8,2878, # 2860 + 29,42,29,15,9,29,9,2886,29,26,8,48,29,15,29,15,15,2896,9,15, # 2880 + 29,15,30,2902,8,15,8,15,8,2908,10,40,31,15,9,15,8,2916,9,15, # 2900 + 8,22,21,36,9,18,9,2926,15,28,9,15,10,15,12,15,9,15,16,2938, # 2920 + 18,16,9,26,22,15,9,15,9,15,9,15,9,2952,9,15,9,2956,9,15, # 2940 + 15,15,9,2962,9,15,9,15,9,2968,9,2970,9,15,10,15,15,15,12,15, # 2960 + 9,15,10,18,9,15,9,28,9,48,8,15,15,40,9,15,9,36,9,2998, # 2980 + 9,3000,9,15,10,15,9,30,46,15,9,3010,9,30,8,15,10,30,10,3018, # 3000 + 12,15,9,3022,16,30,18,15,9,30,22,15,24,15,26,30,28,3036,30,30, # 3020 + 31,3040,9,30,30,15,9,30,30,3048,30,26,9,42,30,30,30,30,9,30, # 3040 + 30,3060,9,15,9,30,30,3066,30,15,9,36,15,30,30,15,8,26,30,3078, # 3060 + 30,15,9,3082,9,18,30,15,30,3088,30,15,9,15,10,15,30,18,9,15, # 3080 + 9,15,9,28,31,15,9,25,9,3108,8,15,9,15,8,15,8,15,9,3118, # 3100 + 15,3120,8,15,9,3124,8,52,8,15,9,30,30,15,8,15,48,3136,9,42, # 3120 + 8,30,10,30,12,15,9,30,16,46,18,22,15,30,22,15,24,15,26,30, # 3140 + 28,30,30,3162,30,15,9,3166,30,3168,9,30,30,30,30,24,23,15,30,30, # 3160 + 30,3180,10,30,30,15,10,3186,12,30,30,3190,30,30,30,30,8,30,30,30, # 3180 + 24,30,30,3202,30,30,12,24,9,3208,30,30,30,18,30,30,22,3216,24,15, # 3200 + 30,3220,28,30,30,30,30,15,30,3228,30,15,31,52,30,30,30,15,9,40, # 3220 + 30,30,30,30,9,30,30,16,15,15,10,3250,30,3252,30,15,9,3256,9,3258, # 3240 + 30,15,10,30,30,30,30,26,10,26,9,3270,30,15,30,24,30,28,10,15, # 3260 + 15,16,30,48,9,15,10,18,28,15,30,15,9,36,30,15,31,15,9,3298, # 3280 + 10,3300,10,15,10,15,10,3306,10,15,9,15,15,3312,9,15,9,30,30,3318, # 3300 + 9,40,9,3322,9,23,9,30,14,3328,12,3330,10,30,16,30,18,46,9,30, # 3320 + 22,15,24,3342,26,30,28,3346,30,30,30,15,10,30,30,13,10,30,30,3358, # 3340 + 30,3360,10,15,30,30,30,30,12,30,30,3370,10,3372,10,30,30,15,30,30, # 3360 + 30,30,9,30,30,30,9,30,30,3388,30,3390,52,30,10,30,30,42,30,24, # 3380 + 30,39,22,40,24,23,30,3406,28,30,30,30,30,3412,30,30,30,15,30,30, # 3400 + 30,30,30,15,31,24,30,30,30,30,25,46,30,3432,9,15,10,30,30,18, # 3420 + 30,15,13,30,10,30,30,15,22,3448,30,30,30,13,24,30,26,3456,30,15, # 3440 + 30,3460,30,3462,9,15,9,3466,30,3468,9,10,15,22,10,24,30,18,9,48, # 3460 + 30,3480,30,42,10,15,30,39,31,15,9,3490,9,11,10,13,10,15,12,3498, # 3480 + 9,15,10,30,30,11,10,15,9,30,10,3510,9,30,10,30,12,3516,9,30, # 3500 + 26,30,18,15,12,30,22,3526,24,3528,26,30,28,3532,30,30,30,26,10,3538, # 3520 + 30,3540,11,30,30,30,30,3546,11,15,30,52,30,30,10,30,30,3556,10,3558, # 3540 + 11,30,30,12,30,12,11,30,15,42,30,3570,11,30,30,30,30,48,10,30, # 3560 + 10,3580,30,3582,30,30,30,16,10,36,10,15,30,3592,11,15,11,18,10,58, # 3580 + 30,15,10,13,30,15,30,3606,9,15,30,22,30,3612,9,15,31,3616,9,15, # 3600 + 10,15,9,3622,10,28,11,15,9,25,11,3630,15,15,10,15,10,3636,9,15, # 3620 + 9,15,8,3642,11,15,9,15,26,40,10,15,9,15,12,15,9,25,9,3658, # 3640 + 11,15,11,15,15,15,10,18,9,15,9,3670,10,3672,10,15,9,3676,8,21, # 3660 + 15,15,10,28,27,15,10,24,9,15,10,3690,10,18,10,16,15,3696,10,26, # 3680 + 16,3700,18,15,24,15,22,15,24,3708,26,15,28,46,10,15,10,15,9,3718, # 3700 + 10,3720,10,15,11,24,9,3726,15,15,9,15,10,3732,10,15,9,36,9,3738, # 3720 + 9,15,10,18,15,39,10,15,9,22,10,30,30,26,9,15,10,30,12,15, # 3740 + 15,3760,10,52,12,15,10,3766,16,3768,18,15,9,30,22,24,58,15,26,3778, # 3760 + 28,30,30,30,30,15,10,30,30,15,10,30,30,3792,30,15,10,3796,30,30, # 3780 + 30,30,10,3802,30,15,9,46,31,30,30,36,30,15,9,30,9,30,30,15, # 3800 + 10,3820,30,3822,30,15,10,42,10,30,30,15,30,3832,30,15,10,15,10,15, # 3820 + 30,30,10,15,10,26,10,3846,30,15,9,3850,30,3852,30,15,15,15,30,16, # 3840 + 30,15,10,3862,30,15,9,15,9,52,9,48,31,15,10,30,30,3876,10,15, # 3860 + 10,3880,9,15,10,30,10,30,15,3888,10,30,16,30,18,15,9,30,22,15, # 3880 + 24,48,26,30,60,30,30,3906,30,15,9,3910,30,15,11,30,30,3916,30,3918, # 3900 + 15,15,30,3922,30,30,9,30,30,3928,9,3930,9,30,30,15,30,30,30,30, # 3920 + 9,30,30,3942,9,30,30,3946,30,30,12,30,15,58,30,30,30,30,30,36, # 3940 + 26,40,24,15,30,30,28,3966,30,48,30,28,30,36,30,24,30,40,30,30, # 3960 + 30,18,30,16,30,30,30,30,30,3988,30,22,10,24,10,30,30,28,30,30, # 3980 + 31,4000,10,4002,30,30,10,4006,30,30,30,30,9,4012,9,30,30,30,30,4018, # 4000 + 30,4020,9,26,9,24,30,4026,9,30,9,30,30,36,30,30,9,26,30,30, # 4020 + 30,30,10,20,30,18,30,30,15,4048,30,4050,18,15,9,15,28,4056,30,15, # 4040 + 26,30,30,16,31,30,10,48,27,30,9,30,10,4072,21,30,19,30,16,4078, # 4060 + 18,30,10,30,22,15,24,60,26,30,28,4090,30,4092,30,30,4095,30,30,4098, # 4080 + 9,30,30,30,30,15,9,15,30,30,30,4110,15,30,30,15,10,22,10,30, # 4100 + 30,15,30,15,11,30,9,4126,30,4128,10,30,30,4132,30,15,11,30,10,4138, # 4120 + 30,40,30,30,30,15,10,15,10,15,30,30,9,4152,11,30,9,4156,30,4158, # 4140 + 15,30,30,22,30,15,29,24,30,22,30,42,29,15,30,24,29,4176,10,15, # 4160 + 10,36,30,46,29,15,30,52,30,58,29,15,31,15,29,15,29,15,10,15, # 4180 + 12,4200,29,15,29,15,29,15,15,15,10,4210,29,15,9,15,9,4216,10,4218, # 4200 + 29,15,9,40,29,24,29,15,10,4228,29,4230,29,15,10,15,29,18,13,26, # 4220 + 15,4240,10,4242,30,15,9,30,30,15,12,15,9,4252,13,15,31,30,10,4258, # 4240 + 12,4260,10,30,16,30,18,42,9,30,22,4270,24,4272,26,30,28,30,30,30, # 4260 + 30,15,10,4282,30,15,9,30,63,4288,30,15,10,52,30,30,30,4296,9,30, # 4280 + 30,15,10,15,15,30,30,58,30,30,30,30,9,30,30,30,9,30,30,30, # 4300 + 30,30,12,30,10,30,30,4326,30,30,30,60,22,15,24,15,30,4336,28,4338, # 4320 + 30,30,30,42,30,30,30,30,30,4348,30,30,30,30,30,28,30,4356,30,30, # 4340 + 30,48,30,4362,13,18,13,30,30,16,30,15,30,4372,30,30,30,15,30,30, # 4360 + 30,30,30,15,31,30,12,40,30,15,30,4390,30,22,13,15,13,4396,30,52, # 4380 + 15,26,12,30,13,30,30,15,13,4408,30,15,30,15,10,30,30,30,30,15, # 4400 + 12,4420,30,4422,13,15,13,20,12,42,30,15,15,15,30,15,30,30,12,22, # 4420 + 30,4440,11,15,13,15,30,4446,31,15,10,4450,12,60,13,15,12,4456,13,15, # 4440 + 13,15,13,4462,15,15,11,15,10,40,12,16,13,15,13,24,13,36,13,15, # 4460 + 16,4480,10,4482,10,15,12,15,13,4488,13,15,10,4492,13,15,15,15,10,25, # 4480 + 10,15,10,15,10,15,12,4506,13,26,13,15,15,4512,10,15,10,4516,10,4518, # 4500 + 10,15,12,4522,13,24,10,15,15,15,11,22,10,15,10,15,10,15,10,15, # 4520 + 12,18,13,15,63,15,10,4546,10,4548,10,15,10,28,10,15,12,15,12,46, # 4540 + 15,4560,11,26,11,15,10,4566,10,15,12,15,12,16,13,15,31,22,11,18, # 4560 + 10,15,10,4582,10,15,10,15,12,15,13,4590,15,15,10,15,10,4596,12,15, # 4580 + 10,42,10,4602,12,15,9,16,15,15,13,15,11,15,9,15,9,18,10,30, # 4600 + 30,4620,12,15,16,36,15,15,13,30,15,30,15,40,15,30,16,4636,18,4638, # 4620 + 31,30,22,4642,24,15,26,30,28,4648,30,4650,30,15,15,30,30,4656,12,30, # 4640 + 30,58,30,4662,14,15,30,30,30,30,14,30,63,4672,12,15,14,30,30,4678, # 4660 + 30,30,30,30,14,30,30,42,15,30,30,4690,30,30,14,30,15,30,30,36, # 4680 + 35,30,30,4702,22,15,24,15,30,30,28,30,30,30,30,30,30,52,30,30, # 4700 + 30,4720,30,4722,30,30,30,30,30,4728,30,31,30,4732,30,15,36,30,12,30, # 4720 + 30,15,30,15,30,30,30,46,30,15,30,4750,30,48,30,15,30,66,30,4758, # 4740 + 30,15,30,30,30,15,13,15,31,18,30,30,11,15,10,30,10,39,30,58, # 4760 + 15,30,30,4782,30,15,12,4786,30,4788,30,15,10,4792,30,15,14,15,15,4798, # 4780 + 15,4800,30,15,10,15,30,24,30,30,10,16,30,4812,14,15,15,4816,30,60, # 4800 + 30,15,10,22,28,24,12,15,12,15,30,4830,31,26,12,15,10,23,10,15, # 4820 + 10,46,11,28,12,15,15,36,15,15,11,15,10,22,10,15,11,15,10,42, # 4840 + 11,4860,14,15,18,17,12,30,30,15,10,4870,11,30,11,15,12,4876,11,30, # 4860 + 15,16,10,30,16,30,18,26,10,4888,22,66,24,15,26,30,28,58,30,30, # 4880 + 30,28,12,4902,30,15,10,30,30,4908,30,15,15,4912,30,30,30,30,10,4918, # 4900 + 30,15,11,15,10,30,30,15,30,15,12,4930,11,4932,30,15,11,4936,30,30, # 4920 + 30,60,10,4942,15,30,30,15,30,48,30,4950,10,15,10,15,30,4956,10,15, # 4940 + 15,40,11,30,30,15,12,4966,30,4968,30,15,10,4972,30,30,30,15,12,15, # 4960 + 30,16,10,15,11,30,10,4986,30,15,10,15,30,4992,30,30,10,18,30,4998, # 4980 + 11,15,10,5002,30,18,30,15,15,5008,10,5010,11,15,10,15,30,28,30,15, # 5000 + 10,5020,10,5022,31,15,10,15,10,46,10,15,9,15,10,15,10,15,10,5038, # 5020 + 15,5040,10,15,11,15,10,48,9,15,10,5050,10,30,30,15,63,15,10,5058, # 5040 + 11,15,10,60,10,30,12,15,10,36,16,30,18,15,10,30,22,5076,24,15, # 5060 + 26,5080,28,30,30,30,30,5086,31,30,30,15,12,30,30,30,30,15,10,5098, # 5080 + 30,5100,30,30,15,30,30,5106,11,15,10,30,30,5112,30,15,10,30,10,5118, # 5100 + 30,39,10,46,30,40,30,15,10,30,13,30,30,15,30,30,30,15,10,15, # 5120 + 11,52,30,36,11,15,13,5146,13,45,30,15,31,5152,30,15,30,26,10,30, # 5140 + 30,30,30,15,12,15,30,5166,15,15,15,5170,15,30,30,15,15,30,30,5178, # 5160 + 30,30,15,70,63,30,15,30,15,5188,30,28,30,30,16,30,18,5196,15,30, # 5180 + 22,15,30,42,30,30,28,40,39,5208,30,36,15,30,30,15,31,30,30,30, # 5200 + 30,22,15,15,30,30,30,5226,15,30,30,5230,24,5232,26,30,30,5236,30,31, # 5220 + 30,30,13,48,36,30,15,30,40,30,30,58,15,30,15,30,30,30,30,30, # 5240 + 30,5260,22,18,24,15,30,30,28,30,30,30,30,5272,30,30,30,30,30,5278, # 5260 + 31,5280,30,30,30,30,30,30,30,30,30,30,30,66,15,30,15,5296,30,16, # 5280 + 30,15,30,5302,30,30,30,15,30,5308,30,46,63,30,30,30,30,30,30,26, # 5300 + 30,30,30,5322,15,18,30,16,30,5328,12,16,10,5332,30,30,30,15,15,30, # 5320 + 30,48,30,15,31,30,30,5346,30,15,10,5350,30,52,11,15,15,30,15,30, # 5340 + 30,15,12,30,30,30,30,30,10,30,30,40,15,30,15,42,30,30,30,30, # 5360 + 16,5380,18,15,14,30,22,5386,30,18,30,30,28,5392,30,30,30,15,12,5398, # 5380 + 30,15,30,30,30,30,30,5406,31,15,30,30,30,5412,9,30,30,5416,12,5418, # 5400 + 11,30,30,15,30,15,13,66,12,60,30,5430,11,30,30,30,30,5436,15,30, # 5420 + 40,5440,30,5442,30,30,30,15,11,5448,11,15,30,30,15,15,15,30,10,52, # 5440 + 30,42,41,30,30,38,30,15,11,30,30,5470,30,30,12,16,30,5476,16,5478, # 5460 + 18,30,10,5482,30,18,24,15,30,30,30,30,30,31,30,30,38,22,36,30, # 5480 + 30,5500,40,5502,42,30,10,5506,14,15,12,24,30,36,30,15,10,15,10,5518, # 5500 + 30,5520,10,15,10,15,30,5526,10,15,30,5530,30,15,10,15,31,48,10,28, # 5520 + 10,15,9,25,9,15,10,15,12,30,30,15,15,15,11,30,10,5556,10,30, # 5540 + 10,66,12,5562,9,30,16,30,18,5568,10,30,22,5572,24,24,26,30,28,30, # 5560 + 30,5580,30,15,15,30,30,36,12,30,30,5590,30,15,13,15,30,30,30,30, # 5580 + 15,30,30,15,11,15,10,30,30,70,30,30,30,30,10,30,30,40,12,30, # 5600 + 30,30,30,5622,12,30,11,30,30,30,30,30,30,42,22,15,24,15,30,5638, # 5620 + 28,5640,30,30,30,30,30,5646,30,30,30,5650,30,5652,30,30,30,5656,30,5658, # 5640 + 30,30,30,30,30,15,10,30,10,5668,30,52,30,15,30,30,30,30,30,15, # 5660 + 30,30,30,5682,30,30,30,46,30,5688,30,30,30,5692,30,15,63,30,30,40, # 5680 + 30,5700,10,15,11,30,30,30,30,18,11,5710,30,28,30,15,30,5716,30,30, # 5700 + 30,15,30,58,30,24,10,15,31,30,10,30,30,15,11,15,30,5736,30,30, # 5720 + 11,5740,30,5742,15,15,10,30,30,5748,30,70,11,30,11,15,14,15,11,30, # 5740 + 30,30,30,15,11,15,11,72,30,15,11,28,11,22,30,15,16,52,30,5778, # 5760 + 30,15,11,5782,30,15,10,15,10,15,30,5790,31,15,10,15,11,15,10,15, # 5780 + 11,5800,11,15,9,15,11,5806,15,39,10,15,11,5812,12,15,10,15,10,15, # 5800 + 10,5820,13,15,63,24,10,5826,11,15,10,16,10,18,10,15,10,15,13,5838, # 5820 + 15,15,10,5842,10,15,10,15,11,5848,10,5850,10,15,12,15,15,5856,10,15, # 5840 + 10,5860,10,27,11,15,10,5866,10,5868,11,15,15,15,13,46,11,15,10,5878, # 5860 + 10,5880,10,15,10,15,12,15,22,21,13,42,13,70,11,15,13,5896,13,16, # 5880 + 13,15,13,5902,15,16,13,18,12,18,13,22,13,72,13,15,13,60,13,15, # 5900 + 15,30,30,5922,13,15,13,5926,13,48,13,30,13,30,13,15,15,30,16,5938, # 5920 + 18,15,10,30,22,16,24,18,26,30,28,30,30,5952,30,15,12,30,30,58, # 5940 + 13,30,30,66,30,15,13,15,30,46,45,30,11,42,30,24,13,42,11,36, # 5960 + 30,5980,30,30,30,30,16,5986,30,52,13,30,30,30,30,30,26,30,28,30, # 5980 + 30,31,30,30,30,30,36,6006,24,15,40,6010,42,30,30,30,46,30,30,30, # 6000 + 30,30,30,30,30,30,30,30,30,6028,30,45,30,30,30,30,30,6036,13,30, # 6020 + 13,30,30,6042,30,15,30,6046,30,30,30,15,30,6052,30,30,30,30,30,72, # 6040 + 30,30,30,30,30,30,30,6066,12,30,30,30,30,6072,12,24,12,58,30,6078, # 6060 + 46,30,10,30,30,15,30,24,30,6088,30,6090,30,15,30,15,30,15,13,15, # 6080 + 30,6100,30,30,30,15,12,30,30,40,30,30,31,6112,30,30,11,30,10,30, # 6100 + 30,6120,30,30,16,48,18,15,15,45,22,6130,30,6132,30,30,28,30,30,30, # 6120 + 30,15,12,6142,30,30,30,30,30,30,30,6150,30,15,30,30,30,46,11,30, # 6140 + 30,60,30,6162,30,30,30,15,30,30,30,30,11,6172,30,30,31,45,30,36, # 6160 + 30,30,12,30,11,30,30,30,30,30,30,40,22,15,24,15,30,6196,28,6198, # 6180 + 30,30,30,6202,30,30,30,30,63,30,30,6210,30,30,30,30,30,6216,30,30, # 6200 + 30,6220,30,48,47,30,11,44,30,6228,30,15,30,38,30,36,30,15,30,30, # 6220 + 30,6240,30,30,30,30,30,6246,30,30,30,30,30,36,30,31,30,6256,30,30, # 6240 + 36,15,15,6262,44,30,42,30,15,6268,46,6270,48,30,30,30,30,6276,30,16, # 6260 + 30,15,30,60,15,15,30,6286,30,30,30,26,15,15,30,30,30,30,30,6298, # 6280 + 30,6300,15,15,31,30,30,30,30,15,15,6310,15,58,15,15,15,6316,30,70, # 6300 + 30,15,15,6322,15,39,30,16,15,6328,15,30,30,15,15,30,30,6336,30,15, # 6320 + 15,16,30,6342,15,15,15,15,30,18,30,15,15,6352,15,18,15,15,15,6358, # 6340 + 30,6360,30,15,15,15,30,6366,31,15,15,22,15,6372,15,15,15,15,15,6378, # 6360 + 15,15,15,15,15,15,15,15,15,6388,15,70,15,15,15,15,15,6396,15,78, # 6380 + 24,36,15,18,11,15,15,42,15,48,15,15,15,52,15,15,15,16,15,48, # 6400 + 15,6420,15,22,15,24,15,6426,15,15,15,58,15,15,13,15,15,40,10,46, # 6420 + 15,15,15,16,15,15,15,15,15,6448,15,6450,11,26,13,16,15,15,15,15, # 6440 + 15,15,15,22,63,15,15,28,15,6468,12,15,15,6472,15,15,15,15,15,15, # 6460 + 15,6480,15,15,13,15,13,15,15,15,15,6490,15,42,15,15,31,72,13,66, # 6480 + 15,15,12,15,11,15,15,26,15,22,15,16,15,15,13,15,15,18,12,16, # 6500 + 11,6520,15,15,15,15,15,60,24,6528,13,15,15,46,11,17,12,15,15,15, # 6520 + 15,30,30,15,15,15,15,6546,11,15,14,6550,11,6552,12,15,15,78,16,30, # 6540 + 31,6560,15,6562,22,15,24,16,26,6568,28,6570,30,30,30,24,15,6576,30,15, # 6560 + 11,6580,30,30,30,15,11,15,30,30,30,30,63,30,30,15,11,15,11,6598, # 6580 + 30,15,30,15,11,30,10,6606,30,15,10,30,30,30,30,16,11,30,11,6618, # 6600 + 30,15,30,36,30,52,11,15,11,15,30,30,11,15,11,30,11,6636,30,15, # 6620 + 15,30,30,15,30,15,11,30,30,60,30,15,11,6652,30,15,15,15,11,6658, # 6640 + 11,6660,30,15,11,15,30,58,30,30,12,15,30,6672,12,15,11,30,30,6678, # 6660 + 30,15,12,40,12,15,14,15,18,6688,30,6690,30,15,11,15,12,36,30,15, # 6680 + 14,6700,12,6702,30,16,11,30,30,6708,30,15,11,48,30,15,11,15,14,6718, # 6700 + 30,30,30,80,11,24,12,23,11,15,11,52,51,6732,30,48,15,6736,30,22, # 6720 + 30,42,11,40,12,15,11,15,16,16,18,42,31,30,22,28,24,28,26,15, # 6740 + 28,6760,30,6762,12,15,10,66,36,17,11,15,40,15,42,24,15,26,46,6778, # 6760 + 48,6780,15,15,52,15,15,17,12,15,13,6790,15,6792,15,15,15,15,15,15, # 6780 + 15,15,15,6802,15,17,15,15,15,23,15,48,15,15,15,15,15,16,15,15, # 6800 + 15,18,15,6822,15,24,15,6826,15,6828,15,15,15,6832,15,15,15,15,15,15, # 6820 + 15,6840,15,15,15,15,15,40,63,21,15,15,15,15,15,15,15,6856,15,6858, # 6840 + 15,15,15,6862,15,15,15,15,15,6868,15,6870,15,16,15,15,15,15,15,15, # 6860 + 31,15,15,6882,15,15,15,70,15,6888,15,15,15,60,15,15,15,15,15,6898, # 6880 + 15,66,15,15,15,15,15,6906,15,15,15,6910,26,30,30,15,15,6916,15,30, # 6900 + 15,16,15,30,15,30,15,15,15,40,16,30,18,15,15,30,22,24,24,26, # 6920 + 26,30,28,52,31,30,30,6946,15,6948,30,15,15,30,30,30,30,15,15,6958, # 6940 + 30,6960,30,30,15,30,30,6966,15,16,15,6970,30,18,30,15,63,6976,15,30, # 6960 + 30,15,15,6982,30,30,30,15,15,30,15,6990,30,15,30,30,30,6996,15,15, # 6980 + 15,7000,30,46,15,15,15,30,15,42,30,15,15,7012,30,15,30,15,15,7018, # 7000 + 30,30,30,15,15,24,30,7026,15,15,15,78,15,30,30,15,15,30,30,7038, # 7020 + 30,30,15,7042,30,30,15,30,15,52,30,30,30,30,16,30,18,7056,15,30, # 7040 + 22,30,30,30,30,30,28,36,30,7068,30,15,16,30,30,30,30,30,30,7078, # 7060 + 30,72,30,15,30,30,30,30,15,30,30,15,30,40,30,30,30,46,30,39, # 7080 + 30,30,15,7102,30,30,30,30,30,7108,30,30,30,30,15,30,30,30,30,30, # 7100 + 30,7120,22,16,30,15,30,7126,28,7128,30,30,30,30,30,30,31,30,30,58, # 7120 + 30,36,30,30,30,30,30,30,30,30,30,7150,30,22,13,30,15,30,30,7158, # 7140 + 30,16,30,30,30,30,30,15,30,66,30,70,30,30,30,30,30,7176,30,30, # 7160 + 30,42,30,15,15,30,30,7186,30,30,13,15,13,7192,30,30,30,30,15,30, # 7180 + 30,18,30,30,30,30,30,7206,30,80,30,7210,30,7212,15,15,30,30,30,7218, # 7200 + 30,15,10,30,30,30,30,30,30,7228,30,30,63,30,30,30,30,7236,30,30, # 7220 + 16,30,18,7242,14,30,22,7246,30,30,30,30,28,7252,30,30,30,15,13,30, # 7240 + 30,52,30,30,31,30,30,42,30,15,30,30,30,30,13,30,30,18,30,30, # 7260 + 30,30,30,7282,30,15,11,30,12,36,30,30,30,30,30,30,30,7296,30,30, # 7280 + 15,48,30,66,30,30,30,7306,15,7308,30,16,30,70,13,15,13,30,15,30, # 7300 + 30,7320,30,30,30,24,30,16,31,30,30,7330,30,7332,15,15,30,15,15,40, # 7320 + 15,30,15,30,30,15,15,15,30,7348,30,7350,15,15,30,30,15,15,15,30, # 7340 + 30,30,30,36,15,30,15,52,15,7368,15,30,30,72,30,58,15,15,15,46, # 7360 + 30,60,15,30,15,30,30,82,15,30,30,30,30,7392,15,15,30,16,15,48, # 7380 + 24,15,30,30,30,15,15,15,15,30,30,7410,15,15,30,30,30,7416,15,30, # 7400 + 30,40,30,15,28,30,16,30,18,16,15,30,22,7432,30,20,26,30,28,42, # 7420 + 30,30,30,18,15,30,30,22,15,30,30,7450,30,28,15,15,31,7456,30,7458, # 7440 + 15,30,30,16,15,15,15,30,30,15,30,30,30,30,15,30,30,7476,15,30, # 7460 + 30,7480,30,30,15,30,15,7486,30,7488,30,30,30,58,57,15,24,54,30,7498, # 7480 + 28,30,30,48,30,46,30,7506,30,30,30,30,30,30,30,36,30,7516,30,72, # 7500 + 30,30,30,7522,30,31,15,30,15,7528,36,16,30,30,40,30,42,7536,30,30, # 7520 + 46,7540,48,30,30,30,52,7546,30,7548,30,30,58,30,30,30,22,30,30,7558, # 7540 + 30,7560,28,30,30,30,30,30,30,30,30,66,30,7572,30,30,30,7576,30,30, # 7560 + 30,30,30,7582,30,30,30,26,30,7588,30,7590,30,15,30,16,30,70,30,30, # 7580 + 30,30,30,7602,30,30,30,7606,30,30,30,30,30,30,30,39,58,30,13,30, # 7600 + 30,7620,30,30,15,60,15,30,30,30,30,30,30,30,30,15,30,30,30,7638, # 7620 + 30,30,30,7642,30,15,30,15,31,7648,30,30,30,30,30,15,15,15,30,30, # 7640 + 30,46,30,78,30,30,15,30,30,7668,30,30,30,7672,14,30,15,15,15,30, # 7660 + 15,7680,30,30,30,15,15,7686,15,15,30,7690,15,48,30,30,30,42,30,7698, # 7680 + 30,30,30,7702,30,15,30,15,15,15,30,17,31,30,30,15,15,7716,15,15, # 7700 + 14,15,14,7722,30,30,30,7726,15,58,30,30,30,15,15,30,15,15,14,70, # 7720 + 13,7740,15,30,63,30,15,60,59,15,15,56,15,7752,14,15,30,7756,17,7758, # 7740 + 30,15,30,16,16,17,30,15,28,38,22,36,30,15,30,24,31,30,30,31, # 7760 + 14,30,30,42,36,15,14,30,40,7788,42,30,15,7792,46,15,48,30,16,30, # 7780 + 56,28,15,30,22,15,58,36,60,30,28,72,30,30,30,15,15,7816,30,16, # 7800 + 15,30,30,7822,30,24,14,15,30,7828,30,40,15,30,30,16,15,16,13,30, # 7820 + 30,7840,30,15,14,30,15,30,30,46,15,30,30,7852,30,15,15,80,13,30, # 7840 + 30,23,30,30,30,15,15,7866,15,15,30,30,15,7872,13,30,15,7876,30,7878, # 7860 + 15,30,30,7882,30,15,15,30,30,30,30,15,15,15,30,15,15,52,15,30, # 7880 + 15,7900,30,15,18,15,30,7906,30,30,15,26,30,40,15,16,15,30,30,7918, # 7900 + 30,7920,15,30,15,24,15,7926,15,30,30,30,30,7932,15,15,30,7936,30,16, # 7920 + 15,30,30,46,30,15,30,30,30,7948,30,7950,30,16,30,18,15,72,30,22, # 7940 + 30,30,30,7962,30,28,30,30,31,30,15,15,30,30,30,15,30,30,30,78, # 7960 + 30,22,15,30,30,30,30,48,30,30,22,60,30,7992,30,30,28,30,30,30, # 7980 + 63,30,30,52,30,15,30,30,30,8008,30,8010,30,18,30,30,30,8016,30,30, # 8000 + 30,15,30,70,30,30,30,22,30,15,30,30,31,30,30,15,30,30,30,8038, # 8020 + 30,15,30,30,30,30,30,15,30,30,30,82,15,8052,30,15,30,30,15,8058, # 8040 + 15,30,30,30,30,15,15,30,30,8068,30,15,30,30,30,30,30,40,30,15, # 8060 + 30,8080,15,58,30,30,30,8086,30,8088,15,15,30,8092,30,30,31,18,30,30, # 8080 + 15,8100,30,30,30,30,30,66,15,30,15,8110,15,25,15,30,30,8116,30,22, # 8100 + 15,15,15,8122,30,15,15,30,63,62,30,46,59,30,30,30,30,78,53,15, # 8120 + 51,17,15,16,30,27,30,8146,30,28,41,22,39,30,30,26,30,28,33,40, # 8140 + 31,8160,29,30,30,36,30,8166,23,40,21,8170,18,15,17,46,22,48,30,8178, # 8160 + 26,80,28,48,30,30,30,58,26,60,30,8190,8191,30,30,30,30,15,15,24, # 8180 + 30,58,30,30,15,30,30,28,30,8208,30,30,30,42,30,22,15,30,30,8218, # 8200 + 30,8220,15,30,31,30,30,18,15,30,15,8230,30,8232,30,30,30,8236,15,57, # 8220 + 15,15,30,8242,15,15,15,30,15,72,30,36,15,30,30,15,62,22,15,30, # 8240 + 30,30,30,8262,15,15,30,18,15,8268,15,30,15,8272,30,24,15,15,30,30, # 8260 + 30,48,14,15,30,30,15,8286,15,30,30,8290,30,8292,15,30,15,8296,15,42, # 8280 + 15,30,30,30,30,16,15,15,15,15,30,8310,15,30,15,30,30,8316,15,30, # 8300 + 30,52,30,15,15,17,30,25,15,8328,15,15,30,30,30,15,15,15,14,30, # 8320 + 30,18,15,80,30,30,30,16,15,30,30,30,30,8352,14,30,16,60,18,15, # 8340 + 14,57,22,8362,30,30,26,30,28,8368,30,30,30,30,15,66,30,8376,30,30, # 8360 + 30,30,30,82,63,30,30,8386,30,8388,15,30,30,22,30,16,30,30,30,36, # 8380 + 30,30,30,30,30,30,30,30,15,30,30,30,30,46,15,30,31,30,30,8418, # 8400 + 30,30,30,8422,22,24,24,15,30,8428,28,8430,30,30,30,30,30,30,30,30, # 8420 + 30,30,30,8442,30,30,30,8446,30,30,30,30,30,78,30,15,15,30,15,30, # 8440 + 30,8460,30,30,30,30,30,8466,30,16,30,42,30,36,30,30,30,48,30,60, # 8460 + 30,30,30,30,30,15,22,30,30,30,30,30,28,15,29,30,30,30,30,30, # 8480 + 29,8500,30,15,30,30,30,46,30,66,65,16,30,8512,30,15,29,15,30,56, # 8500 + 30,8520,30,15,29,15,30,8526,30,30,30,44,30,42,29,30,30,8536,30,8538, # 8520 + 30,31,29,8542,29,30,36,30,15,82,40,30,42,15,15,20,46,42,48,30, # 8540 + 29,30,52,8562,30,30,30,30,58,30,60,30,30,8572,30,24,66,30,30,28, # 8560 + 30,8580,30,15,29,22,15,30,30,18,30,70,30,30,30,30,30,8596,30,8598, # 8580 + 30,15,30,30,16,30,18,15,31,8608,22,78,30,30,29,30,28,30,30,30, # 8600 + 30,36,15,8622,30,16,30,8626,30,8628,30,15,29,88,30,30,30,30,15,52, # 8620 + 30,8640,30,16,30,30,30,8646,30,15,15,40,30,30,30,16,29,30,30,30, # 8640 + 30,15,29,8662,30,30,30,80,30,8668,30,30,31,15,15,24,30,8676,15,15, # 8660 + 15,8680,15,30,30,15,15,30,30,8688,30,15,15,8692,30,30,30,15,15,8698, # 8680 + 30,18,15,16,16,30,15,8706,30,15,15,30,30,8712,30,30,15,30,30,8718, # 8700 + 15,30,15,30,30,30,30,30,16,30,18,8730,15,30,22,30,30,8736,30,30, # 8720 + 28,8740,30,30,30,15,15,8746,30,30,30,30,30,8752,30,30,30,15,30,30, # 8740 + 30,8760,15,30,30,15,30,30,63,30,30,48,30,30,30,30,15,66,30,8778, # 8760 + 30,30,30,8782,30,30,30,30,15,30,30,58,30,30,30,30,22,30,30,30, # 8780 + 31,30,28,8802,30,30,30,8806,30,30,30,30,30,30,30,30,30,30,30,8818, # 8800 + 30,8820,30,30,30,30,30,30,30,80,30,8830,30,72,30,15,30,8836,30,8838, # 8820 + 30,15,30,36,30,30,30,30,30,8848,30,52,30,30,30,30,30,16,30,30, # 8840 + 30,8860,30,8862,31,15,15,8866,30,48,30,30,15,30,30,70,30,30,30,30, # 8860 + 30,82,30,15,30,28,30,8886,15,15,30,30,30,8892,30,16,63,15,30,30, # 8880 + 30,30,30,30,30,30,15,30,30,58,30,30,30,30,15,30,15,36,15,30, # 8900 + 15,30,30,8922,30,15,15,78,15,8928,30,30,15,8932,30,30,30,30,30,30, # 8920 + 30,8940,30,30,30,40,30,22,15,30,30,8950,30,30,30,15,15,52,15,30, # 8940 + 30,30,30,8962,30,30,30,30,30,8968,30,8970,30,18,30,30,16,46,18,15, # 8960 + 30,30,30,30,30,30,26,30,30,88,30,36,31,30,15,30,30,15,30,8998, # 8980 + 30,9000,30,15,15,30,30,9006,30,30,15,9010,30,9012,30,16,30,70,69,30, # 9000 + 30,66,15,30,30,30,30,60,15,9028,30,30,30,15,16,30,30,30,30,48, # 9020 + 30,9040,30,9042,30,15,28,82,30,9048,30,15,15,34,36,30,31,30,40,9058, # 9040 + 42,16,30,24,46,30,48,9066,30,18,52,46,30,42,15,15,58,30,60,30, # 9060 + 30,63,15,30,66,30,30,30,70,60,30,9090,15,30,15,30,30,30,30,30, # 9080 + 16,30,18,9102,15,30,22,30,30,9108,30,30,28,30,30,30,30,15,15,30, # 9100 + 30,30,30,30,30,72,30,9126,30,16,30,30,30,9132,15,30,30,9136,30,30, # 9120 + 30,30,30,40,30,15,15,30,15,30,30,9150,63,80,30,30,30,9156,30,30, # 9140 + 15,9160,30,16,30,30,30,88,15,52,30,30,30,9172,15,24,15,30,15,66, # 9160 + 30,9180,30,30,30,30,30,9186,30,30,30,30,30,28,15,15,30,30,30,9198, # 9180 + 30,30,30,9202,30,30,15,15,30,9208,30,60,15,15,30,30,30,30,15,30, # 9200 + 30,9220,30,22,15,30,15,9226,30,18,30,30,30,30,30,15,30,15,15,9238, # 9220 + 30,9240,30,30,15,30,30,16,31,30,30,30,30,18,15,15,30,9256,15,46, # 9240 + 15,26,30,58,30,16,15,15,15,15,15,72,71,15,30,68,30,9276,15,30, # 9260 + 63,9280,30,9282,15,30,15,36,16,15,18,30,15,9292,30,48,24,15,26,16, # 9280 + 28,70,30,31,15,17,30,40,36,30,30,9310,40,66,42,30,30,26,46,9318, # 9300 + 48,30,30,9322,52,24,30,15,15,30,58,17,60,30,30,63,68,9336,66,30, # 9320 + 30,9340,70,9342,72,30,30,15,15,9348,15,40,30,46,30,15,16,15,15,48, # 9340 + 30,15,15,15,30,15,30,16,15,26,15,9370,30,15,15,15,31,9376,15,82, # 9360 + 15,15,15,17,15,15,15,15,15,40,15,9390,15,15,15,15,15,9396,15,15, # 9380 + 15,15,15,9402,15,15,15,22,15,9408,15,15,15,9412,15,15,15,15,15,9418, # 9400 + 15,9420,15,26,15,15,15,15,15,15,15,9430,15,9432,15,15,15,9436,15,9438, # 9420 + 15,15,15,15,15,15,15,15,15,15,15,15,15,29,15,16,15,48,15,15, # 9440 + 15,9460,15,9462,15,15,15,9466,15,16,15,15,36,9472,15,24,15,18,15,9478, # 9460 + 15,18,15,15,15,23,15,52,15,16,15,9490,15,15,15,22,15,9496,15,26, # 9480 + 15,28,15,30,31,18,15,15,15,36,15,9510,15,17,15,15,15,30,30,15, # 9500 + 15,9520,15,88,15,15,15,30,15,30,15,26,15,9532,16,30,63,15,15,9538, # 9520 + 22,15,24,15,26,30,28,9546,30,30,30,9550,15,40,30,15,15,30,30,78, # 9540 + 30,15,15,72,30,30,30,30,31,30,30,17,15,15,15,30,30,60,30,15, # 9560 + 15,30,15,30,30,15,15,9586,30,42,30,15,15,52,15,30,30,15,30,30, # 9580 + 30,9600,15,15,15,15,30,30,15,15,15,30,15,9612,30,15,15,58,30,9618, # 9600 + 30,15,15,9622,30,30,30,15,15,9628,30,9630,31,15,15,30,15,30,30,15, # 9620 + 15,30,30,9642,30,30,15,30,30,9648,15,30,15,48,30,30,30,30,16,30, # 9640 + 18,9660,15,30,63,30,30,30,30,30,28,30,30,30,30,15,15,9676,30,9678, # 9660 + 30,30,30,30,30,30,30,15,30,9688,30,30,15,30,30,17,30,9696,30,30, # 9680 + 30,88,30,30,30,30,15,30,30,30,30,30,30,30,30,30,30,30,15,9718, # 9700 + 30,9720,30,30,30,30,22,70,30,30,30,36,28,9732,30,30,30,30,30,9738, # 9720 + 30,30,30,9742,30,30,30,30,30,9748,30,48,30,30,30,30,30,30,30,30, # 9740 + 30,42,30,30,30,16,30,9766,30,9768,30,16,30,30,30,30,30,30,30,30, # 9760 + 30,9780,30,30,30,30,30,9786,30,30,30,9790,30,30,30,16,15,96,30,40, # 9780 + 30,80,15,9802,30,15,30,30,30,30,30,9810,30,15,30,15,30,9816,15,15, # 9800 + 30,30,30,30,31,16,15,30,30,9828,30,30,30,9832,30,30,15,30,30,9838, # 9820 + 30,30,30,30,16,30,18,42,15,30,22,9850,30,58,30,30,28,9856,30,9858, # 9840 + 30,30,15,30,30,30,30,30,30,70,30,9870,30,30,30,78,30,30,17,30, # 9860 + 30,40,30,9882,30,30,30,9886,30,15,15,30,30,30,30,30,30,30,30,30, # 9880 + 30,9900,30,30,30,30,30,9906,30,30,30,30,30,30,30,30,30,46,30,15, # 9900 + 16,30,30,9922,30,24,30,30,30,9928,30,9930,30,30,30,30,30,39,15,15, # 9920 + 30,9940,30,60,30,30,30,30,30,9948,17,15,31,36,30,30,15,16,30,46, # 9940 + 30,30,15,40,30,30,30,9966,15,30,15,58,30,9972,30,30,30,30,30,30, # 9960 + 30,15,15,66,30,30,30,30,15,30,30,96,30,30,30,30,30,18,15,15 # 9980 +) + + +def lower_bound(order: int) -> int: + r""" + Return the best known lower bound on the number of MOLS of + the given ``order``. + + The source of this information is Table 3.87 in the Handbook of + Combinatorial Designs, 2nd edition, by Colbourn and Dinitz. A few + updates have subsequently been provided on Jeff Dinitz's website. + + Parameters + ---------- + + order : int + The order (also known as the side) for which you'd like a lower + bound on the number of MOLS instances. In the language of the + Handbook, this is ``n``, and it should be between 0 and 9999. + + Returns + ------- + + int + A lower bound on the number of MOLS. + + Raises + ------ + + IndexError + If you ask for an order that isn't contained in the table. + + Examples + -------- + + There are no MOLS of order zero:: + + sage: from sage.combinat.designs import MOLS_handbook_data + sage: MOLS_handbook_data.lower_bound(0) + 0 + + """ + return _LOWER_BOUNDS[order] diff --git a/src/sage/combinat/designs/latin_squares.py b/src/sage/combinat/designs/latin_squares.py index 69b19540c22..27452495862 100644 --- a/src/sage/combinat/designs/latin_squares.py +++ b/src/sage/combinat/designs/latin_squares.py @@ -75,7 +75,7 @@ 0| + + 20| 40| - 60| + + 60| 80| 100| 120| @@ -126,7 +126,6 @@ from sage.rings.integer import Integer from sage.categories.sets_cat import EmptySetError from sage.misc.unknown import Unknown -from sage.env import COMBINATORIAL_DESIGN_DATA_DIR def are_mutually_orthogonal_latin_squares(l, verbose=False): @@ -500,13 +499,13 @@ def MOLS_table(start,stop=None,compare=False,width=None): 0| + + 20| 40| - 60| + + 60| 80| sage: MOLS_table(50, 100, compare=True) 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ________________________________________________________________________________ 40| - 60| + + 60| 80| """ from .orthogonal_arrays import largest_available_k @@ -520,11 +519,6 @@ def MOLS_table(start,stop=None,compare=False,width=None): if stop <= start: return - if compare: - handbook_file = open("{}/MOLS_table.txt".format(COMBINATORIAL_DESIGN_DATA_DIR), 'r') - hb = [int(_) for _ in handbook_file.readlines()[9].split(',')] - handbook_file.close() - # choose an appropriate width (needs to be >= 3 because "+oo" should fit) if width is None: width = max(3, Integer(stop-1).ndigits(10)) @@ -537,9 +531,11 @@ def MOLS_table(start,stop=None,compare=False,width=None): print("\n{:>{width}}|".format(i, width=width), end="") k = largest_available_k(i)-2 if compare: - if i < 2 or hb[i] == k: + from . import MOLS_handbook_data + lower_bound = MOLS_handbook_data.lower_bound(i) + if i < 2 or lower_bound == k: c = "" - elif hb[i] < k: + elif lower_bound < k: c = "+" else: c = "-" diff --git a/src/sage/databases/jones.py b/src/sage/databases/jones.py index aaab1397f0a..5f996662964 100644 --- a/src/sage/databases/jones.py +++ b/src/sage/databases/jones.py @@ -79,8 +79,6 @@ from sage.features.databases import DatabaseJones -JONESDATA = os.path.join(SAGE_SHARE, 'jones') # should match the filename set in DatabaseJones - def sortkey(K): """ @@ -160,8 +158,10 @@ def _init(self, path): for Y in os.listdir(Z): if Y[-3:] == ".gp": self._load(Z, Y) - os.makedirs(JONESDATA, exist_ok=True) - save(self.root, JONESDATA + "/jones.sobj") + + data_dir = os.path.dirname(DatabaseJones().absolute_filename()) + os.makedirs(data_dir, exist_ok=True) + save(self.root, os.path.join(data_dir, "jones.sobj")) def unramified_outside(self, S, d=None, var='a'): """ diff --git a/src/sage/databases/sql_db.py b/src/sage/databases/sql_db.py index 469fe454afb..7e027673cc0 100644 --- a/src/sage/databases/sql_db.py +++ b/src/sage/databases/sql_db.py @@ -250,7 +250,6 @@ def construct_skeleton(database): skeleton = {} cur = database.__connection__.cursor() exe = cur.execute("SELECT name FROM sqlite_master WHERE TYPE='table'") - from sage.env import GRAPHS_DATA_DIR for table in exe.fetchall(): skeleton[table[0]] = {} exe1 = cur.execute("PRAGMA table_info(%s)" % table[0]) @@ -264,8 +263,7 @@ def construct_skeleton(database): exe2 = cur.execute("PRAGMA index_list(%s)" % table[0]) for col in exe2.fetchall(): if col[1].find('sqlite') == -1: - if database.__dblocation__ == \ - os.path.join(GRAPHS_DATA_DIR,'graphs.db'): + if os.path.basename(database.__dblocation__) == 'graphs.db': name = col[1] else: name = col[1][len(table[0])+3:] diff --git a/src/sage/env.py b/src/sage/env.py index 39d09528788..cadcb820c5a 100644 --- a/src/sage/env.py +++ b/src/sage/env.py @@ -195,18 +195,26 @@ def var(key: str, *fallbacks: Optional[str], force: bool = False) -> Optional[st SAGE_ARCHFLAGS = var("SAGE_ARCHFLAGS", "unset") SAGE_PKG_CONFIG_PATH = var("SAGE_PKG_CONFIG_PATH") +# colon-separated search path for databases. +SAGE_DATA_PATH = var("SAGE_DATA_PATH", + os.pathsep.join(filter(None, [ + join(DOT_SAGE, "db"), + join(SAGE_SHARE, "sagemath"), + SAGE_SHARE, + ]))) + +# database directories, the default is to search in SAGE_DATA_PATH +CREMONA_LARGE_DATA_DIR = var("CREMONA_LARGE_DATA_DIR") +CREMONA_MINI_DATA_DIR = var("CREMONA_MINI_DATA_DIR") +ELLCURVE_DATA_DIR = var("ELLCURVE_DATA_DIR") +GRAPHS_DATA_DIR = var("GRAPHS_DATA_DIR") +POLYTOPE_DATA_DIR = var("POLYTOPE_DATA_DIR") + # installation directories for various packages -GRAPHS_DATA_DIR = var("GRAPHS_DATA_DIR", join(SAGE_SHARE, "graphs")) -ELLCURVE_DATA_DIR = var("ELLCURVE_DATA_DIR", join(SAGE_SHARE, "ellcurves")) -POLYTOPE_DATA_DIR = var("POLYTOPE_DATA_DIR", join(SAGE_SHARE, "reflexive_polytopes")) - -COMBINATORIAL_DESIGN_DATA_DIR = var("COMBINATORIAL_DESIGN_DATA_DIR", join(SAGE_SHARE, "combinatorial_designs")) -CREMONA_MINI_DATA_DIR = var("CREMONA_MINI_DATA_DIR", join(SAGE_SHARE, "cremona")) -CREMONA_LARGE_DATA_DIR = var("CREMONA_LARGE_DATA_DIR", join(SAGE_SHARE, "cremona")) -JMOL_DIR = var("JMOL_DIR", join(SAGE_SHARE, "jmol")) +JMOL_DIR = var("JMOL_DIR") MATHJAX_DIR = var("MATHJAX_DIR", join(SAGE_SHARE, "mathjax")) MTXLIB = var("MTXLIB", join(SAGE_SHARE, "meataxe")) -THREEJS_DIR = var("THREEJS_DIR", join(SAGE_SHARE, "threejs-sage")) +THREEJS_DIR = var("THREEJS_DIR") PPLPY_DOCS = var("PPLPY_DOCS", join(SAGE_SHARE, "doc", "pplpy")) MAXIMA = var("MAXIMA", "maxima") MAXIMA_FAS = var("MAXIMA_FAS") @@ -313,6 +321,7 @@ def sage_include_directories(use_sources=False): return dirs + def get_cblas_pc_module_name() -> str: """ Return the name of the BLAS libraries to be used. @@ -420,7 +429,7 @@ def cython_aliases(required_modules=None, aliases["ECL_INCDIR"] = list(map(lambda s: s[2:], filter(lambda s: s.startswith('-I'), ecl_cflags))) aliases["ECL_LIBDIR"] = list(map(lambda s: s[2:], filter(lambda s: s.startswith('-L'), ecl_libs))) aliases["ECL_LIBRARIES"] = list(map(lambda s: s[2:], filter(lambda s: s.startswith('-l'), ecl_libs))) - aliases["ECL_LIBEXTRA"] = list(filter(lambda s: not s.startswith(('-l','-L')), ecl_libs)) + aliases["ECL_LIBEXTRA"] = list(filter(lambda s: not s.startswith(('-l', '-L')), ecl_libs)) continue else: try: @@ -439,7 +448,7 @@ def cython_aliases(required_modules=None, # include search order matters. aliases[var + "INCDIR"] = pc['include_dirs'] aliases[var + "LIBDIR"] = pc['library_dirs'] - aliases[var + "LIBEXTRA"] = list(filter(lambda s: not s.startswith(('-l','-L')), libs.split())) + aliases[var + "LIBEXTRA"] = list(filter(lambda s: not s.startswith(('-l', '-L')), libs.split())) aliases[var + "LIBRARIES"] = pc['libraries'] # uname-specific flags diff --git a/src/sage/features/__init__.py b/src/sage/features/__init__.py index d5669c3c9ff..ea8fd6bdb05 100644 --- a/src/sage/features/__init__.py +++ b/src/sage/features/__init__.py @@ -416,6 +416,7 @@ def unhide(self): """ self._hidden = False + class FeatureNotPresentError(RuntimeError): r""" A missing feature error. @@ -791,7 +792,9 @@ class StaticFile(FileFeature): EXAMPLES:: sage: from sage.features import StaticFile - sage: StaticFile(name="no_such_file", filename="KaT1aihu", search_path=("/",), spkg="some_spkg", url="http://rand.om").require() # optional - sage_spkg + sage: StaticFile(name="no_such_file", filename="KaT1aihu", # optional - sage_spkg + ....: search_path="/", spkg="some_spkg", + ....: url="http://rand.om").require() Traceback (most recent call last): ... FeatureNotPresentError: no_such_file is not available. @@ -799,18 +802,27 @@ class StaticFile(FileFeature): To install no_such_file...you can try to run...sage -i some_spkg... Further installation instructions might be available at http://rand.om. """ - def __init__(self, name, filename, search_path=None, **kwds): + def __init__(self, name, filename, *, search_path=None, **kwds): r""" TESTS:: sage: from sage.features import StaticFile - sage: StaticFile(name="null", filename="null", search_path=("/dev",)) + sage: StaticFile(name="null", filename="null", search_path="/dev") Feature('null') + sage: sh = StaticFile(name="shell", filename="sh", + ....: search_path=("/dev", "/bin", "/usr")) + sage: sh + Feature('shell') + sage: sh.absolute_filename() + '/bin/sh' + """ Feature.__init__(self, name, **kwds) self.filename = filename if search_path is None: self.search_path = [SAGE_SHARE] + elif isinstance(search_path, str): + self.search_path = [search_path] else: self.search_path = list(search_path) diff --git a/src/sage/features/databases.py b/src/sage/features/databases.py index bca8c094b30..1410dc1167d 100644 --- a/src/sage/features/databases.py +++ b/src/sage/features/databases.py @@ -16,14 +16,27 @@ # https://www.gnu.org/licenses/ # ***************************************************************************** +import os from . import StaticFile, PythonModule -from sage.env import ( - CREMONA_MINI_DATA_DIR, CREMONA_LARGE_DATA_DIR, - POLYTOPE_DATA_DIR) +from sage.env import SAGE_DATA_PATH -CREMONA_DATA_DIRS = set([CREMONA_MINI_DATA_DIR, CREMONA_LARGE_DATA_DIR]) +def sage_data_path(data_name): + r""" + Search path for database `data_name`. + + EXAMPLES:: + + sage: from sage.features.databases import sage_data_path + sage: sage_data_path("cremona") + ['.../cremona'] + """ + if not SAGE_DATA_PATH: + return [] + + return [os.path.join(p, data_name) + for p in SAGE_DATA_PATH.split(os.pathsep)] class DatabaseCremona(StaticFile): @@ -44,7 +57,7 @@ class DatabaseCremona(StaticFile): sage: DatabaseCremona().is_present() # optional - database_cremona_ellcurve FeatureTestResult('database_cremona_ellcurve', True) """ - def __init__(self, name="cremona", spkg="database_cremona_ellcurve"): + def __init__(self, name="cremona"): r""" TESTS:: @@ -52,14 +65,86 @@ def __init__(self, name="cremona", spkg="database_cremona_ellcurve"): sage: isinstance(DatabaseCremona(), DatabaseCremona) True """ + from sage.env import CREMONA_MINI_DATA_DIR, CREMONA_LARGE_DATA_DIR + CREMONA_DATA_DIRS = set([CREMONA_MINI_DATA_DIR, CREMONA_LARGE_DATA_DIR]) + CREMONA_DATA_DIRS.discard(None) + search_path = CREMONA_DATA_DIRS or sage_data_path("cremona") + + spkg = "database_cremona_ellcurve" + spkg_type = "optional" + if name == 'cremona_mini': + spkg = "elliptic_curves" + spkg_type = "standard" + StaticFile.__init__(self, f"database_{name}_ellcurve", - filename='{}.db'.format(name.replace(' ', '_')), - search_path=CREMONA_DATA_DIRS, + filename=f"{name}.db", + search_path=search_path, spkg=spkg, + type=spkg_type, url="https://github.com/JohnCremona/ecdata", description="Cremona's database of elliptic curves") +class DatabaseEllcurves(StaticFile): + r""" + A :class:`~sage.features.Feature` which describes the presence of + William Stein's database of interesting curves. + + EXAMPLES:: + + sage: from sage.features.databases import DatabaseEllcurves + sage: bool(DatabaseEllcurves().is_present()) # optional - database_ellcurves + True + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.databases import DatabaseEllcurves + sage: isinstance(DatabaseEllcurves(), DatabaseEllcurves) + True + """ + from sage.env import ELLCURVE_DATA_DIR + search_path = ELLCURVE_DATA_DIR or sage_data_path("ellcurves") + + StaticFile.__init__(self, "database_ellcurves", + filename='rank0', + search_path=search_path, + spkg="elliptic_curves", + type="standard", + description="William Stein's database of interesting curve") + + +class DatabaseGraphs(StaticFile): + r""" + A :class:`~sage.features.Feature` which describes the presence of + the graphs database. + + EXAMPLES:: + + sage: from sage.features.databases import DatabaseGraphs + sage: bool(DatabaseGraphs().is_present()) # optional - database_graphs + True + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.databases import DatabaseGraphs + sage: isinstance(DatabaseGraphs(), DatabaseGraphs) + True + """ + from sage.env import GRAPHS_DATA_DIR + search_path = GRAPHS_DATA_DIR or sage_data_path("graphs") + + StaticFile.__init__(self, "database_graphs", + filename='graphs.db', + search_path=search_path, + spkg="graphs", + type="standard", + description="A database of graphs") + + class DatabaseJones(StaticFile): r""" A :class:`~sage.features.Feature` which describes the presence of @@ -80,7 +165,8 @@ def __init__(self): True """ StaticFile.__init__(self, "database_jones_numfield", - filename='jones/jones.sobj', + filename='jones.sobj', + search_path=sage_data_path("jones"), spkg="database_jones_numfield", description="John Jones's tables of number fields") @@ -146,27 +232,43 @@ class DatabaseReflexivePolytopes(StaticFile): EXAMPLES:: sage: from sage.features.databases import DatabaseReflexivePolytopes - sage: bool(DatabaseReflexivePolytopes().is_present()) # optional - polytopes_db + sage: bool(DatabaseReflexivePolytopes().is_present()) # optional - polytopes_db True - sage: bool(DatabaseReflexivePolytopes('polytopes_db_4d', 'Hodge4d').is_present()) # optional - polytopes_db_4d + sage: bool(DatabaseReflexivePolytopes('polytopes_db_4d').is_present()) # optional - polytopes_db_4d True """ - def __init__(self, name='polytopes_db', dirname='Full3D'): + def __init__(self, name='polytopes_db'): """ TESTS:: sage: from sage.features.databases import DatabaseReflexivePolytopes sage: isinstance(DatabaseReflexivePolytopes(), DatabaseReflexivePolytopes) True + sage: DatabaseReflexivePolytopes().filename + 'Full3d' + sage: DatabaseReflexivePolytopes('polytopes_db_4d').filename + 'Hodge4d' """ - StaticFile.__init__(self, name, dirname, - search_path=[POLYTOPE_DATA_DIR]) + from sage.env import POLYTOPE_DATA_DIR + search_path = POLYTOPE_DATA_DIR or sage_data_path("reflexive_polytopes") + + dirname = "Full3d" + if name == "polytopes_db_4d": + dirname = "Hodge4d" + + StaticFile.__init__(self, name, + filename=dirname, + search_path=search_path) def all_features(): - return [DatabaseCremona(), DatabaseCremona('cremona_mini'), + return [PythonModule('conway_polynomials'), + DatabaseCremona(), + DatabaseCremona('cremona_mini'), + DatabaseEllcurves(), + DatabaseGraphs(), DatabaseJones(), DatabaseKnotInfo(), DatabaseCubicHecke(), DatabaseReflexivePolytopes(), - DatabaseReflexivePolytopes('polytopes_db_4d', 'Hodge4d')] + DatabaseReflexivePolytopes('polytopes_db_4d')] diff --git a/src/sage/features/jmol.py b/src/sage/features/jmol.py new file mode 100644 index 00000000000..47ea7426991 --- /dev/null +++ b/src/sage/features/jmol.py @@ -0,0 +1,43 @@ +import os + +from . import StaticFile + + +class JmolDataJar(StaticFile): + r""" + A :class:`~sage.features.Feature` which describes the presence of + JmolData.jar in a few standard locations. + + EXAMPLES:: + + sage: from sage.features.jmol import JmolDataJar + sage: bool(JmolDataJar().is_present()) # needs jmol + True + """ + + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.jmol import JmolDataJar + sage: isinstance(JmolDataJar(), JmolDataJar) + True + """ + from sage.env import SAGE_SHARE, JMOL_DIR + + jmol_search_path = JMOL_DIR or ( + os.path.join(SAGE_SHARE, "sagemath", "jmol"), + os.path.join(SAGE_SHARE, "jmol") + ) + + StaticFile.__init__( + self, name="jmol", + filename="JmolData.jar", + search_path=jmol_search_path, + spkg="jmol", + type="standard", + description="Java viewer for chemical structures in 3D") + + +def all_features(): + return [JmolDataJar()] diff --git a/src/sage/features/threejs.py b/src/sage/features/threejs.py new file mode 100644 index 00000000000..4517523918d --- /dev/null +++ b/src/sage/features/threejs.py @@ -0,0 +1,64 @@ +import os + +from . import StaticFile + + +class Threejs(StaticFile): + r""" + A :class:`~sage.features.Feature` which describes the presence of + threejs-sage in a few standard locations. + + EXAMPLES:: + + sage: from sage.features.threejs import Threejs + sage: bool(Threejs().is_present()) # needs threejs + True + """ + + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.threejs import Threejs + sage: isinstance(Threejs(), Threejs) + True + """ + from sage.env import SAGE_SHARE, THREEJS_DIR + + version = self.required_version() + + threejs_search_path = THREEJS_DIR or ( + os.path.join(SAGE_SHARE, "jupyter", "nbextensions", "threejs-sage"), + os.path.join(SAGE_SHARE, "sagemath", "threejs-sage"), + os.path.join(SAGE_SHARE, "sage", "threejs"), + os.path.join(SAGE_SHARE, "threejs-sage") + ) + + StaticFile.__init__( + self, name="threejs", + filename=os.path.join(version, "three.min.js"), + spkg="threejs", + type="standard", + search_path=threejs_search_path, + description="JavaScript library to display 3D graphics") + + def required_version(self): + """ + Return the version of threejs that Sage requires. + + EXAMPLES:: + + sage: from sage.features.threejs import Threejs + sage: Threejs().required_version() + 'r...' + """ + from sage.env import SAGE_EXTCODE + + filename = os.path.join(SAGE_EXTCODE, 'threejs', 'threejs-version.txt') + + with open(filename) as f: + return f.read().strip() + + +def all_features(): + return [Threejs()] diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index 449143c8999..db25b345f4a 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -123,7 +123,7 @@ from sage.arith.misc import GCD as gcd from sage.combinat.posets.posets import FinitePoset -from sage.env import POLYTOPE_DATA_DIR +from sage.features.databases import DatabaseReflexivePolytopes from sage.geometry.cone import _ambient_space_point, integral_length from sage.geometry.hasse_diagram import lattice_from_incidences from sage.geometry.point_collection import (PointCollection, @@ -451,8 +451,10 @@ def ReflexivePolytopes(dim): if dim not in [2, 3]: raise NotImplementedError("only 2- and 3-dimensional reflexive polytopes are available!") if _rp[dim] is None: + db = DatabaseReflexivePolytopes() rp = read_all_polytopes( - os.path.join(POLYTOPE_DATA_DIR, "reflexive_polytopes_%dd" % dim)) + os.path.join(os.path.dirname(db.absolute_filename()), + f'reflexive_polytopes_{dim}d')) for n, p in enumerate(rp): # Data files have normal form of reflexive polytopes p.normal_form.set_cache(p._vertices) diff --git a/src/sage/geometry/polyhedron/palp_database.py b/src/sage/geometry/polyhedron/palp_database.py index 29b729cec18..60846d8df23 100644 --- a/src/sage/geometry/polyhedron/palp_database.py +++ b/src/sage/geometry/polyhedron/palp_database.py @@ -36,6 +36,7 @@ from sage.structure.sage_object import SageObject from sage.rings.integer_ring import ZZ from sage.features.palp import PalpExecutable +from sage.features.databases import DatabaseReflexivePolytopes from sage.interfaces.process import terminate @@ -108,9 +109,10 @@ def __init__(self, dim, data_basename=None, output='Polyhedron'): if data_basename is not None: self._data_basename = data_basename else: - from sage.env import POLYTOPE_DATA_DIR - self._data_basename = os.path.join(POLYTOPE_DATA_DIR, - 'Full{}d'.format(dim), 'zzdb') + db = DatabaseReflexivePolytopes() + self._data_basename = os.path.join( + os.path.dirname(db.absolute_filename()), + f'Full{dim}d', 'zzdb') info = self._data_basename + '.info' if not os.path.exists(info): raise ValueError('Cannot find PALP database: {}'.format(info)) @@ -431,9 +433,8 @@ def __init__(self, h11, h21, data_basename=None, **kwds): """ dim = 4 if data_basename is None: - from sage.env import POLYTOPE_DATA_DIR - data_basename = os.path.join(POLYTOPE_DATA_DIR, - 'Hodge4d', 'all') + db = DatabaseReflexivePolytopes('polytopes_db_4d') + data_basename = os.path.join(db.absolute_filename(), 'all') info = data_basename + '.vinfo' if not os.path.exists(info): raise ValueError( diff --git a/src/sage/graphs/graph_database.py b/src/sage/graphs/graph_database.py index 9dec951aa98..653201c3d10 100644 --- a/src/sage/graphs/graph_database.py +++ b/src/sage/graphs/graph_database.py @@ -49,9 +49,9 @@ import re from sage.rings.integer import Integer from sage.databases.sql_db import SQLDatabase, SQLQuery -from sage.env import GRAPHS_DATA_DIR +from sage.features.databases import DatabaseGraphs from sage.graphs.graph import Graph -dblocation = os.path.join(GRAPHS_DATA_DIR, 'graphs.db') +dblocation = DatabaseGraphs().absolute_filename() def degseq_to_data(degree_sequence): diff --git a/src/sage/graphs/isgci.py b/src/sage/graphs/isgci.py index 7c2fae74ba7..440135956c3 100644 --- a/src/sage/graphs/isgci.py +++ b/src/sage/graphs/isgci.py @@ -378,7 +378,7 @@ class is defined by the exclusion of subgraphs, one can write a generic from sage.structure.sage_object import SageObject from sage.structure.unique_representation import CachedRepresentation, UniqueRepresentation from sage.misc.unknown import Unknown -from sage.env import GRAPHS_DATA_DIR +from sage.features.databases import DatabaseGraphs from sage.misc.cachefunc import cached_method import os @@ -796,6 +796,7 @@ def _download_db(self): sage: graph_classes._download_db() # optional - internet """ import tempfile + data_dir = os.path.dirname(DatabaseGraphs().absolute_filename()) u = urlopen('https://www.graphclasses.org/data.zip', context=default_context()) with tempfile.NamedTemporaryFile(suffix=".zip") as f: @@ -804,29 +805,24 @@ def _download_db(self): # Save a systemwide updated copy whenever possible try: - z.extract(_XML_FILE, GRAPHS_DATA_DIR) - z.extract(_SMALLGRAPHS_FILE, GRAPHS_DATA_DIR) + z.extract(_XML_FILE, data_dir) + z.extract(_SMALLGRAPHS_FILE, data_dir) except OSError: pass - def _parse_db(self, directory): + def _parse_db(self): r""" Parse the ISGCI database and stores its content in ``self``. - INPUT: - - - ``directory`` -- the name of the directory containing the latest - version of the database. - EXAMPLES:: - sage: from sage.env import GRAPHS_DATA_DIR - sage: graph_classes._parse_db(GRAPHS_DATA_DIR) + sage: graph_classes._parse_db() """ import xml.etree.cElementTree as ET from sage.graphs.graph import Graph - xml_file = os.path.join(GRAPHS_DATA_DIR, _XML_FILE) + data_dir = os.path.dirname(DatabaseGraphs().absolute_filename()) + xml_file = os.path.join(data_dir, _XML_FILE) tree = ET.ElementTree(file=xml_file) root = tree.getroot() DB = _XML_to_dict(root) @@ -838,7 +834,7 @@ def _parse_db(self, directory): inclusions = DB['Inclusions']['incl'] # Parses the list of ISGCI small graphs - smallgraph_file = open(os.path.join(GRAPHS_DATA_DIR, _SMALLGRAPHS_FILE), 'r') + smallgraph_file = open(os.path.join(data_dir, _SMALLGRAPHS_FILE), 'r') smallgraphs = {} for line in smallgraph_file.readlines(): @@ -901,24 +897,7 @@ def _get_ISGCI(self): sage: graph_classes._get_ISGCI() # long time (4s on sage.math, 2012) """ - from sage.misc.misc import SAGE_DB - - try: - open(os.path.join(SAGE_DB, _XML_FILE)) - - # Which copy is the most recent on the disk ? - if (os.path.getmtime(os.path.join(SAGE_DB, _XML_FILE)) > - os.path.getmtime(os.path.join(GRAPHS_DATA_DIR, _XML_FILE))): - - directory = os.path.join(SAGE_DB, _XML_FILE) - - else: - directory = os.path.join(GRAPHS_DATA_DIR, _XML_FILE) - - except OSError: - directory = os.path.join(GRAPHS_DATA_DIR, _XML_FILE) - - self._parse_db(directory) + self._parse_db() def show_all(self): r""" diff --git a/src/sage/graphs/strongly_regular_db.pyx b/src/sage/graphs/strongly_regular_db.pyx index 632016b0703..453f711731c 100644 --- a/src/sage/graphs/strongly_regular_db.pyx +++ b/src/sage/graphs/strongly_regular_db.pyx @@ -1233,7 +1233,7 @@ def SRG_from_RSHCD(v, k, l, mu, existence=False, check=True): if (e**2 == 1 and k == (n-1-a+e)/2 and l == (n-2*a)/4 - (1-e) and - mu== (n-2*a)/4 and + mu == (n-2*a)/4 and regular_symmetric_hadamard_matrix_with_constant_diagonal(n, sgn(a)*e, existence=True) is True): if existence: return True @@ -2415,7 +2415,7 @@ def SRG_416_100_36_20(): """ from sage.libs.gap.libgap import libgap libgap.load_package("AtlasRep") - g=libgap.AtlasGroup("G2(4)", libgap.NrMovedPoints, 416) + g = libgap.AtlasGroup("G2(4)", libgap.NrMovedPoints, 416) h = Graph() h.add_edges(g.Orbit([1, 5],libgap.OnSets)) h.relabel() @@ -2439,7 +2439,7 @@ def SRG_560_208_72_80(): """ from sage.libs.gap.libgap import libgap libgap.load_package("AtlasRep") - g=libgap.AtlasGroup("Sz8", libgap.NrMovedPoints, 560) + g = libgap.AtlasGroup("Sz8", libgap.NrMovedPoints, 560) h = Graph() h.add_edges(g.Orbit([1, 2],libgap.OnSets)) @@ -2503,7 +2503,7 @@ def strongly_regular_from_two_intersection_set(M): for v in M: # u is adjacent with all vertices on a uv line. g.add_edges([[u, tuple([u[i] + qq*v[i] for i in range(k)])] - for qq in K if not qq==K.zero()]) + for qq in K if not qq == K.zero()]) g.relabel() e = QQ((1,k)) qq = g.num_verts()**e @@ -3264,8 +3264,9 @@ cdef load_brouwer_database() noexcept: if _brouwer_database is not None: return - from sage.env import GRAPHS_DATA_DIR - filename = os.path.join(GRAPHS_DATA_DIR, 'brouwer_srg_database.json') + from sage.features.databases import DatabaseGraphs + data_dir = os.path.dirname(DatabaseGraphs().absolute_filename()) + filename = os.path.join(data_dir, 'brouwer_srg_database.json') with open(filename) as fobj: database = json.load(fobj) diff --git a/src/sage/interfaces/jmoldata.py b/src/sage/interfaces/jmoldata.py index e7354e05c70..add4b453b3d 100644 --- a/src/sage/interfaces/jmoldata.py +++ b/src/sage/interfaces/jmoldata.py @@ -21,7 +21,7 @@ from sage.structure.sage_object import SageObject -from sage.env import JMOL_DIR +from sage.features.jmol import JmolDataJar from sage.misc.temporary_file import tmp_filename from sage.cpython.string import bytes_to_str @@ -79,11 +79,11 @@ def jmolpath(self): sage: from sage.interfaces.jmoldata import JmolData sage: JData = JmolData() - sage: JData.jmolpath() + sage: JData.jmolpath() # needs jmol '.../JmolData.jar' """ - jmolpath = os.path.join(JMOL_DIR, "JmolData.jar") + jmolpath = JmolDataJar().absolute_filename() return jmolpath @@ -100,7 +100,7 @@ def is_jmol_available(self): sage: type(JData.is_jmol_available()) <... 'bool'> """ - if not os.path.isfile(self.jmolpath()): + if not JmolDataJar().is_present(): return False if not self.is_jvm_available(): diff --git a/src/sage/repl/ipython_kernel/install.py b/src/sage/repl/ipython_kernel/install.py index 0adeab04bcd..e62c0175331 100644 --- a/src/sage/repl/ipython_kernel/install.py +++ b/src/sage/repl/ipython_kernel/install.py @@ -23,7 +23,6 @@ SAGE_EXTCODE, SAGE_VENV, SAGE_VERSION, - THREEJS_DIR, ) @@ -123,6 +122,7 @@ def use_local_threejs(self): EXAMPLES:: + sage: # needs threejs sage: from sage.repl.ipython_kernel.install import SageKernelSpec sage: spec = SageKernelSpec(prefix=tmp_dir()) sage: spec.use_local_threejs() @@ -130,7 +130,10 @@ def use_local_threejs(self): sage: os.path.isdir(threejs) True """ - src = THREEJS_DIR + from sage.features.threejs import Threejs + if not Threejs().is_present(): + return + src = os.path.dirname(os.path.dirname(Threejs().absolute_filename())) dst = os.path.join(self.nbextensions_dir, 'threejs-sage') self.symlink(src, dst) diff --git a/src/sage/repl/rich_output/backend_ipython.py b/src/sage/repl/rich_output/backend_ipython.py index ba17b9244b4..7f39e37bf8f 100644 --- a/src/sage/repl/rich_output/backend_ipython.py +++ b/src/sage/repl/rich_output/backend_ipython.py @@ -409,15 +409,18 @@ def threejs_offline_scripts(self): EXAMPLES:: + sage: # needs threejs sage: from sage.repl.rich_output.backend_ipython import BackendIPythonCommandline sage: backend = BackendIPythonCommandline() - sage: backend.threejs_offline_scripts() # needs sage.plot + sage: backend.threejs_offline_scripts() '...'.format(script) @@ -596,7 +599,7 @@ def threejs_offline_scripts(self): '... - """.format(_required_threejs_version(), CDN_script) + """.format(Threejs().required_version(), CDN_script) diff --git a/src/sage/repl/rich_output/display_manager.py b/src/sage/repl/rich_output/display_manager.py index f6bec0209e1..cfece92a810 100644 --- a/src/sage/repl/rich_output/display_manager.py +++ b/src/sage/repl/rich_output/display_manager.py @@ -46,22 +46,6 @@ from sage.repl.rich_output.preferences import DisplayPreferences -def _required_threejs_version(): - """ - Return the version of threejs that Sage requires. - - EXAMPLES:: - - sage: from sage.repl.rich_output.display_manager import _required_threejs_version - sage: _required_threejs_version() # needs sage.plot - 'r...' - """ - import os - import sage.env - with open(os.path.join(sage.env.SAGE_EXTCODE, 'threejs', 'threejs-version.txt')) as f: - return f.read().strip() - - class DisplayException(Exception): """ Base exception for all rich output-related exceptions. @@ -768,8 +752,9 @@ def threejs_scripts(self, online): ValueError: current backend does not support offline threejs graphics """ + from sage.features.threejs import Threejs if online: - version = _required_threejs_version() + version = Threejs().required_version() return """ """.format(version) diff --git a/src/sage/schemes/elliptic_curves/ec_database.py b/src/sage/schemes/elliptic_curves/ec_database.py index f66ee2d1d31..34099d620bb 100644 --- a/src/sage/schemes/elliptic_curves/ec_database.py +++ b/src/sage/schemes/elliptic_curves/ec_database.py @@ -132,8 +132,10 @@ def rank(self, rank, tors=0, n=10, labels=False): sage: elliptic_curves.rank(6, n=3, labels=True) [] """ - from sage.env import ELLCURVE_DATA_DIR - data = os.path.join(ELLCURVE_DATA_DIR, 'rank%s' % rank) + from sage.features.databases import DatabaseEllcurves + db = DatabaseEllcurves() + data = os.path.join(os.path.dirname(db.absolute_filename()), + f'rank{rank}') try: f = open(data) except OSError: