ZhengyangZhang commited on
Commit
b260509
·
verified ·
1 Parent(s): ccca77b

Add files using upload-large-folder tool

Browse files
Files changed (50) hide show
  1. lib/python3.12/site-packages/sympy/benchmarks/__init__.py +0 -0
  2. lib/python3.12/site-packages/sympy/benchmarks/__pycache__/__init__.cpython-312.pyc +0 -0
  3. lib/python3.12/site-packages/sympy/benchmarks/__pycache__/bench_discrete_log.cpython-312.pyc +0 -0
  4. lib/python3.12/site-packages/sympy/benchmarks/__pycache__/bench_meijerint.cpython-312.pyc +0 -0
  5. lib/python3.12/site-packages/sympy/benchmarks/__pycache__/bench_symbench.cpython-312.pyc +0 -0
  6. lib/python3.12/site-packages/sympy/benchmarks/bench_discrete_log.py +83 -0
  7. lib/python3.12/site-packages/sympy/benchmarks/bench_meijerint.py +261 -0
  8. lib/python3.12/site-packages/sympy/benchmarks/bench_symbench.py +134 -0
  9. lib/python3.12/site-packages/sympy/categories/__init__.py +33 -0
  10. lib/python3.12/site-packages/sympy/categories/__pycache__/__init__.cpython-312.pyc +0 -0
  11. lib/python3.12/site-packages/sympy/categories/__pycache__/baseclasses.cpython-312.pyc +0 -0
  12. lib/python3.12/site-packages/sympy/categories/__pycache__/diagram_drawing.cpython-312.pyc +0 -0
  13. lib/python3.12/site-packages/sympy/categories/baseclasses.py +978 -0
  14. lib/python3.12/site-packages/sympy/categories/diagram_drawing.py +2582 -0
  15. lib/python3.12/site-packages/sympy/categories/tests/__init__.py +0 -0
  16. lib/python3.12/site-packages/sympy/categories/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  17. lib/python3.12/site-packages/sympy/categories/tests/__pycache__/test_baseclasses.cpython-312.pyc +0 -0
  18. lib/python3.12/site-packages/sympy/categories/tests/__pycache__/test_drawing.cpython-312.pyc +0 -0
  19. lib/python3.12/site-packages/sympy/categories/tests/test_baseclasses.py +209 -0
  20. lib/python3.12/site-packages/sympy/categories/tests/test_drawing.py +919 -0
  21. lib/python3.12/site-packages/sympy/liealgebras/__init__.py +3 -0
  22. lib/python3.12/site-packages/sympy/liealgebras/cartan_matrix.py +25 -0
  23. lib/python3.12/site-packages/sympy/liealgebras/cartan_type.py +73 -0
  24. lib/python3.12/site-packages/sympy/liealgebras/dynkin_diagram.py +24 -0
  25. lib/python3.12/site-packages/sympy/liealgebras/root_system.py +199 -0
  26. lib/python3.12/site-packages/sympy/liealgebras/tests/__init__.py +0 -0
  27. lib/python3.12/site-packages/sympy/liealgebras/tests/__pycache__/test_weyl_group.cpython-312.pyc +0 -0
  28. lib/python3.12/site-packages/sympy/liealgebras/tests/test_cartan_matrix.py +10 -0
  29. lib/python3.12/site-packages/sympy/liealgebras/tests/test_cartan_type.py +12 -0
  30. lib/python3.12/site-packages/sympy/liealgebras/tests/test_dynkin_diagram.py +9 -0
  31. lib/python3.12/site-packages/sympy/liealgebras/tests/test_root_system.py +18 -0
  32. lib/python3.12/site-packages/sympy/liealgebras/tests/test_type_A.py +17 -0
  33. lib/python3.12/site-packages/sympy/liealgebras/tests/test_type_B.py +17 -0
  34. lib/python3.12/site-packages/sympy/liealgebras/tests/test_type_C.py +22 -0
  35. lib/python3.12/site-packages/sympy/liealgebras/tests/test_type_D.py +19 -0
  36. lib/python3.12/site-packages/sympy/liealgebras/tests/test_type_E.py +19 -0
  37. lib/python3.12/site-packages/sympy/liealgebras/tests/test_type_F.py +24 -0
  38. lib/python3.12/site-packages/sympy/liealgebras/tests/test_type_G.py +16 -0
  39. lib/python3.12/site-packages/sympy/liealgebras/tests/test_weyl_group.py +35 -0
  40. lib/python3.12/site-packages/sympy/liealgebras/type_a.py +166 -0
  41. lib/python3.12/site-packages/sympy/liealgebras/type_b.py +172 -0
  42. lib/python3.12/site-packages/sympy/liealgebras/type_c.py +171 -0
  43. lib/python3.12/site-packages/sympy/liealgebras/type_d.py +175 -0
  44. lib/python3.12/site-packages/sympy/liealgebras/type_e.py +287 -0
  45. lib/python3.12/site-packages/sympy/liealgebras/type_f.py +162 -0
  46. lib/python3.12/site-packages/sympy/liealgebras/type_g.py +111 -0
  47. lib/python3.12/site-packages/sympy/liealgebras/weyl_group.py +403 -0
  48. lib/python3.12/site-packages/sympy/simplify/__pycache__/cse_main.cpython-312.pyc +0 -0
  49. lib/python3.12/site-packages/sympy/simplify/__pycache__/epathtools.cpython-312.pyc +0 -0
  50. lib/python3.12/site-packages/sympy/simplify/__pycache__/fu.cpython-312.pyc +0 -0
lib/python3.12/site-packages/sympy/benchmarks/__init__.py ADDED
File without changes
lib/python3.12/site-packages/sympy/benchmarks/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (195 Bytes). View file
 
lib/python3.12/site-packages/sympy/benchmarks/__pycache__/bench_discrete_log.cpython-312.pyc ADDED
Binary file (2.56 kB). View file
 
lib/python3.12/site-packages/sympy/benchmarks/__pycache__/bench_meijerint.cpython-312.pyc ADDED
Binary file (12.9 kB). View file
 
lib/python3.12/site-packages/sympy/benchmarks/__pycache__/bench_symbench.cpython-312.pyc ADDED
Binary file (7.03 kB). View file
 
lib/python3.12/site-packages/sympy/benchmarks/bench_discrete_log.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from time import time
3
+ from sympy.ntheory.residue_ntheory import (discrete_log,
4
+ _discrete_log_trial_mul, _discrete_log_shanks_steps,
5
+ _discrete_log_pollard_rho, _discrete_log_pohlig_hellman)
6
+
7
+
8
+ # Cyclic group (Z/pZ)* with p prime, order p - 1 and generator g
9
+ data_set_1 = [
10
+ # p, p - 1, g
11
+ [191, 190, 19],
12
+ [46639, 46638, 6],
13
+ [14789363, 14789362, 2],
14
+ [4254225211, 4254225210, 2],
15
+ [432751500361, 432751500360, 7],
16
+ [158505390797053, 158505390797052, 2],
17
+ [6575202655312007, 6575202655312006, 5],
18
+ [8430573471995353769, 8430573471995353768, 3],
19
+ [3938471339744997827267, 3938471339744997827266, 2],
20
+ [875260951364705563393093, 875260951364705563393092, 5],
21
+ ]
22
+
23
+
24
+ # Cyclic sub-groups of (Z/nZ)* with prime order p and generator g
25
+ # (n, p are primes and n = 2 * p + 1)
26
+ data_set_2 = [
27
+ # n, p, g
28
+ [227, 113, 3],
29
+ [2447, 1223, 2],
30
+ [24527, 12263, 2],
31
+ [245639, 122819, 2],
32
+ [2456747, 1228373, 3],
33
+ [24567899, 12283949, 3],
34
+ [245679023, 122839511, 2],
35
+ [2456791307, 1228395653, 3],
36
+ [24567913439, 12283956719, 2],
37
+ [245679135407, 122839567703, 2],
38
+ [2456791354763, 1228395677381, 3],
39
+ [24567913550903, 12283956775451, 2],
40
+ [245679135509519, 122839567754759, 2],
41
+ ]
42
+
43
+
44
+ # Cyclic sub-groups of (Z/nZ)* with smooth order o and generator g
45
+ data_set_3 = [
46
+ # n, o, g
47
+ [2**118, 2**116, 3],
48
+ ]
49
+
50
+
51
+ def bench_discrete_log(data_set, algo=None):
52
+ if algo is None:
53
+ f = discrete_log
54
+ elif algo == 'trial':
55
+ f = _discrete_log_trial_mul
56
+ elif algo == 'shanks':
57
+ f = _discrete_log_shanks_steps
58
+ elif algo == 'rho':
59
+ f = _discrete_log_pollard_rho
60
+ elif algo == 'ph':
61
+ f = _discrete_log_pohlig_hellman
62
+ else:
63
+ raise ValueError("Argument 'algo' should be one"
64
+ " of ('trial', 'shanks', 'rho' or 'ph')")
65
+
66
+ for i, data in enumerate(data_set):
67
+ for j, (n, p, g) in enumerate(data):
68
+ t = time()
69
+ l = f(n, pow(g, p - 1, n), g, p)
70
+ t = time() - t
71
+ print('[%02d-%03d] %15.10f' % (i, j, t))
72
+ assert l == p - 1
73
+
74
+
75
+ if __name__ == '__main__':
76
+ algo = sys.argv[1] \
77
+ if len(sys.argv) > 1 else None
78
+ data_set = [
79
+ data_set_1,
80
+ data_set_2,
81
+ data_set_3,
82
+ ]
83
+ bench_discrete_log(data_set, algo)
lib/python3.12/site-packages/sympy/benchmarks/bench_meijerint.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # conceal the implicit import from the code quality tester
2
+ from sympy.core.numbers import (oo, pi)
3
+ from sympy.core.symbol import (Symbol, symbols)
4
+ from sympy.functions.elementary.exponential import exp
5
+ from sympy.functions.elementary.miscellaneous import sqrt
6
+ from sympy.functions.special.bessel import besseli
7
+ from sympy.functions.special.gamma_functions import gamma
8
+ from sympy.integrals.integrals import integrate
9
+ from sympy.integrals.transforms import (mellin_transform,
10
+ inverse_fourier_transform, inverse_mellin_transform,
11
+ laplace_transform, inverse_laplace_transform, fourier_transform)
12
+
13
+ LT = laplace_transform
14
+ FT = fourier_transform
15
+ MT = mellin_transform
16
+ IFT = inverse_fourier_transform
17
+ ILT = inverse_laplace_transform
18
+ IMT = inverse_mellin_transform
19
+
20
+ from sympy.abc import x, y
21
+ nu, beta, rho = symbols('nu beta rho')
22
+
23
+ apos, bpos, cpos, dpos, posk, p = symbols('a b c d k p', positive=True)
24
+ k = Symbol('k', real=True)
25
+ negk = Symbol('k', negative=True)
26
+
27
+ mu1, mu2 = symbols('mu1 mu2', real=True, nonzero=True, finite=True)
28
+ sigma1, sigma2 = symbols('sigma1 sigma2', real=True, nonzero=True,
29
+ finite=True, positive=True)
30
+ rate = Symbol('lambda', positive=True)
31
+
32
+
33
+ def normal(x, mu, sigma):
34
+ return 1/sqrt(2*pi*sigma**2)*exp(-(x - mu)**2/2/sigma**2)
35
+
36
+
37
+ def exponential(x, rate):
38
+ return rate*exp(-rate*x)
39
+ alpha, beta = symbols('alpha beta', positive=True)
40
+ betadist = x**(alpha - 1)*(1 + x)**(-alpha - beta)*gamma(alpha + beta) \
41
+ /gamma(alpha)/gamma(beta)
42
+ kint = Symbol('k', integer=True, positive=True)
43
+ chi = 2**(1 - kint/2)*x**(kint - 1)*exp(-x**2/2)/gamma(kint/2)
44
+ chisquared = 2**(-k/2)/gamma(k/2)*x**(k/2 - 1)*exp(-x/2)
45
+ dagum = apos*p/x*(x/bpos)**(apos*p)/(1 + x**apos/bpos**apos)**(p + 1)
46
+ d1, d2 = symbols('d1 d2', positive=True)
47
+ f = sqrt(((d1*x)**d1 * d2**d2)/(d1*x + d2)**(d1 + d2))/x \
48
+ /gamma(d1/2)/gamma(d2/2)*gamma((d1 + d2)/2)
49
+ nupos, sigmapos = symbols('nu sigma', positive=True)
50
+ rice = x/sigmapos**2*exp(-(x**2 + nupos**2)/2/sigmapos**2)*besseli(0, x*
51
+ nupos/sigmapos**2)
52
+ mu = Symbol('mu', real=True)
53
+ laplace = exp(-abs(x - mu)/bpos)/2/bpos
54
+
55
+ u = Symbol('u', polar=True)
56
+ tpos = Symbol('t', positive=True)
57
+
58
+
59
+ def E(expr):
60
+ integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1),
61
+ (x, 0, oo), (y, -oo, oo), meijerg=True)
62
+ integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1),
63
+ (y, -oo, oo), (x, 0, oo), meijerg=True)
64
+
65
+ bench = [
66
+ 'MT(x**nu*Heaviside(x - 1), x, s)',
67
+ 'MT(x**nu*Heaviside(1 - x), x, s)',
68
+ 'MT((1-x)**(beta - 1)*Heaviside(1-x), x, s)',
69
+ 'MT((x-1)**(beta - 1)*Heaviside(x-1), x, s)',
70
+ 'MT((1+x)**(-rho), x, s)',
71
+ 'MT(abs(1-x)**(-rho), x, s)',
72
+ 'MT((1-x)**(beta-1)*Heaviside(1-x) + a*(x-1)**(beta-1)*Heaviside(x-1), x, s)',
73
+ 'MT((x**a-b**a)/(x-b), x, s)',
74
+ 'MT((x**a-bpos**a)/(x-bpos), x, s)',
75
+ 'MT(exp(-x), x, s)',
76
+ 'MT(exp(-1/x), x, s)',
77
+ 'MT(log(x)**4*Heaviside(1-x), x, s)',
78
+ 'MT(log(x)**3*Heaviside(x-1), x, s)',
79
+ 'MT(log(x + 1), x, s)',
80
+ 'MT(log(1/x + 1), x, s)',
81
+ 'MT(log(abs(1 - x)), x, s)',
82
+ 'MT(log(abs(1 - 1/x)), x, s)',
83
+ 'MT(log(x)/(x+1), x, s)',
84
+ 'MT(log(x)**2/(x+1), x, s)',
85
+ 'MT(log(x)/(x+1)**2, x, s)',
86
+ 'MT(erf(sqrt(x)), x, s)',
87
+
88
+ 'MT(besselj(a, 2*sqrt(x)), x, s)',
89
+ 'MT(sin(sqrt(x))*besselj(a, sqrt(x)), x, s)',
90
+ 'MT(cos(sqrt(x))*besselj(a, sqrt(x)), x, s)',
91
+ 'MT(besselj(a, sqrt(x))**2, x, s)',
92
+ 'MT(besselj(a, sqrt(x))*besselj(-a, sqrt(x)), x, s)',
93
+ 'MT(besselj(a - 1, sqrt(x))*besselj(a, sqrt(x)), x, s)',
94
+ 'MT(besselj(a, sqrt(x))*besselj(b, sqrt(x)), x, s)',
95
+ 'MT(besselj(a, sqrt(x))**2 + besselj(-a, sqrt(x))**2, x, s)',
96
+ 'MT(bessely(a, 2*sqrt(x)), x, s)',
97
+ 'MT(sin(sqrt(x))*bessely(a, sqrt(x)), x, s)',
98
+ 'MT(cos(sqrt(x))*bessely(a, sqrt(x)), x, s)',
99
+ 'MT(besselj(a, sqrt(x))*bessely(a, sqrt(x)), x, s)',
100
+ 'MT(besselj(a, sqrt(x))*bessely(b, sqrt(x)), x, s)',
101
+ 'MT(bessely(a, sqrt(x))**2, x, s)',
102
+
103
+ 'MT(besselk(a, 2*sqrt(x)), x, s)',
104
+ 'MT(besselj(a, 2*sqrt(2*sqrt(x)))*besselk(a, 2*sqrt(2*sqrt(x))), x, s)',
105
+ 'MT(besseli(a, sqrt(x))*besselk(a, sqrt(x)), x, s)',
106
+ 'MT(besseli(b, sqrt(x))*besselk(a, sqrt(x)), x, s)',
107
+ 'MT(exp(-x/2)*besselk(a, x/2), x, s)',
108
+
109
+ # later: ILT, IMT
110
+
111
+ 'LT((t-apos)**bpos*exp(-cpos*(t-apos))*Heaviside(t-apos), t, s)',
112
+ 'LT(t**apos, t, s)',
113
+ 'LT(Heaviside(t), t, s)',
114
+ 'LT(Heaviside(t - apos), t, s)',
115
+ 'LT(1 - exp(-apos*t), t, s)',
116
+ 'LT((exp(2*t)-1)*exp(-bpos - t)*Heaviside(t)/2, t, s, noconds=True)',
117
+ 'LT(exp(t), t, s)',
118
+ 'LT(exp(2*t), t, s)',
119
+ 'LT(exp(apos*t), t, s)',
120
+ 'LT(log(t/apos), t, s)',
121
+ 'LT(erf(t), t, s)',
122
+ 'LT(sin(apos*t), t, s)',
123
+ 'LT(cos(apos*t), t, s)',
124
+ 'LT(exp(-apos*t)*sin(bpos*t), t, s)',
125
+ 'LT(exp(-apos*t)*cos(bpos*t), t, s)',
126
+ 'LT(besselj(0, t), t, s, noconds=True)',
127
+ 'LT(besselj(1, t), t, s, noconds=True)',
128
+
129
+ 'FT(Heaviside(1 - abs(2*apos*x)), x, k)',
130
+ 'FT(Heaviside(1-abs(apos*x))*(1-abs(apos*x)), x, k)',
131
+ 'FT(exp(-apos*x)*Heaviside(x), x, k)',
132
+ 'IFT(1/(apos + 2*pi*I*x), x, posk, noconds=False)',
133
+ 'IFT(1/(apos + 2*pi*I*x), x, -posk, noconds=False)',
134
+ 'IFT(1/(apos + 2*pi*I*x), x, negk)',
135
+ 'FT(x*exp(-apos*x)*Heaviside(x), x, k)',
136
+ 'FT(exp(-apos*x)*sin(bpos*x)*Heaviside(x), x, k)',
137
+ 'FT(exp(-apos*x**2), x, k)',
138
+ 'IFT(sqrt(pi/apos)*exp(-(pi*k)**2/apos), k, x)',
139
+ 'FT(exp(-apos*abs(x)), x, k)',
140
+
141
+ 'integrate(normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
142
+ 'integrate(x*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
143
+ 'integrate(x**2*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
144
+ 'integrate(x**3*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
145
+ 'integrate(normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
146
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
147
+ 'integrate(x*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
148
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
149
+ 'integrate(y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
150
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
151
+ 'integrate(x*y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
152
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
153
+ 'integrate((x+y+1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
154
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
155
+ 'integrate((x+y-1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
156
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
157
+ 'integrate(x**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
158
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
159
+ 'integrate(y**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
160
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
161
+ 'integrate(exponential(x, rate), (x, 0, oo), meijerg=True)',
162
+ 'integrate(x*exponential(x, rate), (x, 0, oo), meijerg=True)',
163
+ 'integrate(x**2*exponential(x, rate), (x, 0, oo), meijerg=True)',
164
+ 'E(1)',
165
+ 'E(x*y)',
166
+ 'E(x*y**2)',
167
+ 'E((x+y+1)**2)',
168
+ 'E(x+y+1)',
169
+ 'E((x+y-1)**2)',
170
+ 'integrate(betadist, (x, 0, oo), meijerg=True)',
171
+ 'integrate(x*betadist, (x, 0, oo), meijerg=True)',
172
+ 'integrate(x**2*betadist, (x, 0, oo), meijerg=True)',
173
+ 'integrate(chi, (x, 0, oo), meijerg=True)',
174
+ 'integrate(x*chi, (x, 0, oo), meijerg=True)',
175
+ 'integrate(x**2*chi, (x, 0, oo), meijerg=True)',
176
+ 'integrate(chisquared, (x, 0, oo), meijerg=True)',
177
+ 'integrate(x*chisquared, (x, 0, oo), meijerg=True)',
178
+ 'integrate(x**2*chisquared, (x, 0, oo), meijerg=True)',
179
+ 'integrate(((x-k)/sqrt(2*k))**3*chisquared, (x, 0, oo), meijerg=True)',
180
+ 'integrate(dagum, (x, 0, oo), meijerg=True)',
181
+ 'integrate(x*dagum, (x, 0, oo), meijerg=True)',
182
+ 'integrate(x**2*dagum, (x, 0, oo), meijerg=True)',
183
+ 'integrate(f, (x, 0, oo), meijerg=True)',
184
+ 'integrate(x*f, (x, 0, oo), meijerg=True)',
185
+ 'integrate(x**2*f, (x, 0, oo), meijerg=True)',
186
+ 'integrate(rice, (x, 0, oo), meijerg=True)',
187
+ 'integrate(laplace, (x, -oo, oo), meijerg=True)',
188
+ 'integrate(x*laplace, (x, -oo, oo), meijerg=True)',
189
+ 'integrate(x**2*laplace, (x, -oo, oo), meijerg=True)',
190
+ 'integrate(log(x) * x**(k-1) * exp(-x) / gamma(k), (x, 0, oo))',
191
+
192
+ 'integrate(sin(z*x)*(x**2-1)**(-(y+S(1)/2)), (x, 1, oo), meijerg=True)',
193
+ 'integrate(besselj(0,x)*besselj(1,x)*exp(-x**2), (x, 0, oo), meijerg=True)',
194
+ 'integrate(besselj(0,x)*besselj(1,x)*besselk(0,x), (x, 0, oo), meijerg=True)',
195
+ 'integrate(besselj(0,x)*besselj(1,x)*exp(-x**2), (x, 0, oo), meijerg=True)',
196
+ 'integrate(besselj(a,x)*besselj(b,x)/x, (x,0,oo), meijerg=True)',
197
+
198
+ 'hyperexpand(meijerg((-s - a/2 + 1, -s + a/2 + 1), (-a/2 - S(1)/2, -s + a/2 + S(3)/2), (a/2, -a/2), (-a/2 - S(1)/2, -s + a/2 + S(3)/2), 1))',
199
+ "gammasimp(S('2**(2*s)*(-pi*gamma(-a + 1)*gamma(a + 1)*gamma(-a - s + 1)*gamma(-a + s - 1/2)*gamma(a - s + 3/2)*gamma(a + s + 1)/(a*(a + s)) - gamma(-a - 1/2)*gamma(-a + 1)*gamma(a + 1)*gamma(a + 3/2)*gamma(-s + 3/2)*gamma(s - 1/2)*gamma(-a + s + 1)*gamma(a - s + 1)/(a*(-a + s)))*gamma(-2*s + 1)*gamma(s + 1)/(pi*s*gamma(-a - 1/2)*gamma(a + 3/2)*gamma(-s + 1)*gamma(-s + 3/2)*gamma(s - 1/2)*gamma(-a - s + 1)*gamma(-a + s - 1/2)*gamma(a - s + 1)*gamma(a - s + 3/2))'))",
200
+
201
+ 'mellin_transform(E1(x), x, s)',
202
+ 'inverse_mellin_transform(gamma(s)/s, s, x, (0, oo))',
203
+ 'mellin_transform(expint(a, x), x, s)',
204
+ 'mellin_transform(Si(x), x, s)',
205
+ 'inverse_mellin_transform(-2**s*sqrt(pi)*gamma((s + 1)/2)/(2*s*gamma(-s/2 + 1)), s, x, (-1, 0))',
206
+ 'mellin_transform(Ci(sqrt(x)), x, s)',
207
+ 'inverse_mellin_transform(-4**s*sqrt(pi)*gamma(s)/(2*s*gamma(-s + S(1)/2)),s, u, (0, 1))',
208
+ 'laplace_transform(Ci(x), x, s)',
209
+ 'laplace_transform(expint(a, x), x, s)',
210
+ 'laplace_transform(expint(1, x), x, s)',
211
+ 'laplace_transform(expint(2, x), x, s)',
212
+ 'inverse_laplace_transform(-log(1 + s**2)/2/s, s, u)',
213
+ 'inverse_laplace_transform(log(s + 1)/s, s, x)',
214
+ 'inverse_laplace_transform((s - log(s + 1))/s**2, s, x)',
215
+ 'laplace_transform(Chi(x), x, s)',
216
+ 'laplace_transform(Shi(x), x, s)',
217
+
218
+ 'integrate(exp(-z*x)/x, (x, 1, oo), meijerg=True, conds="none")',
219
+ 'integrate(exp(-z*x)/x**2, (x, 1, oo), meijerg=True, conds="none")',
220
+ 'integrate(exp(-z*x)/x**3, (x, 1, oo), meijerg=True,conds="none")',
221
+ 'integrate(-cos(x)/x, (x, tpos, oo), meijerg=True)',
222
+ 'integrate(-sin(x)/x, (x, tpos, oo), meijerg=True)',
223
+ 'integrate(sin(x)/x, (x, 0, z), meijerg=True)',
224
+ 'integrate(sinh(x)/x, (x, 0, z), meijerg=True)',
225
+ 'integrate(exp(-x)/x, x, meijerg=True)',
226
+ 'integrate(exp(-x)/x**2, x, meijerg=True)',
227
+ 'integrate(cos(u)/u, u, meijerg=True)',
228
+ 'integrate(cosh(u)/u, u, meijerg=True)',
229
+ 'integrate(expint(1, x), x, meijerg=True)',
230
+ 'integrate(expint(2, x), x, meijerg=True)',
231
+ 'integrate(Si(x), x, meijerg=True)',
232
+ 'integrate(Ci(u), u, meijerg=True)',
233
+ 'integrate(Shi(x), x, meijerg=True)',
234
+ 'integrate(Chi(u), u, meijerg=True)',
235
+ 'integrate(Si(x)*exp(-x), (x, 0, oo), meijerg=True)',
236
+ 'integrate(expint(1, x)*sin(x), (x, 0, oo), meijerg=True)'
237
+ ]
238
+
239
+ from time import time
240
+ from sympy.core.cache import clear_cache
241
+ import sys
242
+
243
+ timings = []
244
+
245
+ if __name__ == '__main__':
246
+ for n, string in enumerate(bench):
247
+ clear_cache()
248
+ _t = time()
249
+ exec(string)
250
+ _t = time() - _t
251
+ timings += [(_t, string)]
252
+ sys.stdout.write('.')
253
+ sys.stdout.flush()
254
+ if n % (len(bench) // 10) == 0:
255
+ sys.stdout.write('%s' % (10*n // len(bench)))
256
+ print()
257
+
258
+ timings.sort(key=lambda x: -x[0])
259
+
260
+ for ti, string in timings:
261
+ print('%.2fs %s' % (ti, string))
lib/python3.12/site-packages/sympy/benchmarks/bench_symbench.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ from sympy.core.random import random
3
+ from sympy.core.numbers import (I, Integer, pi)
4
+ from sympy.core.symbol import Symbol
5
+ from sympy.core.sympify import sympify
6
+ from sympy.functions.elementary.miscellaneous import sqrt
7
+ from sympy.functions.elementary.trigonometric import sin
8
+ from sympy.polys.polytools import factor
9
+ from sympy.simplify.simplify import simplify
10
+ from sympy.abc import x, y, z
11
+ from timeit import default_timer as clock
12
+
13
+
14
+ def bench_R1():
15
+ "real(f(f(f(f(f(f(f(f(f(f(i/2)))))))))))"
16
+ def f(z):
17
+ return sqrt(Integer(1)/3)*z**2 + I/3
18
+ f(f(f(f(f(f(f(f(f(f(I/2)))))))))).as_real_imag()[0]
19
+
20
+
21
+ def bench_R2():
22
+ "Hermite polynomial hermite(15, y)"
23
+ def hermite(n, y):
24
+ if n == 1:
25
+ return 2*y
26
+ if n == 0:
27
+ return 1
28
+ return (2*y*hermite(n - 1, y) - 2*(n - 1)*hermite(n - 2, y)).expand()
29
+
30
+ hermite(15, y)
31
+
32
+
33
+ def bench_R3():
34
+ "a = [bool(f==f) for _ in range(10)]"
35
+ f = x + y + z
36
+ [bool(f == f) for _ in range(10)]
37
+
38
+
39
+ def bench_R4():
40
+ # we don't have Tuples
41
+ pass
42
+
43
+
44
+ def bench_R5():
45
+ "blowup(L, 8); L=uniq(L)"
46
+ def blowup(L, n):
47
+ for i in range(n):
48
+ L.append( (L[i] + L[i + 1]) * L[i + 2] )
49
+
50
+ def uniq(x):
51
+ v = set(x)
52
+ return v
53
+ L = [x, y, z]
54
+ blowup(L, 8)
55
+ L = uniq(L)
56
+
57
+
58
+ def bench_R6():
59
+ "sum(simplify((x+sin(i))/x+(x-sin(i))/x) for i in range(100))"
60
+ sum(simplify((x + sin(i))/x + (x - sin(i))/x) for i in range(100))
61
+
62
+
63
+ def bench_R7():
64
+ "[f.subs(x, random()) for _ in range(10**4)]"
65
+ f = x**24 + 34*x**12 + 45*x**3 + 9*x**18 + 34*x**10 + 32*x**21
66
+ [f.subs(x, random()) for _ in range(10**4)]
67
+
68
+
69
+ def bench_R8():
70
+ "right(x^2,0,5,10^4)"
71
+ def right(f, a, b, n):
72
+ a = sympify(a)
73
+ b = sympify(b)
74
+ n = sympify(n)
75
+ x = f.atoms(Symbol).pop()
76
+ Deltax = (b - a)/n
77
+ c = a
78
+ est = 0
79
+ for i in range(n):
80
+ c += Deltax
81
+ est += f.subs(x, c)
82
+ return est*Deltax
83
+
84
+ right(x**2, 0, 5, 10**4)
85
+
86
+
87
+ def _bench_R9():
88
+ "factor(x^20 - pi^5*y^20)"
89
+ factor(x**20 - pi**5*y**20)
90
+
91
+
92
+ def bench_R10():
93
+ "v = [-pi,-pi+1/10..,pi]"
94
+ def srange(min, max, step):
95
+ v = [min]
96
+ while (max - v[-1]).evalf() > 0:
97
+ v.append(v[-1] + step)
98
+ return v[:-1]
99
+ srange(-pi, pi, sympify(1)/10)
100
+
101
+
102
+ def bench_R11():
103
+ "a = [random() + random()*I for w in [0..1000]]"
104
+ [random() + random()*I for w in range(1000)]
105
+
106
+
107
+ def bench_S1():
108
+ "e=(x+y+z+1)**7;f=e*(e+1);f.expand()"
109
+ e = (x + y + z + 1)**7
110
+ f = e*(e + 1)
111
+ f.expand()
112
+
113
+
114
+ if __name__ == '__main__':
115
+ benchmarks = [
116
+ bench_R1,
117
+ bench_R2,
118
+ bench_R3,
119
+ bench_R5,
120
+ bench_R6,
121
+ bench_R7,
122
+ bench_R8,
123
+ #_bench_R9,
124
+ bench_R10,
125
+ bench_R11,
126
+ #bench_S1,
127
+ ]
128
+
129
+ report = []
130
+ for b in benchmarks:
131
+ t = clock()
132
+ b()
133
+ t = clock() - t
134
+ print("%s%65s: %f" % (b.__name__, b.__doc__, t))
lib/python3.12/site-packages/sympy/categories/__init__.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Category Theory module.
3
+
4
+ Provides some of the fundamental category-theory-related classes,
5
+ including categories, morphisms, diagrams. Functors are not
6
+ implemented yet.
7
+
8
+ The general reference work this module tries to follow is
9
+
10
+ [JoyOfCats] J. Adamek, H. Herrlich. G. E. Strecker: Abstract and
11
+ Concrete Categories. The Joy of Cats.
12
+
13
+ The latest version of this book should be available for free download
14
+ from
15
+
16
+ katmat.math.uni-bremen.de/acc/acc.pdf
17
+
18
+ """
19
+
20
+ from .baseclasses import (Object, Morphism, IdentityMorphism,
21
+ NamedMorphism, CompositeMorphism, Category,
22
+ Diagram)
23
+
24
+ from .diagram_drawing import (DiagramGrid, XypicDiagramDrawer,
25
+ xypic_draw_diagram, preview_diagram)
26
+
27
+ __all__ = [
28
+ 'Object', 'Morphism', 'IdentityMorphism', 'NamedMorphism',
29
+ 'CompositeMorphism', 'Category', 'Diagram',
30
+
31
+ 'DiagramGrid', 'XypicDiagramDrawer', 'xypic_draw_diagram',
32
+ 'preview_diagram',
33
+ ]
lib/python3.12/site-packages/sympy/categories/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (1.1 kB). View file
 
lib/python3.12/site-packages/sympy/categories/__pycache__/baseclasses.cpython-312.pyc ADDED
Binary file (34.5 kB). View file
 
lib/python3.12/site-packages/sympy/categories/__pycache__/diagram_drawing.cpython-312.pyc ADDED
Binary file (85.4 kB). View file
 
lib/python3.12/site-packages/sympy/categories/baseclasses.py ADDED
@@ -0,0 +1,978 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core import S, Basic, Dict, Symbol, Tuple, sympify
2
+ from sympy.core.symbol import Str
3
+ from sympy.sets import Set, FiniteSet, EmptySet
4
+ from sympy.utilities.iterables import iterable
5
+
6
+
7
+ class Class(Set):
8
+ r"""
9
+ The base class for any kind of class in the set-theoretic sense.
10
+
11
+ Explanation
12
+ ===========
13
+
14
+ In axiomatic set theories, everything is a class. A class which
15
+ can be a member of another class is a set. A class which is not a
16
+ member of another class is a proper class. The class `\{1, 2\}`
17
+ is a set; the class of all sets is a proper class.
18
+
19
+ This class is essentially a synonym for :class:`sympy.core.Set`.
20
+ The goal of this class is to assure easier migration to the
21
+ eventual proper implementation of set theory.
22
+ """
23
+ is_proper = False
24
+
25
+
26
+ class Object(Symbol):
27
+ """
28
+ The base class for any kind of object in an abstract category.
29
+
30
+ Explanation
31
+ ===========
32
+
33
+ While technically any instance of :class:`~.Basic` will do, this
34
+ class is the recommended way to create abstract objects in
35
+ abstract categories.
36
+ """
37
+
38
+
39
+ class Morphism(Basic):
40
+ """
41
+ The base class for any morphism in an abstract category.
42
+
43
+ Explanation
44
+ ===========
45
+
46
+ In abstract categories, a morphism is an arrow between two
47
+ category objects. The object where the arrow starts is called the
48
+ domain, while the object where the arrow ends is called the
49
+ codomain.
50
+
51
+ Two morphisms between the same pair of objects are considered to
52
+ be the same morphisms. To distinguish between morphisms between
53
+ the same objects use :class:`NamedMorphism`.
54
+
55
+ It is prohibited to instantiate this class. Use one of the
56
+ derived classes instead.
57
+
58
+ See Also
59
+ ========
60
+
61
+ IdentityMorphism, NamedMorphism, CompositeMorphism
62
+ """
63
+ def __new__(cls, domain, codomain):
64
+ raise(NotImplementedError(
65
+ "Cannot instantiate Morphism. Use derived classes instead."))
66
+
67
+ @property
68
+ def domain(self):
69
+ """
70
+ Returns the domain of the morphism.
71
+
72
+ Examples
73
+ ========
74
+
75
+ >>> from sympy.categories import Object, NamedMorphism
76
+ >>> A = Object("A")
77
+ >>> B = Object("B")
78
+ >>> f = NamedMorphism(A, B, "f")
79
+ >>> f.domain
80
+ Object("A")
81
+
82
+ """
83
+ return self.args[0]
84
+
85
+ @property
86
+ def codomain(self):
87
+ """
88
+ Returns the codomain of the morphism.
89
+
90
+ Examples
91
+ ========
92
+
93
+ >>> from sympy.categories import Object, NamedMorphism
94
+ >>> A = Object("A")
95
+ >>> B = Object("B")
96
+ >>> f = NamedMorphism(A, B, "f")
97
+ >>> f.codomain
98
+ Object("B")
99
+
100
+ """
101
+ return self.args[1]
102
+
103
+ def compose(self, other):
104
+ r"""
105
+ Composes self with the supplied morphism.
106
+
107
+ The order of elements in the composition is the usual order,
108
+ i.e., to construct `g\circ f` use ``g.compose(f)``.
109
+
110
+ Examples
111
+ ========
112
+
113
+ >>> from sympy.categories import Object, NamedMorphism
114
+ >>> A = Object("A")
115
+ >>> B = Object("B")
116
+ >>> C = Object("C")
117
+ >>> f = NamedMorphism(A, B, "f")
118
+ >>> g = NamedMorphism(B, C, "g")
119
+ >>> g * f
120
+ CompositeMorphism((NamedMorphism(Object("A"), Object("B"), "f"),
121
+ NamedMorphism(Object("B"), Object("C"), "g")))
122
+ >>> (g * f).domain
123
+ Object("A")
124
+ >>> (g * f).codomain
125
+ Object("C")
126
+
127
+ """
128
+ return CompositeMorphism(other, self)
129
+
130
+ def __mul__(self, other):
131
+ r"""
132
+ Composes self with the supplied morphism.
133
+
134
+ The semantics of this operation is given by the following
135
+ equation: ``g * f == g.compose(f)`` for composable morphisms
136
+ ``g`` and ``f``.
137
+
138
+ See Also
139
+ ========
140
+
141
+ compose
142
+ """
143
+ return self.compose(other)
144
+
145
+
146
+ class IdentityMorphism(Morphism):
147
+ """
148
+ Represents an identity morphism.
149
+
150
+ Explanation
151
+ ===========
152
+
153
+ An identity morphism is a morphism with equal domain and codomain,
154
+ which acts as an identity with respect to composition.
155
+
156
+ Examples
157
+ ========
158
+
159
+ >>> from sympy.categories import Object, NamedMorphism, IdentityMorphism
160
+ >>> A = Object("A")
161
+ >>> B = Object("B")
162
+ >>> f = NamedMorphism(A, B, "f")
163
+ >>> id_A = IdentityMorphism(A)
164
+ >>> id_B = IdentityMorphism(B)
165
+ >>> f * id_A == f
166
+ True
167
+ >>> id_B * f == f
168
+ True
169
+
170
+ See Also
171
+ ========
172
+
173
+ Morphism
174
+ """
175
+ def __new__(cls, domain):
176
+ return Basic.__new__(cls, domain)
177
+
178
+ @property
179
+ def codomain(self):
180
+ return self.domain
181
+
182
+
183
+ class NamedMorphism(Morphism):
184
+ """
185
+ Represents a morphism which has a name.
186
+
187
+ Explanation
188
+ ===========
189
+
190
+ Names are used to distinguish between morphisms which have the
191
+ same domain and codomain: two named morphisms are equal if they
192
+ have the same domains, codomains, and names.
193
+
194
+ Examples
195
+ ========
196
+
197
+ >>> from sympy.categories import Object, NamedMorphism
198
+ >>> A = Object("A")
199
+ >>> B = Object("B")
200
+ >>> f = NamedMorphism(A, B, "f")
201
+ >>> f
202
+ NamedMorphism(Object("A"), Object("B"), "f")
203
+ >>> f.name
204
+ 'f'
205
+
206
+ See Also
207
+ ========
208
+
209
+ Morphism
210
+ """
211
+ def __new__(cls, domain, codomain, name):
212
+ if not name:
213
+ raise ValueError("Empty morphism names not allowed.")
214
+
215
+ if not isinstance(name, Str):
216
+ name = Str(name)
217
+
218
+ return Basic.__new__(cls, domain, codomain, name)
219
+
220
+ @property
221
+ def name(self):
222
+ """
223
+ Returns the name of the morphism.
224
+
225
+ Examples
226
+ ========
227
+
228
+ >>> from sympy.categories import Object, NamedMorphism
229
+ >>> A = Object("A")
230
+ >>> B = Object("B")
231
+ >>> f = NamedMorphism(A, B, "f")
232
+ >>> f.name
233
+ 'f'
234
+
235
+ """
236
+ return self.args[2].name
237
+
238
+
239
+ class CompositeMorphism(Morphism):
240
+ r"""
241
+ Represents a morphism which is a composition of other morphisms.
242
+
243
+ Explanation
244
+ ===========
245
+
246
+ Two composite morphisms are equal if the morphisms they were
247
+ obtained from (components) are the same and were listed in the
248
+ same order.
249
+
250
+ The arguments to the constructor for this class should be listed
251
+ in diagram order: to obtain the composition `g\circ f` from the
252
+ instances of :class:`Morphism` ``g`` and ``f`` use
253
+ ``CompositeMorphism(f, g)``.
254
+
255
+ Examples
256
+ ========
257
+
258
+ >>> from sympy.categories import Object, NamedMorphism, CompositeMorphism
259
+ >>> A = Object("A")
260
+ >>> B = Object("B")
261
+ >>> C = Object("C")
262
+ >>> f = NamedMorphism(A, B, "f")
263
+ >>> g = NamedMorphism(B, C, "g")
264
+ >>> g * f
265
+ CompositeMorphism((NamedMorphism(Object("A"), Object("B"), "f"),
266
+ NamedMorphism(Object("B"), Object("C"), "g")))
267
+ >>> CompositeMorphism(f, g) == g * f
268
+ True
269
+
270
+ """
271
+ @staticmethod
272
+ def _add_morphism(t, morphism):
273
+ """
274
+ Intelligently adds ``morphism`` to tuple ``t``.
275
+
276
+ Explanation
277
+ ===========
278
+
279
+ If ``morphism`` is a composite morphism, its components are
280
+ added to the tuple. If ``morphism`` is an identity, nothing
281
+ is added to the tuple.
282
+
283
+ No composability checks are performed.
284
+ """
285
+ if isinstance(morphism, CompositeMorphism):
286
+ # ``morphism`` is a composite morphism; we have to
287
+ # denest its components.
288
+ return t + morphism.components
289
+ elif isinstance(morphism, IdentityMorphism):
290
+ # ``morphism`` is an identity. Nothing happens.
291
+ return t
292
+ else:
293
+ return t + Tuple(morphism)
294
+
295
+ def __new__(cls, *components):
296
+ if components and not isinstance(components[0], Morphism):
297
+ # Maybe the user has explicitly supplied a list of
298
+ # morphisms.
299
+ return CompositeMorphism.__new__(cls, *components[0])
300
+
301
+ normalised_components = Tuple()
302
+
303
+ for current, following in zip(components, components[1:]):
304
+ if not isinstance(current, Morphism) or \
305
+ not isinstance(following, Morphism):
306
+ raise TypeError("All components must be morphisms.")
307
+
308
+ if current.codomain != following.domain:
309
+ raise ValueError("Uncomposable morphisms.")
310
+
311
+ normalised_components = CompositeMorphism._add_morphism(
312
+ normalised_components, current)
313
+
314
+ # We haven't added the last morphism to the list of normalised
315
+ # components. Add it now.
316
+ normalised_components = CompositeMorphism._add_morphism(
317
+ normalised_components, components[-1])
318
+
319
+ if not normalised_components:
320
+ # If ``normalised_components`` is empty, only identities
321
+ # were supplied. Since they all were composable, they are
322
+ # all the same identities.
323
+ return components[0]
324
+ elif len(normalised_components) == 1:
325
+ # No sense to construct a whole CompositeMorphism.
326
+ return normalised_components[0]
327
+
328
+ return Basic.__new__(cls, normalised_components)
329
+
330
+ @property
331
+ def components(self):
332
+ """
333
+ Returns the components of this composite morphism.
334
+
335
+ Examples
336
+ ========
337
+
338
+ >>> from sympy.categories import Object, NamedMorphism
339
+ >>> A = Object("A")
340
+ >>> B = Object("B")
341
+ >>> C = Object("C")
342
+ >>> f = NamedMorphism(A, B, "f")
343
+ >>> g = NamedMorphism(B, C, "g")
344
+ >>> (g * f).components
345
+ (NamedMorphism(Object("A"), Object("B"), "f"),
346
+ NamedMorphism(Object("B"), Object("C"), "g"))
347
+
348
+ """
349
+ return self.args[0]
350
+
351
+ @property
352
+ def domain(self):
353
+ """
354
+ Returns the domain of this composite morphism.
355
+
356
+ The domain of the composite morphism is the domain of its
357
+ first component.
358
+
359
+ Examples
360
+ ========
361
+
362
+ >>> from sympy.categories import Object, NamedMorphism
363
+ >>> A = Object("A")
364
+ >>> B = Object("B")
365
+ >>> C = Object("C")
366
+ >>> f = NamedMorphism(A, B, "f")
367
+ >>> g = NamedMorphism(B, C, "g")
368
+ >>> (g * f).domain
369
+ Object("A")
370
+
371
+ """
372
+ return self.components[0].domain
373
+
374
+ @property
375
+ def codomain(self):
376
+ """
377
+ Returns the codomain of this composite morphism.
378
+
379
+ The codomain of the composite morphism is the codomain of its
380
+ last component.
381
+
382
+ Examples
383
+ ========
384
+
385
+ >>> from sympy.categories import Object, NamedMorphism
386
+ >>> A = Object("A")
387
+ >>> B = Object("B")
388
+ >>> C = Object("C")
389
+ >>> f = NamedMorphism(A, B, "f")
390
+ >>> g = NamedMorphism(B, C, "g")
391
+ >>> (g * f).codomain
392
+ Object("C")
393
+
394
+ """
395
+ return self.components[-1].codomain
396
+
397
+ def flatten(self, new_name):
398
+ """
399
+ Forgets the composite structure of this morphism.
400
+
401
+ Explanation
402
+ ===========
403
+
404
+ If ``new_name`` is not empty, returns a :class:`NamedMorphism`
405
+ with the supplied name, otherwise returns a :class:`Morphism`.
406
+ In both cases the domain of the new morphism is the domain of
407
+ this composite morphism and the codomain of the new morphism
408
+ is the codomain of this composite morphism.
409
+
410
+ Examples
411
+ ========
412
+
413
+ >>> from sympy.categories import Object, NamedMorphism
414
+ >>> A = Object("A")
415
+ >>> B = Object("B")
416
+ >>> C = Object("C")
417
+ >>> f = NamedMorphism(A, B, "f")
418
+ >>> g = NamedMorphism(B, C, "g")
419
+ >>> (g * f).flatten("h")
420
+ NamedMorphism(Object("A"), Object("C"), "h")
421
+
422
+ """
423
+ return NamedMorphism(self.domain, self.codomain, new_name)
424
+
425
+
426
+ class Category(Basic):
427
+ r"""
428
+ An (abstract) category.
429
+
430
+ Explanation
431
+ ===========
432
+
433
+ A category [JoyOfCats] is a quadruple `\mbox{K} = (O, \hom, id,
434
+ \circ)` consisting of
435
+
436
+ * a (set-theoretical) class `O`, whose members are called
437
+ `K`-objects,
438
+
439
+ * for each pair `(A, B)` of `K`-objects, a set `\hom(A, B)` whose
440
+ members are called `K`-morphisms from `A` to `B`,
441
+
442
+ * for a each `K`-object `A`, a morphism `id:A\rightarrow A`,
443
+ called the `K`-identity of `A`,
444
+
445
+ * a composition law `\circ` associating with every `K`-morphisms
446
+ `f:A\rightarrow B` and `g:B\rightarrow C` a `K`-morphism `g\circ
447
+ f:A\rightarrow C`, called the composite of `f` and `g`.
448
+
449
+ Composition is associative, `K`-identities are identities with
450
+ respect to composition, and the sets `\hom(A, B)` are pairwise
451
+ disjoint.
452
+
453
+ This class knows nothing about its objects and morphisms.
454
+ Concrete cases of (abstract) categories should be implemented as
455
+ classes derived from this one.
456
+
457
+ Certain instances of :class:`Diagram` can be asserted to be
458
+ commutative in a :class:`Category` by supplying the argument
459
+ ``commutative_diagrams`` in the constructor.
460
+
461
+ Examples
462
+ ========
463
+
464
+ >>> from sympy.categories import Object, NamedMorphism, Diagram, Category
465
+ >>> from sympy import FiniteSet
466
+ >>> A = Object("A")
467
+ >>> B = Object("B")
468
+ >>> C = Object("C")
469
+ >>> f = NamedMorphism(A, B, "f")
470
+ >>> g = NamedMorphism(B, C, "g")
471
+ >>> d = Diagram([f, g])
472
+ >>> K = Category("K", commutative_diagrams=[d])
473
+ >>> K.commutative_diagrams == FiniteSet(d)
474
+ True
475
+
476
+ See Also
477
+ ========
478
+
479
+ Diagram
480
+ """
481
+ def __new__(cls, name, objects=EmptySet, commutative_diagrams=EmptySet):
482
+ if not name:
483
+ raise ValueError("A Category cannot have an empty name.")
484
+
485
+ if not isinstance(name, Str):
486
+ name = Str(name)
487
+
488
+ if not isinstance(objects, Class):
489
+ objects = Class(objects)
490
+
491
+ new_category = Basic.__new__(cls, name, objects,
492
+ FiniteSet(*commutative_diagrams))
493
+ return new_category
494
+
495
+ @property
496
+ def name(self):
497
+ """
498
+ Returns the name of this category.
499
+
500
+ Examples
501
+ ========
502
+
503
+ >>> from sympy.categories import Category
504
+ >>> K = Category("K")
505
+ >>> K.name
506
+ 'K'
507
+
508
+ """
509
+ return self.args[0].name
510
+
511
+ @property
512
+ def objects(self):
513
+ """
514
+ Returns the class of objects of this category.
515
+
516
+ Examples
517
+ ========
518
+
519
+ >>> from sympy.categories import Object, Category
520
+ >>> from sympy import FiniteSet
521
+ >>> A = Object("A")
522
+ >>> B = Object("B")
523
+ >>> K = Category("K", FiniteSet(A, B))
524
+ >>> K.objects
525
+ Class({Object("A"), Object("B")})
526
+
527
+ """
528
+ return self.args[1]
529
+
530
+ @property
531
+ def commutative_diagrams(self):
532
+ """
533
+ Returns the :class:`~.FiniteSet` of diagrams which are known to
534
+ be commutative in this category.
535
+
536
+ Examples
537
+ ========
538
+
539
+ >>> from sympy.categories import Object, NamedMorphism, Diagram, Category
540
+ >>> from sympy import FiniteSet
541
+ >>> A = Object("A")
542
+ >>> B = Object("B")
543
+ >>> C = Object("C")
544
+ >>> f = NamedMorphism(A, B, "f")
545
+ >>> g = NamedMorphism(B, C, "g")
546
+ >>> d = Diagram([f, g])
547
+ >>> K = Category("K", commutative_diagrams=[d])
548
+ >>> K.commutative_diagrams == FiniteSet(d)
549
+ True
550
+
551
+ """
552
+ return self.args[2]
553
+
554
+ def hom(self, A, B):
555
+ raise NotImplementedError(
556
+ "hom-sets are not implemented in Category.")
557
+
558
+ def all_morphisms(self):
559
+ raise NotImplementedError(
560
+ "Obtaining the class of morphisms is not implemented in Category.")
561
+
562
+
563
+ class Diagram(Basic):
564
+ r"""
565
+ Represents a diagram in a certain category.
566
+
567
+ Explanation
568
+ ===========
569
+
570
+ Informally, a diagram is a collection of objects of a category and
571
+ certain morphisms between them. A diagram is still a monoid with
572
+ respect to morphism composition; i.e., identity morphisms, as well
573
+ as all composites of morphisms included in the diagram belong to
574
+ the diagram. For a more formal approach to this notion see
575
+ [Pare1970].
576
+
577
+ The components of composite morphisms are also added to the
578
+ diagram. No properties are assigned to such morphisms by default.
579
+
580
+ A commutative diagram is often accompanied by a statement of the
581
+ following kind: "if such morphisms with such properties exist,
582
+ then such morphisms which such properties exist and the diagram is
583
+ commutative". To represent this, an instance of :class:`Diagram`
584
+ includes a collection of morphisms which are the premises and
585
+ another collection of conclusions. ``premises`` and
586
+ ``conclusions`` associate morphisms belonging to the corresponding
587
+ categories with the :class:`~.FiniteSet`'s of their properties.
588
+
589
+ The set of properties of a composite morphism is the intersection
590
+ of the sets of properties of its components. The domain and
591
+ codomain of a conclusion morphism should be among the domains and
592
+ codomains of the morphisms listed as the premises of a diagram.
593
+
594
+ No checks are carried out of whether the supplied object and
595
+ morphisms do belong to one and the same category.
596
+
597
+ Examples
598
+ ========
599
+
600
+ >>> from sympy.categories import Object, NamedMorphism, Diagram
601
+ >>> from sympy import pprint, default_sort_key
602
+ >>> A = Object("A")
603
+ >>> B = Object("B")
604
+ >>> C = Object("C")
605
+ >>> f = NamedMorphism(A, B, "f")
606
+ >>> g = NamedMorphism(B, C, "g")
607
+ >>> d = Diagram([f, g])
608
+ >>> premises_keys = sorted(d.premises.keys(), key=default_sort_key)
609
+ >>> pprint(premises_keys, use_unicode=False)
610
+ [g*f:A-->C, id:A-->A, id:B-->B, id:C-->C, f:A-->B, g:B-->C]
611
+ >>> pprint(d.premises, use_unicode=False)
612
+ {g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet,
613
+ id:C-->C: EmptySet, f:A-->B: EmptySet, g:B-->C: EmptySet}
614
+ >>> d = Diagram([f, g], {g * f: "unique"})
615
+ >>> pprint(d.conclusions,use_unicode=False)
616
+ {g*f:A-->C: {unique}}
617
+
618
+ References
619
+ ==========
620
+
621
+ [Pare1970] B. Pareigis: Categories and functors. Academic Press, 1970.
622
+
623
+ """
624
+ @staticmethod
625
+ def _set_dict_union(dictionary, key, value):
626
+ """
627
+ If ``key`` is in ``dictionary``, set the new value of ``key``
628
+ to be the union between the old value and ``value``.
629
+ Otherwise, set the value of ``key`` to ``value.
630
+
631
+ Returns ``True`` if the key already was in the dictionary and
632
+ ``False`` otherwise.
633
+ """
634
+ if key in dictionary:
635
+ dictionary[key] = dictionary[key] | value
636
+ return True
637
+ else:
638
+ dictionary[key] = value
639
+ return False
640
+
641
+ @staticmethod
642
+ def _add_morphism_closure(morphisms, morphism, props, add_identities=True,
643
+ recurse_composites=True):
644
+ """
645
+ Adds a morphism and its attributes to the supplied dictionary
646
+ ``morphisms``. If ``add_identities`` is True, also adds the
647
+ identity morphisms for the domain and the codomain of
648
+ ``morphism``.
649
+ """
650
+ if not Diagram._set_dict_union(morphisms, morphism, props):
651
+ # We have just added a new morphism.
652
+
653
+ if isinstance(morphism, IdentityMorphism):
654
+ if props:
655
+ # Properties for identity morphisms don't really
656
+ # make sense, because very much is known about
657
+ # identity morphisms already, so much that they
658
+ # are trivial. Having properties for identity
659
+ # morphisms would only be confusing.
660
+ raise ValueError(
661
+ "Instances of IdentityMorphism cannot have properties.")
662
+ return
663
+
664
+ if add_identities:
665
+ empty = EmptySet
666
+
667
+ id_dom = IdentityMorphism(morphism.domain)
668
+ id_cod = IdentityMorphism(morphism.codomain)
669
+
670
+ Diagram._set_dict_union(morphisms, id_dom, empty)
671
+ Diagram._set_dict_union(morphisms, id_cod, empty)
672
+
673
+ for existing_morphism, existing_props in list(morphisms.items()):
674
+ new_props = existing_props & props
675
+ if morphism.domain == existing_morphism.codomain:
676
+ left = morphism * existing_morphism
677
+ Diagram._set_dict_union(morphisms, left, new_props)
678
+ if morphism.codomain == existing_morphism.domain:
679
+ right = existing_morphism * morphism
680
+ Diagram._set_dict_union(morphisms, right, new_props)
681
+
682
+ if isinstance(morphism, CompositeMorphism) and recurse_composites:
683
+ # This is a composite morphism, add its components as
684
+ # well.
685
+ empty = EmptySet
686
+ for component in morphism.components:
687
+ Diagram._add_morphism_closure(morphisms, component, empty,
688
+ add_identities)
689
+
690
+ def __new__(cls, *args):
691
+ """
692
+ Construct a new instance of Diagram.
693
+
694
+ Explanation
695
+ ===========
696
+
697
+ If no arguments are supplied, an empty diagram is created.
698
+
699
+ If at least an argument is supplied, ``args[0]`` is
700
+ interpreted as the premises of the diagram. If ``args[0]`` is
701
+ a list, it is interpreted as a list of :class:`Morphism`'s, in
702
+ which each :class:`Morphism` has an empty set of properties.
703
+ If ``args[0]`` is a Python dictionary or a :class:`Dict`, it
704
+ is interpreted as a dictionary associating to some
705
+ :class:`Morphism`'s some properties.
706
+
707
+ If at least two arguments are supplied ``args[1]`` is
708
+ interpreted as the conclusions of the diagram. The type of
709
+ ``args[1]`` is interpreted in exactly the same way as the type
710
+ of ``args[0]``. If only one argument is supplied, the diagram
711
+ has no conclusions.
712
+
713
+ Examples
714
+ ========
715
+
716
+ >>> from sympy.categories import Object, NamedMorphism
717
+ >>> from sympy.categories import IdentityMorphism, Diagram
718
+ >>> A = Object("A")
719
+ >>> B = Object("B")
720
+ >>> C = Object("C")
721
+ >>> f = NamedMorphism(A, B, "f")
722
+ >>> g = NamedMorphism(B, C, "g")
723
+ >>> d = Diagram([f, g])
724
+ >>> IdentityMorphism(A) in d.premises.keys()
725
+ True
726
+ >>> g * f in d.premises.keys()
727
+ True
728
+ >>> d = Diagram([f, g], {g * f: "unique"})
729
+ >>> d.conclusions[g * f]
730
+ {unique}
731
+
732
+ """
733
+ premises = {}
734
+ conclusions = {}
735
+
736
+ # Here we will keep track of the objects which appear in the
737
+ # premises.
738
+ objects = EmptySet
739
+
740
+ if len(args) >= 1:
741
+ # We've got some premises in the arguments.
742
+ premises_arg = args[0]
743
+
744
+ if isinstance(premises_arg, list):
745
+ # The user has supplied a list of morphisms, none of
746
+ # which have any attributes.
747
+ empty = EmptySet
748
+
749
+ for morphism in premises_arg:
750
+ objects |= FiniteSet(morphism.domain, morphism.codomain)
751
+ Diagram._add_morphism_closure(premises, morphism, empty)
752
+ elif isinstance(premises_arg, (dict, Dict)):
753
+ # The user has supplied a dictionary of morphisms and
754
+ # their properties.
755
+ for morphism, props in premises_arg.items():
756
+ objects |= FiniteSet(morphism.domain, morphism.codomain)
757
+ Diagram._add_morphism_closure(
758
+ premises, morphism, FiniteSet(*props) if iterable(props) else FiniteSet(props))
759
+
760
+ if len(args) >= 2:
761
+ # We also have some conclusions.
762
+ conclusions_arg = args[1]
763
+
764
+ if isinstance(conclusions_arg, list):
765
+ # The user has supplied a list of morphisms, none of
766
+ # which have any attributes.
767
+ empty = EmptySet
768
+
769
+ for morphism in conclusions_arg:
770
+ # Check that no new objects appear in conclusions.
771
+ if ((sympify(objects.contains(morphism.domain)) is S.true) and
772
+ (sympify(objects.contains(morphism.codomain)) is S.true)):
773
+ # No need to add identities and recurse
774
+ # composites this time.
775
+ Diagram._add_morphism_closure(
776
+ conclusions, morphism, empty, add_identities=False,
777
+ recurse_composites=False)
778
+ elif isinstance(conclusions_arg, (dict, Dict)):
779
+ # The user has supplied a dictionary of morphisms and
780
+ # their properties.
781
+ for morphism, props in conclusions_arg.items():
782
+ # Check that no new objects appear in conclusions.
783
+ if (morphism.domain in objects) and \
784
+ (morphism.codomain in objects):
785
+ # No need to add identities and recurse
786
+ # composites this time.
787
+ Diagram._add_morphism_closure(
788
+ conclusions, morphism, FiniteSet(*props) if iterable(props) else FiniteSet(props),
789
+ add_identities=False, recurse_composites=False)
790
+
791
+ return Basic.__new__(cls, Dict(premises), Dict(conclusions), objects)
792
+
793
+ @property
794
+ def premises(self):
795
+ """
796
+ Returns the premises of this diagram.
797
+
798
+ Examples
799
+ ========
800
+
801
+ >>> from sympy.categories import Object, NamedMorphism
802
+ >>> from sympy.categories import IdentityMorphism, Diagram
803
+ >>> from sympy import pretty
804
+ >>> A = Object("A")
805
+ >>> B = Object("B")
806
+ >>> f = NamedMorphism(A, B, "f")
807
+ >>> id_A = IdentityMorphism(A)
808
+ >>> id_B = IdentityMorphism(B)
809
+ >>> d = Diagram([f])
810
+ >>> print(pretty(d.premises, use_unicode=False))
811
+ {id:A-->A: EmptySet, id:B-->B: EmptySet, f:A-->B: EmptySet}
812
+
813
+ """
814
+ return self.args[0]
815
+
816
+ @property
817
+ def conclusions(self):
818
+ """
819
+ Returns the conclusions of this diagram.
820
+
821
+ Examples
822
+ ========
823
+
824
+ >>> from sympy.categories import Object, NamedMorphism
825
+ >>> from sympy.categories import IdentityMorphism, Diagram
826
+ >>> from sympy import FiniteSet
827
+ >>> A = Object("A")
828
+ >>> B = Object("B")
829
+ >>> C = Object("C")
830
+ >>> f = NamedMorphism(A, B, "f")
831
+ >>> g = NamedMorphism(B, C, "g")
832
+ >>> d = Diagram([f, g])
833
+ >>> IdentityMorphism(A) in d.premises.keys()
834
+ True
835
+ >>> g * f in d.premises.keys()
836
+ True
837
+ >>> d = Diagram([f, g], {g * f: "unique"})
838
+ >>> d.conclusions[g * f] == FiniteSet("unique")
839
+ True
840
+
841
+ """
842
+ return self.args[1]
843
+
844
+ @property
845
+ def objects(self):
846
+ """
847
+ Returns the :class:`~.FiniteSet` of objects that appear in this
848
+ diagram.
849
+
850
+ Examples
851
+ ========
852
+
853
+ >>> from sympy.categories import Object, NamedMorphism, Diagram
854
+ >>> A = Object("A")
855
+ >>> B = Object("B")
856
+ >>> C = Object("C")
857
+ >>> f = NamedMorphism(A, B, "f")
858
+ >>> g = NamedMorphism(B, C, "g")
859
+ >>> d = Diagram([f, g])
860
+ >>> d.objects
861
+ {Object("A"), Object("B"), Object("C")}
862
+
863
+ """
864
+ return self.args[2]
865
+
866
+ def hom(self, A, B):
867
+ """
868
+ Returns a 2-tuple of sets of morphisms between objects ``A`` and
869
+ ``B``: one set of morphisms listed as premises, and the other set
870
+ of morphisms listed as conclusions.
871
+
872
+ Examples
873
+ ========
874
+
875
+ >>> from sympy.categories import Object, NamedMorphism, Diagram
876
+ >>> from sympy import pretty
877
+ >>> A = Object("A")
878
+ >>> B = Object("B")
879
+ >>> C = Object("C")
880
+ >>> f = NamedMorphism(A, B, "f")
881
+ >>> g = NamedMorphism(B, C, "g")
882
+ >>> d = Diagram([f, g], {g * f: "unique"})
883
+ >>> print(pretty(d.hom(A, C), use_unicode=False))
884
+ ({g*f:A-->C}, {g*f:A-->C})
885
+
886
+ See Also
887
+ ========
888
+ Object, Morphism
889
+ """
890
+ premises = EmptySet
891
+ conclusions = EmptySet
892
+
893
+ for morphism in self.premises.keys():
894
+ if (morphism.domain == A) and (morphism.codomain == B):
895
+ premises |= FiniteSet(morphism)
896
+ for morphism in self.conclusions.keys():
897
+ if (morphism.domain == A) and (morphism.codomain == B):
898
+ conclusions |= FiniteSet(morphism)
899
+
900
+ return (premises, conclusions)
901
+
902
+ def is_subdiagram(self, diagram):
903
+ """
904
+ Checks whether ``diagram`` is a subdiagram of ``self``.
905
+ Diagram `D'` is a subdiagram of `D` if all premises
906
+ (conclusions) of `D'` are contained in the premises
907
+ (conclusions) of `D`. The morphisms contained
908
+ both in `D'` and `D` should have the same properties for `D'`
909
+ to be a subdiagram of `D`.
910
+
911
+ Examples
912
+ ========
913
+
914
+ >>> from sympy.categories import Object, NamedMorphism, Diagram
915
+ >>> A = Object("A")
916
+ >>> B = Object("B")
917
+ >>> C = Object("C")
918
+ >>> f = NamedMorphism(A, B, "f")
919
+ >>> g = NamedMorphism(B, C, "g")
920
+ >>> d = Diagram([f, g], {g * f: "unique"})
921
+ >>> d1 = Diagram([f])
922
+ >>> d.is_subdiagram(d1)
923
+ True
924
+ >>> d1.is_subdiagram(d)
925
+ False
926
+ """
927
+ premises = all((m in self.premises) and
928
+ (diagram.premises[m] == self.premises[m])
929
+ for m in diagram.premises)
930
+ if not premises:
931
+ return False
932
+
933
+ conclusions = all((m in self.conclusions) and
934
+ (diagram.conclusions[m] == self.conclusions[m])
935
+ for m in diagram.conclusions)
936
+
937
+ # Premises is surely ``True`` here.
938
+ return conclusions
939
+
940
+ def subdiagram_from_objects(self, objects):
941
+ """
942
+ If ``objects`` is a subset of the objects of ``self``, returns
943
+ a diagram which has as premises all those premises of ``self``
944
+ which have a domains and codomains in ``objects``, likewise
945
+ for conclusions. Properties are preserved.
946
+
947
+ Examples
948
+ ========
949
+
950
+ >>> from sympy.categories import Object, NamedMorphism, Diagram
951
+ >>> from sympy import FiniteSet
952
+ >>> A = Object("A")
953
+ >>> B = Object("B")
954
+ >>> C = Object("C")
955
+ >>> f = NamedMorphism(A, B, "f")
956
+ >>> g = NamedMorphism(B, C, "g")
957
+ >>> d = Diagram([f, g], {f: "unique", g*f: "veryunique"})
958
+ >>> d1 = d.subdiagram_from_objects(FiniteSet(A, B))
959
+ >>> d1 == Diagram([f], {f: "unique"})
960
+ True
961
+ """
962
+ if not objects.is_subset(self.objects):
963
+ raise ValueError(
964
+ "Supplied objects should all belong to the diagram.")
965
+
966
+ new_premises = {}
967
+ for morphism, props in self.premises.items():
968
+ if ((sympify(objects.contains(morphism.domain)) is S.true) and
969
+ (sympify(objects.contains(morphism.codomain)) is S.true)):
970
+ new_premises[morphism] = props
971
+
972
+ new_conclusions = {}
973
+ for morphism, props in self.conclusions.items():
974
+ if ((sympify(objects.contains(morphism.domain)) is S.true) and
975
+ (sympify(objects.contains(morphism.codomain)) is S.true)):
976
+ new_conclusions[morphism] = props
977
+
978
+ return Diagram(new_premises, new_conclusions)
lib/python3.12/site-packages/sympy/categories/diagram_drawing.py ADDED
@@ -0,0 +1,2582 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ r"""
2
+ This module contains the functionality to arrange the nodes of a
3
+ diagram on an abstract grid, and then to produce a graphical
4
+ representation of the grid.
5
+
6
+ The currently supported back-ends are Xy-pic [Xypic].
7
+
8
+ Layout Algorithm
9
+ ================
10
+
11
+ This section provides an overview of the algorithms implemented in
12
+ :class:`DiagramGrid` to lay out diagrams.
13
+
14
+ The first step of the algorithm is the removal composite and identity
15
+ morphisms which do not have properties in the supplied diagram. The
16
+ premises and conclusions of the diagram are then merged.
17
+
18
+ The generic layout algorithm begins with the construction of the
19
+ "skeleton" of the diagram. The skeleton is an undirected graph which
20
+ has the objects of the diagram as vertices and has an (undirected)
21
+ edge between each pair of objects between which there exist morphisms.
22
+ The direction of the morphisms does not matter at this stage. The
23
+ skeleton also includes an edge between each pair of vertices `A` and
24
+ `C` such that there exists an object `B` which is connected via
25
+ a morphism to `A`, and via a morphism to `C`.
26
+
27
+ The skeleton constructed in this way has the property that every
28
+ object is a vertex of a triangle formed by three edges of the
29
+ skeleton. This property lies at the base of the generic layout
30
+ algorithm.
31
+
32
+ After the skeleton has been constructed, the algorithm lists all
33
+ triangles which can be formed. Note that some triangles will not have
34
+ all edges corresponding to morphisms which will actually be drawn.
35
+ Triangles which have only one edge or less which will actually be
36
+ drawn are immediately discarded.
37
+
38
+ The list of triangles is sorted according to the number of edges which
39
+ correspond to morphisms, then the triangle with the least number of such
40
+ edges is selected. One of such edges is picked and the corresponding
41
+ objects are placed horizontally, on a grid. This edge is recorded to
42
+ be in the fringe. The algorithm then finds a "welding" of a triangle
43
+ to the fringe. A welding is an edge in the fringe where a triangle
44
+ could be attached. If the algorithm succeeds in finding such a
45
+ welding, it adds to the grid that vertex of the triangle which was not
46
+ yet included in any edge in the fringe and records the two new edges in
47
+ the fringe. This process continues iteratively until all objects of
48
+ the diagram has been placed or until no more weldings can be found.
49
+
50
+ An edge is only removed from the fringe when a welding to this edge
51
+ has been found, and there is no room around this edge to place
52
+ another vertex.
53
+
54
+ When no more weldings can be found, but there are still triangles
55
+ left, the algorithm searches for a possibility of attaching one of the
56
+ remaining triangles to the existing structure by a vertex. If such a
57
+ possibility is found, the corresponding edge of the found triangle is
58
+ placed in the found space and the iterative process of welding
59
+ triangles restarts.
60
+
61
+ When logical groups are supplied, each of these groups is laid out
62
+ independently. Then a diagram is constructed in which groups are
63
+ objects and any two logical groups between which there exist morphisms
64
+ are connected via a morphism. This diagram is laid out. Finally,
65
+ the grid which includes all objects of the initial diagram is
66
+ constructed by replacing the cells which contain logical groups with
67
+ the corresponding laid out grids, and by correspondingly expanding the
68
+ rows and columns.
69
+
70
+ The sequential layout algorithm begins by constructing the
71
+ underlying undirected graph defined by the morphisms obtained after
72
+ simplifying premises and conclusions and merging them (see above).
73
+ The vertex with the minimal degree is then picked up and depth-first
74
+ search is started from it. All objects which are located at distance
75
+ `n` from the root in the depth-first search tree, are positioned in
76
+ the `n`-th column of the resulting grid. The sequential layout will
77
+ therefore attempt to lay the objects out along a line.
78
+
79
+ References
80
+ ==========
81
+
82
+ .. [Xypic] https://xy-pic.sourceforge.net/
83
+
84
+ """
85
+ from sympy.categories import (CompositeMorphism, IdentityMorphism,
86
+ NamedMorphism, Diagram)
87
+ from sympy.core import Dict, Symbol, default_sort_key
88
+ from sympy.printing.latex import latex
89
+ from sympy.sets import FiniteSet
90
+ from sympy.utilities.iterables import iterable
91
+ from sympy.utilities.decorator import doctest_depends_on
92
+
93
+ from itertools import chain
94
+
95
+
96
+ __doctest_requires__ = {('preview_diagram',): 'pyglet'}
97
+
98
+
99
+ class _GrowableGrid:
100
+ """
101
+ Holds a growable grid of objects.
102
+
103
+ Explanation
104
+ ===========
105
+
106
+ It is possible to append or prepend a row or a column to the grid
107
+ using the corresponding methods. Prepending rows or columns has
108
+ the effect of changing the coordinates of the already existing
109
+ elements.
110
+
111
+ This class currently represents a naive implementation of the
112
+ functionality with little attempt at optimisation.
113
+ """
114
+ def __init__(self, width, height):
115
+ self._width = width
116
+ self._height = height
117
+
118
+ self._array = [[None for j in range(width)] for i in range(height)]
119
+
120
+ @property
121
+ def width(self):
122
+ return self._width
123
+
124
+ @property
125
+ def height(self):
126
+ return self._height
127
+
128
+ def __getitem__(self, i_j):
129
+ """
130
+ Returns the element located at in the i-th line and j-th
131
+ column.
132
+ """
133
+ i, j = i_j
134
+ return self._array[i][j]
135
+
136
+ def __setitem__(self, i_j, newvalue):
137
+ """
138
+ Sets the element located at in the i-th line and j-th
139
+ column.
140
+ """
141
+ i, j = i_j
142
+ self._array[i][j] = newvalue
143
+
144
+ def append_row(self):
145
+ """
146
+ Appends an empty row to the grid.
147
+ """
148
+ self._height += 1
149
+ self._array.append([None for j in range(self._width)])
150
+
151
+ def append_column(self):
152
+ """
153
+ Appends an empty column to the grid.
154
+ """
155
+ self._width += 1
156
+ for i in range(self._height):
157
+ self._array[i].append(None)
158
+
159
+ def prepend_row(self):
160
+ """
161
+ Prepends the grid with an empty row.
162
+ """
163
+ self._height += 1
164
+ self._array.insert(0, [None for j in range(self._width)])
165
+
166
+ def prepend_column(self):
167
+ """
168
+ Prepends the grid with an empty column.
169
+ """
170
+ self._width += 1
171
+ for i in range(self._height):
172
+ self._array[i].insert(0, None)
173
+
174
+
175
+ class DiagramGrid:
176
+ r"""
177
+ Constructs and holds the fitting of the diagram into a grid.
178
+
179
+ Explanation
180
+ ===========
181
+
182
+ The mission of this class is to analyse the structure of the
183
+ supplied diagram and to place its objects on a grid such that,
184
+ when the objects and the morphisms are actually drawn, the diagram
185
+ would be "readable", in the sense that there will not be many
186
+ intersections of moprhisms. This class does not perform any
187
+ actual drawing. It does strive nevertheless to offer sufficient
188
+ metadata to draw a diagram.
189
+
190
+ Consider the following simple diagram.
191
+
192
+ >>> from sympy.categories import Object, NamedMorphism
193
+ >>> from sympy.categories import Diagram, DiagramGrid
194
+ >>> from sympy import pprint
195
+ >>> A = Object("A")
196
+ >>> B = Object("B")
197
+ >>> C = Object("C")
198
+ >>> f = NamedMorphism(A, B, "f")
199
+ >>> g = NamedMorphism(B, C, "g")
200
+ >>> diagram = Diagram([f, g])
201
+
202
+ The simplest way to have a diagram laid out is the following:
203
+
204
+ >>> grid = DiagramGrid(diagram)
205
+ >>> (grid.width, grid.height)
206
+ (2, 2)
207
+ >>> pprint(grid)
208
+ A B
209
+ <BLANKLINE>
210
+ C
211
+
212
+ Sometimes one sees the diagram as consisting of logical groups.
213
+ One can advise ``DiagramGrid`` as to such groups by employing the
214
+ ``groups`` keyword argument.
215
+
216
+ Consider the following diagram:
217
+
218
+ >>> D = Object("D")
219
+ >>> f = NamedMorphism(A, B, "f")
220
+ >>> g = NamedMorphism(B, C, "g")
221
+ >>> h = NamedMorphism(D, A, "h")
222
+ >>> k = NamedMorphism(D, B, "k")
223
+ >>> diagram = Diagram([f, g, h, k])
224
+
225
+ Lay it out with generic layout:
226
+
227
+ >>> grid = DiagramGrid(diagram)
228
+ >>> pprint(grid)
229
+ A B D
230
+ <BLANKLINE>
231
+ C
232
+
233
+ Now, we can group the objects `A` and `D` to have them near one
234
+ another:
235
+
236
+ >>> grid = DiagramGrid(diagram, groups=[[A, D], B, C])
237
+ >>> pprint(grid)
238
+ B C
239
+ <BLANKLINE>
240
+ A D
241
+
242
+ Note how the positioning of the other objects changes.
243
+
244
+ Further indications can be supplied to the constructor of
245
+ :class:`DiagramGrid` using keyword arguments. The currently
246
+ supported hints are explained in the following paragraphs.
247
+
248
+ :class:`DiagramGrid` does not automatically guess which layout
249
+ would suit the supplied diagram better. Consider, for example,
250
+ the following linear diagram:
251
+
252
+ >>> E = Object("E")
253
+ >>> f = NamedMorphism(A, B, "f")
254
+ >>> g = NamedMorphism(B, C, "g")
255
+ >>> h = NamedMorphism(C, D, "h")
256
+ >>> i = NamedMorphism(D, E, "i")
257
+ >>> diagram = Diagram([f, g, h, i])
258
+
259
+ When laid out with the generic layout, it does not get to look
260
+ linear:
261
+
262
+ >>> grid = DiagramGrid(diagram)
263
+ >>> pprint(grid)
264
+ A B
265
+ <BLANKLINE>
266
+ C D
267
+ <BLANKLINE>
268
+ E
269
+
270
+ To get it laid out in a line, use ``layout="sequential"``:
271
+
272
+ >>> grid = DiagramGrid(diagram, layout="sequential")
273
+ >>> pprint(grid)
274
+ A B C D E
275
+
276
+ One may sometimes need to transpose the resulting layout. While
277
+ this can always be done by hand, :class:`DiagramGrid` provides a
278
+ hint for that purpose:
279
+
280
+ >>> grid = DiagramGrid(diagram, layout="sequential", transpose=True)
281
+ >>> pprint(grid)
282
+ A
283
+ <BLANKLINE>
284
+ B
285
+ <BLANKLINE>
286
+ C
287
+ <BLANKLINE>
288
+ D
289
+ <BLANKLINE>
290
+ E
291
+
292
+ Separate hints can also be provided for each group. For an
293
+ example, refer to ``tests/test_drawing.py``, and see the different
294
+ ways in which the five lemma [FiveLemma] can be laid out.
295
+
296
+ See Also
297
+ ========
298
+
299
+ Diagram
300
+
301
+ References
302
+ ==========
303
+
304
+ .. [FiveLemma] https://en.wikipedia.org/wiki/Five_lemma
305
+ """
306
+ @staticmethod
307
+ def _simplify_morphisms(morphisms):
308
+ """
309
+ Given a dictionary mapping morphisms to their properties,
310
+ returns a new dictionary in which there are no morphisms which
311
+ do not have properties, and which are compositions of other
312
+ morphisms included in the dictionary. Identities are dropped
313
+ as well.
314
+ """
315
+ newmorphisms = {}
316
+ for morphism, props in morphisms.items():
317
+ if isinstance(morphism, CompositeMorphism) and not props:
318
+ continue
319
+ elif isinstance(morphism, IdentityMorphism):
320
+ continue
321
+ else:
322
+ newmorphisms[morphism] = props
323
+ return newmorphisms
324
+
325
+ @staticmethod
326
+ def _merge_premises_conclusions(premises, conclusions):
327
+ """
328
+ Given two dictionaries of morphisms and their properties,
329
+ produces a single dictionary which includes elements from both
330
+ dictionaries. If a morphism has some properties in premises
331
+ and also in conclusions, the properties in conclusions take
332
+ priority.
333
+ """
334
+ return dict(chain(premises.items(), conclusions.items()))
335
+
336
+ @staticmethod
337
+ def _juxtapose_edges(edge1, edge2):
338
+ """
339
+ If ``edge1`` and ``edge2`` have precisely one common endpoint,
340
+ returns an edge which would form a triangle with ``edge1`` and
341
+ ``edge2``.
342
+
343
+ If ``edge1`` and ``edge2`` do not have a common endpoint,
344
+ returns ``None``.
345
+
346
+ If ``edge1`` and ``edge`` are the same edge, returns ``None``.
347
+ """
348
+ intersection = edge1 & edge2
349
+ if len(intersection) != 1:
350
+ # The edges either have no common points or are equal.
351
+ return None
352
+
353
+ # The edges have a common endpoint. Extract the different
354
+ # endpoints and set up the new edge.
355
+ return (edge1 - intersection) | (edge2 - intersection)
356
+
357
+ @staticmethod
358
+ def _add_edge_append(dictionary, edge, elem):
359
+ """
360
+ If ``edge`` is not in ``dictionary``, adds ``edge`` to the
361
+ dictionary and sets its value to ``[elem]``. Otherwise
362
+ appends ``elem`` to the value of existing entry.
363
+
364
+ Note that edges are undirected, thus `(A, B) = (B, A)`.
365
+ """
366
+ if edge in dictionary:
367
+ dictionary[edge].append(elem)
368
+ else:
369
+ dictionary[edge] = [elem]
370
+
371
+ @staticmethod
372
+ def _build_skeleton(morphisms):
373
+ """
374
+ Creates a dictionary which maps edges to corresponding
375
+ morphisms. Thus for a morphism `f:A\rightarrow B`, the edge
376
+ `(A, B)` will be associated with `f`. This function also adds
377
+ to the list those edges which are formed by juxtaposition of
378
+ two edges already in the list. These new edges are not
379
+ associated with any morphism and are only added to assure that
380
+ the diagram can be decomposed into triangles.
381
+ """
382
+ edges = {}
383
+ # Create edges for morphisms.
384
+ for morphism in morphisms:
385
+ DiagramGrid._add_edge_append(
386
+ edges, frozenset([morphism.domain, morphism.codomain]), morphism)
387
+
388
+ # Create new edges by juxtaposing existing edges.
389
+ edges1 = dict(edges)
390
+ for w in edges1:
391
+ for v in edges1:
392
+ wv = DiagramGrid._juxtapose_edges(w, v)
393
+ if wv and wv not in edges:
394
+ edges[wv] = []
395
+
396
+ return edges
397
+
398
+ @staticmethod
399
+ def _list_triangles(edges):
400
+ """
401
+ Builds the set of triangles formed by the supplied edges. The
402
+ triangles are arbitrary and need not be commutative. A
403
+ triangle is a set that contains all three of its sides.
404
+ """
405
+ triangles = set()
406
+
407
+ for w in edges:
408
+ for v in edges:
409
+ wv = DiagramGrid._juxtapose_edges(w, v)
410
+ if wv and wv in edges:
411
+ triangles.add(frozenset([w, v, wv]))
412
+
413
+ return triangles
414
+
415
+ @staticmethod
416
+ def _drop_redundant_triangles(triangles, skeleton):
417
+ """
418
+ Returns a list which contains only those triangles who have
419
+ morphisms associated with at least two edges.
420
+ """
421
+ return [tri for tri in triangles
422
+ if len([e for e in tri if skeleton[e]]) >= 2]
423
+
424
+ @staticmethod
425
+ def _morphism_length(morphism):
426
+ """
427
+ Returns the length of a morphism. The length of a morphism is
428
+ the number of components it consists of. A non-composite
429
+ morphism is of length 1.
430
+ """
431
+ if isinstance(morphism, CompositeMorphism):
432
+ return len(morphism.components)
433
+ else:
434
+ return 1
435
+
436
+ @staticmethod
437
+ def _compute_triangle_min_sizes(triangles, edges):
438
+ r"""
439
+ Returns a dictionary mapping triangles to their minimal sizes.
440
+ The minimal size of a triangle is the sum of maximal lengths
441
+ of morphisms associated to the sides of the triangle. The
442
+ length of a morphism is the number of components it consists
443
+ of. A non-composite morphism is of length 1.
444
+
445
+ Sorting triangles by this metric attempts to address two
446
+ aspects of layout. For triangles with only simple morphisms
447
+ in the edge, this assures that triangles with all three edges
448
+ visible will get typeset after triangles with less visible
449
+ edges, which sometimes minimizes the necessity in diagonal
450
+ arrows. For triangles with composite morphisms in the edges,
451
+ this assures that objects connected with shorter morphisms
452
+ will be laid out first, resulting the visual proximity of
453
+ those objects which are connected by shorter morphisms.
454
+ """
455
+ triangle_sizes = {}
456
+ for triangle in triangles:
457
+ size = 0
458
+ for e in triangle:
459
+ morphisms = edges[e]
460
+ if morphisms:
461
+ size += max(DiagramGrid._morphism_length(m)
462
+ for m in morphisms)
463
+ triangle_sizes[triangle] = size
464
+ return triangle_sizes
465
+
466
+ @staticmethod
467
+ def _triangle_objects(triangle):
468
+ """
469
+ Given a triangle, returns the objects included in it.
470
+ """
471
+ # A triangle is a frozenset of three two-element frozensets
472
+ # (the edges). This chains the three edges together and
473
+ # creates a frozenset from the iterator, thus producing a
474
+ # frozenset of objects of the triangle.
475
+ return frozenset(chain(*tuple(triangle)))
476
+
477
+ @staticmethod
478
+ def _other_vertex(triangle, edge):
479
+ """
480
+ Given a triangle and an edge of it, returns the vertex which
481
+ opposes the edge.
482
+ """
483
+ # This gets the set of objects of the triangle and then
484
+ # subtracts the set of objects employed in ``edge`` to get the
485
+ # vertex opposite to ``edge``.
486
+ return list(DiagramGrid._triangle_objects(triangle) - set(edge))[0]
487
+
488
+ @staticmethod
489
+ def _empty_point(pt, grid):
490
+ """
491
+ Checks if the cell at coordinates ``pt`` is either empty or
492
+ out of the bounds of the grid.
493
+ """
494
+ if (pt[0] < 0) or (pt[1] < 0) or \
495
+ (pt[0] >= grid.height) or (pt[1] >= grid.width):
496
+ return True
497
+ return grid[pt] is None
498
+
499
+ @staticmethod
500
+ def _put_object(coords, obj, grid, fringe):
501
+ """
502
+ Places an object at the coordinate ``cords`` in ``grid``,
503
+ growing the grid and updating ``fringe``, if necessary.
504
+ Returns (0, 0) if no row or column has been prepended, (1, 0)
505
+ if a row was prepended, (0, 1) if a column was prepended and
506
+ (1, 1) if both a column and a row were prepended.
507
+ """
508
+ (i, j) = coords
509
+ offset = (0, 0)
510
+ if i == -1:
511
+ grid.prepend_row()
512
+ i = 0
513
+ offset = (1, 0)
514
+ for k in range(len(fringe)):
515
+ ((i1, j1), (i2, j2)) = fringe[k]
516
+ fringe[k] = ((i1 + 1, j1), (i2 + 1, j2))
517
+ elif i == grid.height:
518
+ grid.append_row()
519
+
520
+ if j == -1:
521
+ j = 0
522
+ offset = (offset[0], 1)
523
+ grid.prepend_column()
524
+ for k in range(len(fringe)):
525
+ ((i1, j1), (i2, j2)) = fringe[k]
526
+ fringe[k] = ((i1, j1 + 1), (i2, j2 + 1))
527
+ elif j == grid.width:
528
+ grid.append_column()
529
+
530
+ grid[i, j] = obj
531
+ return offset
532
+
533
+ @staticmethod
534
+ def _choose_target_cell(pt1, pt2, edge, obj, skeleton, grid):
535
+ """
536
+ Given two points, ``pt1`` and ``pt2``, and the welding edge
537
+ ``edge``, chooses one of the two points to place the opposing
538
+ vertex ``obj`` of the triangle. If neither of this points
539
+ fits, returns ``None``.
540
+ """
541
+ pt1_empty = DiagramGrid._empty_point(pt1, grid)
542
+ pt2_empty = DiagramGrid._empty_point(pt2, grid)
543
+
544
+ if pt1_empty and pt2_empty:
545
+ # Both cells are empty. Of these two, choose that cell
546
+ # which will assure that a visible edge of the triangle
547
+ # will be drawn perpendicularly to the current welding
548
+ # edge.
549
+
550
+ A = grid[edge[0]]
551
+
552
+ if skeleton.get(frozenset([A, obj])):
553
+ return pt1
554
+ else:
555
+ return pt2
556
+ if pt1_empty:
557
+ return pt1
558
+ elif pt2_empty:
559
+ return pt2
560
+ else:
561
+ return None
562
+
563
+ @staticmethod
564
+ def _find_triangle_to_weld(triangles, fringe, grid):
565
+ """
566
+ Finds, if possible, a triangle and an edge in the ``fringe`` to
567
+ which the triangle could be attached. Returns the tuple
568
+ containing the triangle and the index of the corresponding
569
+ edge in the ``fringe``.
570
+
571
+ This function relies on the fact that objects are unique in
572
+ the diagram.
573
+ """
574
+ for triangle in triangles:
575
+ for (a, b) in fringe:
576
+ if frozenset([grid[a], grid[b]]) in triangle:
577
+ return (triangle, (a, b))
578
+ return None
579
+
580
+ @staticmethod
581
+ def _weld_triangle(tri, welding_edge, fringe, grid, skeleton):
582
+ """
583
+ If possible, welds the triangle ``tri`` to ``fringe`` and
584
+ returns ``False``. If this method encounters a degenerate
585
+ situation in the fringe and corrects it such that a restart of
586
+ the search is required, it returns ``True`` (which means that
587
+ a restart in finding triangle weldings is required).
588
+
589
+ A degenerate situation is a situation when an edge listed in
590
+ the fringe does not belong to the visual boundary of the
591
+ diagram.
592
+ """
593
+ a, b = welding_edge
594
+ target_cell = None
595
+
596
+ obj = DiagramGrid._other_vertex(tri, (grid[a], grid[b]))
597
+
598
+ # We now have a triangle and an edge where it can be welded to
599
+ # the fringe. Decide where to place the other vertex of the
600
+ # triangle and check for degenerate situations en route.
601
+
602
+ if (abs(a[0] - b[0]) == 1) and (abs(a[1] - b[1]) == 1):
603
+ # A diagonal edge.
604
+ target_cell = (a[0], b[1])
605
+ if grid[target_cell]:
606
+ # That cell is already occupied.
607
+ target_cell = (b[0], a[1])
608
+
609
+ if grid[target_cell]:
610
+ # Degenerate situation, this edge is not
611
+ # on the actual fringe. Correct the
612
+ # fringe and go on.
613
+ fringe.remove((a, b))
614
+ return True
615
+ elif a[0] == b[0]:
616
+ # A horizontal edge. We first attempt to build the
617
+ # triangle in the downward direction.
618
+
619
+ down_left = a[0] + 1, a[1]
620
+ down_right = a[0] + 1, b[1]
621
+
622
+ target_cell = DiagramGrid._choose_target_cell(
623
+ down_left, down_right, (a, b), obj, skeleton, grid)
624
+
625
+ if not target_cell:
626
+ # No room below this edge. Check above.
627
+ up_left = a[0] - 1, a[1]
628
+ up_right = a[0] - 1, b[1]
629
+
630
+ target_cell = DiagramGrid._choose_target_cell(
631
+ up_left, up_right, (a, b), obj, skeleton, grid)
632
+
633
+ if not target_cell:
634
+ # This edge is not in the fringe, remove it
635
+ # and restart.
636
+ fringe.remove((a, b))
637
+ return True
638
+ elif a[1] == b[1]:
639
+ # A vertical edge. We will attempt to place the other
640
+ # vertex of the triangle to the right of this edge.
641
+ right_up = a[0], a[1] + 1
642
+ right_down = b[0], a[1] + 1
643
+
644
+ target_cell = DiagramGrid._choose_target_cell(
645
+ right_up, right_down, (a, b), obj, skeleton, grid)
646
+
647
+ if not target_cell:
648
+ # No room to the left. See what's to the right.
649
+ left_up = a[0], a[1] - 1
650
+ left_down = b[0], a[1] - 1
651
+
652
+ target_cell = DiagramGrid._choose_target_cell(
653
+ left_up, left_down, (a, b), obj, skeleton, grid)
654
+
655
+ if not target_cell:
656
+ # This edge is not in the fringe, remove it
657
+ # and restart.
658
+ fringe.remove((a, b))
659
+ return True
660
+
661
+ # We now know where to place the other vertex of the
662
+ # triangle.
663
+ offset = DiagramGrid._put_object(target_cell, obj, grid, fringe)
664
+
665
+ # Take care of the displacement of coordinates if a row or
666
+ # a column was prepended.
667
+ target_cell = (target_cell[0] + offset[0],
668
+ target_cell[1] + offset[1])
669
+ a = (a[0] + offset[0], a[1] + offset[1])
670
+ b = (b[0] + offset[0], b[1] + offset[1])
671
+
672
+ fringe.extend([(a, target_cell), (b, target_cell)])
673
+
674
+ # No restart is required.
675
+ return False
676
+
677
+ @staticmethod
678
+ def _triangle_key(tri, triangle_sizes):
679
+ """
680
+ Returns a key for the supplied triangle. It should be the
681
+ same independently of the hash randomisation.
682
+ """
683
+ objects = sorted(
684
+ DiagramGrid._triangle_objects(tri), key=default_sort_key)
685
+ return (triangle_sizes[tri], default_sort_key(objects))
686
+
687
+ @staticmethod
688
+ def _pick_root_edge(tri, skeleton):
689
+ """
690
+ For a given triangle always picks the same root edge. The
691
+ root edge is the edge that will be placed first on the grid.
692
+ """
693
+ candidates = [sorted(e, key=default_sort_key)
694
+ for e in tri if skeleton[e]]
695
+ sorted_candidates = sorted(candidates, key=default_sort_key)
696
+ # Don't forget to assure the proper ordering of the vertices
697
+ # in this edge.
698
+ return tuple(sorted(sorted_candidates[0], key=default_sort_key))
699
+
700
+ @staticmethod
701
+ def _drop_irrelevant_triangles(triangles, placed_objects):
702
+ """
703
+ Returns only those triangles whose set of objects is not
704
+ completely included in ``placed_objects``.
705
+ """
706
+ return [tri for tri in triangles if not placed_objects.issuperset(
707
+ DiagramGrid._triangle_objects(tri))]
708
+
709
+ @staticmethod
710
+ def _grow_pseudopod(triangles, fringe, grid, skeleton, placed_objects):
711
+ """
712
+ Starting from an object in the existing structure on the ``grid``,
713
+ adds an edge to which a triangle from ``triangles`` could be
714
+ welded. If this method has found a way to do so, it returns
715
+ the object it has just added.
716
+
717
+ This method should be applied when ``_weld_triangle`` cannot
718
+ find weldings any more.
719
+ """
720
+ for i in range(grid.height):
721
+ for j in range(grid.width):
722
+ obj = grid[i, j]
723
+ if not obj:
724
+ continue
725
+
726
+ # Here we need to choose a triangle which has only
727
+ # ``obj`` in common with the existing structure. The
728
+ # situations when this is not possible should be
729
+ # handled elsewhere.
730
+
731
+ def good_triangle(tri):
732
+ objs = DiagramGrid._triangle_objects(tri)
733
+ return obj in objs and \
734
+ placed_objects & (objs - {obj}) == set()
735
+
736
+ tris = [tri for tri in triangles if good_triangle(tri)]
737
+ if not tris:
738
+ # This object is not interesting.
739
+ continue
740
+
741
+ # Pick the "simplest" of the triangles which could be
742
+ # attached. Remember that the list of triangles is
743
+ # sorted according to their "simplicity" (see
744
+ # _compute_triangle_min_sizes for the metric).
745
+ #
746
+ # Note that ``tris`` are sequentially built from
747
+ # ``triangles``, so we don't have to worry about hash
748
+ # randomisation.
749
+ tri = tris[0]
750
+
751
+ # We have found a triangle which could be attached to
752
+ # the existing structure by a vertex.
753
+
754
+ candidates = sorted([e for e in tri if skeleton[e]],
755
+ key=lambda e: FiniteSet(*e).sort_key())
756
+ edges = [e for e in candidates if obj in e]
757
+
758
+ # Note that a meaningful edge (i.e., and edge that is
759
+ # associated with a morphism) containing ``obj``
760
+ # always exists. That's because all triangles are
761
+ # guaranteed to have at least two meaningful edges.
762
+ # See _drop_redundant_triangles.
763
+
764
+ # Get the object at the other end of the edge.
765
+ edge = edges[0]
766
+ other_obj = tuple(edge - frozenset([obj]))[0]
767
+
768
+ # Now check for free directions. When checking for
769
+ # free directions, prefer the horizontal and vertical
770
+ # directions.
771
+ neighbours = [(i - 1, j), (i, j + 1), (i + 1, j), (i, j - 1),
772
+ (i - 1, j - 1), (i - 1, j + 1), (i + 1, j - 1), (i + 1, j + 1)]
773
+
774
+ for pt in neighbours:
775
+ if DiagramGrid._empty_point(pt, grid):
776
+ # We have a found a place to grow the
777
+ # pseudopod into.
778
+ offset = DiagramGrid._put_object(
779
+ pt, other_obj, grid, fringe)
780
+
781
+ i += offset[0]
782
+ j += offset[1]
783
+ pt = (pt[0] + offset[0], pt[1] + offset[1])
784
+ fringe.append(((i, j), pt))
785
+
786
+ return other_obj
787
+
788
+ # This diagram is actually cooler that I can handle. Fail cowardly.
789
+ return None
790
+
791
+ @staticmethod
792
+ def _handle_groups(diagram, groups, merged_morphisms, hints):
793
+ """
794
+ Given the slightly preprocessed morphisms of the diagram,
795
+ produces a grid laid out according to ``groups``.
796
+
797
+ If a group has hints, it is laid out with those hints only,
798
+ without any influence from ``hints``. Otherwise, it is laid
799
+ out with ``hints``.
800
+ """
801
+ def lay_out_group(group, local_hints):
802
+ """
803
+ If ``group`` is a set of objects, uses a ``DiagramGrid``
804
+ to lay it out and returns the grid. Otherwise returns the
805
+ object (i.e., ``group``). If ``local_hints`` is not
806
+ empty, it is supplied to ``DiagramGrid`` as the dictionary
807
+ of hints. Otherwise, the ``hints`` argument of
808
+ ``_handle_groups`` is used.
809
+ """
810
+ if isinstance(group, FiniteSet):
811
+ # Set up the corresponding object-to-group
812
+ # mappings.
813
+ for obj in group:
814
+ obj_groups[obj] = group
815
+
816
+ # Lay out the current group.
817
+ if local_hints:
818
+ groups_grids[group] = DiagramGrid(
819
+ diagram.subdiagram_from_objects(group), **local_hints)
820
+ else:
821
+ groups_grids[group] = DiagramGrid(
822
+ diagram.subdiagram_from_objects(group), **hints)
823
+ else:
824
+ obj_groups[group] = group
825
+
826
+ def group_to_finiteset(group):
827
+ """
828
+ Converts ``group`` to a :class:``FiniteSet`` if it is an
829
+ iterable.
830
+ """
831
+ if iterable(group):
832
+ return FiniteSet(*group)
833
+ else:
834
+ return group
835
+
836
+ obj_groups = {}
837
+ groups_grids = {}
838
+
839
+ # We would like to support various containers to represent
840
+ # groups. To achieve that, before laying each group out, it
841
+ # should be converted to a FiniteSet, because that is what the
842
+ # following code expects.
843
+
844
+ if isinstance(groups, (dict, Dict)):
845
+ finiteset_groups = {}
846
+ for group, local_hints in groups.items():
847
+ finiteset_group = group_to_finiteset(group)
848
+ finiteset_groups[finiteset_group] = local_hints
849
+ lay_out_group(group, local_hints)
850
+ groups = finiteset_groups
851
+ else:
852
+ finiteset_groups = []
853
+ for group in groups:
854
+ finiteset_group = group_to_finiteset(group)
855
+ finiteset_groups.append(finiteset_group)
856
+ lay_out_group(finiteset_group, None)
857
+ groups = finiteset_groups
858
+
859
+ new_morphisms = []
860
+ for morphism in merged_morphisms:
861
+ dom = obj_groups[morphism.domain]
862
+ cod = obj_groups[morphism.codomain]
863
+ # Note that we are not really interested in morphisms
864
+ # which do not employ two different groups, because
865
+ # these do not influence the layout.
866
+ if dom != cod:
867
+ # These are essentially unnamed morphisms; they are
868
+ # not going to mess in the final layout. By giving
869
+ # them the same names, we avoid unnecessary
870
+ # duplicates.
871
+ new_morphisms.append(NamedMorphism(dom, cod, "dummy"))
872
+
873
+ # Lay out the new diagram. Since these are dummy morphisms,
874
+ # properties and conclusions are irrelevant.
875
+ top_grid = DiagramGrid(Diagram(new_morphisms))
876
+
877
+ # We now have to substitute the groups with the corresponding
878
+ # grids, laid out at the beginning of this function. Compute
879
+ # the size of each row and column in the grid, so that all
880
+ # nested grids fit.
881
+
882
+ def group_size(group):
883
+ """
884
+ For the supplied group (or object, eventually), returns
885
+ the size of the cell that will hold this group (object).
886
+ """
887
+ if group in groups_grids:
888
+ grid = groups_grids[group]
889
+ return (grid.height, grid.width)
890
+ else:
891
+ return (1, 1)
892
+
893
+ row_heights = [max(group_size(top_grid[i, j])[0]
894
+ for j in range(top_grid.width))
895
+ for i in range(top_grid.height)]
896
+
897
+ column_widths = [max(group_size(top_grid[i, j])[1]
898
+ for i in range(top_grid.height))
899
+ for j in range(top_grid.width)]
900
+
901
+ grid = _GrowableGrid(sum(column_widths), sum(row_heights))
902
+
903
+ real_row = 0
904
+ real_column = 0
905
+ for logical_row in range(top_grid.height):
906
+ for logical_column in range(top_grid.width):
907
+ obj = top_grid[logical_row, logical_column]
908
+
909
+ if obj in groups_grids:
910
+ # This is a group. Copy the corresponding grid in
911
+ # place.
912
+ local_grid = groups_grids[obj]
913
+ for i in range(local_grid.height):
914
+ for j in range(local_grid.width):
915
+ grid[real_row + i,
916
+ real_column + j] = local_grid[i, j]
917
+ else:
918
+ # This is an object. Just put it there.
919
+ grid[real_row, real_column] = obj
920
+
921
+ real_column += column_widths[logical_column]
922
+ real_column = 0
923
+ real_row += row_heights[logical_row]
924
+
925
+ return grid
926
+
927
+ @staticmethod
928
+ def _generic_layout(diagram, merged_morphisms):
929
+ """
930
+ Produces the generic layout for the supplied diagram.
931
+ """
932
+ all_objects = set(diagram.objects)
933
+ if len(all_objects) == 1:
934
+ # There only one object in the diagram, just put in on 1x1
935
+ # grid.
936
+ grid = _GrowableGrid(1, 1)
937
+ grid[0, 0] = tuple(all_objects)[0]
938
+ return grid
939
+
940
+ skeleton = DiagramGrid._build_skeleton(merged_morphisms)
941
+
942
+ grid = _GrowableGrid(2, 1)
943
+
944
+ if len(skeleton) == 1:
945
+ # This diagram contains only one morphism. Draw it
946
+ # horizontally.
947
+ objects = sorted(all_objects, key=default_sort_key)
948
+ grid[0, 0] = objects[0]
949
+ grid[0, 1] = objects[1]
950
+
951
+ return grid
952
+
953
+ triangles = DiagramGrid._list_triangles(skeleton)
954
+ triangles = DiagramGrid._drop_redundant_triangles(triangles, skeleton)
955
+ triangle_sizes = DiagramGrid._compute_triangle_min_sizes(
956
+ triangles, skeleton)
957
+
958
+ triangles = sorted(triangles, key=lambda tri:
959
+ DiagramGrid._triangle_key(tri, triangle_sizes))
960
+
961
+ # Place the first edge on the grid.
962
+ root_edge = DiagramGrid._pick_root_edge(triangles[0], skeleton)
963
+ grid[0, 0], grid[0, 1] = root_edge
964
+ fringe = [((0, 0), (0, 1))]
965
+
966
+ # Record which objects we now have on the grid.
967
+ placed_objects = set(root_edge)
968
+
969
+ while placed_objects != all_objects:
970
+ welding = DiagramGrid._find_triangle_to_weld(
971
+ triangles, fringe, grid)
972
+
973
+ if welding:
974
+ (triangle, welding_edge) = welding
975
+
976
+ restart_required = DiagramGrid._weld_triangle(
977
+ triangle, welding_edge, fringe, grid, skeleton)
978
+ if restart_required:
979
+ continue
980
+
981
+ placed_objects.update(
982
+ DiagramGrid._triangle_objects(triangle))
983
+ else:
984
+ # No more weldings found. Try to attach triangles by
985
+ # vertices.
986
+ new_obj = DiagramGrid._grow_pseudopod(
987
+ triangles, fringe, grid, skeleton, placed_objects)
988
+
989
+ if not new_obj:
990
+ # No more triangles can be attached, not even by
991
+ # the edge. We will set up a new diagram out of
992
+ # what has been left, laid it out independently,
993
+ # and then attach it to this one.
994
+
995
+ remaining_objects = all_objects - placed_objects
996
+
997
+ remaining_diagram = diagram.subdiagram_from_objects(
998
+ FiniteSet(*remaining_objects))
999
+ remaining_grid = DiagramGrid(remaining_diagram)
1000
+
1001
+ # Now, let's glue ``remaining_grid`` to ``grid``.
1002
+ final_width = grid.width + remaining_grid.width
1003
+ final_height = max(grid.height, remaining_grid.height)
1004
+ final_grid = _GrowableGrid(final_width, final_height)
1005
+
1006
+ for i in range(grid.width):
1007
+ for j in range(grid.height):
1008
+ final_grid[i, j] = grid[i, j]
1009
+
1010
+ start_j = grid.width
1011
+ for i in range(remaining_grid.height):
1012
+ for j in range(remaining_grid.width):
1013
+ final_grid[i, start_j + j] = remaining_grid[i, j]
1014
+
1015
+ return final_grid
1016
+
1017
+ placed_objects.add(new_obj)
1018
+
1019
+ triangles = DiagramGrid._drop_irrelevant_triangles(
1020
+ triangles, placed_objects)
1021
+
1022
+ return grid
1023
+
1024
+ @staticmethod
1025
+ def _get_undirected_graph(objects, merged_morphisms):
1026
+ """
1027
+ Given the objects and the relevant morphisms of a diagram,
1028
+ returns the adjacency lists of the underlying undirected
1029
+ graph.
1030
+ """
1031
+ adjlists = {}
1032
+ for obj in objects:
1033
+ adjlists[obj] = []
1034
+
1035
+ for morphism in merged_morphisms:
1036
+ adjlists[morphism.domain].append(morphism.codomain)
1037
+ adjlists[morphism.codomain].append(morphism.domain)
1038
+
1039
+ # Assure that the objects in the adjacency list are always in
1040
+ # the same order.
1041
+ for obj in adjlists.keys():
1042
+ adjlists[obj].sort(key=default_sort_key)
1043
+
1044
+ return adjlists
1045
+
1046
+ @staticmethod
1047
+ def _sequential_layout(diagram, merged_morphisms):
1048
+ r"""
1049
+ Lays out the diagram in "sequential" layout. This method
1050
+ will attempt to produce a result as close to a line as
1051
+ possible. For linear diagrams, the result will actually be a
1052
+ line.
1053
+ """
1054
+ objects = diagram.objects
1055
+ sorted_objects = sorted(objects, key=default_sort_key)
1056
+
1057
+ # Set up the adjacency lists of the underlying undirected
1058
+ # graph of ``merged_morphisms``.
1059
+ adjlists = DiagramGrid._get_undirected_graph(objects, merged_morphisms)
1060
+
1061
+ root = min(sorted_objects, key=lambda x: len(adjlists[x]))
1062
+ grid = _GrowableGrid(1, 1)
1063
+ grid[0, 0] = root
1064
+
1065
+ placed_objects = {root}
1066
+
1067
+ def place_objects(pt, placed_objects):
1068
+ """
1069
+ Does depth-first search in the underlying graph of the
1070
+ diagram and places the objects en route.
1071
+ """
1072
+ # We will start placing new objects from here.
1073
+ new_pt = (pt[0], pt[1] + 1)
1074
+
1075
+ for adjacent_obj in adjlists[grid[pt]]:
1076
+ if adjacent_obj in placed_objects:
1077
+ # This object has already been placed.
1078
+ continue
1079
+
1080
+ DiagramGrid._put_object(new_pt, adjacent_obj, grid, [])
1081
+ placed_objects.add(adjacent_obj)
1082
+ placed_objects.update(place_objects(new_pt, placed_objects))
1083
+
1084
+ new_pt = (new_pt[0] + 1, new_pt[1])
1085
+
1086
+ return placed_objects
1087
+
1088
+ place_objects((0, 0), placed_objects)
1089
+
1090
+ return grid
1091
+
1092
+ @staticmethod
1093
+ def _drop_inessential_morphisms(merged_morphisms):
1094
+ r"""
1095
+ Removes those morphisms which should appear in the diagram,
1096
+ but which have no relevance to object layout.
1097
+
1098
+ Currently this removes "loop" morphisms: the non-identity
1099
+ morphisms with the same domains and codomains.
1100
+ """
1101
+ morphisms = [m for m in merged_morphisms if m.domain != m.codomain]
1102
+ return morphisms
1103
+
1104
+ @staticmethod
1105
+ def _get_connected_components(objects, merged_morphisms):
1106
+ """
1107
+ Given a container of morphisms, returns a list of connected
1108
+ components formed by these morphisms. A connected component
1109
+ is represented by a diagram consisting of the corresponding
1110
+ morphisms.
1111
+ """
1112
+ component_index = {}
1113
+ for o in objects:
1114
+ component_index[o] = None
1115
+
1116
+ # Get the underlying undirected graph of the diagram.
1117
+ adjlist = DiagramGrid._get_undirected_graph(objects, merged_morphisms)
1118
+
1119
+ def traverse_component(object, current_index):
1120
+ """
1121
+ Does a depth-first search traversal of the component
1122
+ containing ``object``.
1123
+ """
1124
+ component_index[object] = current_index
1125
+ for o in adjlist[object]:
1126
+ if component_index[o] is None:
1127
+ traverse_component(o, current_index)
1128
+
1129
+ # Traverse all components.
1130
+ current_index = 0
1131
+ for o in adjlist:
1132
+ if component_index[o] is None:
1133
+ traverse_component(o, current_index)
1134
+ current_index += 1
1135
+
1136
+ # List the objects of the components.
1137
+ component_objects = [[] for i in range(current_index)]
1138
+ for o, idx in component_index.items():
1139
+ component_objects[idx].append(o)
1140
+
1141
+ # Finally, list the morphisms belonging to each component.
1142
+ #
1143
+ # Note: If some objects are isolated, they will not get any
1144
+ # morphisms at this stage, and since the layout algorithm
1145
+ # relies, we are essentially going to lose this object.
1146
+ # Therefore, check if there are isolated objects and, for each
1147
+ # of them, provide the trivial identity morphism. It will get
1148
+ # discarded later, but the object will be there.
1149
+
1150
+ component_morphisms = []
1151
+ for component in component_objects:
1152
+ current_morphisms = {}
1153
+ for m in merged_morphisms:
1154
+ if (m.domain in component) and (m.codomain in component):
1155
+ current_morphisms[m] = merged_morphisms[m]
1156
+
1157
+ if len(component) == 1:
1158
+ # Let's add an identity morphism, for the sake of
1159
+ # surely having morphisms in this component.
1160
+ current_morphisms[IdentityMorphism(component[0])] = FiniteSet()
1161
+
1162
+ component_morphisms.append(Diagram(current_morphisms))
1163
+
1164
+ return component_morphisms
1165
+
1166
+ def __init__(self, diagram, groups=None, **hints):
1167
+ premises = DiagramGrid._simplify_morphisms(diagram.premises)
1168
+ conclusions = DiagramGrid._simplify_morphisms(diagram.conclusions)
1169
+ all_merged_morphisms = DiagramGrid._merge_premises_conclusions(
1170
+ premises, conclusions)
1171
+ merged_morphisms = DiagramGrid._drop_inessential_morphisms(
1172
+ all_merged_morphisms)
1173
+
1174
+ # Store the merged morphisms for later use.
1175
+ self._morphisms = all_merged_morphisms
1176
+
1177
+ components = DiagramGrid._get_connected_components(
1178
+ diagram.objects, all_merged_morphisms)
1179
+
1180
+ if groups and (groups != diagram.objects):
1181
+ # Lay out the diagram according to the groups.
1182
+ self._grid = DiagramGrid._handle_groups(
1183
+ diagram, groups, merged_morphisms, hints)
1184
+ elif len(components) > 1:
1185
+ # Note that we check for connectedness _before_ checking
1186
+ # the layout hints because the layout strategies don't
1187
+ # know how to deal with disconnected diagrams.
1188
+
1189
+ # The diagram is disconnected. Lay out the components
1190
+ # independently.
1191
+ grids = []
1192
+
1193
+ # Sort the components to eventually get the grids arranged
1194
+ # in a fixed, hash-independent order.
1195
+ components = sorted(components, key=default_sort_key)
1196
+
1197
+ for component in components:
1198
+ grid = DiagramGrid(component, **hints)
1199
+ grids.append(grid)
1200
+
1201
+ # Throw the grids together, in a line.
1202
+ total_width = sum(g.width for g in grids)
1203
+ total_height = max(g.height for g in grids)
1204
+
1205
+ grid = _GrowableGrid(total_width, total_height)
1206
+ start_j = 0
1207
+ for g in grids:
1208
+ for i in range(g.height):
1209
+ for j in range(g.width):
1210
+ grid[i, start_j + j] = g[i, j]
1211
+
1212
+ start_j += g.width
1213
+
1214
+ self._grid = grid
1215
+ elif "layout" in hints:
1216
+ if hints["layout"] == "sequential":
1217
+ self._grid = DiagramGrid._sequential_layout(
1218
+ diagram, merged_morphisms)
1219
+ else:
1220
+ self._grid = DiagramGrid._generic_layout(diagram, merged_morphisms)
1221
+
1222
+ if hints.get("transpose"):
1223
+ # Transpose the resulting grid.
1224
+ grid = _GrowableGrid(self._grid.height, self._grid.width)
1225
+ for i in range(self._grid.height):
1226
+ for j in range(self._grid.width):
1227
+ grid[j, i] = self._grid[i, j]
1228
+ self._grid = grid
1229
+
1230
+ @property
1231
+ def width(self):
1232
+ """
1233
+ Returns the number of columns in this diagram layout.
1234
+
1235
+ Examples
1236
+ ========
1237
+
1238
+ >>> from sympy.categories import Object, NamedMorphism
1239
+ >>> from sympy.categories import Diagram, DiagramGrid
1240
+ >>> A = Object("A")
1241
+ >>> B = Object("B")
1242
+ >>> C = Object("C")
1243
+ >>> f = NamedMorphism(A, B, "f")
1244
+ >>> g = NamedMorphism(B, C, "g")
1245
+ >>> diagram = Diagram([f, g])
1246
+ >>> grid = DiagramGrid(diagram)
1247
+ >>> grid.width
1248
+ 2
1249
+
1250
+ """
1251
+ return self._grid.width
1252
+
1253
+ @property
1254
+ def height(self):
1255
+ """
1256
+ Returns the number of rows in this diagram layout.
1257
+
1258
+ Examples
1259
+ ========
1260
+
1261
+ >>> from sympy.categories import Object, NamedMorphism
1262
+ >>> from sympy.categories import Diagram, DiagramGrid
1263
+ >>> A = Object("A")
1264
+ >>> B = Object("B")
1265
+ >>> C = Object("C")
1266
+ >>> f = NamedMorphism(A, B, "f")
1267
+ >>> g = NamedMorphism(B, C, "g")
1268
+ >>> diagram = Diagram([f, g])
1269
+ >>> grid = DiagramGrid(diagram)
1270
+ >>> grid.height
1271
+ 2
1272
+
1273
+ """
1274
+ return self._grid.height
1275
+
1276
+ def __getitem__(self, i_j):
1277
+ """
1278
+ Returns the object placed in the row ``i`` and column ``j``.
1279
+ The indices are 0-based.
1280
+
1281
+ Examples
1282
+ ========
1283
+
1284
+ >>> from sympy.categories import Object, NamedMorphism
1285
+ >>> from sympy.categories import Diagram, DiagramGrid
1286
+ >>> A = Object("A")
1287
+ >>> B = Object("B")
1288
+ >>> C = Object("C")
1289
+ >>> f = NamedMorphism(A, B, "f")
1290
+ >>> g = NamedMorphism(B, C, "g")
1291
+ >>> diagram = Diagram([f, g])
1292
+ >>> grid = DiagramGrid(diagram)
1293
+ >>> (grid[0, 0], grid[0, 1])
1294
+ (Object("A"), Object("B"))
1295
+ >>> (grid[1, 0], grid[1, 1])
1296
+ (None, Object("C"))
1297
+
1298
+ """
1299
+ i, j = i_j
1300
+ return self._grid[i, j]
1301
+
1302
+ @property
1303
+ def morphisms(self):
1304
+ """
1305
+ Returns those morphisms (and their properties) which are
1306
+ sufficiently meaningful to be drawn.
1307
+
1308
+ Examples
1309
+ ========
1310
+
1311
+ >>> from sympy.categories import Object, NamedMorphism
1312
+ >>> from sympy.categories import Diagram, DiagramGrid
1313
+ >>> A = Object("A")
1314
+ >>> B = Object("B")
1315
+ >>> C = Object("C")
1316
+ >>> f = NamedMorphism(A, B, "f")
1317
+ >>> g = NamedMorphism(B, C, "g")
1318
+ >>> diagram = Diagram([f, g])
1319
+ >>> grid = DiagramGrid(diagram)
1320
+ >>> grid.morphisms
1321
+ {NamedMorphism(Object("A"), Object("B"), "f"): EmptySet,
1322
+ NamedMorphism(Object("B"), Object("C"), "g"): EmptySet}
1323
+
1324
+ """
1325
+ return self._morphisms
1326
+
1327
+ def __str__(self):
1328
+ """
1329
+ Produces a string representation of this class.
1330
+
1331
+ This method returns a string representation of the underlying
1332
+ list of lists of objects.
1333
+
1334
+ Examples
1335
+ ========
1336
+
1337
+ >>> from sympy.categories import Object, NamedMorphism
1338
+ >>> from sympy.categories import Diagram, DiagramGrid
1339
+ >>> A = Object("A")
1340
+ >>> B = Object("B")
1341
+ >>> C = Object("C")
1342
+ >>> f = NamedMorphism(A, B, "f")
1343
+ >>> g = NamedMorphism(B, C, "g")
1344
+ >>> diagram = Diagram([f, g])
1345
+ >>> grid = DiagramGrid(diagram)
1346
+ >>> print(grid)
1347
+ [[Object("A"), Object("B")],
1348
+ [None, Object("C")]]
1349
+
1350
+ """
1351
+ return repr(self._grid._array)
1352
+
1353
+
1354
+ class ArrowStringDescription:
1355
+ r"""
1356
+ Stores the information necessary for producing an Xy-pic
1357
+ description of an arrow.
1358
+
1359
+ The principal goal of this class is to abstract away the string
1360
+ representation of an arrow and to also provide the functionality
1361
+ to produce the actual Xy-pic string.
1362
+
1363
+ ``unit`` sets the unit which will be used to specify the amount of
1364
+ curving and other distances. ``horizontal_direction`` should be a
1365
+ string of ``"r"`` or ``"l"`` specifying the horizontal offset of the
1366
+ target cell of the arrow relatively to the current one.
1367
+ ``vertical_direction`` should specify the vertical offset using a
1368
+ series of either ``"d"`` or ``"u"``. ``label_position`` should be
1369
+ either ``"^"``, ``"_"``, or ``"|"`` to specify that the label should
1370
+ be positioned above the arrow, below the arrow or just over the arrow,
1371
+ in a break. Note that the notions "above" and "below" are relative
1372
+ to arrow direction. ``label`` stores the morphism label.
1373
+
1374
+ This works as follows (disregard the yet unexplained arguments):
1375
+
1376
+ >>> from sympy.categories.diagram_drawing import ArrowStringDescription
1377
+ >>> astr = ArrowStringDescription(
1378
+ ... unit="mm", curving=None, curving_amount=None,
1379
+ ... looping_start=None, looping_end=None, horizontal_direction="d",
1380
+ ... vertical_direction="r", label_position="_", label="f")
1381
+ >>> print(str(astr))
1382
+ \ar[dr]_{f}
1383
+
1384
+ ``curving`` should be one of ``"^"``, ``"_"`` to specify in which
1385
+ direction the arrow is going to curve. ``curving_amount`` is a number
1386
+ describing how many ``unit``'s the morphism is going to curve:
1387
+
1388
+ >>> astr = ArrowStringDescription(
1389
+ ... unit="mm", curving="^", curving_amount=12,
1390
+ ... looping_start=None, looping_end=None, horizontal_direction="d",
1391
+ ... vertical_direction="r", label_position="_", label="f")
1392
+ >>> print(str(astr))
1393
+ \ar@/^12mm/[dr]_{f}
1394
+
1395
+ ``looping_start`` and ``looping_end`` are currently only used for
1396
+ loop morphisms, those which have the same domain and codomain.
1397
+ These two attributes should store a valid Xy-pic direction and
1398
+ specify, correspondingly, the direction the arrow gets out into
1399
+ and the direction the arrow gets back from:
1400
+
1401
+ >>> astr = ArrowStringDescription(
1402
+ ... unit="mm", curving=None, curving_amount=None,
1403
+ ... looping_start="u", looping_end="l", horizontal_direction="",
1404
+ ... vertical_direction="", label_position="_", label="f")
1405
+ >>> print(str(astr))
1406
+ \ar@(u,l)[]_{f}
1407
+
1408
+ ``label_displacement`` controls how far the arrow label is from
1409
+ the ends of the arrow. For example, to position the arrow label
1410
+ near the arrow head, use ">":
1411
+
1412
+ >>> astr = ArrowStringDescription(
1413
+ ... unit="mm", curving="^", curving_amount=12,
1414
+ ... looping_start=None, looping_end=None, horizontal_direction="d",
1415
+ ... vertical_direction="r", label_position="_", label="f")
1416
+ >>> astr.label_displacement = ">"
1417
+ >>> print(str(astr))
1418
+ \ar@/^12mm/[dr]_>{f}
1419
+
1420
+ Finally, ``arrow_style`` is used to specify the arrow style. To
1421
+ get a dashed arrow, for example, use "{-->}" as arrow style:
1422
+
1423
+ >>> astr = ArrowStringDescription(
1424
+ ... unit="mm", curving="^", curving_amount=12,
1425
+ ... looping_start=None, looping_end=None, horizontal_direction="d",
1426
+ ... vertical_direction="r", label_position="_", label="f")
1427
+ >>> astr.arrow_style = "{-->}"
1428
+ >>> print(str(astr))
1429
+ \ar@/^12mm/@{-->}[dr]_{f}
1430
+
1431
+ Notes
1432
+ =====
1433
+
1434
+ Instances of :class:`ArrowStringDescription` will be constructed
1435
+ by :class:`XypicDiagramDrawer` and provided for further use in
1436
+ formatters. The user is not expected to construct instances of
1437
+ :class:`ArrowStringDescription` themselves.
1438
+
1439
+ To be able to properly utilise this class, the reader is encouraged
1440
+ to checkout the Xy-pic user guide, available at [Xypic].
1441
+
1442
+ See Also
1443
+ ========
1444
+
1445
+ XypicDiagramDrawer
1446
+
1447
+ References
1448
+ ==========
1449
+
1450
+ .. [Xypic] https://xy-pic.sourceforge.net/
1451
+ """
1452
+ def __init__(self, unit, curving, curving_amount, looping_start,
1453
+ looping_end, horizontal_direction, vertical_direction,
1454
+ label_position, label):
1455
+ self.unit = unit
1456
+ self.curving = curving
1457
+ self.curving_amount = curving_amount
1458
+ self.looping_start = looping_start
1459
+ self.looping_end = looping_end
1460
+ self.horizontal_direction = horizontal_direction
1461
+ self.vertical_direction = vertical_direction
1462
+ self.label_position = label_position
1463
+ self.label = label
1464
+
1465
+ self.label_displacement = ""
1466
+ self.arrow_style = ""
1467
+
1468
+ # This flag shows that the position of the label of this
1469
+ # morphism was set while typesetting a curved morphism and
1470
+ # should not be modified later.
1471
+ self.forced_label_position = False
1472
+
1473
+ def __str__(self):
1474
+ if self.curving:
1475
+ curving_str = "@/%s%d%s/" % (self.curving, self.curving_amount,
1476
+ self.unit)
1477
+ else:
1478
+ curving_str = ""
1479
+
1480
+ if self.looping_start and self.looping_end:
1481
+ looping_str = "@(%s,%s)" % (self.looping_start, self.looping_end)
1482
+ else:
1483
+ looping_str = ""
1484
+
1485
+ if self.arrow_style:
1486
+
1487
+ style_str = "@" + self.arrow_style
1488
+ else:
1489
+ style_str = ""
1490
+
1491
+ return "\\ar%s%s%s[%s%s]%s%s{%s}" % \
1492
+ (curving_str, looping_str, style_str, self.horizontal_direction,
1493
+ self.vertical_direction, self.label_position,
1494
+ self.label_displacement, self.label)
1495
+
1496
+
1497
+ class XypicDiagramDrawer:
1498
+ r"""
1499
+ Given a :class:`~.Diagram` and the corresponding
1500
+ :class:`DiagramGrid`, produces the Xy-pic representation of the
1501
+ diagram.
1502
+
1503
+ The most important method in this class is ``draw``. Consider the
1504
+ following triangle diagram:
1505
+
1506
+ >>> from sympy.categories import Object, NamedMorphism, Diagram
1507
+ >>> from sympy.categories import DiagramGrid, XypicDiagramDrawer
1508
+ >>> A = Object("A")
1509
+ >>> B = Object("B")
1510
+ >>> C = Object("C")
1511
+ >>> f = NamedMorphism(A, B, "f")
1512
+ >>> g = NamedMorphism(B, C, "g")
1513
+ >>> diagram = Diagram([f, g], {g * f: "unique"})
1514
+
1515
+ To draw this diagram, its objects need to be laid out with a
1516
+ :class:`DiagramGrid`::
1517
+
1518
+ >>> grid = DiagramGrid(diagram)
1519
+
1520
+ Finally, the drawing:
1521
+
1522
+ >>> drawer = XypicDiagramDrawer()
1523
+ >>> print(drawer.draw(diagram, grid))
1524
+ \xymatrix{
1525
+ A \ar[d]_{g\circ f} \ar[r]^{f} & B \ar[ld]^{g} \\
1526
+ C &
1527
+ }
1528
+
1529
+ For further details see the docstring of this method.
1530
+
1531
+ To control the appearance of the arrows, formatters are used. The
1532
+ dictionary ``arrow_formatters`` maps morphisms to formatter
1533
+ functions. A formatter is accepts an
1534
+ :class:`ArrowStringDescription` and is allowed to modify any of
1535
+ the arrow properties exposed thereby. For example, to have all
1536
+ morphisms with the property ``unique`` appear as dashed arrows,
1537
+ and to have their names prepended with `\exists !`, the following
1538
+ should be done:
1539
+
1540
+ >>> def formatter(astr):
1541
+ ... astr.label = r"\exists !" + astr.label
1542
+ ... astr.arrow_style = "{-->}"
1543
+ >>> drawer.arrow_formatters["unique"] = formatter
1544
+ >>> print(drawer.draw(diagram, grid))
1545
+ \xymatrix{
1546
+ A \ar@{-->}[d]_{\exists !g\circ f} \ar[r]^{f} & B \ar[ld]^{g} \\
1547
+ C &
1548
+ }
1549
+
1550
+ To modify the appearance of all arrows in the diagram, set
1551
+ ``default_arrow_formatter``. For example, to place all morphism
1552
+ labels a little bit farther from the arrow head so that they look
1553
+ more centred, do as follows:
1554
+
1555
+ >>> def default_formatter(astr):
1556
+ ... astr.label_displacement = "(0.45)"
1557
+ >>> drawer.default_arrow_formatter = default_formatter
1558
+ >>> print(drawer.draw(diagram, grid))
1559
+ \xymatrix{
1560
+ A \ar@{-->}[d]_(0.45){\exists !g\circ f} \ar[r]^(0.45){f} & B \ar[ld]^(0.45){g} \\
1561
+ C &
1562
+ }
1563
+
1564
+ In some diagrams some morphisms are drawn as curved arrows.
1565
+ Consider the following diagram:
1566
+
1567
+ >>> D = Object("D")
1568
+ >>> E = Object("E")
1569
+ >>> h = NamedMorphism(D, A, "h")
1570
+ >>> k = NamedMorphism(D, B, "k")
1571
+ >>> diagram = Diagram([f, g, h, k])
1572
+ >>> grid = DiagramGrid(diagram)
1573
+ >>> drawer = XypicDiagramDrawer()
1574
+ >>> print(drawer.draw(diagram, grid))
1575
+ \xymatrix{
1576
+ A \ar[r]_{f} & B \ar[d]^{g} & D \ar[l]^{k} \ar@/_3mm/[ll]_{h} \\
1577
+ & C &
1578
+ }
1579
+
1580
+ To control how far the morphisms are curved by default, one can
1581
+ use the ``unit`` and ``default_curving_amount`` attributes:
1582
+
1583
+ >>> drawer.unit = "cm"
1584
+ >>> drawer.default_curving_amount = 1
1585
+ >>> print(drawer.draw(diagram, grid))
1586
+ \xymatrix{
1587
+ A \ar[r]_{f} & B \ar[d]^{g} & D \ar[l]^{k} \ar@/_1cm/[ll]_{h} \\
1588
+ & C &
1589
+ }
1590
+
1591
+ In some diagrams, there are multiple curved morphisms between the
1592
+ same two objects. To control by how much the curving changes
1593
+ between two such successive morphisms, use
1594
+ ``default_curving_step``:
1595
+
1596
+ >>> drawer.default_curving_step = 1
1597
+ >>> h1 = NamedMorphism(A, D, "h1")
1598
+ >>> diagram = Diagram([f, g, h, k, h1])
1599
+ >>> grid = DiagramGrid(diagram)
1600
+ >>> print(drawer.draw(diagram, grid))
1601
+ \xymatrix{
1602
+ A \ar[r]_{f} \ar@/^1cm/[rr]^{h_{1}} & B \ar[d]^{g} & D \ar[l]^{k} \ar@/_2cm/[ll]_{h} \\
1603
+ & C &
1604
+ }
1605
+
1606
+ The default value of ``default_curving_step`` is 4 units.
1607
+
1608
+ See Also
1609
+ ========
1610
+
1611
+ draw, ArrowStringDescription
1612
+ """
1613
+ def __init__(self):
1614
+ self.unit = "mm"
1615
+ self.default_curving_amount = 3
1616
+ self.default_curving_step = 4
1617
+
1618
+ # This dictionary maps properties to the corresponding arrow
1619
+ # formatters.
1620
+ self.arrow_formatters = {}
1621
+
1622
+ # This is the default arrow formatter which will be applied to
1623
+ # each arrow independently of its properties.
1624
+ self.default_arrow_formatter = None
1625
+
1626
+ @staticmethod
1627
+ def _process_loop_morphism(i, j, grid, morphisms_str_info, object_coords):
1628
+ """
1629
+ Produces the information required for constructing the string
1630
+ representation of a loop morphism. This function is invoked
1631
+ from ``_process_morphism``.
1632
+
1633
+ See Also
1634
+ ========
1635
+
1636
+ _process_morphism
1637
+ """
1638
+ curving = ""
1639
+ label_pos = "^"
1640
+ looping_start = ""
1641
+ looping_end = ""
1642
+
1643
+ # This is a loop morphism. Count how many morphisms stick
1644
+ # in each of the four quadrants. Note that straight
1645
+ # vertical and horizontal morphisms count in two quadrants
1646
+ # at the same time (i.e., a morphism going up counts both
1647
+ # in the first and the second quadrants).
1648
+
1649
+ # The usual numbering (counterclockwise) of quadrants
1650
+ # applies.
1651
+ quadrant = [0, 0, 0, 0]
1652
+
1653
+ obj = grid[i, j]
1654
+
1655
+ for m, m_str_info in morphisms_str_info.items():
1656
+ if (m.domain == obj) and (m.codomain == obj):
1657
+ # That's another loop morphism. Check how it
1658
+ # loops and mark the corresponding quadrants as
1659
+ # busy.
1660
+ (l_s, l_e) = (m_str_info.looping_start, m_str_info.looping_end)
1661
+
1662
+ if (l_s, l_e) == ("r", "u"):
1663
+ quadrant[0] += 1
1664
+ elif (l_s, l_e) == ("u", "l"):
1665
+ quadrant[1] += 1
1666
+ elif (l_s, l_e) == ("l", "d"):
1667
+ quadrant[2] += 1
1668
+ elif (l_s, l_e) == ("d", "r"):
1669
+ quadrant[3] += 1
1670
+
1671
+ continue
1672
+ if m.domain == obj:
1673
+ (end_i, end_j) = object_coords[m.codomain]
1674
+ goes_out = True
1675
+ elif m.codomain == obj:
1676
+ (end_i, end_j) = object_coords[m.domain]
1677
+ goes_out = False
1678
+ else:
1679
+ continue
1680
+
1681
+ d_i = end_i - i
1682
+ d_j = end_j - j
1683
+ m_curving = m_str_info.curving
1684
+
1685
+ if (d_i != 0) and (d_j != 0):
1686
+ # This is really a diagonal morphism. Detect the
1687
+ # quadrant.
1688
+ if (d_i > 0) and (d_j > 0):
1689
+ quadrant[0] += 1
1690
+ elif (d_i > 0) and (d_j < 0):
1691
+ quadrant[1] += 1
1692
+ elif (d_i < 0) and (d_j < 0):
1693
+ quadrant[2] += 1
1694
+ elif (d_i < 0) and (d_j > 0):
1695
+ quadrant[3] += 1
1696
+ elif d_i == 0:
1697
+ # Knowing where the other end of the morphism is
1698
+ # and which way it goes, we now have to decide
1699
+ # which quadrant is now the upper one and which is
1700
+ # the lower one.
1701
+ if d_j > 0:
1702
+ if goes_out:
1703
+ upper_quadrant = 0
1704
+ lower_quadrant = 3
1705
+ else:
1706
+ upper_quadrant = 3
1707
+ lower_quadrant = 0
1708
+ else:
1709
+ if goes_out:
1710
+ upper_quadrant = 2
1711
+ lower_quadrant = 1
1712
+ else:
1713
+ upper_quadrant = 1
1714
+ lower_quadrant = 2
1715
+
1716
+ if m_curving:
1717
+ if m_curving == "^":
1718
+ quadrant[upper_quadrant] += 1
1719
+ elif m_curving == "_":
1720
+ quadrant[lower_quadrant] += 1
1721
+ else:
1722
+ # This morphism counts in both upper and lower
1723
+ # quadrants.
1724
+ quadrant[upper_quadrant] += 1
1725
+ quadrant[lower_quadrant] += 1
1726
+ elif d_j == 0:
1727
+ # Knowing where the other end of the morphism is
1728
+ # and which way it goes, we now have to decide
1729
+ # which quadrant is now the left one and which is
1730
+ # the right one.
1731
+ if d_i < 0:
1732
+ if goes_out:
1733
+ left_quadrant = 1
1734
+ right_quadrant = 0
1735
+ else:
1736
+ left_quadrant = 0
1737
+ right_quadrant = 1
1738
+ else:
1739
+ if goes_out:
1740
+ left_quadrant = 3
1741
+ right_quadrant = 2
1742
+ else:
1743
+ left_quadrant = 2
1744
+ right_quadrant = 3
1745
+
1746
+ if m_curving:
1747
+ if m_curving == "^":
1748
+ quadrant[left_quadrant] += 1
1749
+ elif m_curving == "_":
1750
+ quadrant[right_quadrant] += 1
1751
+ else:
1752
+ # This morphism counts in both upper and lower
1753
+ # quadrants.
1754
+ quadrant[left_quadrant] += 1
1755
+ quadrant[right_quadrant] += 1
1756
+
1757
+ # Pick the freest quadrant to curve our morphism into.
1758
+ freest_quadrant = 0
1759
+ for i in range(4):
1760
+ if quadrant[i] < quadrant[freest_quadrant]:
1761
+ freest_quadrant = i
1762
+
1763
+ # Now set up proper looping.
1764
+ (looping_start, looping_end) = [("r", "u"), ("u", "l"), ("l", "d"),
1765
+ ("d", "r")][freest_quadrant]
1766
+
1767
+ return (curving, label_pos, looping_start, looping_end)
1768
+
1769
+ @staticmethod
1770
+ def _process_horizontal_morphism(i, j, target_j, grid, morphisms_str_info,
1771
+ object_coords):
1772
+ """
1773
+ Produces the information required for constructing the string
1774
+ representation of a horizontal morphism. This function is
1775
+ invoked from ``_process_morphism``.
1776
+
1777
+ See Also
1778
+ ========
1779
+
1780
+ _process_morphism
1781
+ """
1782
+ # The arrow is horizontal. Check if it goes from left to
1783
+ # right (``backwards == False``) or from right to left
1784
+ # (``backwards == True``).
1785
+ backwards = False
1786
+ start = j
1787
+ end = target_j
1788
+ if end < start:
1789
+ (start, end) = (end, start)
1790
+ backwards = True
1791
+
1792
+ # Let's see which objects are there between ``start`` and
1793
+ # ``end``, and then count how many morphisms stick out
1794
+ # upwards, and how many stick out downwards.
1795
+ #
1796
+ # For example, consider the situation:
1797
+ #
1798
+ # B1 C1
1799
+ # | |
1800
+ # A--B--C--D
1801
+ # |
1802
+ # B2
1803
+ #
1804
+ # Between the objects `A` and `D` there are two objects:
1805
+ # `B` and `C`. Further, there are two morphisms which
1806
+ # stick out upward (the ones between `B1` and `B` and
1807
+ # between `C` and `C1`) and one morphism which sticks out
1808
+ # downward (the one between `B and `B2`).
1809
+ #
1810
+ # We need this information to decide how to curve the
1811
+ # arrow between `A` and `D`. First of all, since there
1812
+ # are two objects between `A` and `D``, we must curve the
1813
+ # arrow. Then, we will have it curve downward, because
1814
+ # there is more space (less morphisms stick out downward
1815
+ # than upward).
1816
+ up = []
1817
+ down = []
1818
+ straight_horizontal = []
1819
+ for k in range(start + 1, end):
1820
+ obj = grid[i, k]
1821
+ if not obj:
1822
+ continue
1823
+
1824
+ for m in morphisms_str_info:
1825
+ if m.domain == obj:
1826
+ (end_i, end_j) = object_coords[m.codomain]
1827
+ elif m.codomain == obj:
1828
+ (end_i, end_j) = object_coords[m.domain]
1829
+ else:
1830
+ continue
1831
+
1832
+ if end_i > i:
1833
+ down.append(m)
1834
+ elif end_i < i:
1835
+ up.append(m)
1836
+ elif not morphisms_str_info[m].curving:
1837
+ # This is a straight horizontal morphism,
1838
+ # because it has no curving.
1839
+ straight_horizontal.append(m)
1840
+
1841
+ if len(up) < len(down):
1842
+ # More morphisms stick out downward than upward, let's
1843
+ # curve the morphism up.
1844
+ if backwards:
1845
+ curving = "_"
1846
+ label_pos = "_"
1847
+ else:
1848
+ curving = "^"
1849
+ label_pos = "^"
1850
+
1851
+ # Assure that the straight horizontal morphisms have
1852
+ # their labels on the lower side of the arrow.
1853
+ for m in straight_horizontal:
1854
+ (i1, j1) = object_coords[m.domain]
1855
+ (i2, j2) = object_coords[m.codomain]
1856
+
1857
+ m_str_info = morphisms_str_info[m]
1858
+ if j1 < j2:
1859
+ m_str_info.label_position = "_"
1860
+ else:
1861
+ m_str_info.label_position = "^"
1862
+
1863
+ # Don't allow any further modifications of the
1864
+ # position of this label.
1865
+ m_str_info.forced_label_position = True
1866
+ else:
1867
+ # More morphisms stick out downward than upward, let's
1868
+ # curve the morphism up.
1869
+ if backwards:
1870
+ curving = "^"
1871
+ label_pos = "^"
1872
+ else:
1873
+ curving = "_"
1874
+ label_pos = "_"
1875
+
1876
+ # Assure that the straight horizontal morphisms have
1877
+ # their labels on the upper side of the arrow.
1878
+ for m in straight_horizontal:
1879
+ (i1, j1) = object_coords[m.domain]
1880
+ (i2, j2) = object_coords[m.codomain]
1881
+
1882
+ m_str_info = morphisms_str_info[m]
1883
+ if j1 < j2:
1884
+ m_str_info.label_position = "^"
1885
+ else:
1886
+ m_str_info.label_position = "_"
1887
+
1888
+ # Don't allow any further modifications of the
1889
+ # position of this label.
1890
+ m_str_info.forced_label_position = True
1891
+
1892
+ return (curving, label_pos)
1893
+
1894
+ @staticmethod
1895
+ def _process_vertical_morphism(i, j, target_i, grid, morphisms_str_info,
1896
+ object_coords):
1897
+ """
1898
+ Produces the information required for constructing the string
1899
+ representation of a vertical morphism. This function is
1900
+ invoked from ``_process_morphism``.
1901
+
1902
+ See Also
1903
+ ========
1904
+
1905
+ _process_morphism
1906
+ """
1907
+ # This arrow is vertical. Check if it goes from top to
1908
+ # bottom (``backwards == False``) or from bottom to top
1909
+ # (``backwards == True``).
1910
+ backwards = False
1911
+ start = i
1912
+ end = target_i
1913
+ if end < start:
1914
+ (start, end) = (end, start)
1915
+ backwards = True
1916
+
1917
+ # Let's see which objects are there between ``start`` and
1918
+ # ``end``, and then count how many morphisms stick out to
1919
+ # the left, and how many stick out to the right.
1920
+ #
1921
+ # See the corresponding comment in the previous branch of
1922
+ # this if-statement for more details.
1923
+ left = []
1924
+ right = []
1925
+ straight_vertical = []
1926
+ for k in range(start + 1, end):
1927
+ obj = grid[k, j]
1928
+ if not obj:
1929
+ continue
1930
+
1931
+ for m in morphisms_str_info:
1932
+ if m.domain == obj:
1933
+ (end_i, end_j) = object_coords[m.codomain]
1934
+ elif m.codomain == obj:
1935
+ (end_i, end_j) = object_coords[m.domain]
1936
+ else:
1937
+ continue
1938
+
1939
+ if end_j > j:
1940
+ right.append(m)
1941
+ elif end_j < j:
1942
+ left.append(m)
1943
+ elif not morphisms_str_info[m].curving:
1944
+ # This is a straight vertical morphism,
1945
+ # because it has no curving.
1946
+ straight_vertical.append(m)
1947
+
1948
+ if len(left) < len(right):
1949
+ # More morphisms stick out to the left than to the
1950
+ # right, let's curve the morphism to the right.
1951
+ if backwards:
1952
+ curving = "^"
1953
+ label_pos = "^"
1954
+ else:
1955
+ curving = "_"
1956
+ label_pos = "_"
1957
+
1958
+ # Assure that the straight vertical morphisms have
1959
+ # their labels on the left side of the arrow.
1960
+ for m in straight_vertical:
1961
+ (i1, j1) = object_coords[m.domain]
1962
+ (i2, j2) = object_coords[m.codomain]
1963
+
1964
+ m_str_info = morphisms_str_info[m]
1965
+ if i1 < i2:
1966
+ m_str_info.label_position = "^"
1967
+ else:
1968
+ m_str_info.label_position = "_"
1969
+
1970
+ # Don't allow any further modifications of the
1971
+ # position of this label.
1972
+ m_str_info.forced_label_position = True
1973
+ else:
1974
+ # More morphisms stick out to the right than to the
1975
+ # left, let's curve the morphism to the left.
1976
+ if backwards:
1977
+ curving = "_"
1978
+ label_pos = "_"
1979
+ else:
1980
+ curving = "^"
1981
+ label_pos = "^"
1982
+
1983
+ # Assure that the straight vertical morphisms have
1984
+ # their labels on the right side of the arrow.
1985
+ for m in straight_vertical:
1986
+ (i1, j1) = object_coords[m.domain]
1987
+ (i2, j2) = object_coords[m.codomain]
1988
+
1989
+ m_str_info = morphisms_str_info[m]
1990
+ if i1 < i2:
1991
+ m_str_info.label_position = "_"
1992
+ else:
1993
+ m_str_info.label_position = "^"
1994
+
1995
+ # Don't allow any further modifications of the
1996
+ # position of this label.
1997
+ m_str_info.forced_label_position = True
1998
+
1999
+ return (curving, label_pos)
2000
+
2001
+ def _process_morphism(self, diagram, grid, morphism, object_coords,
2002
+ morphisms, morphisms_str_info):
2003
+ """
2004
+ Given the required information, produces the string
2005
+ representation of ``morphism``.
2006
+ """
2007
+ def repeat_string_cond(times, str_gt, str_lt):
2008
+ """
2009
+ If ``times > 0``, repeats ``str_gt`` ``times`` times.
2010
+ Otherwise, repeats ``str_lt`` ``-times`` times.
2011
+ """
2012
+ if times > 0:
2013
+ return str_gt * times
2014
+ else:
2015
+ return str_lt * (-times)
2016
+
2017
+ def count_morphisms_undirected(A, B):
2018
+ """
2019
+ Counts how many processed morphisms there are between the
2020
+ two supplied objects.
2021
+ """
2022
+ return len([m for m in morphisms_str_info
2023
+ if {m.domain, m.codomain} == {A, B}])
2024
+
2025
+ def count_morphisms_filtered(dom, cod, curving):
2026
+ """
2027
+ Counts the processed morphisms which go out of ``dom``
2028
+ into ``cod`` with curving ``curving``.
2029
+ """
2030
+ return len([m for m, m_str_info in morphisms_str_info.items()
2031
+ if (m.domain, m.codomain) == (dom, cod) and
2032
+ (m_str_info.curving == curving)])
2033
+
2034
+ (i, j) = object_coords[morphism.domain]
2035
+ (target_i, target_j) = object_coords[morphism.codomain]
2036
+
2037
+ # We now need to determine the direction of
2038
+ # the arrow.
2039
+ delta_i = target_i - i
2040
+ delta_j = target_j - j
2041
+ vertical_direction = repeat_string_cond(delta_i,
2042
+ "d", "u")
2043
+ horizontal_direction = repeat_string_cond(delta_j,
2044
+ "r", "l")
2045
+
2046
+ curving = ""
2047
+ label_pos = "^"
2048
+ looping_start = ""
2049
+ looping_end = ""
2050
+
2051
+ if (delta_i == 0) and (delta_j == 0):
2052
+ # This is a loop morphism.
2053
+ (curving, label_pos, looping_start,
2054
+ looping_end) = XypicDiagramDrawer._process_loop_morphism(
2055
+ i, j, grid, morphisms_str_info, object_coords)
2056
+ elif (delta_i == 0) and (abs(j - target_j) > 1):
2057
+ # This is a horizontal morphism.
2058
+ (curving, label_pos) = XypicDiagramDrawer._process_horizontal_morphism(
2059
+ i, j, target_j, grid, morphisms_str_info, object_coords)
2060
+ elif (delta_j == 0) and (abs(i - target_i) > 1):
2061
+ # This is a vertical morphism.
2062
+ (curving, label_pos) = XypicDiagramDrawer._process_vertical_morphism(
2063
+ i, j, target_i, grid, morphisms_str_info, object_coords)
2064
+
2065
+ count = count_morphisms_undirected(morphism.domain, morphism.codomain)
2066
+ curving_amount = ""
2067
+ if curving:
2068
+ # This morphisms should be curved anyway.
2069
+ curving_amount = self.default_curving_amount + count * \
2070
+ self.default_curving_step
2071
+ elif count:
2072
+ # There are no objects between the domain and codomain of
2073
+ # the current morphism, but this is not there already are
2074
+ # some morphisms with the same domain and codomain, so we
2075
+ # have to curve this one.
2076
+ curving = "^"
2077
+ filtered_morphisms = count_morphisms_filtered(
2078
+ morphism.domain, morphism.codomain, curving)
2079
+ curving_amount = self.default_curving_amount + \
2080
+ filtered_morphisms * \
2081
+ self.default_curving_step
2082
+
2083
+ # Let's now get the name of the morphism.
2084
+ morphism_name = ""
2085
+ if isinstance(morphism, IdentityMorphism):
2086
+ morphism_name = "id_{%s}" + latex(grid[i, j])
2087
+ elif isinstance(morphism, CompositeMorphism):
2088
+ component_names = [latex(Symbol(component.name)) for
2089
+ component in morphism.components]
2090
+ component_names.reverse()
2091
+ morphism_name = "\\circ ".join(component_names)
2092
+ elif isinstance(morphism, NamedMorphism):
2093
+ morphism_name = latex(Symbol(morphism.name))
2094
+
2095
+ return ArrowStringDescription(
2096
+ self.unit, curving, curving_amount, looping_start,
2097
+ looping_end, horizontal_direction, vertical_direction,
2098
+ label_pos, morphism_name)
2099
+
2100
+ @staticmethod
2101
+ def _check_free_space_horizontal(dom_i, dom_j, cod_j, grid):
2102
+ """
2103
+ For a horizontal morphism, checks whether there is free space
2104
+ (i.e., space not occupied by any objects) above the morphism
2105
+ or below it.
2106
+ """
2107
+ if dom_j < cod_j:
2108
+ (start, end) = (dom_j, cod_j)
2109
+ backwards = False
2110
+ else:
2111
+ (start, end) = (cod_j, dom_j)
2112
+ backwards = True
2113
+
2114
+ # Check for free space above.
2115
+ if dom_i == 0:
2116
+ free_up = True
2117
+ else:
2118
+ free_up = all(grid[dom_i - 1, j] for j in
2119
+ range(start, end + 1))
2120
+
2121
+ # Check for free space below.
2122
+ if dom_i == grid.height - 1:
2123
+ free_down = True
2124
+ else:
2125
+ free_down = not any(grid[dom_i + 1, j] for j in
2126
+ range(start, end + 1))
2127
+
2128
+ return (free_up, free_down, backwards)
2129
+
2130
+ @staticmethod
2131
+ def _check_free_space_vertical(dom_i, cod_i, dom_j, grid):
2132
+ """
2133
+ For a vertical morphism, checks whether there is free space
2134
+ (i.e., space not occupied by any objects) to the left of the
2135
+ morphism or to the right of it.
2136
+ """
2137
+ if dom_i < cod_i:
2138
+ (start, end) = (dom_i, cod_i)
2139
+ backwards = False
2140
+ else:
2141
+ (start, end) = (cod_i, dom_i)
2142
+ backwards = True
2143
+
2144
+ # Check if there's space to the left.
2145
+ if dom_j == 0:
2146
+ free_left = True
2147
+ else:
2148
+ free_left = not any(grid[i, dom_j - 1] for i in
2149
+ range(start, end + 1))
2150
+
2151
+ if dom_j == grid.width - 1:
2152
+ free_right = True
2153
+ else:
2154
+ free_right = not any(grid[i, dom_j + 1] for i in
2155
+ range(start, end + 1))
2156
+
2157
+ return (free_left, free_right, backwards)
2158
+
2159
+ @staticmethod
2160
+ def _check_free_space_diagonal(dom_i, cod_i, dom_j, cod_j, grid):
2161
+ """
2162
+ For a diagonal morphism, checks whether there is free space
2163
+ (i.e., space not occupied by any objects) above the morphism
2164
+ or below it.
2165
+ """
2166
+ def abs_xrange(start, end):
2167
+ if start < end:
2168
+ return range(start, end + 1)
2169
+ else:
2170
+ return range(end, start + 1)
2171
+
2172
+ if dom_i < cod_i and dom_j < cod_j:
2173
+ # This morphism goes from top-left to
2174
+ # bottom-right.
2175
+ (start_i, start_j) = (dom_i, dom_j)
2176
+ (end_i, end_j) = (cod_i, cod_j)
2177
+ backwards = False
2178
+ elif dom_i > cod_i and dom_j > cod_j:
2179
+ # This morphism goes from bottom-right to
2180
+ # top-left.
2181
+ (start_i, start_j) = (cod_i, cod_j)
2182
+ (end_i, end_j) = (dom_i, dom_j)
2183
+ backwards = True
2184
+ if dom_i < cod_i and dom_j > cod_j:
2185
+ # This morphism goes from top-right to
2186
+ # bottom-left.
2187
+ (start_i, start_j) = (dom_i, dom_j)
2188
+ (end_i, end_j) = (cod_i, cod_j)
2189
+ backwards = True
2190
+ elif dom_i > cod_i and dom_j < cod_j:
2191
+ # This morphism goes from bottom-left to
2192
+ # top-right.
2193
+ (start_i, start_j) = (cod_i, cod_j)
2194
+ (end_i, end_j) = (dom_i, dom_j)
2195
+ backwards = False
2196
+
2197
+ # This is an attempt at a fast and furious strategy to
2198
+ # decide where there is free space on the two sides of
2199
+ # a diagonal morphism. For a diagonal morphism
2200
+ # starting at ``(start_i, start_j)`` and ending at
2201
+ # ``(end_i, end_j)`` the rectangle defined by these
2202
+ # two points is considered. The slope of the diagonal
2203
+ # ``alpha`` is then computed. Then, for every cell
2204
+ # ``(i, j)`` within the rectangle, the slope
2205
+ # ``alpha1`` of the line through ``(start_i,
2206
+ # start_j)`` and ``(i, j)`` is considered. If
2207
+ # ``alpha1`` is between 0 and ``alpha``, the point
2208
+ # ``(i, j)`` is above the diagonal, if ``alpha1`` is
2209
+ # between ``alpha`` and infinity, the point is below
2210
+ # the diagonal. Also note that, with some beforehand
2211
+ # precautions, this trick works for both the main and
2212
+ # the secondary diagonals of the rectangle.
2213
+
2214
+ # I have considered the possibility to only follow the
2215
+ # shorter diagonals immediately above and below the
2216
+ # main (or secondary) diagonal. This, however,
2217
+ # wouldn't have resulted in much performance gain or
2218
+ # better detection of outer edges, because of
2219
+ # relatively small sizes of diagram grids, while the
2220
+ # code would have become harder to understand.
2221
+
2222
+ alpha = float(end_i - start_i)/(end_j - start_j)
2223
+ free_up = True
2224
+ free_down = True
2225
+ for i in abs_xrange(start_i, end_i):
2226
+ if not free_up and not free_down:
2227
+ break
2228
+
2229
+ for j in abs_xrange(start_j, end_j):
2230
+ if not free_up and not free_down:
2231
+ break
2232
+
2233
+ if (i, j) == (start_i, start_j):
2234
+ continue
2235
+
2236
+ if j == start_j:
2237
+ alpha1 = "inf"
2238
+ else:
2239
+ alpha1 = float(i - start_i)/(j - start_j)
2240
+
2241
+ if grid[i, j]:
2242
+ if (alpha1 == "inf") or (abs(alpha1) > abs(alpha)):
2243
+ free_down = False
2244
+ elif abs(alpha1) < abs(alpha):
2245
+ free_up = False
2246
+
2247
+ return (free_up, free_down, backwards)
2248
+
2249
+ def _push_labels_out(self, morphisms_str_info, grid, object_coords):
2250
+ """
2251
+ For all straight morphisms which form the visual boundary of
2252
+ the laid out diagram, puts their labels on their outer sides.
2253
+ """
2254
+ def set_label_position(free1, free2, pos1, pos2, backwards, m_str_info):
2255
+ """
2256
+ Given the information about room available to one side and
2257
+ to the other side of a morphism (``free1`` and ``free2``),
2258
+ sets the position of the morphism label in such a way that
2259
+ it is on the freer side. This latter operations involves
2260
+ choice between ``pos1`` and ``pos2``, taking ``backwards``
2261
+ in consideration.
2262
+
2263
+ Thus this function will do nothing if either both ``free1
2264
+ == True`` and ``free2 == True`` or both ``free1 == False``
2265
+ and ``free2 == False``. In either case, choosing one side
2266
+ over the other presents no advantage.
2267
+ """
2268
+ if backwards:
2269
+ (pos1, pos2) = (pos2, pos1)
2270
+
2271
+ if free1 and not free2:
2272
+ m_str_info.label_position = pos1
2273
+ elif free2 and not free1:
2274
+ m_str_info.label_position = pos2
2275
+
2276
+ for m, m_str_info in morphisms_str_info.items():
2277
+ if m_str_info.curving or m_str_info.forced_label_position:
2278
+ # This is either a curved morphism, and curved
2279
+ # morphisms have other magic, or the position of this
2280
+ # label has already been fixed.
2281
+ continue
2282
+
2283
+ if m.domain == m.codomain:
2284
+ # This is a loop morphism, their labels, again have a
2285
+ # different magic.
2286
+ continue
2287
+
2288
+ (dom_i, dom_j) = object_coords[m.domain]
2289
+ (cod_i, cod_j) = object_coords[m.codomain]
2290
+
2291
+ if dom_i == cod_i:
2292
+ # Horizontal morphism.
2293
+ (free_up, free_down,
2294
+ backwards) = XypicDiagramDrawer._check_free_space_horizontal(
2295
+ dom_i, dom_j, cod_j, grid)
2296
+
2297
+ set_label_position(free_up, free_down, "^", "_",
2298
+ backwards, m_str_info)
2299
+ elif dom_j == cod_j:
2300
+ # Vertical morphism.
2301
+ (free_left, free_right,
2302
+ backwards) = XypicDiagramDrawer._check_free_space_vertical(
2303
+ dom_i, cod_i, dom_j, grid)
2304
+
2305
+ set_label_position(free_left, free_right, "_", "^",
2306
+ backwards, m_str_info)
2307
+ else:
2308
+ # A diagonal morphism.
2309
+ (free_up, free_down,
2310
+ backwards) = XypicDiagramDrawer._check_free_space_diagonal(
2311
+ dom_i, cod_i, dom_j, cod_j, grid)
2312
+
2313
+ set_label_position(free_up, free_down, "^", "_",
2314
+ backwards, m_str_info)
2315
+
2316
+ @staticmethod
2317
+ def _morphism_sort_key(morphism, object_coords):
2318
+ """
2319
+ Provides a morphism sorting key such that horizontal or
2320
+ vertical morphisms between neighbouring objects come
2321
+ first, then horizontal or vertical morphisms between more
2322
+ far away objects, and finally, all other morphisms.
2323
+ """
2324
+ (i, j) = object_coords[morphism.domain]
2325
+ (target_i, target_j) = object_coords[morphism.codomain]
2326
+
2327
+ if morphism.domain == morphism.codomain:
2328
+ # Loop morphisms should get after diagonal morphisms
2329
+ # so that the proper direction in which to curve the
2330
+ # loop can be determined.
2331
+ return (3, 0, default_sort_key(morphism))
2332
+
2333
+ if target_i == i:
2334
+ return (1, abs(target_j - j), default_sort_key(morphism))
2335
+
2336
+ if target_j == j:
2337
+ return (1, abs(target_i - i), default_sort_key(morphism))
2338
+
2339
+ # Diagonal morphism.
2340
+ return (2, 0, default_sort_key(morphism))
2341
+
2342
+ @staticmethod
2343
+ def _build_xypic_string(diagram, grid, morphisms,
2344
+ morphisms_str_info, diagram_format):
2345
+ """
2346
+ Given a collection of :class:`ArrowStringDescription`
2347
+ describing the morphisms of a diagram and the object layout
2348
+ information of a diagram, produces the final Xy-pic picture.
2349
+ """
2350
+ # Build the mapping between objects and morphisms which have
2351
+ # them as domains.
2352
+ object_morphisms = {}
2353
+ for obj in diagram.objects:
2354
+ object_morphisms[obj] = []
2355
+ for morphism in morphisms:
2356
+ object_morphisms[morphism.domain].append(morphism)
2357
+
2358
+ result = "\\xymatrix%s{\n" % diagram_format
2359
+
2360
+ for i in range(grid.height):
2361
+ for j in range(grid.width):
2362
+ obj = grid[i, j]
2363
+ if obj:
2364
+ result += latex(obj) + " "
2365
+
2366
+ morphisms_to_draw = object_morphisms[obj]
2367
+ for morphism in morphisms_to_draw:
2368
+ result += str(morphisms_str_info[morphism]) + " "
2369
+
2370
+ # Don't put the & after the last column.
2371
+ if j < grid.width - 1:
2372
+ result += "& "
2373
+
2374
+ # Don't put the line break after the last row.
2375
+ if i < grid.height - 1:
2376
+ result += "\\\\"
2377
+ result += "\n"
2378
+
2379
+ result += "}\n"
2380
+
2381
+ return result
2382
+
2383
+ def draw(self, diagram, grid, masked=None, diagram_format=""):
2384
+ r"""
2385
+ Returns the Xy-pic representation of ``diagram`` laid out in
2386
+ ``grid``.
2387
+
2388
+ Consider the following simple triangle diagram.
2389
+
2390
+ >>> from sympy.categories import Object, NamedMorphism, Diagram
2391
+ >>> from sympy.categories import DiagramGrid, XypicDiagramDrawer
2392
+ >>> A = Object("A")
2393
+ >>> B = Object("B")
2394
+ >>> C = Object("C")
2395
+ >>> f = NamedMorphism(A, B, "f")
2396
+ >>> g = NamedMorphism(B, C, "g")
2397
+ >>> diagram = Diagram([f, g], {g * f: "unique"})
2398
+
2399
+ To draw this diagram, its objects need to be laid out with a
2400
+ :class:`DiagramGrid`::
2401
+
2402
+ >>> grid = DiagramGrid(diagram)
2403
+
2404
+ Finally, the drawing:
2405
+
2406
+ >>> drawer = XypicDiagramDrawer()
2407
+ >>> print(drawer.draw(diagram, grid))
2408
+ \xymatrix{
2409
+ A \ar[d]_{g\circ f} \ar[r]^{f} & B \ar[ld]^{g} \\
2410
+ C &
2411
+ }
2412
+
2413
+ The argument ``masked`` can be used to skip morphisms in the
2414
+ presentation of the diagram:
2415
+
2416
+ >>> print(drawer.draw(diagram, grid, masked=[g * f]))
2417
+ \xymatrix{
2418
+ A \ar[r]^{f} & B \ar[ld]^{g} \\
2419
+ C &
2420
+ }
2421
+
2422
+ Finally, the ``diagram_format`` argument can be used to
2423
+ specify the format string of the diagram. For example, to
2424
+ increase the spacing by 1 cm, proceeding as follows:
2425
+
2426
+ >>> print(drawer.draw(diagram, grid, diagram_format="@+1cm"))
2427
+ \xymatrix@+1cm{
2428
+ A \ar[d]_{g\circ f} \ar[r]^{f} & B \ar[ld]^{g} \\
2429
+ C &
2430
+ }
2431
+
2432
+ """
2433
+ # This method works in several steps. It starts by removing
2434
+ # the masked morphisms, if necessary, and then maps objects to
2435
+ # their positions in the grid (coordinate tuples). Remember
2436
+ # that objects are unique in ``Diagram`` and in the layout
2437
+ # produced by ``DiagramGrid``, so every object is mapped to a
2438
+ # single coordinate pair.
2439
+ #
2440
+ # The next step is the central step and is concerned with
2441
+ # analysing the morphisms of the diagram and deciding how to
2442
+ # draw them. For example, how to curve the arrows is decided
2443
+ # at this step. The bulk of the analysis is implemented in
2444
+ # ``_process_morphism``, to the result of which the
2445
+ # appropriate formatters are applied.
2446
+ #
2447
+ # The result of the previous step is a list of
2448
+ # ``ArrowStringDescription``. After the analysis and
2449
+ # application of formatters, some extra logic tries to assure
2450
+ # better positioning of morphism labels (for example, an
2451
+ # attempt is made to avoid the situations when arrows cross
2452
+ # labels). This functionality constitutes the next step and
2453
+ # is implemented in ``_push_labels_out``. Note that label
2454
+ # positions which have been set via a formatter are not
2455
+ # affected in this step.
2456
+ #
2457
+ # Finally, at the closing step, the array of
2458
+ # ``ArrowStringDescription`` and the layout information
2459
+ # incorporated in ``DiagramGrid`` are combined to produce the
2460
+ # resulting Xy-pic picture. This part of code lies in
2461
+ # ``_build_xypic_string``.
2462
+
2463
+ if not masked:
2464
+ morphisms_props = grid.morphisms
2465
+ else:
2466
+ morphisms_props = {}
2467
+ for m, props in grid.morphisms.items():
2468
+ if m in masked:
2469
+ continue
2470
+ morphisms_props[m] = props
2471
+
2472
+ # Build the mapping between objects and their position in the
2473
+ # grid.
2474
+ object_coords = {}
2475
+ for i in range(grid.height):
2476
+ for j in range(grid.width):
2477
+ if grid[i, j]:
2478
+ object_coords[grid[i, j]] = (i, j)
2479
+
2480
+ morphisms = sorted(morphisms_props,
2481
+ key=lambda m: XypicDiagramDrawer._morphism_sort_key(
2482
+ m, object_coords))
2483
+
2484
+ # Build the tuples defining the string representations of
2485
+ # morphisms.
2486
+ morphisms_str_info = {}
2487
+ for morphism in morphisms:
2488
+ string_description = self._process_morphism(
2489
+ diagram, grid, morphism, object_coords, morphisms,
2490
+ morphisms_str_info)
2491
+
2492
+ if self.default_arrow_formatter:
2493
+ self.default_arrow_formatter(string_description)
2494
+
2495
+ for prop in morphisms_props[morphism]:
2496
+ # prop is a Symbol. TODO: Find out why.
2497
+ if prop.name in self.arrow_formatters:
2498
+ formatter = self.arrow_formatters[prop.name]
2499
+ formatter(string_description)
2500
+
2501
+ morphisms_str_info[morphism] = string_description
2502
+
2503
+ # Reposition the labels a bit.
2504
+ self._push_labels_out(morphisms_str_info, grid, object_coords)
2505
+
2506
+ return XypicDiagramDrawer._build_xypic_string(
2507
+ diagram, grid, morphisms, morphisms_str_info, diagram_format)
2508
+
2509
+
2510
+ def xypic_draw_diagram(diagram, masked=None, diagram_format="",
2511
+ groups=None, **hints):
2512
+ r"""
2513
+ Provides a shortcut combining :class:`DiagramGrid` and
2514
+ :class:`XypicDiagramDrawer`. Returns an Xy-pic presentation of
2515
+ ``diagram``. The argument ``masked`` is a list of morphisms which
2516
+ will be not be drawn. The argument ``diagram_format`` is the
2517
+ format string inserted after "\xymatrix". ``groups`` should be a
2518
+ set of logical groups. The ``hints`` will be passed directly to
2519
+ the constructor of :class:`DiagramGrid`.
2520
+
2521
+ For more information about the arguments, see the docstrings of
2522
+ :class:`DiagramGrid` and ``XypicDiagramDrawer.draw``.
2523
+
2524
+ Examples
2525
+ ========
2526
+
2527
+ >>> from sympy.categories import Object, NamedMorphism, Diagram
2528
+ >>> from sympy.categories import xypic_draw_diagram
2529
+ >>> A = Object("A")
2530
+ >>> B = Object("B")
2531
+ >>> C = Object("C")
2532
+ >>> f = NamedMorphism(A, B, "f")
2533
+ >>> g = NamedMorphism(B, C, "g")
2534
+ >>> diagram = Diagram([f, g], {g * f: "unique"})
2535
+ >>> print(xypic_draw_diagram(diagram))
2536
+ \xymatrix{
2537
+ A \ar[d]_{g\circ f} \ar[r]^{f} & B \ar[ld]^{g} \\
2538
+ C &
2539
+ }
2540
+
2541
+ See Also
2542
+ ========
2543
+
2544
+ XypicDiagramDrawer, DiagramGrid
2545
+ """
2546
+ grid = DiagramGrid(diagram, groups, **hints)
2547
+ drawer = XypicDiagramDrawer()
2548
+ return drawer.draw(diagram, grid, masked, diagram_format)
2549
+
2550
+
2551
+ @doctest_depends_on(exe=('latex', 'dvipng'), modules=('pyglet',))
2552
+ def preview_diagram(diagram, masked=None, diagram_format="", groups=None,
2553
+ output='png', viewer=None, euler=True, **hints):
2554
+ """
2555
+ Combines the functionality of ``xypic_draw_diagram`` and
2556
+ ``sympy.printing.preview``. The arguments ``masked``,
2557
+ ``diagram_format``, ``groups``, and ``hints`` are passed to
2558
+ ``xypic_draw_diagram``, while ``output``, ``viewer, and ``euler``
2559
+ are passed to ``preview``.
2560
+
2561
+ Examples
2562
+ ========
2563
+
2564
+ >>> from sympy.categories import Object, NamedMorphism, Diagram
2565
+ >>> from sympy.categories import preview_diagram
2566
+ >>> A = Object("A")
2567
+ >>> B = Object("B")
2568
+ >>> C = Object("C")
2569
+ >>> f = NamedMorphism(A, B, "f")
2570
+ >>> g = NamedMorphism(B, C, "g")
2571
+ >>> d = Diagram([f, g], {g * f: "unique"})
2572
+ >>> preview_diagram(d)
2573
+
2574
+ See Also
2575
+ ========
2576
+
2577
+ XypicDiagramDrawer
2578
+ """
2579
+ from sympy.printing import preview
2580
+ latex_output = xypic_draw_diagram(diagram, masked, diagram_format,
2581
+ groups, **hints)
2582
+ preview(latex_output, output, viewer, euler, ("xypic",))
lib/python3.12/site-packages/sympy/categories/tests/__init__.py ADDED
File without changes
lib/python3.12/site-packages/sympy/categories/tests/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (201 Bytes). View file
 
lib/python3.12/site-packages/sympy/categories/tests/__pycache__/test_baseclasses.cpython-312.pyc ADDED
Binary file (10.2 kB). View file
 
lib/python3.12/site-packages/sympy/categories/tests/__pycache__/test_drawing.cpython-312.pyc ADDED
Binary file (33.5 kB). View file
 
lib/python3.12/site-packages/sympy/categories/tests/test_baseclasses.py ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.categories import (Object, Morphism, IdentityMorphism,
2
+ NamedMorphism, CompositeMorphism,
3
+ Diagram, Category)
4
+ from sympy.categories.baseclasses import Class
5
+ from sympy.testing.pytest import raises
6
+ from sympy.core.containers import (Dict, Tuple)
7
+ from sympy.sets import EmptySet
8
+ from sympy.sets.sets import FiniteSet
9
+
10
+
11
+ def test_morphisms():
12
+ A = Object("A")
13
+ B = Object("B")
14
+ C = Object("C")
15
+ D = Object("D")
16
+
17
+ # Test the base morphism.
18
+ f = NamedMorphism(A, B, "f")
19
+ assert f.domain == A
20
+ assert f.codomain == B
21
+ assert f == NamedMorphism(A, B, "f")
22
+
23
+ # Test identities.
24
+ id_A = IdentityMorphism(A)
25
+ id_B = IdentityMorphism(B)
26
+ assert id_A.domain == A
27
+ assert id_A.codomain == A
28
+ assert id_A == IdentityMorphism(A)
29
+ assert id_A != id_B
30
+
31
+ # Test named morphisms.
32
+ g = NamedMorphism(B, C, "g")
33
+ assert g.name == "g"
34
+ assert g != f
35
+ assert g == NamedMorphism(B, C, "g")
36
+ assert g != NamedMorphism(B, C, "f")
37
+
38
+ # Test composite morphisms.
39
+ assert f == CompositeMorphism(f)
40
+
41
+ k = g.compose(f)
42
+ assert k.domain == A
43
+ assert k.codomain == C
44
+ assert k.components == Tuple(f, g)
45
+ assert g * f == k
46
+ assert CompositeMorphism(f, g) == k
47
+
48
+ assert CompositeMorphism(g * f) == g * f
49
+
50
+ # Test the associativity of composition.
51
+ h = NamedMorphism(C, D, "h")
52
+
53
+ p = h * g
54
+ u = h * g * f
55
+
56
+ assert h * k == u
57
+ assert p * f == u
58
+ assert CompositeMorphism(f, g, h) == u
59
+
60
+ # Test flattening.
61
+ u2 = u.flatten("u")
62
+ assert isinstance(u2, NamedMorphism)
63
+ assert u2.name == "u"
64
+ assert u2.domain == A
65
+ assert u2.codomain == D
66
+
67
+ # Test identities.
68
+ assert f * id_A == f
69
+ assert id_B * f == f
70
+ assert id_A * id_A == id_A
71
+ assert CompositeMorphism(id_A) == id_A
72
+
73
+ # Test bad compositions.
74
+ raises(ValueError, lambda: f * g)
75
+
76
+ raises(TypeError, lambda: f.compose(None))
77
+ raises(TypeError, lambda: id_A.compose(None))
78
+ raises(TypeError, lambda: f * None)
79
+ raises(TypeError, lambda: id_A * None)
80
+
81
+ raises(TypeError, lambda: CompositeMorphism(f, None, 1))
82
+
83
+ raises(ValueError, lambda: NamedMorphism(A, B, ""))
84
+ raises(NotImplementedError, lambda: Morphism(A, B))
85
+
86
+
87
+ def test_diagram():
88
+ A = Object("A")
89
+ B = Object("B")
90
+ C = Object("C")
91
+
92
+ f = NamedMorphism(A, B, "f")
93
+ g = NamedMorphism(B, C, "g")
94
+ id_A = IdentityMorphism(A)
95
+ id_B = IdentityMorphism(B)
96
+
97
+ empty = EmptySet
98
+
99
+ # Test the addition of identities.
100
+ d1 = Diagram([f])
101
+
102
+ assert d1.objects == FiniteSet(A, B)
103
+ assert d1.hom(A, B) == (FiniteSet(f), empty)
104
+ assert d1.hom(A, A) == (FiniteSet(id_A), empty)
105
+ assert d1.hom(B, B) == (FiniteSet(id_B), empty)
106
+
107
+ assert d1 == Diagram([id_A, f])
108
+ assert d1 == Diagram([f, f])
109
+
110
+ # Test the addition of composites.
111
+ d2 = Diagram([f, g])
112
+ homAC = d2.hom(A, C)[0]
113
+
114
+ assert d2.objects == FiniteSet(A, B, C)
115
+ assert g * f in d2.premises.keys()
116
+ assert homAC == FiniteSet(g * f)
117
+
118
+ # Test equality, inequality and hash.
119
+ d11 = Diagram([f])
120
+
121
+ assert d1 == d11
122
+ assert d1 != d2
123
+ assert hash(d1) == hash(d11)
124
+
125
+ d11 = Diagram({f: "unique"})
126
+ assert d1 != d11
127
+
128
+ # Make sure that (re-)adding composites (with new properties)
129
+ # works as expected.
130
+ d = Diagram([f, g], {g * f: "unique"})
131
+ assert d.conclusions == Dict({g * f: FiniteSet("unique")})
132
+
133
+ # Check the hom-sets when there are premises and conclusions.
134
+ assert d.hom(A, C) == (FiniteSet(g * f), FiniteSet(g * f))
135
+ d = Diagram([f, g], [g * f])
136
+ assert d.hom(A, C) == (FiniteSet(g * f), FiniteSet(g * f))
137
+
138
+ # Check how the properties of composite morphisms are computed.
139
+ d = Diagram({f: ["unique", "isomorphism"], g: "unique"})
140
+ assert d.premises[g * f] == FiniteSet("unique")
141
+
142
+ # Check that conclusion morphisms with new objects are not allowed.
143
+ d = Diagram([f], [g])
144
+ assert d.conclusions == Dict({})
145
+
146
+ # Test an empty diagram.
147
+ d = Diagram()
148
+ assert d.premises == Dict({})
149
+ assert d.conclusions == Dict({})
150
+ assert d.objects == empty
151
+
152
+ # Check a SymPy Dict object.
153
+ d = Diagram(Dict({f: FiniteSet("unique", "isomorphism"), g: "unique"}))
154
+ assert d.premises[g * f] == FiniteSet("unique")
155
+
156
+ # Check the addition of components of composite morphisms.
157
+ d = Diagram([g * f])
158
+ assert f in d.premises
159
+ assert g in d.premises
160
+
161
+ # Check subdiagrams.
162
+ d = Diagram([f, g], {g * f: "unique"})
163
+
164
+ d1 = Diagram([f])
165
+ assert d.is_subdiagram(d1)
166
+ assert not d1.is_subdiagram(d)
167
+
168
+ d = Diagram([NamedMorphism(B, A, "f'")])
169
+ assert not d.is_subdiagram(d1)
170
+ assert not d1.is_subdiagram(d)
171
+
172
+ d1 = Diagram([f, g], {g * f: ["unique", "something"]})
173
+ assert not d.is_subdiagram(d1)
174
+ assert not d1.is_subdiagram(d)
175
+
176
+ d = Diagram({f: "blooh"})
177
+ d1 = Diagram({f: "bleeh"})
178
+ assert not d.is_subdiagram(d1)
179
+ assert not d1.is_subdiagram(d)
180
+
181
+ d = Diagram([f, g], {f: "unique", g * f: "veryunique"})
182
+ d1 = d.subdiagram_from_objects(FiniteSet(A, B))
183
+ assert d1 == Diagram([f], {f: "unique"})
184
+ raises(ValueError, lambda: d.subdiagram_from_objects(FiniteSet(A,
185
+ Object("D"))))
186
+
187
+ raises(ValueError, lambda: Diagram({IdentityMorphism(A): "unique"}))
188
+
189
+
190
+ def test_category():
191
+ A = Object("A")
192
+ B = Object("B")
193
+ C = Object("C")
194
+
195
+ f = NamedMorphism(A, B, "f")
196
+ g = NamedMorphism(B, C, "g")
197
+
198
+ d1 = Diagram([f, g])
199
+ d2 = Diagram([f])
200
+
201
+ objects = d1.objects | d2.objects
202
+
203
+ K = Category("K", objects, commutative_diagrams=[d1, d2])
204
+
205
+ assert K.name == "K"
206
+ assert K.objects == Class(objects)
207
+ assert K.commutative_diagrams == FiniteSet(d1, d2)
208
+
209
+ raises(ValueError, lambda: Category(""))
lib/python3.12/site-packages/sympy/categories/tests/test_drawing.py ADDED
@@ -0,0 +1,919 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.categories.diagram_drawing import _GrowableGrid, ArrowStringDescription
2
+ from sympy.categories import (DiagramGrid, Object, NamedMorphism,
3
+ Diagram, XypicDiagramDrawer, xypic_draw_diagram)
4
+ from sympy.sets.sets import FiniteSet
5
+
6
+
7
+ def test_GrowableGrid():
8
+ grid = _GrowableGrid(1, 2)
9
+
10
+ # Check dimensions.
11
+ assert grid.width == 1
12
+ assert grid.height == 2
13
+
14
+ # Check initialization of elements.
15
+ assert grid[0, 0] is None
16
+ assert grid[1, 0] is None
17
+
18
+ # Check assignment to elements.
19
+ grid[0, 0] = 1
20
+ grid[1, 0] = "two"
21
+
22
+ assert grid[0, 0] == 1
23
+ assert grid[1, 0] == "two"
24
+
25
+ # Check appending a row.
26
+ grid.append_row()
27
+
28
+ assert grid.width == 1
29
+ assert grid.height == 3
30
+
31
+ assert grid[0, 0] == 1
32
+ assert grid[1, 0] == "two"
33
+ assert grid[2, 0] is None
34
+
35
+ # Check appending a column.
36
+ grid.append_column()
37
+ assert grid.width == 2
38
+ assert grid.height == 3
39
+
40
+ assert grid[0, 0] == 1
41
+ assert grid[1, 0] == "two"
42
+ assert grid[2, 0] is None
43
+
44
+ assert grid[0, 1] is None
45
+ assert grid[1, 1] is None
46
+ assert grid[2, 1] is None
47
+
48
+ grid = _GrowableGrid(1, 2)
49
+ grid[0, 0] = 1
50
+ grid[1, 0] = "two"
51
+
52
+ # Check prepending a row.
53
+ grid.prepend_row()
54
+ assert grid.width == 1
55
+ assert grid.height == 3
56
+
57
+ assert grid[0, 0] is None
58
+ assert grid[1, 0] == 1
59
+ assert grid[2, 0] == "two"
60
+
61
+ # Check prepending a column.
62
+ grid.prepend_column()
63
+ assert grid.width == 2
64
+ assert grid.height == 3
65
+
66
+ assert grid[0, 0] is None
67
+ assert grid[1, 0] is None
68
+ assert grid[2, 0] is None
69
+
70
+ assert grid[0, 1] is None
71
+ assert grid[1, 1] == 1
72
+ assert grid[2, 1] == "two"
73
+
74
+
75
+ def test_DiagramGrid():
76
+ # Set up some objects and morphisms.
77
+ A = Object("A")
78
+ B = Object("B")
79
+ C = Object("C")
80
+ D = Object("D")
81
+ E = Object("E")
82
+
83
+ f = NamedMorphism(A, B, "f")
84
+ g = NamedMorphism(B, C, "g")
85
+ h = NamedMorphism(D, A, "h")
86
+ k = NamedMorphism(D, B, "k")
87
+
88
+ # A one-morphism diagram.
89
+ d = Diagram([f])
90
+ grid = DiagramGrid(d)
91
+
92
+ assert grid.width == 2
93
+ assert grid.height == 1
94
+ assert grid[0, 0] == A
95
+ assert grid[0, 1] == B
96
+ assert grid.morphisms == {f: FiniteSet()}
97
+
98
+ # A triangle.
99
+ d = Diagram([f, g], {g * f: "unique"})
100
+ grid = DiagramGrid(d)
101
+
102
+ assert grid.width == 2
103
+ assert grid.height == 2
104
+ assert grid[0, 0] == A
105
+ assert grid[0, 1] == B
106
+ assert grid[1, 0] == C
107
+ assert grid[1, 1] is None
108
+ assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(),
109
+ g * f: FiniteSet("unique")}
110
+
111
+ # A triangle with a "loop" morphism.
112
+ l_A = NamedMorphism(A, A, "l_A")
113
+ d = Diagram([f, g, l_A])
114
+ grid = DiagramGrid(d)
115
+
116
+ assert grid.width == 2
117
+ assert grid.height == 2
118
+ assert grid[0, 0] == A
119
+ assert grid[0, 1] == B
120
+ assert grid[1, 0] is None
121
+ assert grid[1, 1] == C
122
+ assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), l_A: FiniteSet()}
123
+
124
+ # A simple diagram.
125
+ d = Diagram([f, g, h, k])
126
+ grid = DiagramGrid(d)
127
+
128
+ assert grid.width == 3
129
+ assert grid.height == 2
130
+ assert grid[0, 0] == A
131
+ assert grid[0, 1] == B
132
+ assert grid[0, 2] == D
133
+ assert grid[1, 0] is None
134
+ assert grid[1, 1] == C
135
+ assert grid[1, 2] is None
136
+ assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(),
137
+ k: FiniteSet()}
138
+
139
+ assert str(grid) == '[[Object("A"), Object("B"), Object("D")], ' \
140
+ '[None, Object("C"), None]]'
141
+
142
+ # A chain of morphisms.
143
+ f = NamedMorphism(A, B, "f")
144
+ g = NamedMorphism(B, C, "g")
145
+ h = NamedMorphism(C, D, "h")
146
+ k = NamedMorphism(D, E, "k")
147
+ d = Diagram([f, g, h, k])
148
+ grid = DiagramGrid(d)
149
+
150
+ assert grid.width == 3
151
+ assert grid.height == 3
152
+ assert grid[0, 0] == A
153
+ assert grid[0, 1] == B
154
+ assert grid[0, 2] is None
155
+ assert grid[1, 0] is None
156
+ assert grid[1, 1] == C
157
+ assert grid[1, 2] == D
158
+ assert grid[2, 0] is None
159
+ assert grid[2, 1] is None
160
+ assert grid[2, 2] == E
161
+ assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(),
162
+ k: FiniteSet()}
163
+
164
+ # A square.
165
+ f = NamedMorphism(A, B, "f")
166
+ g = NamedMorphism(B, D, "g")
167
+ h = NamedMorphism(A, C, "h")
168
+ k = NamedMorphism(C, D, "k")
169
+ d = Diagram([f, g, h, k])
170
+ grid = DiagramGrid(d)
171
+
172
+ assert grid.width == 2
173
+ assert grid.height == 2
174
+ assert grid[0, 0] == A
175
+ assert grid[0, 1] == B
176
+ assert grid[1, 0] == C
177
+ assert grid[1, 1] == D
178
+ assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(),
179
+ k: FiniteSet()}
180
+
181
+ # A strange diagram which resulted from a typo when creating a
182
+ # test for five lemma, but which allowed to stop one extra problem
183
+ # in the algorithm.
184
+ A = Object("A")
185
+ B = Object("B")
186
+ C = Object("C")
187
+ D = Object("D")
188
+ E = Object("E")
189
+ A_ = Object("A'")
190
+ B_ = Object("B'")
191
+ C_ = Object("C'")
192
+ D_ = Object("D'")
193
+ E_ = Object("E'")
194
+
195
+ f = NamedMorphism(A, B, "f")
196
+ g = NamedMorphism(B, C, "g")
197
+ h = NamedMorphism(C, D, "h")
198
+ i = NamedMorphism(D, E, "i")
199
+
200
+ # These 4 morphisms should be between primed objects.
201
+ j = NamedMorphism(A, B, "j")
202
+ k = NamedMorphism(B, C, "k")
203
+ l = NamedMorphism(C, D, "l")
204
+ m = NamedMorphism(D, E, "m")
205
+
206
+ o = NamedMorphism(A, A_, "o")
207
+ p = NamedMorphism(B, B_, "p")
208
+ q = NamedMorphism(C, C_, "q")
209
+ r = NamedMorphism(D, D_, "r")
210
+ s = NamedMorphism(E, E_, "s")
211
+
212
+ d = Diagram([f, g, h, i, j, k, l, m, o, p, q, r, s])
213
+ grid = DiagramGrid(d)
214
+
215
+ assert grid.width == 3
216
+ assert grid.height == 4
217
+ assert grid[0, 0] is None
218
+ assert grid[0, 1] == A
219
+ assert grid[0, 2] == A_
220
+ assert grid[1, 0] == C
221
+ assert grid[1, 1] == B
222
+ assert grid[1, 2] == B_
223
+ assert grid[2, 0] == C_
224
+ assert grid[2, 1] == D
225
+ assert grid[2, 2] == D_
226
+ assert grid[3, 0] is None
227
+ assert grid[3, 1] == E
228
+ assert grid[3, 2] == E_
229
+
230
+ morphisms = {}
231
+ for m in [f, g, h, i, j, k, l, m, o, p, q, r, s]:
232
+ morphisms[m] = FiniteSet()
233
+ assert grid.morphisms == morphisms
234
+
235
+ # A cube.
236
+ A1 = Object("A1")
237
+ A2 = Object("A2")
238
+ A3 = Object("A3")
239
+ A4 = Object("A4")
240
+ A5 = Object("A5")
241
+ A6 = Object("A6")
242
+ A7 = Object("A7")
243
+ A8 = Object("A8")
244
+
245
+ # The top face of the cube.
246
+ f1 = NamedMorphism(A1, A2, "f1")
247
+ f2 = NamedMorphism(A1, A3, "f2")
248
+ f3 = NamedMorphism(A2, A4, "f3")
249
+ f4 = NamedMorphism(A3, A4, "f3")
250
+
251
+ # The bottom face of the cube.
252
+ f5 = NamedMorphism(A5, A6, "f5")
253
+ f6 = NamedMorphism(A5, A7, "f6")
254
+ f7 = NamedMorphism(A6, A8, "f7")
255
+ f8 = NamedMorphism(A7, A8, "f8")
256
+
257
+ # The remaining morphisms.
258
+ f9 = NamedMorphism(A1, A5, "f9")
259
+ f10 = NamedMorphism(A2, A6, "f10")
260
+ f11 = NamedMorphism(A3, A7, "f11")
261
+ f12 = NamedMorphism(A4, A8, "f11")
262
+
263
+ d = Diagram([f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12])
264
+ grid = DiagramGrid(d)
265
+
266
+ assert grid.width == 4
267
+ assert grid.height == 3
268
+ assert grid[0, 0] is None
269
+ assert grid[0, 1] == A5
270
+ assert grid[0, 2] == A6
271
+ assert grid[0, 3] is None
272
+ assert grid[1, 0] is None
273
+ assert grid[1, 1] == A1
274
+ assert grid[1, 2] == A2
275
+ assert grid[1, 3] is None
276
+ assert grid[2, 0] == A7
277
+ assert grid[2, 1] == A3
278
+ assert grid[2, 2] == A4
279
+ assert grid[2, 3] == A8
280
+
281
+ morphisms = {}
282
+ for m in [f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12]:
283
+ morphisms[m] = FiniteSet()
284
+ assert grid.morphisms == morphisms
285
+
286
+ # A line diagram.
287
+ A = Object("A")
288
+ B = Object("B")
289
+ C = Object("C")
290
+ D = Object("D")
291
+ E = Object("E")
292
+
293
+ f = NamedMorphism(A, B, "f")
294
+ g = NamedMorphism(B, C, "g")
295
+ h = NamedMorphism(C, D, "h")
296
+ i = NamedMorphism(D, E, "i")
297
+ d = Diagram([f, g, h, i])
298
+ grid = DiagramGrid(d, layout="sequential")
299
+
300
+ assert grid.width == 5
301
+ assert grid.height == 1
302
+ assert grid[0, 0] == A
303
+ assert grid[0, 1] == B
304
+ assert grid[0, 2] == C
305
+ assert grid[0, 3] == D
306
+ assert grid[0, 4] == E
307
+ assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(),
308
+ i: FiniteSet()}
309
+
310
+ # Test the transposed version.
311
+ grid = DiagramGrid(d, layout="sequential", transpose=True)
312
+
313
+ assert grid.width == 1
314
+ assert grid.height == 5
315
+ assert grid[0, 0] == A
316
+ assert grid[1, 0] == B
317
+ assert grid[2, 0] == C
318
+ assert grid[3, 0] == D
319
+ assert grid[4, 0] == E
320
+ assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(),
321
+ i: FiniteSet()}
322
+
323
+ # A pullback.
324
+ m1 = NamedMorphism(A, B, "m1")
325
+ m2 = NamedMorphism(A, C, "m2")
326
+ s1 = NamedMorphism(B, D, "s1")
327
+ s2 = NamedMorphism(C, D, "s2")
328
+ f1 = NamedMorphism(E, B, "f1")
329
+ f2 = NamedMorphism(E, C, "f2")
330
+ g = NamedMorphism(E, A, "g")
331
+
332
+ d = Diagram([m1, m2, s1, s2, f1, f2], {g: "unique"})
333
+ grid = DiagramGrid(d)
334
+
335
+ assert grid.width == 3
336
+ assert grid.height == 2
337
+ assert grid[0, 0] == A
338
+ assert grid[0, 1] == B
339
+ assert grid[0, 2] == E
340
+ assert grid[1, 0] == C
341
+ assert grid[1, 1] == D
342
+ assert grid[1, 2] is None
343
+
344
+ morphisms = {g: FiniteSet("unique")}
345
+ for m in [m1, m2, s1, s2, f1, f2]:
346
+ morphisms[m] = FiniteSet()
347
+ assert grid.morphisms == morphisms
348
+
349
+ # Test the pullback with sequential layout, just for stress
350
+ # testing.
351
+ grid = DiagramGrid(d, layout="sequential")
352
+
353
+ assert grid.width == 5
354
+ assert grid.height == 1
355
+ assert grid[0, 0] == D
356
+ assert grid[0, 1] == B
357
+ assert grid[0, 2] == A
358
+ assert grid[0, 3] == C
359
+ assert grid[0, 4] == E
360
+ assert grid.morphisms == morphisms
361
+
362
+ # Test a pullback with object grouping.
363
+ grid = DiagramGrid(d, groups=FiniteSet(E, FiniteSet(A, B, C, D)))
364
+
365
+ assert grid.width == 3
366
+ assert grid.height == 2
367
+ assert grid[0, 0] == E
368
+ assert grid[0, 1] == A
369
+ assert grid[0, 2] == B
370
+ assert grid[1, 0] is None
371
+ assert grid[1, 1] == C
372
+ assert grid[1, 2] == D
373
+ assert grid.morphisms == morphisms
374
+
375
+ # Five lemma, actually.
376
+ A = Object("A")
377
+ B = Object("B")
378
+ C = Object("C")
379
+ D = Object("D")
380
+ E = Object("E")
381
+ A_ = Object("A'")
382
+ B_ = Object("B'")
383
+ C_ = Object("C'")
384
+ D_ = Object("D'")
385
+ E_ = Object("E'")
386
+
387
+ f = NamedMorphism(A, B, "f")
388
+ g = NamedMorphism(B, C, "g")
389
+ h = NamedMorphism(C, D, "h")
390
+ i = NamedMorphism(D, E, "i")
391
+
392
+ j = NamedMorphism(A_, B_, "j")
393
+ k = NamedMorphism(B_, C_, "k")
394
+ l = NamedMorphism(C_, D_, "l")
395
+ m = NamedMorphism(D_, E_, "m")
396
+
397
+ o = NamedMorphism(A, A_, "o")
398
+ p = NamedMorphism(B, B_, "p")
399
+ q = NamedMorphism(C, C_, "q")
400
+ r = NamedMorphism(D, D_, "r")
401
+ s = NamedMorphism(E, E_, "s")
402
+
403
+ d = Diagram([f, g, h, i, j, k, l, m, o, p, q, r, s])
404
+ grid = DiagramGrid(d)
405
+
406
+ assert grid.width == 5
407
+ assert grid.height == 3
408
+ assert grid[0, 0] is None
409
+ assert grid[0, 1] == A
410
+ assert grid[0, 2] == A_
411
+ assert grid[0, 3] is None
412
+ assert grid[0, 4] is None
413
+ assert grid[1, 0] == C
414
+ assert grid[1, 1] == B
415
+ assert grid[1, 2] == B_
416
+ assert grid[1, 3] == C_
417
+ assert grid[1, 4] is None
418
+ assert grid[2, 0] == D
419
+ assert grid[2, 1] == E
420
+ assert grid[2, 2] is None
421
+ assert grid[2, 3] == D_
422
+ assert grid[2, 4] == E_
423
+
424
+ morphisms = {}
425
+ for m in [f, g, h, i, j, k, l, m, o, p, q, r, s]:
426
+ morphisms[m] = FiniteSet()
427
+ assert grid.morphisms == morphisms
428
+
429
+ # Test the five lemma with object grouping.
430
+ grid = DiagramGrid(d, FiniteSet(
431
+ FiniteSet(A, B, C, D, E), FiniteSet(A_, B_, C_, D_, E_)))
432
+
433
+ assert grid.width == 6
434
+ assert grid.height == 3
435
+ assert grid[0, 0] == A
436
+ assert grid[0, 1] == B
437
+ assert grid[0, 2] is None
438
+ assert grid[0, 3] == A_
439
+ assert grid[0, 4] == B_
440
+ assert grid[0, 5] is None
441
+ assert grid[1, 0] is None
442
+ assert grid[1, 1] == C
443
+ assert grid[1, 2] == D
444
+ assert grid[1, 3] is None
445
+ assert grid[1, 4] == C_
446
+ assert grid[1, 5] == D_
447
+ assert grid[2, 0] is None
448
+ assert grid[2, 1] is None
449
+ assert grid[2, 2] == E
450
+ assert grid[2, 3] is None
451
+ assert grid[2, 4] is None
452
+ assert grid[2, 5] == E_
453
+ assert grid.morphisms == morphisms
454
+
455
+ # Test the five lemma with object grouping, but mixing containers
456
+ # to represent groups.
457
+ grid = DiagramGrid(d, [(A, B, C, D, E), {A_, B_, C_, D_, E_}])
458
+
459
+ assert grid.width == 6
460
+ assert grid.height == 3
461
+ assert grid[0, 0] == A
462
+ assert grid[0, 1] == B
463
+ assert grid[0, 2] is None
464
+ assert grid[0, 3] == A_
465
+ assert grid[0, 4] == B_
466
+ assert grid[0, 5] is None
467
+ assert grid[1, 0] is None
468
+ assert grid[1, 1] == C
469
+ assert grid[1, 2] == D
470
+ assert grid[1, 3] is None
471
+ assert grid[1, 4] == C_
472
+ assert grid[1, 5] == D_
473
+ assert grid[2, 0] is None
474
+ assert grid[2, 1] is None
475
+ assert grid[2, 2] == E
476
+ assert grid[2, 3] is None
477
+ assert grid[2, 4] is None
478
+ assert grid[2, 5] == E_
479
+ assert grid.morphisms == morphisms
480
+
481
+ # Test the five lemma with object grouping and hints.
482
+ grid = DiagramGrid(d, {
483
+ FiniteSet(A, B, C, D, E): {"layout": "sequential",
484
+ "transpose": True},
485
+ FiniteSet(A_, B_, C_, D_, E_): {"layout": "sequential",
486
+ "transpose": True}},
487
+ transpose=True)
488
+
489
+ assert grid.width == 5
490
+ assert grid.height == 2
491
+ assert grid[0, 0] == A
492
+ assert grid[0, 1] == B
493
+ assert grid[0, 2] == C
494
+ assert grid[0, 3] == D
495
+ assert grid[0, 4] == E
496
+ assert grid[1, 0] == A_
497
+ assert grid[1, 1] == B_
498
+ assert grid[1, 2] == C_
499
+ assert grid[1, 3] == D_
500
+ assert grid[1, 4] == E_
501
+ assert grid.morphisms == morphisms
502
+
503
+ # A two-triangle disconnected diagram.
504
+ f = NamedMorphism(A, B, "f")
505
+ g = NamedMorphism(B, C, "g")
506
+ f_ = NamedMorphism(A_, B_, "f")
507
+ g_ = NamedMorphism(B_, C_, "g")
508
+ d = Diagram([f, g, f_, g_], {g * f: "unique", g_ * f_: "unique"})
509
+ grid = DiagramGrid(d)
510
+
511
+ assert grid.width == 4
512
+ assert grid.height == 2
513
+ assert grid[0, 0] == A
514
+ assert grid[0, 1] == B
515
+ assert grid[0, 2] == A_
516
+ assert grid[0, 3] == B_
517
+ assert grid[1, 0] == C
518
+ assert grid[1, 1] is None
519
+ assert grid[1, 2] == C_
520
+ assert grid[1, 3] is None
521
+ assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), f_: FiniteSet(),
522
+ g_: FiniteSet(), g * f: FiniteSet("unique"),
523
+ g_ * f_: FiniteSet("unique")}
524
+
525
+ # A two-morphism disconnected diagram.
526
+ f = NamedMorphism(A, B, "f")
527
+ g = NamedMorphism(C, D, "g")
528
+ d = Diagram([f, g])
529
+ grid = DiagramGrid(d)
530
+
531
+ assert grid.width == 4
532
+ assert grid.height == 1
533
+ assert grid[0, 0] == A
534
+ assert grid[0, 1] == B
535
+ assert grid[0, 2] == C
536
+ assert grid[0, 3] == D
537
+ assert grid.morphisms == {f: FiniteSet(), g: FiniteSet()}
538
+
539
+ # Test a one-object diagram.
540
+ f = NamedMorphism(A, A, "f")
541
+ d = Diagram([f])
542
+ grid = DiagramGrid(d)
543
+
544
+ assert grid.width == 1
545
+ assert grid.height == 1
546
+ assert grid[0, 0] == A
547
+
548
+ # Test a two-object disconnected diagram.
549
+ g = NamedMorphism(B, B, "g")
550
+ d = Diagram([f, g])
551
+ grid = DiagramGrid(d)
552
+
553
+ assert grid.width == 2
554
+ assert grid.height == 1
555
+ assert grid[0, 0] == A
556
+ assert grid[0, 1] == B
557
+
558
+
559
+ def test_DiagramGrid_pseudopod():
560
+ # Test a diagram in which even growing a pseudopod does not
561
+ # eventually help.
562
+ A = Object("A")
563
+ B = Object("B")
564
+ C = Object("C")
565
+ D = Object("D")
566
+ E = Object("E")
567
+ F = Object("F")
568
+ A_ = Object("A'")
569
+ B_ = Object("B'")
570
+ C_ = Object("C'")
571
+ D_ = Object("D'")
572
+ E_ = Object("E'")
573
+
574
+ f1 = NamedMorphism(A, B, "f1")
575
+ f2 = NamedMorphism(A, C, "f2")
576
+ f3 = NamedMorphism(A, D, "f3")
577
+ f4 = NamedMorphism(A, E, "f4")
578
+ f5 = NamedMorphism(A, A_, "f5")
579
+ f6 = NamedMorphism(A, B_, "f6")
580
+ f7 = NamedMorphism(A, C_, "f7")
581
+ f8 = NamedMorphism(A, D_, "f8")
582
+ f9 = NamedMorphism(A, E_, "f9")
583
+ f10 = NamedMorphism(A, F, "f10")
584
+ d = Diagram([f1, f2, f3, f4, f5, f6, f7, f8, f9, f10])
585
+ grid = DiagramGrid(d)
586
+
587
+ assert grid.width == 5
588
+ assert grid.height == 3
589
+ assert grid[0, 0] == E
590
+ assert grid[0, 1] == C
591
+ assert grid[0, 2] == C_
592
+ assert grid[0, 3] == E_
593
+ assert grid[0, 4] == F
594
+ assert grid[1, 0] == D
595
+ assert grid[1, 1] == A
596
+ assert grid[1, 2] == A_
597
+ assert grid[1, 3] is None
598
+ assert grid[1, 4] is None
599
+ assert grid[2, 0] == D_
600
+ assert grid[2, 1] == B
601
+ assert grid[2, 2] == B_
602
+ assert grid[2, 3] is None
603
+ assert grid[2, 4] is None
604
+
605
+ morphisms = {}
606
+ for f in [f1, f2, f3, f4, f5, f6, f7, f8, f9, f10]:
607
+ morphisms[f] = FiniteSet()
608
+ assert grid.morphisms == morphisms
609
+
610
+
611
+ def test_ArrowStringDescription():
612
+ astr = ArrowStringDescription("cm", "", None, "", "", "d", "r", "_", "f")
613
+ assert str(astr) == "\\ar[dr]_{f}"
614
+
615
+ astr = ArrowStringDescription("cm", "", 12, "", "", "d", "r", "_", "f")
616
+ assert str(astr) == "\\ar[dr]_{f}"
617
+
618
+ astr = ArrowStringDescription("cm", "^", 12, "", "", "d", "r", "_", "f")
619
+ assert str(astr) == "\\ar@/^12cm/[dr]_{f}"
620
+
621
+ astr = ArrowStringDescription("cm", "", 12, "r", "", "d", "r", "_", "f")
622
+ assert str(astr) == "\\ar[dr]_{f}"
623
+
624
+ astr = ArrowStringDescription("cm", "", 12, "r", "u", "d", "r", "_", "f")
625
+ assert str(astr) == "\\ar@(r,u)[dr]_{f}"
626
+
627
+ astr = ArrowStringDescription("cm", "", 12, "r", "u", "d", "r", "_", "f")
628
+ assert str(astr) == "\\ar@(r,u)[dr]_{f}"
629
+
630
+ astr = ArrowStringDescription("cm", "", 12, "r", "u", "d", "r", "_", "f")
631
+ astr.arrow_style = "{-->}"
632
+ assert str(astr) == "\\ar@(r,u)@{-->}[dr]_{f}"
633
+
634
+ astr = ArrowStringDescription("cm", "_", 12, "", "", "d", "r", "_", "f")
635
+ astr.arrow_style = "{-->}"
636
+ assert str(astr) == "\\ar@/_12cm/@{-->}[dr]_{f}"
637
+
638
+
639
+ def test_XypicDiagramDrawer_line():
640
+ # A linear diagram.
641
+ A = Object("A")
642
+ B = Object("B")
643
+ C = Object("C")
644
+ D = Object("D")
645
+ E = Object("E")
646
+
647
+ f = NamedMorphism(A, B, "f")
648
+ g = NamedMorphism(B, C, "g")
649
+ h = NamedMorphism(C, D, "h")
650
+ i = NamedMorphism(D, E, "i")
651
+ d = Diagram([f, g, h, i])
652
+ grid = DiagramGrid(d, layout="sequential")
653
+ drawer = XypicDiagramDrawer()
654
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
655
+ "A \\ar[r]^{f} & B \\ar[r]^{g} & C \\ar[r]^{h} & D \\ar[r]^{i} & E \n" \
656
+ "}\n"
657
+
658
+ # The same diagram, transposed.
659
+ grid = DiagramGrid(d, layout="sequential", transpose=True)
660
+ drawer = XypicDiagramDrawer()
661
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
662
+ "A \\ar[d]^{f} \\\\\n" \
663
+ "B \\ar[d]^{g} \\\\\n" \
664
+ "C \\ar[d]^{h} \\\\\n" \
665
+ "D \\ar[d]^{i} \\\\\n" \
666
+ "E \n" \
667
+ "}\n"
668
+
669
+
670
+ def test_XypicDiagramDrawer_triangle():
671
+ # A triangle diagram.
672
+ A = Object("A")
673
+ B = Object("B")
674
+ C = Object("C")
675
+ f = NamedMorphism(A, B, "f")
676
+ g = NamedMorphism(B, C, "g")
677
+
678
+ d = Diagram([f, g], {g * f: "unique"})
679
+ grid = DiagramGrid(d)
680
+ drawer = XypicDiagramDrawer()
681
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
682
+ "A \\ar[d]_{g\\circ f} \\ar[r]^{f} & B \\ar[ld]^{g} \\\\\n" \
683
+ "C & \n" \
684
+ "}\n"
685
+
686
+ # The same diagram, transposed.
687
+ grid = DiagramGrid(d, transpose=True)
688
+ drawer = XypicDiagramDrawer()
689
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
690
+ "A \\ar[r]^{g\\circ f} \\ar[d]_{f} & C \\\\\n" \
691
+ "B \\ar[ru]_{g} & \n" \
692
+ "}\n"
693
+
694
+ # The same diagram, with a masked morphism.
695
+ assert drawer.draw(d, grid, masked=[g]) == "\\xymatrix{\n" \
696
+ "A \\ar[r]^{g\\circ f} \\ar[d]_{f} & C \\\\\n" \
697
+ "B & \n" \
698
+ "}\n"
699
+
700
+ # The same diagram with a formatter for "unique".
701
+ def formatter(astr):
702
+ astr.label = "\\exists !" + astr.label
703
+ astr.arrow_style = "{-->}"
704
+
705
+ drawer.arrow_formatters["unique"] = formatter
706
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
707
+ "A \\ar@{-->}[r]^{\\exists !g\\circ f} \\ar[d]_{f} & C \\\\\n" \
708
+ "B \\ar[ru]_{g} & \n" \
709
+ "}\n"
710
+
711
+ # The same diagram with a default formatter.
712
+ def default_formatter(astr):
713
+ astr.label_displacement = "(0.45)"
714
+
715
+ drawer.default_arrow_formatter = default_formatter
716
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
717
+ "A \\ar@{-->}[r]^(0.45){\\exists !g\\circ f} \\ar[d]_(0.45){f} & C \\\\\n" \
718
+ "B \\ar[ru]_(0.45){g} & \n" \
719
+ "}\n"
720
+
721
+ # A triangle diagram with a lot of morphisms between the same
722
+ # objects.
723
+ f1 = NamedMorphism(B, A, "f1")
724
+ f2 = NamedMorphism(A, B, "f2")
725
+ g1 = NamedMorphism(C, B, "g1")
726
+ g2 = NamedMorphism(B, C, "g2")
727
+ d = Diagram([f, f1, f2, g, g1, g2], {f1 * g1: "unique", g2 * f2: "unique"})
728
+
729
+ grid = DiagramGrid(d, transpose=True)
730
+ drawer = XypicDiagramDrawer()
731
+ assert drawer.draw(d, grid, masked=[f1*g1*g2*f2, g2*f2*f1*g1]) == \
732
+ "\\xymatrix{\n" \
733
+ "A \\ar[r]^{g_{2}\\circ f_{2}} \\ar[d]_{f} \\ar@/^3mm/[d]^{f_{2}} " \
734
+ "& C \\ar@/^3mm/[l]^{f_{1}\\circ g_{1}} \\ar@/^3mm/[ld]^{g_{1}} \\\\\n" \
735
+ "B \\ar@/^3mm/[u]^{f_{1}} \\ar[ru]_{g} \\ar@/^3mm/[ru]^{g_{2}} & \n" \
736
+ "}\n"
737
+
738
+
739
+ def test_XypicDiagramDrawer_cube():
740
+ # A cube diagram.
741
+ A1 = Object("A1")
742
+ A2 = Object("A2")
743
+ A3 = Object("A3")
744
+ A4 = Object("A4")
745
+ A5 = Object("A5")
746
+ A6 = Object("A6")
747
+ A7 = Object("A7")
748
+ A8 = Object("A8")
749
+
750
+ # The top face of the cube.
751
+ f1 = NamedMorphism(A1, A2, "f1")
752
+ f2 = NamedMorphism(A1, A3, "f2")
753
+ f3 = NamedMorphism(A2, A4, "f3")
754
+ f4 = NamedMorphism(A3, A4, "f3")
755
+
756
+ # The bottom face of the cube.
757
+ f5 = NamedMorphism(A5, A6, "f5")
758
+ f6 = NamedMorphism(A5, A7, "f6")
759
+ f7 = NamedMorphism(A6, A8, "f7")
760
+ f8 = NamedMorphism(A7, A8, "f8")
761
+
762
+ # The remaining morphisms.
763
+ f9 = NamedMorphism(A1, A5, "f9")
764
+ f10 = NamedMorphism(A2, A6, "f10")
765
+ f11 = NamedMorphism(A3, A7, "f11")
766
+ f12 = NamedMorphism(A4, A8, "f11")
767
+
768
+ d = Diagram([f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12])
769
+ grid = DiagramGrid(d)
770
+ drawer = XypicDiagramDrawer()
771
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
772
+ "& A_{5} \\ar[r]^{f_{5}} \\ar[ldd]_{f_{6}} & A_{6} \\ar[rdd]^{f_{7}} " \
773
+ "& \\\\\n" \
774
+ "& A_{1} \\ar[r]^{f_{1}} \\ar[d]^{f_{2}} \\ar[u]^{f_{9}} & A_{2} " \
775
+ "\\ar[d]^{f_{3}} \\ar[u]_{f_{10}} & \\\\\n" \
776
+ "A_{7} \\ar@/_3mm/[rrr]_{f_{8}} & A_{3} \\ar[r]^{f_{3}} \\ar[l]_{f_{11}} " \
777
+ "& A_{4} \\ar[r]^{f_{11}} & A_{8} \n" \
778
+ "}\n"
779
+
780
+ # The same diagram, transposed.
781
+ grid = DiagramGrid(d, transpose=True)
782
+ drawer = XypicDiagramDrawer()
783
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
784
+ "& & A_{7} \\ar@/^3mm/[ddd]^{f_{8}} \\\\\n" \
785
+ "A_{5} \\ar[d]_{f_{5}} \\ar[rru]^{f_{6}} & A_{1} \\ar[d]^{f_{1}} " \
786
+ "\\ar[r]^{f_{2}} \\ar[l]^{f_{9}} & A_{3} \\ar[d]_{f_{3}} " \
787
+ "\\ar[u]^{f_{11}} \\\\\n" \
788
+ "A_{6} \\ar[rrd]_{f_{7}} & A_{2} \\ar[r]^{f_{3}} \\ar[l]^{f_{10}} " \
789
+ "& A_{4} \\ar[d]_{f_{11}} \\\\\n" \
790
+ "& & A_{8} \n" \
791
+ "}\n"
792
+
793
+
794
+ def test_XypicDiagramDrawer_curved_and_loops():
795
+ # A simple diagram, with a curved arrow.
796
+ A = Object("A")
797
+ B = Object("B")
798
+ C = Object("C")
799
+ D = Object("D")
800
+
801
+ f = NamedMorphism(A, B, "f")
802
+ g = NamedMorphism(B, C, "g")
803
+ h = NamedMorphism(D, A, "h")
804
+ k = NamedMorphism(D, B, "k")
805
+ d = Diagram([f, g, h, k])
806
+ grid = DiagramGrid(d)
807
+ drawer = XypicDiagramDrawer()
808
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
809
+ "A \\ar[r]_{f} & B \\ar[d]^{g} & D \\ar[l]^{k} \\ar@/_3mm/[ll]_{h} \\\\\n" \
810
+ "& C & \n" \
811
+ "}\n"
812
+
813
+ # The same diagram, transposed.
814
+ grid = DiagramGrid(d, transpose=True)
815
+ drawer = XypicDiagramDrawer()
816
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
817
+ "A \\ar[d]^{f} & \\\\\n" \
818
+ "B \\ar[r]^{g} & C \\\\\n" \
819
+ "D \\ar[u]_{k} \\ar@/^3mm/[uu]^{h} & \n" \
820
+ "}\n"
821
+
822
+ # The same diagram, larger and rotated.
823
+ assert drawer.draw(d, grid, diagram_format="@+1cm@dr") == \
824
+ "\\xymatrix@+1cm@dr{\n" \
825
+ "A \\ar[d]^{f} & \\\\\n" \
826
+ "B \\ar[r]^{g} & C \\\\\n" \
827
+ "D \\ar[u]_{k} \\ar@/^3mm/[uu]^{h} & \n" \
828
+ "}\n"
829
+
830
+ # A simple diagram with three curved arrows.
831
+ h1 = NamedMorphism(D, A, "h1")
832
+ h2 = NamedMorphism(A, D, "h2")
833
+ k = NamedMorphism(D, B, "k")
834
+ d = Diagram([f, g, h, k, h1, h2])
835
+ grid = DiagramGrid(d)
836
+ drawer = XypicDiagramDrawer()
837
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
838
+ "A \\ar[r]_{f} \\ar@/^3mm/[rr]^{h_{2}} & B \\ar[d]^{g} & D \\ar[l]^{k} " \
839
+ "\\ar@/_7mm/[ll]_{h} \\ar@/_11mm/[ll]_{h_{1}} \\\\\n" \
840
+ "& C & \n" \
841
+ "}\n"
842
+
843
+ # The same diagram, transposed.
844
+ grid = DiagramGrid(d, transpose=True)
845
+ drawer = XypicDiagramDrawer()
846
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
847
+ "A \\ar[d]^{f} \\ar@/_3mm/[dd]_{h_{2}} & \\\\\n" \
848
+ "B \\ar[r]^{g} & C \\\\\n" \
849
+ "D \\ar[u]_{k} \\ar@/^7mm/[uu]^{h} \\ar@/^11mm/[uu]^{h_{1}} & \n" \
850
+ "}\n"
851
+
852
+ # The same diagram, with "loop" morphisms.
853
+ l_A = NamedMorphism(A, A, "l_A")
854
+ l_D = NamedMorphism(D, D, "l_D")
855
+ l_C = NamedMorphism(C, C, "l_C")
856
+ d = Diagram([f, g, h, k, h1, h2, l_A, l_D, l_C])
857
+ grid = DiagramGrid(d)
858
+ drawer = XypicDiagramDrawer()
859
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
860
+ "A \\ar[r]_{f} \\ar@/^3mm/[rr]^{h_{2}} \\ar@(u,l)[]^{l_{A}} " \
861
+ "& B \\ar[d]^{g} & D \\ar[l]^{k} \\ar@/_7mm/[ll]_{h} " \
862
+ "\\ar@/_11mm/[ll]_{h_{1}} \\ar@(r,u)[]^{l_{D}} \\\\\n" \
863
+ "& C \\ar@(l,d)[]^{l_{C}} & \n" \
864
+ "}\n"
865
+
866
+ # The same diagram with "loop" morphisms, transposed.
867
+ grid = DiagramGrid(d, transpose=True)
868
+ drawer = XypicDiagramDrawer()
869
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
870
+ "A \\ar[d]^{f} \\ar@/_3mm/[dd]_{h_{2}} \\ar@(r,u)[]^{l_{A}} & \\\\\n" \
871
+ "B \\ar[r]^{g} & C \\ar@(r,u)[]^{l_{C}} \\\\\n" \
872
+ "D \\ar[u]_{k} \\ar@/^7mm/[uu]^{h} \\ar@/^11mm/[uu]^{h_{1}} " \
873
+ "\\ar@(l,d)[]^{l_{D}} & \n" \
874
+ "}\n"
875
+
876
+ # The same diagram with two "loop" morphisms per object.
877
+ l_A_ = NamedMorphism(A, A, "n_A")
878
+ l_D_ = NamedMorphism(D, D, "n_D")
879
+ l_C_ = NamedMorphism(C, C, "n_C")
880
+ d = Diagram([f, g, h, k, h1, h2, l_A, l_D, l_C, l_A_, l_D_, l_C_])
881
+ grid = DiagramGrid(d)
882
+ drawer = XypicDiagramDrawer()
883
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
884
+ "A \\ar[r]_{f} \\ar@/^3mm/[rr]^{h_{2}} \\ar@(u,l)[]^{l_{A}} " \
885
+ "\\ar@/^3mm/@(l,d)[]^{n_{A}} & B \\ar[d]^{g} & D \\ar[l]^{k} " \
886
+ "\\ar@/_7mm/[ll]_{h} \\ar@/_11mm/[ll]_{h_{1}} \\ar@(r,u)[]^{l_{D}} " \
887
+ "\\ar@/^3mm/@(d,r)[]^{n_{D}} \\\\\n" \
888
+ "& C \\ar@(l,d)[]^{l_{C}} \\ar@/^3mm/@(d,r)[]^{n_{C}} & \n" \
889
+ "}\n"
890
+
891
+ # The same diagram with two "loop" morphisms per object, transposed.
892
+ grid = DiagramGrid(d, transpose=True)
893
+ drawer = XypicDiagramDrawer()
894
+ assert drawer.draw(d, grid) == "\\xymatrix{\n" \
895
+ "A \\ar[d]^{f} \\ar@/_3mm/[dd]_{h_{2}} \\ar@(r,u)[]^{l_{A}} " \
896
+ "\\ar@/^3mm/@(u,l)[]^{n_{A}} & \\\\\n" \
897
+ "B \\ar[r]^{g} & C \\ar@(r,u)[]^{l_{C}} \\ar@/^3mm/@(d,r)[]^{n_{C}} \\\\\n" \
898
+ "D \\ar[u]_{k} \\ar@/^7mm/[uu]^{h} \\ar@/^11mm/[uu]^{h_{1}} " \
899
+ "\\ar@(l,d)[]^{l_{D}} \\ar@/^3mm/@(d,r)[]^{n_{D}} & \n" \
900
+ "}\n"
901
+
902
+
903
+ def test_xypic_draw_diagram():
904
+ # A linear diagram.
905
+ A = Object("A")
906
+ B = Object("B")
907
+ C = Object("C")
908
+ D = Object("D")
909
+ E = Object("E")
910
+
911
+ f = NamedMorphism(A, B, "f")
912
+ g = NamedMorphism(B, C, "g")
913
+ h = NamedMorphism(C, D, "h")
914
+ i = NamedMorphism(D, E, "i")
915
+ d = Diagram([f, g, h, i])
916
+
917
+ grid = DiagramGrid(d, layout="sequential")
918
+ drawer = XypicDiagramDrawer()
919
+ assert drawer.draw(d, grid) == xypic_draw_diagram(d, layout="sequential")
lib/python3.12/site-packages/sympy/liealgebras/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from sympy.liealgebras.cartan_type import CartanType
2
+
3
+ __all__ = ['CartanType']
lib/python3.12/site-packages/sympy/liealgebras/cartan_matrix.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .cartan_type import CartanType
2
+
3
+ def CartanMatrix(ct):
4
+ """Access the Cartan matrix of a specific Lie algebra
5
+
6
+ Examples
7
+ ========
8
+
9
+ >>> from sympy.liealgebras.cartan_matrix import CartanMatrix
10
+ >>> CartanMatrix("A2")
11
+ Matrix([
12
+ [ 2, -1],
13
+ [-1, 2]])
14
+
15
+ >>> CartanMatrix(['C', 3])
16
+ Matrix([
17
+ [ 2, -1, 0],
18
+ [-1, 2, -1],
19
+ [ 0, -2, 2]])
20
+
21
+ This method works by returning the Cartan matrix
22
+ which corresponds to Cartan type t.
23
+ """
24
+
25
+ return CartanType(ct).cartan_matrix()
lib/python3.12/site-packages/sympy/liealgebras/cartan_type.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core import Atom, Basic
2
+
3
+
4
+ class CartanType_generator():
5
+ """
6
+ Constructor for actually creating things
7
+ """
8
+ def __call__(self, *args):
9
+ c = args[0]
10
+ if isinstance(c, list):
11
+ letter, n = c[0], int(c[1])
12
+ elif isinstance(c, str):
13
+ letter, n = c[0], int(c[1:])
14
+ else:
15
+ raise TypeError("Argument must be a string (e.g. 'A3') or a list (e.g. ['A', 3])")
16
+
17
+ if n < 0:
18
+ raise ValueError("Lie algebra rank cannot be negative")
19
+ if letter == "A":
20
+ from . import type_a
21
+ return type_a.TypeA(n)
22
+ if letter == "B":
23
+ from . import type_b
24
+ return type_b.TypeB(n)
25
+
26
+ if letter == "C":
27
+ from . import type_c
28
+ return type_c.TypeC(n)
29
+
30
+ if letter == "D":
31
+ from . import type_d
32
+ return type_d.TypeD(n)
33
+
34
+ if letter == "E":
35
+ if n >= 6 and n <= 8:
36
+ from . import type_e
37
+ return type_e.TypeE(n)
38
+
39
+ if letter == "F":
40
+ if n == 4:
41
+ from . import type_f
42
+ return type_f.TypeF(n)
43
+
44
+ if letter == "G":
45
+ if n == 2:
46
+ from . import type_g
47
+ return type_g.TypeG(n)
48
+
49
+ CartanType = CartanType_generator()
50
+
51
+
52
+ class Standard_Cartan(Atom):
53
+ """
54
+ Concrete base class for Cartan types such as A4, etc
55
+ """
56
+
57
+ def __new__(cls, series, n):
58
+ obj = Basic.__new__(cls)
59
+ obj.n = n
60
+ obj.series = series
61
+ return obj
62
+
63
+ def rank(self):
64
+ """
65
+ Returns the rank of the Lie algebra
66
+ """
67
+ return self.n
68
+
69
+ def series(self):
70
+ """
71
+ Returns the type of the Lie algebra
72
+ """
73
+ return self.series
lib/python3.12/site-packages/sympy/liealgebras/dynkin_diagram.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .cartan_type import CartanType
2
+
3
+
4
+ def DynkinDiagram(t):
5
+ """Display the Dynkin diagram of a given Lie algebra
6
+
7
+ Works by generating the CartanType for the input, t, and then returning the
8
+ Dynkin diagram method from the individual classes.
9
+
10
+ Examples
11
+ ========
12
+
13
+ >>> from sympy.liealgebras.dynkin_diagram import DynkinDiagram
14
+ >>> print(DynkinDiagram("A3"))
15
+ 0---0---0
16
+ 1 2 3
17
+
18
+ >>> print(DynkinDiagram("B4"))
19
+ 0---0---0=>=0
20
+ 1 2 3 4
21
+
22
+ """
23
+
24
+ return CartanType(t).dynkin_diagram()
lib/python3.12/site-packages/sympy/liealgebras/root_system.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .cartan_type import CartanType
2
+ from sympy.core.basic import Atom
3
+
4
+ class RootSystem(Atom):
5
+ """Represent the root system of a simple Lie algebra
6
+
7
+ Every simple Lie algebra has a unique root system. To find the root
8
+ system, we first consider the Cartan subalgebra of g, which is the maximal
9
+ abelian subalgebra, and consider the adjoint action of g on this
10
+ subalgebra. There is a root system associated with this action. Now, a
11
+ root system over a vector space V is a set of finite vectors Phi (called
12
+ roots), which satisfy:
13
+
14
+ 1. The roots span V
15
+ 2. The only scalar multiples of x in Phi are x and -x
16
+ 3. For every x in Phi, the set Phi is closed under reflection
17
+ through the hyperplane perpendicular to x.
18
+ 4. If x and y are roots in Phi, then the projection of y onto
19
+ the line through x is a half-integral multiple of x.
20
+
21
+ Now, there is a subset of Phi, which we will call Delta, such that:
22
+ 1. Delta is a basis of V
23
+ 2. Each root x in Phi can be written x = sum k_y y for y in Delta
24
+
25
+ The elements of Delta are called the simple roots.
26
+ Therefore, we see that the simple roots span the root space of a given
27
+ simple Lie algebra.
28
+
29
+ References
30
+ ==========
31
+
32
+ .. [1] https://en.wikipedia.org/wiki/Root_system
33
+ .. [2] Lie Algebras and Representation Theory - Humphreys
34
+
35
+ """
36
+
37
+ def __new__(cls, cartantype):
38
+ """Create a new RootSystem object
39
+
40
+ This method assigns an attribute called cartan_type to each instance of
41
+ a RootSystem object. When an instance of RootSystem is called, it
42
+ needs an argument, which should be an instance of a simple Lie algebra.
43
+ We then take the CartanType of this argument and set it as the
44
+ cartan_type attribute of the RootSystem instance.
45
+
46
+ """
47
+ obj = Atom.__new__(cls)
48
+ obj.cartan_type = CartanType(cartantype)
49
+ return obj
50
+
51
+ def simple_roots(self):
52
+ """Generate the simple roots of the Lie algebra
53
+
54
+ The rank of the Lie algebra determines the number of simple roots that
55
+ it has. This method obtains the rank of the Lie algebra, and then uses
56
+ the simple_root method from the Lie algebra classes to generate all the
57
+ simple roots.
58
+
59
+ Examples
60
+ ========
61
+
62
+ >>> from sympy.liealgebras.root_system import RootSystem
63
+ >>> c = RootSystem("A3")
64
+ >>> roots = c.simple_roots()
65
+ >>> roots
66
+ {1: [1, -1, 0, 0], 2: [0, 1, -1, 0], 3: [0, 0, 1, -1]}
67
+
68
+ """
69
+ n = self.cartan_type.rank()
70
+ roots = {}
71
+ for i in range(1, n+1):
72
+ root = self.cartan_type.simple_root(i)
73
+ roots[i] = root
74
+ return roots
75
+
76
+
77
+ def all_roots(self):
78
+ """Generate all the roots of a given root system
79
+
80
+ The result is a dictionary where the keys are integer numbers. It
81
+ generates the roots by getting the dictionary of all positive roots
82
+ from the bases classes, and then taking each root, and multiplying it
83
+ by -1 and adding it to the dictionary. In this way all the negative
84
+ roots are generated.
85
+
86
+ """
87
+ alpha = self.cartan_type.positive_roots()
88
+ keys = list(alpha.keys())
89
+ k = max(keys)
90
+ for val in keys:
91
+ k += 1
92
+ root = alpha[val]
93
+ newroot = [-x for x in root]
94
+ alpha[k] = newroot
95
+ return alpha
96
+
97
+ def root_space(self):
98
+ """Return the span of the simple roots
99
+
100
+ The root space is the vector space spanned by the simple roots, i.e. it
101
+ is a vector space with a distinguished basis, the simple roots. This
102
+ method returns a string that represents the root space as the span of
103
+ the simple roots, alpha[1],...., alpha[n].
104
+
105
+ Examples
106
+ ========
107
+
108
+ >>> from sympy.liealgebras.root_system import RootSystem
109
+ >>> c = RootSystem("A3")
110
+ >>> c.root_space()
111
+ 'alpha[1] + alpha[2] + alpha[3]'
112
+
113
+ """
114
+ n = self.cartan_type.rank()
115
+ rs = " + ".join("alpha["+str(i) +"]" for i in range(1, n+1))
116
+ return rs
117
+
118
+ def add_simple_roots(self, root1, root2):
119
+ """Add two simple roots together
120
+
121
+ The function takes as input two integers, root1 and root2. It then
122
+ uses these integers as keys in the dictionary of simple roots, and gets
123
+ the corresponding simple roots, and then adds them together.
124
+
125
+ Examples
126
+ ========
127
+
128
+ >>> from sympy.liealgebras.root_system import RootSystem
129
+ >>> c = RootSystem("A3")
130
+ >>> newroot = c.add_simple_roots(1, 2)
131
+ >>> newroot
132
+ [1, 0, -1, 0]
133
+
134
+ """
135
+
136
+ alpha = self.simple_roots()
137
+ if root1 > len(alpha) or root2 > len(alpha):
138
+ raise ValueError("You've used a root that doesn't exist!")
139
+ a1 = alpha[root1]
140
+ a2 = alpha[root2]
141
+ newroot = [_a1 + _a2 for _a1, _a2 in zip(a1, a2)]
142
+ return newroot
143
+
144
+ def add_as_roots(self, root1, root2):
145
+ """Add two roots together if and only if their sum is also a root
146
+
147
+ It takes as input two vectors which should be roots. It then computes
148
+ their sum and checks if it is in the list of all possible roots. If it
149
+ is, it returns the sum. Otherwise it returns a string saying that the
150
+ sum is not a root.
151
+
152
+ Examples
153
+ ========
154
+
155
+ >>> from sympy.liealgebras.root_system import RootSystem
156
+ >>> c = RootSystem("A3")
157
+ >>> c.add_as_roots([1, 0, -1, 0], [0, 0, 1, -1])
158
+ [1, 0, 0, -1]
159
+ >>> c.add_as_roots([1, -1, 0, 0], [0, 0, -1, 1])
160
+ 'The sum of these two roots is not a root'
161
+
162
+ """
163
+ alpha = self.all_roots()
164
+ newroot = [r1 + r2 for r1, r2 in zip(root1, root2)]
165
+ if newroot in alpha.values():
166
+ return newroot
167
+ else:
168
+ return "The sum of these two roots is not a root"
169
+
170
+
171
+ def cartan_matrix(self):
172
+ """Cartan matrix of Lie algebra associated with this root system
173
+
174
+ Examples
175
+ ========
176
+
177
+ >>> from sympy.liealgebras.root_system import RootSystem
178
+ >>> c = RootSystem("A3")
179
+ >>> c.cartan_matrix()
180
+ Matrix([
181
+ [ 2, -1, 0],
182
+ [-1, 2, -1],
183
+ [ 0, -1, 2]])
184
+ """
185
+ return self.cartan_type.cartan_matrix()
186
+
187
+ def dynkin_diagram(self):
188
+ """Dynkin diagram of the Lie algebra associated with this root system
189
+
190
+ Examples
191
+ ========
192
+
193
+ >>> from sympy.liealgebras.root_system import RootSystem
194
+ >>> c = RootSystem("A3")
195
+ >>> print(c.dynkin_diagram())
196
+ 0---0---0
197
+ 1 2 3
198
+ """
199
+ return self.cartan_type.dynkin_diagram()
lib/python3.12/site-packages/sympy/liealgebras/tests/__init__.py ADDED
File without changes
lib/python3.12/site-packages/sympy/liealgebras/tests/__pycache__/test_weyl_group.cpython-312.pyc ADDED
Binary file (2.92 kB). View file
 
lib/python3.12/site-packages/sympy/liealgebras/tests/test_cartan_matrix.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.liealgebras.cartan_matrix import CartanMatrix
2
+ from sympy.matrices import Matrix
3
+
4
+ def test_CartanMatrix():
5
+ c = CartanMatrix("A3")
6
+ m = Matrix(3, 3, [2, -1, 0, -1, 2, -1, 0, -1, 2])
7
+ assert c == m
8
+ a = CartanMatrix(["G",2])
9
+ mt = Matrix(2, 2, [2, -1, -3, 2])
10
+ assert a == mt
lib/python3.12/site-packages/sympy/liealgebras/tests/test_cartan_type.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.liealgebras.cartan_type import CartanType, Standard_Cartan
2
+
3
+ def test_Standard_Cartan():
4
+ c = CartanType("A4")
5
+ assert c.rank() == 4
6
+ assert c.series == "A"
7
+ m = Standard_Cartan("A", 2)
8
+ assert m.rank() == 2
9
+ assert m.series == "A"
10
+ b = CartanType("B12")
11
+ assert b.rank() == 12
12
+ assert b.series == "B"
lib/python3.12/site-packages/sympy/liealgebras/tests/test_dynkin_diagram.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.liealgebras.dynkin_diagram import DynkinDiagram
2
+
3
+ def test_DynkinDiagram():
4
+ c = DynkinDiagram("A3")
5
+ diag = "0---0---0\n1 2 3"
6
+ assert c == diag
7
+ ct = DynkinDiagram(["B", 3])
8
+ diag2 = "0---0=>=0\n1 2 3"
9
+ assert ct == diag2
lib/python3.12/site-packages/sympy/liealgebras/tests/test_root_system.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.liealgebras.root_system import RootSystem
2
+ from sympy.liealgebras.type_a import TypeA
3
+ from sympy.matrices import Matrix
4
+
5
+ def test_root_system():
6
+ c = RootSystem("A3")
7
+ assert c.cartan_type == TypeA(3)
8
+ assert c.simple_roots() == {1: [1, -1, 0, 0], 2: [0, 1, -1, 0], 3: [0, 0, 1, -1]}
9
+ assert c.root_space() == "alpha[1] + alpha[2] + alpha[3]"
10
+ assert c.cartan_matrix() == Matrix([[ 2, -1, 0], [-1, 2, -1], [ 0, -1, 2]])
11
+ assert c.dynkin_diagram() == "0---0---0\n1 2 3"
12
+ assert c.add_simple_roots(1, 2) == [1, 0, -1, 0]
13
+ assert c.all_roots() == {1: [1, -1, 0, 0], 2: [1, 0, -1, 0],
14
+ 3: [1, 0, 0, -1], 4: [0, 1, -1, 0], 5: [0, 1, 0, -1],
15
+ 6: [0, 0, 1, -1], 7: [-1, 1, 0, 0], 8: [-1, 0, 1, 0],
16
+ 9: [-1, 0, 0, 1], 10: [0, -1, 1, 0],
17
+ 11: [0, -1, 0, 1], 12: [0, 0, -1, 1]}
18
+ assert c.add_as_roots([1, 0, -1, 0], [0, 0, 1, -1]) == [1, 0, 0, -1]
lib/python3.12/site-packages/sympy/liealgebras/tests/test_type_A.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.liealgebras.cartan_type import CartanType
2
+ from sympy.matrices import Matrix
3
+
4
+ def test_type_A():
5
+ c = CartanType("A3")
6
+ m = Matrix(3, 3, [2, -1, 0, -1, 2, -1, 0, -1, 2])
7
+ assert m == c.cartan_matrix()
8
+ assert c.basis() == 8
9
+ assert c.roots() == 12
10
+ assert c.dimension() == 4
11
+ assert c.simple_root(1) == [1, -1, 0, 0]
12
+ assert c.highest_root() == [1, 0, 0, -1]
13
+ assert c.lie_algebra() == "su(4)"
14
+ diag = "0---0---0\n1 2 3"
15
+ assert c.dynkin_diagram() == diag
16
+ assert c.positive_roots() == {1: [1, -1, 0, 0], 2: [1, 0, -1, 0],
17
+ 3: [1, 0, 0, -1], 4: [0, 1, -1, 0], 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]}
lib/python3.12/site-packages/sympy/liealgebras/tests/test_type_B.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.liealgebras.cartan_type import CartanType
2
+ from sympy.matrices import Matrix
3
+
4
+ def test_type_B():
5
+ c = CartanType("B3")
6
+ m = Matrix(3, 3, [2, -1, 0, -1, 2, -2, 0, -1, 2])
7
+ assert m == c.cartan_matrix()
8
+ assert c.dimension() == 3
9
+ assert c.roots() == 18
10
+ assert c.simple_root(3) == [0, 0, 1]
11
+ assert c.basis() == 3
12
+ assert c.lie_algebra() == "so(6)"
13
+ diag = "0---0=>=0\n1 2 3"
14
+ assert c.dynkin_diagram() == diag
15
+ assert c.positive_roots() == {1: [1, -1, 0], 2: [1, 1, 0], 3: [1, 0, -1],
16
+ 4: [1, 0, 1], 5: [0, 1, -1], 6: [0, 1, 1], 7: [1, 0, 0],
17
+ 8: [0, 1, 0], 9: [0, 0, 1]}
lib/python3.12/site-packages/sympy/liealgebras/tests/test_type_C.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.liealgebras.cartan_type import CartanType
2
+ from sympy.matrices import Matrix
3
+
4
+ def test_type_C():
5
+ c = CartanType("C4")
6
+ m = Matrix(4, 4, [2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2, -1, 0, 0, -2, 2])
7
+ assert c.cartan_matrix() == m
8
+ assert c.dimension() == 4
9
+ assert c.simple_root(4) == [0, 0, 0, 2]
10
+ assert c.roots() == 32
11
+ assert c.basis() == 36
12
+ assert c.lie_algebra() == "sp(8)"
13
+ t = CartanType(['C', 3])
14
+ assert t.dimension() == 3
15
+ diag = "0---0---0=<=0\n1 2 3 4"
16
+ assert c.dynkin_diagram() == diag
17
+ assert c.positive_roots() == {1: [1, -1, 0, 0], 2: [1, 1, 0, 0],
18
+ 3: [1, 0, -1, 0], 4: [1, 0, 1, 0], 5: [1, 0, 0, -1],
19
+ 6: [1, 0, 0, 1], 7: [0, 1, -1, 0], 8: [0, 1, 1, 0],
20
+ 9: [0, 1, 0, -1], 10: [0, 1, 0, 1], 11: [0, 0, 1, -1],
21
+ 12: [0, 0, 1, 1], 13: [2, 0, 0, 0], 14: [0, 2, 0, 0], 15: [0, 0, 2, 0],
22
+ 16: [0, 0, 0, 2]}
lib/python3.12/site-packages/sympy/liealgebras/tests/test_type_D.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.liealgebras.cartan_type import CartanType
2
+ from sympy.matrices import Matrix
3
+
4
+
5
+
6
+ def test_type_D():
7
+ c = CartanType("D4")
8
+ m = Matrix(4, 4, [2, -1, 0, 0, -1, 2, -1, -1, 0, -1, 2, 0, 0, -1, 0, 2])
9
+ assert c.cartan_matrix() == m
10
+ assert c.basis() == 6
11
+ assert c.lie_algebra() == "so(8)"
12
+ assert c.roots() == 24
13
+ assert c.simple_root(3) == [0, 0, 1, -1]
14
+ diag = " 3\n 0\n |\n |\n0---0---0\n1 2 4"
15
+ assert diag == c.dynkin_diagram()
16
+ assert c.positive_roots() == {1: [1, -1, 0, 0], 2: [1, 1, 0, 0],
17
+ 3: [1, 0, -1, 0], 4: [1, 0, 1, 0], 5: [1, 0, 0, -1], 6: [1, 0, 0, 1],
18
+ 7: [0, 1, -1, 0], 8: [0, 1, 1, 0], 9: [0, 1, 0, -1], 10: [0, 1, 0, 1],
19
+ 11: [0, 0, 1, -1], 12: [0, 0, 1, 1]}
lib/python3.12/site-packages/sympy/liealgebras/tests/test_type_E.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.liealgebras.cartan_type import CartanType
2
+ from sympy.matrices import Matrix
3
+
4
+ def test_type_E():
5
+ c = CartanType("E6")
6
+ m = Matrix(6, 6, [2, 0, -1, 0, 0, 0, 0, 2, 0, -1, 0, 0,
7
+ -1, 0, 2, -1, 0, 0, 0, -1, -1, 2, -1, 0, 0, 0, 0,
8
+ -1, 2, -1, 0, 0, 0, 0, -1, 2])
9
+ assert c.cartan_matrix() == m
10
+ assert c.dimension() == 8
11
+ assert c.simple_root(6) == [0, 0, 0, -1, 1, 0, 0, 0]
12
+ assert c.roots() == 72
13
+ assert c.basis() == 78
14
+ diag = " "*8 + "2\n" + " "*8 + "0\n" + " "*8 + "|\n" + " "*8 + "|\n"
15
+ diag += "---".join("0" for i in range(1, 6))+"\n"
16
+ diag += "1 " + " ".join(str(i) for i in range(3, 7))
17
+ assert c.dynkin_diagram() == diag
18
+ posroots = c.positive_roots()
19
+ assert posroots[8] == [1, 0, 0, 0, 1, 0, 0, 0]
lib/python3.12/site-packages/sympy/liealgebras/tests/test_type_F.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.liealgebras.cartan_type import CartanType
2
+ from sympy.matrices import Matrix
3
+ from sympy.core.backend import S
4
+
5
+ def test_type_F():
6
+ c = CartanType("F4")
7
+ m = Matrix(4, 4, [2, -1, 0, 0, -1, 2, -2, 0, 0, -1, 2, -1, 0, 0, -1, 2])
8
+ assert c.cartan_matrix() == m
9
+ assert c.dimension() == 4
10
+ assert c.simple_root(1) == [1, -1, 0, 0]
11
+ assert c.simple_root(2) == [0, 1, -1, 0]
12
+ assert c.simple_root(3) == [0, 0, 0, 1]
13
+ assert c.simple_root(4) == [-S.Half, -S.Half, -S.Half, -S.Half]
14
+ assert c.roots() == 48
15
+ assert c.basis() == 52
16
+ diag = "0---0=>=0---0\n" + " ".join(str(i) for i in range(1, 5))
17
+ assert c.dynkin_diagram() == diag
18
+ assert c.positive_roots() == {1: [1, -1, 0, 0], 2: [1, 1, 0, 0], 3: [1, 0, -1, 0],
19
+ 4: [1, 0, 1, 0], 5: [1, 0, 0, -1], 6: [1, 0, 0, 1], 7: [0, 1, -1, 0],
20
+ 8: [0, 1, 1, 0], 9: [0, 1, 0, -1], 10: [0, 1, 0, 1], 11: [0, 0, 1, -1],
21
+ 12: [0, 0, 1, 1], 13: [1, 0, 0, 0], 14: [0, 1, 0, 0], 15: [0, 0, 1, 0],
22
+ 16: [0, 0, 0, 1], 17: [S.Half, S.Half, S.Half, S.Half], 18: [S.Half, -S.Half, S.Half, S.Half],
23
+ 19: [S.Half, S.Half, -S.Half, S.Half], 20: [S.Half, S.Half, S.Half, -S.Half], 21: [S.Half, S.Half, -S.Half, -S.Half],
24
+ 22: [S.Half, -S.Half, S.Half, -S.Half], 23: [S.Half, -S.Half, -S.Half, S.Half], 24: [S.Half, -S.Half, -S.Half, -S.Half]}
lib/python3.12/site-packages/sympy/liealgebras/tests/test_type_G.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ from sympy.liealgebras.cartan_type import CartanType
3
+ from sympy.matrices import Matrix
4
+
5
+ def test_type_G():
6
+ c = CartanType("G2")
7
+ m = Matrix(2, 2, [2, -1, -3, 2])
8
+ assert c.cartan_matrix() == m
9
+ assert c.simple_root(2) == [1, -2, 1]
10
+ assert c.basis() == 14
11
+ assert c.roots() == 12
12
+ assert c.dimension() == 3
13
+ diag = "0≡<≡0\n1 2"
14
+ assert diag == c.dynkin_diagram()
15
+ assert c.positive_roots() == {1: [0, 1, -1], 2: [1, -2, 1], 3: [1, -1, 0],
16
+ 4: [1, 0, 1], 5: [1, 1, -2], 6: [2, -1, -1]}
lib/python3.12/site-packages/sympy/liealgebras/tests/test_weyl_group.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.liealgebras.weyl_group import WeylGroup
2
+ from sympy.matrices import Matrix
3
+
4
+ def test_weyl_group():
5
+ c = WeylGroup("A3")
6
+ assert c.matrix_form('r1*r2') == Matrix([[0, 0, 1, 0], [1, 0, 0, 0],
7
+ [0, 1, 0, 0], [0, 0, 0, 1]])
8
+ assert c.generators() == ['r1', 'r2', 'r3']
9
+ assert c.group_order() == 24.0
10
+ assert c.group_name() == "S4: the symmetric group acting on 4 elements."
11
+ assert c.coxeter_diagram() == "0---0---0\n1 2 3"
12
+ assert c.element_order('r1*r2*r3') == 4
13
+ assert c.element_order('r1*r3*r2*r3') == 3
14
+ d = WeylGroup("B5")
15
+ assert d.group_order() == 3840
16
+ assert d.element_order('r1*r2*r4*r5') == 12
17
+ assert d.matrix_form('r2*r3') == Matrix([[0, 0, 1, 0, 0], [1, 0, 0, 0, 0],
18
+ [0, 1, 0, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1]])
19
+ assert d.element_order('r1*r2*r1*r3*r5') == 6
20
+ e = WeylGroup("D5")
21
+ assert e.element_order('r2*r3*r5') == 4
22
+ assert e.matrix_form('r2*r3*r5') == Matrix([[1, 0, 0, 0, 0], [0, 0, 0, 0, -1],
23
+ [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, -1, 0]])
24
+ f = WeylGroup("G2")
25
+ assert f.element_order('r1*r2*r1*r2') == 3
26
+ assert f.element_order('r2*r1*r1*r2') == 1
27
+
28
+ assert f.matrix_form('r1*r2*r1*r2') == Matrix([[0, 1, 0], [0, 0, 1], [1, 0, 0]])
29
+ g = WeylGroup("F4")
30
+ assert g.matrix_form('r2*r3') == Matrix([[1, 0, 0, 0], [0, 1, 0, 0],
31
+ [0, 0, 0, -1], [0, 0, 1, 0]])
32
+
33
+ assert g.element_order('r2*r3') == 4
34
+ h = WeylGroup("E6")
35
+ assert h.group_order() == 51840
lib/python3.12/site-packages/sympy/liealgebras/type_a.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.liealgebras.cartan_type import Standard_Cartan
2
+ from sympy.core.backend import eye
3
+
4
+
5
+ class TypeA(Standard_Cartan):
6
+ """
7
+ This class contains the information about
8
+ the A series of simple Lie algebras.
9
+ ====
10
+ """
11
+
12
+ def __new__(cls, n):
13
+ if n < 1:
14
+ raise ValueError("n cannot be less than 1")
15
+ return Standard_Cartan.__new__(cls, "A", n)
16
+
17
+
18
+ def dimension(self):
19
+ """Dimension of the vector space V underlying the Lie algebra
20
+
21
+ Examples
22
+ ========
23
+
24
+ >>> from sympy.liealgebras.cartan_type import CartanType
25
+ >>> c = CartanType("A4")
26
+ >>> c.dimension()
27
+ 5
28
+ """
29
+ return self.n+1
30
+
31
+
32
+ def basic_root(self, i, j):
33
+ """
34
+ This is a method just to generate roots
35
+ with a 1 iin the ith position and a -1
36
+ in the jth position.
37
+
38
+ """
39
+
40
+ n = self.n
41
+ root = [0]*(n+1)
42
+ root[i] = 1
43
+ root[j] = -1
44
+ return root
45
+
46
+ def simple_root(self, i):
47
+ """
48
+ Every lie algebra has a unique root system.
49
+ Given a root system Q, there is a subset of the
50
+ roots such that an element of Q is called a
51
+ simple root if it cannot be written as the sum
52
+ of two elements in Q. If we let D denote the
53
+ set of simple roots, then it is clear that every
54
+ element of Q can be written as a linear combination
55
+ of elements of D with all coefficients non-negative.
56
+
57
+ In A_n the ith simple root is the root which has a 1
58
+ in the ith position, a -1 in the (i+1)th position,
59
+ and zeroes elsewhere.
60
+
61
+ This method returns the ith simple root for the A series.
62
+
63
+ Examples
64
+ ========
65
+
66
+ >>> from sympy.liealgebras.cartan_type import CartanType
67
+ >>> c = CartanType("A4")
68
+ >>> c.simple_root(1)
69
+ [1, -1, 0, 0, 0]
70
+
71
+ """
72
+
73
+ return self.basic_root(i-1, i)
74
+
75
+ def positive_roots(self):
76
+ """
77
+ This method generates all the positive roots of
78
+ A_n. This is half of all of the roots of A_n;
79
+ by multiplying all the positive roots by -1 we
80
+ get the negative roots.
81
+
82
+ Examples
83
+ ========
84
+
85
+ >>> from sympy.liealgebras.cartan_type import CartanType
86
+ >>> c = CartanType("A3")
87
+ >>> c.positive_roots()
88
+ {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], 3: [1, 0, 0, -1], 4: [0, 1, -1, 0],
89
+ 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]}
90
+ """
91
+
92
+ n = self.n
93
+ posroots = {}
94
+ k = 0
95
+ for i in range(0, n):
96
+ for j in range(i+1, n+1):
97
+ k += 1
98
+ posroots[k] = self.basic_root(i, j)
99
+ return posroots
100
+
101
+ def highest_root(self):
102
+ """
103
+ Returns the highest weight root for A_n
104
+ """
105
+
106
+ return self.basic_root(0, self.n)
107
+
108
+ def roots(self):
109
+ """
110
+ Returns the total number of roots for A_n
111
+ """
112
+ n = self.n
113
+ return n*(n+1)
114
+
115
+ def cartan_matrix(self):
116
+ """
117
+ Returns the Cartan matrix for A_n.
118
+ The Cartan matrix matrix for a Lie algebra is
119
+ generated by assigning an ordering to the simple
120
+ roots, (alpha[1], ...., alpha[l]). Then the ijth
121
+ entry of the Cartan matrix is (<alpha[i],alpha[j]>).
122
+
123
+ Examples
124
+ ========
125
+
126
+ >>> from sympy.liealgebras.cartan_type import CartanType
127
+ >>> c = CartanType('A4')
128
+ >>> c.cartan_matrix()
129
+ Matrix([
130
+ [ 2, -1, 0, 0],
131
+ [-1, 2, -1, 0],
132
+ [ 0, -1, 2, -1],
133
+ [ 0, 0, -1, 2]])
134
+
135
+ """
136
+
137
+ n = self.n
138
+ m = 2 * eye(n)
139
+ i = 1
140
+ while i < n-1:
141
+ m[i, i+1] = -1
142
+ m[i, i-1] = -1
143
+ i += 1
144
+ m[0,1] = -1
145
+ m[n-1, n-2] = -1
146
+ return m
147
+
148
+ def basis(self):
149
+ """
150
+ Returns the number of independent generators of A_n
151
+ """
152
+ n = self.n
153
+ return n**2 - 1
154
+
155
+ def lie_algebra(self):
156
+ """
157
+ Returns the Lie algebra associated with A_n
158
+ """
159
+ n = self.n
160
+ return "su(" + str(n + 1) + ")"
161
+
162
+ def dynkin_diagram(self):
163
+ n = self.n
164
+ diag = "---".join("0" for i in range(1, n+1)) + "\n"
165
+ diag += " ".join(str(i) for i in range(1, n+1))
166
+ return diag
lib/python3.12/site-packages/sympy/liealgebras/type_b.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .cartan_type import Standard_Cartan
2
+ from sympy.core.backend import eye
3
+
4
+ class TypeB(Standard_Cartan):
5
+
6
+ def __new__(cls, n):
7
+ if n < 2:
8
+ raise ValueError("n cannot be less than 2")
9
+ return Standard_Cartan.__new__(cls, "B", n)
10
+
11
+ def dimension(self):
12
+ """Dimension of the vector space V underlying the Lie algebra
13
+
14
+ Examples
15
+ ========
16
+
17
+ >>> from sympy.liealgebras.cartan_type import CartanType
18
+ >>> c = CartanType("B3")
19
+ >>> c.dimension()
20
+ 3
21
+ """
22
+
23
+ return self.n
24
+
25
+ def basic_root(self, i, j):
26
+ """
27
+ This is a method just to generate roots
28
+ with a 1 iin the ith position and a -1
29
+ in the jth position.
30
+
31
+ """
32
+ root = [0]*self.n
33
+ root[i] = 1
34
+ root[j] = -1
35
+ return root
36
+
37
+ def simple_root(self, i):
38
+ """
39
+ Every lie algebra has a unique root system.
40
+ Given a root system Q, there is a subset of the
41
+ roots such that an element of Q is called a
42
+ simple root if it cannot be written as the sum
43
+ of two elements in Q. If we let D denote the
44
+ set of simple roots, then it is clear that every
45
+ element of Q can be written as a linear combination
46
+ of elements of D with all coefficients non-negative.
47
+
48
+ In B_n the first n-1 simple roots are the same as the
49
+ roots in A_(n-1) (a 1 in the ith position, a -1 in
50
+ the (i+1)th position, and zeroes elsewhere). The n-th
51
+ simple root is the root with a 1 in the nth position
52
+ and zeroes elsewhere.
53
+
54
+ This method returns the ith simple root for the B series.
55
+
56
+ Examples
57
+ ========
58
+
59
+ >>> from sympy.liealgebras.cartan_type import CartanType
60
+ >>> c = CartanType("B3")
61
+ >>> c.simple_root(2)
62
+ [0, 1, -1]
63
+
64
+ """
65
+ n = self.n
66
+ if i < n:
67
+ return self.basic_root(i-1, i)
68
+ else:
69
+ root = [0]*self.n
70
+ root[n-1] = 1
71
+ return root
72
+
73
+ def positive_roots(self):
74
+ """
75
+ This method generates all the positive roots of
76
+ A_n. This is half of all of the roots of B_n;
77
+ by multiplying all the positive roots by -1 we
78
+ get the negative roots.
79
+
80
+ Examples
81
+ ========
82
+
83
+ >>> from sympy.liealgebras.cartan_type import CartanType
84
+ >>> c = CartanType("A3")
85
+ >>> c.positive_roots()
86
+ {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], 3: [1, 0, 0, -1], 4: [0, 1, -1, 0],
87
+ 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]}
88
+ """
89
+
90
+ n = self.n
91
+ posroots = {}
92
+ k = 0
93
+ for i in range(0, n-1):
94
+ for j in range(i+1, n):
95
+ k += 1
96
+ posroots[k] = self.basic_root(i, j)
97
+ k += 1
98
+ root = self.basic_root(i, j)
99
+ root[j] = 1
100
+ posroots[k] = root
101
+
102
+ for i in range(0, n):
103
+ k += 1
104
+ root = [0]*n
105
+ root[i] = 1
106
+ posroots[k] = root
107
+
108
+ return posroots
109
+
110
+ def roots(self):
111
+ """
112
+ Returns the total number of roots for B_n"
113
+ """
114
+
115
+ n = self.n
116
+ return 2*(n**2)
117
+
118
+ def cartan_matrix(self):
119
+ """
120
+ Returns the Cartan matrix for B_n.
121
+ The Cartan matrix matrix for a Lie algebra is
122
+ generated by assigning an ordering to the simple
123
+ roots, (alpha[1], ...., alpha[l]). Then the ijth
124
+ entry of the Cartan matrix is (<alpha[i],alpha[j]>).
125
+
126
+ Examples
127
+ ========
128
+
129
+ >>> from sympy.liealgebras.cartan_type import CartanType
130
+ >>> c = CartanType('B4')
131
+ >>> c.cartan_matrix()
132
+ Matrix([
133
+ [ 2, -1, 0, 0],
134
+ [-1, 2, -1, 0],
135
+ [ 0, -1, 2, -2],
136
+ [ 0, 0, -1, 2]])
137
+
138
+ """
139
+
140
+ n = self.n
141
+ m = 2* eye(n)
142
+ i = 1
143
+ while i < n-1:
144
+ m[i, i+1] = -1
145
+ m[i, i-1] = -1
146
+ i += 1
147
+ m[0, 1] = -1
148
+ m[n-2, n-1] = -2
149
+ m[n-1, n-2] = -1
150
+ return m
151
+
152
+ def basis(self):
153
+ """
154
+ Returns the number of independent generators of B_n
155
+ """
156
+
157
+ n = self.n
158
+ return (n**2 - n)/2
159
+
160
+ def lie_algebra(self):
161
+ """
162
+ Returns the Lie algebra associated with B_n
163
+ """
164
+
165
+ n = self.n
166
+ return "so(" + str(2*n) + ")"
167
+
168
+ def dynkin_diagram(self):
169
+ n = self.n
170
+ diag = "---".join("0" for i in range(1, n)) + "=>=0\n"
171
+ diag += " ".join(str(i) for i in range(1, n+1))
172
+ return diag
lib/python3.12/site-packages/sympy/liealgebras/type_c.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .cartan_type import Standard_Cartan
2
+ from sympy.core.backend import eye
3
+
4
+ class TypeC(Standard_Cartan):
5
+
6
+ def __new__(cls, n):
7
+ if n < 3:
8
+ raise ValueError("n cannot be less than 3")
9
+ return Standard_Cartan.__new__(cls, "C", n)
10
+
11
+
12
+ def dimension(self):
13
+ """Dimension of the vector space V underlying the Lie algebra
14
+
15
+ Examples
16
+ ========
17
+
18
+ >>> from sympy.liealgebras.cartan_type import CartanType
19
+ >>> c = CartanType("C3")
20
+ >>> c.dimension()
21
+ 3
22
+ """
23
+ n = self.n
24
+ return n
25
+
26
+ def basic_root(self, i, j):
27
+ """Generate roots with 1 in ith position and a -1 in jth position
28
+ """
29
+ n = self.n
30
+ root = [0]*n
31
+ root[i] = 1
32
+ root[j] = -1
33
+ return root
34
+
35
+ def simple_root(self, i):
36
+ """The ith simple root for the C series
37
+
38
+ Every lie algebra has a unique root system.
39
+ Given a root system Q, there is a subset of the
40
+ roots such that an element of Q is called a
41
+ simple root if it cannot be written as the sum
42
+ of two elements in Q. If we let D denote the
43
+ set of simple roots, then it is clear that every
44
+ element of Q can be written as a linear combination
45
+ of elements of D with all coefficients non-negative.
46
+
47
+ In C_n, the first n-1 simple roots are the same as
48
+ the roots in A_(n-1) (a 1 in the ith position, a -1
49
+ in the (i+1)th position, and zeroes elsewhere). The
50
+ nth simple root is the root in which there is a 2 in
51
+ the nth position and zeroes elsewhere.
52
+
53
+ Examples
54
+ ========
55
+
56
+ >>> from sympy.liealgebras.cartan_type import CartanType
57
+ >>> c = CartanType("C3")
58
+ >>> c.simple_root(2)
59
+ [0, 1, -1]
60
+
61
+ """
62
+
63
+ n = self.n
64
+ if i < n:
65
+ return self.basic_root(i-1,i)
66
+ else:
67
+ root = [0]*self.n
68
+ root[n-1] = 2
69
+ return root
70
+
71
+
72
+ def positive_roots(self):
73
+ """Generates all the positive roots of A_n
74
+
75
+ This is half of all of the roots of C_n; by multiplying all the
76
+ positive roots by -1 we get the negative roots.
77
+
78
+ Examples
79
+ ========
80
+
81
+ >>> from sympy.liealgebras.cartan_type import CartanType
82
+ >>> c = CartanType("A3")
83
+ >>> c.positive_roots()
84
+ {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], 3: [1, 0, 0, -1], 4: [0, 1, -1, 0],
85
+ 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]}
86
+
87
+ """
88
+
89
+ n = self.n
90
+ posroots = {}
91
+ k = 0
92
+ for i in range(0, n-1):
93
+ for j in range(i+1, n):
94
+ k += 1
95
+ posroots[k] = self.basic_root(i, j)
96
+ k += 1
97
+ root = self.basic_root(i, j)
98
+ root[j] = 1
99
+ posroots[k] = root
100
+
101
+ for i in range(0, n):
102
+ k += 1
103
+ root = [0]*n
104
+ root[i] = 2
105
+ posroots[k] = root
106
+
107
+ return posroots
108
+
109
+ def roots(self):
110
+ """
111
+ Returns the total number of roots for C_n"
112
+ """
113
+
114
+ n = self.n
115
+ return 2*(n**2)
116
+
117
+ def cartan_matrix(self):
118
+ """The Cartan matrix for C_n
119
+
120
+ The Cartan matrix matrix for a Lie algebra is
121
+ generated by assigning an ordering to the simple
122
+ roots, (alpha[1], ...., alpha[l]). Then the ijth
123
+ entry of the Cartan matrix is (<alpha[i],alpha[j]>).
124
+
125
+ Examples
126
+ ========
127
+
128
+ >>> from sympy.liealgebras.cartan_type import CartanType
129
+ >>> c = CartanType('C4')
130
+ >>> c.cartan_matrix()
131
+ Matrix([
132
+ [ 2, -1, 0, 0],
133
+ [-1, 2, -1, 0],
134
+ [ 0, -1, 2, -1],
135
+ [ 0, 0, -2, 2]])
136
+
137
+ """
138
+
139
+ n = self.n
140
+ m = 2 * eye(n)
141
+ i = 1
142
+ while i < n-1:
143
+ m[i, i+1] = -1
144
+ m[i, i-1] = -1
145
+ i += 1
146
+ m[0,1] = -1
147
+ m[n-1, n-2] = -2
148
+ return m
149
+
150
+
151
+ def basis(self):
152
+ """
153
+ Returns the number of independent generators of C_n
154
+ """
155
+
156
+ n = self.n
157
+ return n*(2*n + 1)
158
+
159
+ def lie_algebra(self):
160
+ """
161
+ Returns the Lie algebra associated with C_n"
162
+ """
163
+
164
+ n = self.n
165
+ return "sp(" + str(2*n) + ")"
166
+
167
+ def dynkin_diagram(self):
168
+ n = self.n
169
+ diag = "---".join("0" for i in range(1, n)) + "=<=0\n"
170
+ diag += " ".join(str(i) for i in range(1, n+1))
171
+ return diag
lib/python3.12/site-packages/sympy/liealgebras/type_d.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .cartan_type import Standard_Cartan
2
+ from sympy.core.backend import eye
3
+
4
+ class TypeD(Standard_Cartan):
5
+
6
+ def __new__(cls, n):
7
+ if n < 3:
8
+ raise ValueError("n cannot be less than 3")
9
+ return Standard_Cartan.__new__(cls, "D", n)
10
+
11
+
12
+ def dimension(self):
13
+ """Dmension of the vector space V underlying the Lie algebra
14
+
15
+ Examples
16
+ ========
17
+
18
+ >>> from sympy.liealgebras.cartan_type import CartanType
19
+ >>> c = CartanType("D4")
20
+ >>> c.dimension()
21
+ 4
22
+ """
23
+
24
+ return self.n
25
+
26
+ def basic_root(self, i, j):
27
+ """
28
+ This is a method just to generate roots
29
+ with a 1 iin the ith position and a -1
30
+ in the jth position.
31
+
32
+ """
33
+
34
+ n = self.n
35
+ root = [0]*n
36
+ root[i] = 1
37
+ root[j] = -1
38
+ return root
39
+
40
+ def simple_root(self, i):
41
+ """
42
+ Every lie algebra has a unique root system.
43
+ Given a root system Q, there is a subset of the
44
+ roots such that an element of Q is called a
45
+ simple root if it cannot be written as the sum
46
+ of two elements in Q. If we let D denote the
47
+ set of simple roots, then it is clear that every
48
+ element of Q can be written as a linear combination
49
+ of elements of D with all coefficients non-negative.
50
+
51
+ In D_n, the first n-1 simple roots are the same as
52
+ the roots in A_(n-1) (a 1 in the ith position, a -1
53
+ in the (i+1)th position, and zeroes elsewhere).
54
+ The nth simple root is the root in which there 1s in
55
+ the nth and (n-1)th positions, and zeroes elsewhere.
56
+
57
+ This method returns the ith simple root for the D series.
58
+
59
+ Examples
60
+ ========
61
+
62
+ >>> from sympy.liealgebras.cartan_type import CartanType
63
+ >>> c = CartanType("D4")
64
+ >>> c.simple_root(2)
65
+ [0, 1, -1, 0]
66
+
67
+ """
68
+
69
+ n = self.n
70
+ if i < n:
71
+ return self.basic_root(i-1, i)
72
+ else:
73
+ root = [0]*n
74
+ root[n-2] = 1
75
+ root[n-1] = 1
76
+ return root
77
+
78
+
79
+ def positive_roots(self):
80
+ """
81
+ This method generates all the positive roots of
82
+ A_n. This is half of all of the roots of D_n
83
+ by multiplying all the positive roots by -1 we
84
+ get the negative roots.
85
+
86
+ Examples
87
+ ========
88
+
89
+ >>> from sympy.liealgebras.cartan_type import CartanType
90
+ >>> c = CartanType("A3")
91
+ >>> c.positive_roots()
92
+ {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], 3: [1, 0, 0, -1], 4: [0, 1, -1, 0],
93
+ 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]}
94
+ """
95
+
96
+ n = self.n
97
+ posroots = {}
98
+ k = 0
99
+ for i in range(0, n-1):
100
+ for j in range(i+1, n):
101
+ k += 1
102
+ posroots[k] = self.basic_root(i, j)
103
+ k += 1
104
+ root = self.basic_root(i, j)
105
+ root[j] = 1
106
+ posroots[k] = root
107
+ return posroots
108
+
109
+ def roots(self):
110
+ """
111
+ Returns the total number of roots for D_n"
112
+ """
113
+
114
+ n = self.n
115
+ return 2*n*(n-1)
116
+
117
+ def cartan_matrix(self):
118
+ """
119
+ Returns the Cartan matrix for D_n.
120
+ The Cartan matrix matrix for a Lie algebra is
121
+ generated by assigning an ordering to the simple
122
+ roots, (alpha[1], ...., alpha[l]). Then the ijth
123
+ entry of the Cartan matrix is (<alpha[i],alpha[j]>).
124
+
125
+ Examples
126
+ ========
127
+
128
+ >>> from sympy.liealgebras.cartan_type import CartanType
129
+ >>> c = CartanType('D4')
130
+ >>> c.cartan_matrix()
131
+ Matrix([
132
+ [ 2, -1, 0, 0],
133
+ [-1, 2, -1, -1],
134
+ [ 0, -1, 2, 0],
135
+ [ 0, -1, 0, 2]])
136
+
137
+ """
138
+
139
+ n = self.n
140
+ m = 2*eye(n)
141
+ i = 1
142
+ while i < n-2:
143
+ m[i,i+1] = -1
144
+ m[i,i-1] = -1
145
+ i += 1
146
+ m[n-2, n-3] = -1
147
+ m[n-3, n-1] = -1
148
+ m[n-1, n-3] = -1
149
+ m[0, 1] = -1
150
+ return m
151
+
152
+ def basis(self):
153
+ """
154
+ Returns the number of independent generators of D_n
155
+ """
156
+ n = self.n
157
+ return n*(n-1)/2
158
+
159
+ def lie_algebra(self):
160
+ """
161
+ Returns the Lie algebra associated with D_n"
162
+ """
163
+
164
+ n = self.n
165
+ return "so(" + str(2*n) + ")"
166
+
167
+ def dynkin_diagram(self):
168
+ n = self.n
169
+ diag = " "*4*(n-3) + str(n-1) + "\n"
170
+ diag += " "*4*(n-3) + "0\n"
171
+ diag += " "*4*(n-3) +"|\n"
172
+ diag += " "*4*(n-3) + "|\n"
173
+ diag += "---".join("0" for i in range(1,n)) + "\n"
174
+ diag += " ".join(str(i) for i in range(1, n-1)) + " "+str(n)
175
+ return diag
lib/python3.12/site-packages/sympy/liealgebras/type_e.py ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .cartan_type import Standard_Cartan
2
+ from sympy.core.backend import eye, Rational
3
+
4
+
5
+ class TypeE(Standard_Cartan):
6
+
7
+ def __new__(cls, n):
8
+ if n < 6 or n > 8:
9
+ raise ValueError("Invalid value of n")
10
+ return Standard_Cartan.__new__(cls, "E", n)
11
+
12
+ def dimension(self):
13
+ """Dimension of the vector space V underlying the Lie algebra
14
+
15
+ Examples
16
+ ========
17
+
18
+ >>> from sympy.liealgebras.cartan_type import CartanType
19
+ >>> c = CartanType("E6")
20
+ >>> c.dimension()
21
+ 8
22
+ """
23
+
24
+ return 8
25
+
26
+ def basic_root(self, i, j):
27
+ """
28
+ This is a method just to generate roots
29
+ with a -1 in the ith position and a 1
30
+ in the jth position.
31
+
32
+ """
33
+
34
+ root = [0]*8
35
+ root[i] = -1
36
+ root[j] = 1
37
+ return root
38
+
39
+ def simple_root(self, i):
40
+ """
41
+ Every lie algebra has a unique root system.
42
+ Given a root system Q, there is a subset of the
43
+ roots such that an element of Q is called a
44
+ simple root if it cannot be written as the sum
45
+ of two elements in Q. If we let D denote the
46
+ set of simple roots, then it is clear that every
47
+ element of Q can be written as a linear combination
48
+ of elements of D with all coefficients non-negative.
49
+
50
+ This method returns the ith simple root for E_n.
51
+
52
+ Examples
53
+ ========
54
+
55
+ >>> from sympy.liealgebras.cartan_type import CartanType
56
+ >>> c = CartanType("E6")
57
+ >>> c.simple_root(2)
58
+ [1, 1, 0, 0, 0, 0, 0, 0]
59
+ """
60
+ n = self.n
61
+ if i == 1:
62
+ root = [-0.5]*8
63
+ root[0] = 0.5
64
+ root[7] = 0.5
65
+ return root
66
+ elif i == 2:
67
+ root = [0]*8
68
+ root[1] = 1
69
+ root[0] = 1
70
+ return root
71
+ else:
72
+ if i in (7, 8) and n == 6:
73
+ raise ValueError("E6 only has six simple roots!")
74
+ if i == 8 and n == 7:
75
+ raise ValueError("E7 has only 7 simple roots!")
76
+
77
+ return self.basic_root(i - 3, i - 2)
78
+
79
+ def positive_roots(self):
80
+ """
81
+ This method generates all the positive roots of
82
+ A_n. This is half of all of the roots of E_n;
83
+ by multiplying all the positive roots by -1 we
84
+ get the negative roots.
85
+
86
+ Examples
87
+ ========
88
+
89
+ >>> from sympy.liealgebras.cartan_type import CartanType
90
+ >>> c = CartanType("A3")
91
+ >>> c.positive_roots()
92
+ {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], 3: [1, 0, 0, -1], 4: [0, 1, -1, 0],
93
+ 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]}
94
+ """
95
+ n = self.n
96
+ if n == 6:
97
+ posroots = {}
98
+ k = 0
99
+ for i in range(n-1):
100
+ for j in range(i+1, n-1):
101
+ k += 1
102
+ root = self.basic_root(i, j)
103
+ posroots[k] = root
104
+ k += 1
105
+ root = self.basic_root(i, j)
106
+ root[i] = 1
107
+ posroots[k] = root
108
+
109
+ root = [Rational(1, 2), Rational(1, 2), Rational(1, 2), Rational(1, 2), Rational(1, 2),
110
+ Rational(-1, 2), Rational(-1, 2), Rational(1, 2)]
111
+ for a in range(0, 2):
112
+ for b in range(0, 2):
113
+ for c in range(0, 2):
114
+ for d in range(0, 2):
115
+ for e in range(0, 2):
116
+ if (a + b + c + d + e)%2 == 0:
117
+ k += 1
118
+ if a == 1:
119
+ root[0] = Rational(-1, 2)
120
+ if b == 1:
121
+ root[1] = Rational(-1, 2)
122
+ if c == 1:
123
+ root[2] = Rational(-1, 2)
124
+ if d == 1:
125
+ root[3] = Rational(-1, 2)
126
+ if e == 1:
127
+ root[4] = Rational(-1, 2)
128
+ posroots[k] = root
129
+
130
+ return posroots
131
+ if n == 7:
132
+ posroots = {}
133
+ k = 0
134
+ for i in range(n-1):
135
+ for j in range(i+1, n-1):
136
+ k += 1
137
+ root = self.basic_root(i, j)
138
+ posroots[k] = root
139
+ k += 1
140
+ root = self.basic_root(i, j)
141
+ root[i] = 1
142
+ posroots[k] = root
143
+
144
+ k += 1
145
+ posroots[k] = [0, 0, 0, 0, 0, 1, 1, 0]
146
+ root = [Rational(1, 2), Rational(1, 2), Rational(1, 2), Rational(1, 2), Rational(1, 2),
147
+ Rational(-1, 2), Rational(-1, 2), Rational(1, 2)]
148
+ for a in range(0, 2):
149
+ for b in range(0, 2):
150
+ for c in range(0, 2):
151
+ for d in range(0, 2):
152
+ for e in range(0, 2):
153
+ for f in range(0, 2):
154
+ if (a + b + c + d + e + f)%2 == 0:
155
+ k += 1
156
+ if a == 1:
157
+ root[0] = Rational(-1, 2)
158
+ if b == 1:
159
+ root[1] = Rational(-1, 2)
160
+ if c == 1:
161
+ root[2] = Rational(-1, 2)
162
+ if d == 1:
163
+ root[3] = Rational(-1, 2)
164
+ if e == 1:
165
+ root[4] = Rational(-1, 2)
166
+ if f == 1:
167
+ root[5] = Rational(1, 2)
168
+ posroots[k] = root
169
+
170
+ return posroots
171
+ if n == 8:
172
+ posroots = {}
173
+ k = 0
174
+ for i in range(n):
175
+ for j in range(i+1, n):
176
+ k += 1
177
+ root = self.basic_root(i, j)
178
+ posroots[k] = root
179
+ k += 1
180
+ root = self.basic_root(i, j)
181
+ root[i] = 1
182
+ posroots[k] = root
183
+
184
+ root = [Rational(1, 2), Rational(1, 2), Rational(1, 2), Rational(1, 2), Rational(1, 2),
185
+ Rational(-1, 2), Rational(-1, 2), Rational(1, 2)]
186
+ for a in range(0, 2):
187
+ for b in range(0, 2):
188
+ for c in range(0, 2):
189
+ for d in range(0, 2):
190
+ for e in range(0, 2):
191
+ for f in range(0, 2):
192
+ for g in range(0, 2):
193
+ if (a + b + c + d + e + f + g)%2 == 0:
194
+ k += 1
195
+ if a == 1:
196
+ root[0] = Rational(-1, 2)
197
+ if b == 1:
198
+ root[1] = Rational(-1, 2)
199
+ if c == 1:
200
+ root[2] = Rational(-1, 2)
201
+ if d == 1:
202
+ root[3] = Rational(-1, 2)
203
+ if e == 1:
204
+ root[4] = Rational(-1, 2)
205
+ if f == 1:
206
+ root[5] = Rational(1, 2)
207
+ if g == 1:
208
+ root[6] = Rational(1, 2)
209
+ posroots[k] = root
210
+
211
+ return posroots
212
+
213
+
214
+
215
+ def roots(self):
216
+ """
217
+ Returns the total number of roots of E_n
218
+ """
219
+
220
+ n = self.n
221
+ if n == 6:
222
+ return 72
223
+ if n == 7:
224
+ return 126
225
+ if n == 8:
226
+ return 240
227
+
228
+
229
+ def cartan_matrix(self):
230
+ """
231
+ Returns the Cartan matrix for G_2
232
+ The Cartan matrix matrix for a Lie algebra is
233
+ generated by assigning an ordering to the simple
234
+ roots, (alpha[1], ...., alpha[l]). Then the ijth
235
+ entry of the Cartan matrix is (<alpha[i],alpha[j]>).
236
+
237
+ Examples
238
+ ========
239
+
240
+ >>> from sympy.liealgebras.cartan_type import CartanType
241
+ >>> c = CartanType('A4')
242
+ >>> c.cartan_matrix()
243
+ Matrix([
244
+ [ 2, -1, 0, 0],
245
+ [-1, 2, -1, 0],
246
+ [ 0, -1, 2, -1],
247
+ [ 0, 0, -1, 2]])
248
+
249
+
250
+ """
251
+
252
+ n = self.n
253
+ m = 2*eye(n)
254
+ i = 3
255
+ while i < n-1:
256
+ m[i, i+1] = -1
257
+ m[i, i-1] = -1
258
+ i += 1
259
+ m[0, 2] = m[2, 0] = -1
260
+ m[1, 3] = m[3, 1] = -1
261
+ m[2, 3] = -1
262
+ m[n-1, n-2] = -1
263
+ return m
264
+
265
+
266
+ def basis(self):
267
+ """
268
+ Returns the number of independent generators of E_n
269
+ """
270
+
271
+ n = self.n
272
+ if n == 6:
273
+ return 78
274
+ if n == 7:
275
+ return 133
276
+ if n == 8:
277
+ return 248
278
+
279
+ def dynkin_diagram(self):
280
+ n = self.n
281
+ diag = " "*8 + str(2) + "\n"
282
+ diag += " "*8 + "0\n"
283
+ diag += " "*8 + "|\n"
284
+ diag += " "*8 + "|\n"
285
+ diag += "---".join("0" for i in range(1, n)) + "\n"
286
+ diag += "1 " + " ".join(str(i) for i in range(3, n+1))
287
+ return diag
lib/python3.12/site-packages/sympy/liealgebras/type_f.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .cartan_type import Standard_Cartan
2
+ from sympy.core.backend import Matrix, Rational
3
+
4
+
5
+ class TypeF(Standard_Cartan):
6
+
7
+ def __new__(cls, n):
8
+ if n != 4:
9
+ raise ValueError("n should be 4")
10
+ return Standard_Cartan.__new__(cls, "F", 4)
11
+
12
+ def dimension(self):
13
+ """Dimension of the vector space V underlying the Lie algebra
14
+
15
+ Examples
16
+ ========
17
+
18
+ >>> from sympy.liealgebras.cartan_type import CartanType
19
+ >>> c = CartanType("F4")
20
+ >>> c.dimension()
21
+ 4
22
+ """
23
+
24
+ return 4
25
+
26
+
27
+ def basic_root(self, i, j):
28
+ """Generate roots with 1 in ith position and -1 in jth position
29
+
30
+ """
31
+
32
+ n = self.n
33
+ root = [0]*n
34
+ root[i] = 1
35
+ root[j] = -1
36
+ return root
37
+
38
+ def simple_root(self, i):
39
+ """The ith simple root of F_4
40
+
41
+ Every lie algebra has a unique root system.
42
+ Given a root system Q, there is a subset of the
43
+ roots such that an element of Q is called a
44
+ simple root if it cannot be written as the sum
45
+ of two elements in Q. If we let D denote the
46
+ set of simple roots, then it is clear that every
47
+ element of Q can be written as a linear combination
48
+ of elements of D with all coefficients non-negative.
49
+
50
+ Examples
51
+ ========
52
+
53
+ >>> from sympy.liealgebras.cartan_type import CartanType
54
+ >>> c = CartanType("F4")
55
+ >>> c.simple_root(3)
56
+ [0, 0, 0, 1]
57
+
58
+ """
59
+
60
+ if i < 3:
61
+ return self.basic_root(i-1, i)
62
+ if i == 3:
63
+ root = [0]*4
64
+ root[3] = 1
65
+ return root
66
+ if i == 4:
67
+ root = [Rational(-1, 2)]*4
68
+ return root
69
+
70
+ def positive_roots(self):
71
+ """Generate all the positive roots of A_n
72
+
73
+ This is half of all of the roots of F_4; by multiplying all the
74
+ positive roots by -1 we get the negative roots.
75
+
76
+ Examples
77
+ ========
78
+
79
+ >>> from sympy.liealgebras.cartan_type import CartanType
80
+ >>> c = CartanType("A3")
81
+ >>> c.positive_roots()
82
+ {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], 3: [1, 0, 0, -1], 4: [0, 1, -1, 0],
83
+ 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]}
84
+
85
+ """
86
+
87
+ n = self.n
88
+ posroots = {}
89
+ k = 0
90
+ for i in range(0, n-1):
91
+ for j in range(i+1, n):
92
+ k += 1
93
+ posroots[k] = self.basic_root(i, j)
94
+ k += 1
95
+ root = self.basic_root(i, j)
96
+ root[j] = 1
97
+ posroots[k] = root
98
+
99
+ for i in range(0, n):
100
+ k += 1
101
+ root = [0]*n
102
+ root[i] = 1
103
+ posroots[k] = root
104
+
105
+ k += 1
106
+ root = [Rational(1, 2)]*n
107
+ posroots[k] = root
108
+ for i in range(1, 4):
109
+ k += 1
110
+ root = [Rational(1, 2)]*n
111
+ root[i] = Rational(-1, 2)
112
+ posroots[k] = root
113
+
114
+ posroots[k+1] = [Rational(1, 2), Rational(1, 2), Rational(-1, 2), Rational(-1, 2)]
115
+ posroots[k+2] = [Rational(1, 2), Rational(-1, 2), Rational(1, 2), Rational(-1, 2)]
116
+ posroots[k+3] = [Rational(1, 2), Rational(-1, 2), Rational(-1, 2), Rational(1, 2)]
117
+ posroots[k+4] = [Rational(1, 2), Rational(-1, 2), Rational(-1, 2), Rational(-1, 2)]
118
+
119
+ return posroots
120
+
121
+
122
+ def roots(self):
123
+ """
124
+ Returns the total number of roots for F_4
125
+ """
126
+ return 48
127
+
128
+ def cartan_matrix(self):
129
+ """The Cartan matrix for F_4
130
+
131
+ The Cartan matrix matrix for a Lie algebra is
132
+ generated by assigning an ordering to the simple
133
+ roots, (alpha[1], ...., alpha[l]). Then the ijth
134
+ entry of the Cartan matrix is (<alpha[i],alpha[j]>).
135
+
136
+ Examples
137
+ ========
138
+
139
+ >>> from sympy.liealgebras.cartan_type import CartanType
140
+ >>> c = CartanType('A4')
141
+ >>> c.cartan_matrix()
142
+ Matrix([
143
+ [ 2, -1, 0, 0],
144
+ [-1, 2, -1, 0],
145
+ [ 0, -1, 2, -1],
146
+ [ 0, 0, -1, 2]])
147
+ """
148
+
149
+ m = Matrix( 4, 4, [2, -1, 0, 0, -1, 2, -2, 0, 0,
150
+ -1, 2, -1, 0, 0, -1, 2])
151
+ return m
152
+
153
+ def basis(self):
154
+ """
155
+ Returns the number of independent generators of F_4
156
+ """
157
+ return 52
158
+
159
+ def dynkin_diagram(self):
160
+ diag = "0---0=>=0---0\n"
161
+ diag += " ".join(str(i) for i in range(1, 5))
162
+ return diag
lib/python3.12/site-packages/sympy/liealgebras/type_g.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from .cartan_type import Standard_Cartan
4
+ from sympy.core.backend import Matrix
5
+
6
+ class TypeG(Standard_Cartan):
7
+
8
+ def __new__(cls, n):
9
+ if n != 2:
10
+ raise ValueError("n should be 2")
11
+ return Standard_Cartan.__new__(cls, "G", 2)
12
+
13
+
14
+ def dimension(self):
15
+ """Dimension of the vector space V underlying the Lie algebra
16
+
17
+ Examples
18
+ ========
19
+
20
+ >>> from sympy.liealgebras.cartan_type import CartanType
21
+ >>> c = CartanType("G2")
22
+ >>> c.dimension()
23
+ 3
24
+ """
25
+ return 3
26
+
27
+ def simple_root(self, i):
28
+ """The ith simple root of G_2
29
+
30
+ Every lie algebra has a unique root system.
31
+ Given a root system Q, there is a subset of the
32
+ roots such that an element of Q is called a
33
+ simple root if it cannot be written as the sum
34
+ of two elements in Q. If we let D denote the
35
+ set of simple roots, then it is clear that every
36
+ element of Q can be written as a linear combination
37
+ of elements of D with all coefficients non-negative.
38
+
39
+ Examples
40
+ ========
41
+
42
+ >>> from sympy.liealgebras.cartan_type import CartanType
43
+ >>> c = CartanType("G2")
44
+ >>> c.simple_root(1)
45
+ [0, 1, -1]
46
+
47
+ """
48
+ if i == 1:
49
+ return [0, 1, -1]
50
+ else:
51
+ return [1, -2, 1]
52
+
53
+ def positive_roots(self):
54
+ """Generate all the positive roots of A_n
55
+
56
+ This is half of all of the roots of A_n; by multiplying all the
57
+ positive roots by -1 we get the negative roots.
58
+
59
+ Examples
60
+ ========
61
+
62
+ >>> from sympy.liealgebras.cartan_type import CartanType
63
+ >>> c = CartanType("A3")
64
+ >>> c.positive_roots()
65
+ {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], 3: [1, 0, 0, -1], 4: [0, 1, -1, 0],
66
+ 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]}
67
+
68
+ """
69
+
70
+ roots = {1: [0, 1, -1], 2: [1, -2, 1], 3: [1, -1, 0], 4: [1, 0, 1],
71
+ 5: [1, 1, -2], 6: [2, -1, -1]}
72
+ return roots
73
+
74
+ def roots(self):
75
+ """
76
+ Returns the total number of roots of G_2"
77
+ """
78
+ return 12
79
+
80
+ def cartan_matrix(self):
81
+ """The Cartan matrix for G_2
82
+
83
+ The Cartan matrix matrix for a Lie algebra is
84
+ generated by assigning an ordering to the simple
85
+ roots, (alpha[1], ...., alpha[l]). Then the ijth
86
+ entry of the Cartan matrix is (<alpha[i],alpha[j]>).
87
+
88
+ Examples
89
+ ========
90
+
91
+ >>> from sympy.liealgebras.cartan_type import CartanType
92
+ >>> c = CartanType("G2")
93
+ >>> c.cartan_matrix()
94
+ Matrix([
95
+ [ 2, -1],
96
+ [-3, 2]])
97
+
98
+ """
99
+
100
+ m = Matrix( 2, 2, [2, -1, -3, 2])
101
+ return m
102
+
103
+ def basis(self):
104
+ """
105
+ Returns the number of independent generators of G_2
106
+ """
107
+ return 14
108
+
109
+ def dynkin_diagram(self):
110
+ diag = "0≡<≡0\n1 2"
111
+ return diag
lib/python3.12/site-packages/sympy/liealgebras/weyl_group.py ADDED
@@ -0,0 +1,403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from .cartan_type import CartanType
4
+ from mpmath import fac
5
+ from sympy.core.backend import Matrix, eye, Rational, igcd
6
+ from sympy.core.basic import Atom
7
+
8
+ class WeylGroup(Atom):
9
+
10
+ """
11
+ For each semisimple Lie group, we have a Weyl group. It is a subgroup of
12
+ the isometry group of the root system. Specifically, it's the subgroup
13
+ that is generated by reflections through the hyperplanes orthogonal to
14
+ the roots. Therefore, Weyl groups are reflection groups, and so a Weyl
15
+ group is a finite Coxeter group.
16
+
17
+ """
18
+
19
+ def __new__(cls, cartantype):
20
+ obj = Atom.__new__(cls)
21
+ obj.cartan_type = CartanType(cartantype)
22
+ return obj
23
+
24
+ def generators(self):
25
+ """
26
+ This method creates the generating reflections of the Weyl group for
27
+ a given Lie algebra. For a Lie algebra of rank n, there are n
28
+ different generating reflections. This function returns them as
29
+ a list.
30
+
31
+ Examples
32
+ ========
33
+
34
+ >>> from sympy.liealgebras.weyl_group import WeylGroup
35
+ >>> c = WeylGroup("F4")
36
+ >>> c.generators()
37
+ ['r1', 'r2', 'r3', 'r4']
38
+ """
39
+ n = self.cartan_type.rank()
40
+ generators = []
41
+ for i in range(1, n+1):
42
+ reflection = "r"+str(i)
43
+ generators.append(reflection)
44
+ return generators
45
+
46
+ def group_order(self):
47
+ """
48
+ This method returns the order of the Weyl group.
49
+ For types A, B, C, D, and E the order depends on
50
+ the rank of the Lie algebra. For types F and G,
51
+ the order is fixed.
52
+
53
+ Examples
54
+ ========
55
+
56
+ >>> from sympy.liealgebras.weyl_group import WeylGroup
57
+ >>> c = WeylGroup("D4")
58
+ >>> c.group_order()
59
+ 192.0
60
+ """
61
+ n = self.cartan_type.rank()
62
+ if self.cartan_type.series == "A":
63
+ return fac(n+1)
64
+
65
+ if self.cartan_type.series in ("B", "C"):
66
+ return fac(n)*(2**n)
67
+
68
+ if self.cartan_type.series == "D":
69
+ return fac(n)*(2**(n-1))
70
+
71
+ if self.cartan_type.series == "E":
72
+ if n == 6:
73
+ return 51840
74
+ if n == 7:
75
+ return 2903040
76
+ if n == 8:
77
+ return 696729600
78
+ if self.cartan_type.series == "F":
79
+ return 1152
80
+
81
+ if self.cartan_type.series == "G":
82
+ return 12
83
+
84
+ def group_name(self):
85
+ """
86
+ This method returns some general information about the Weyl group for
87
+ a given Lie algebra. It returns the name of the group and the elements
88
+ it acts on, if relevant.
89
+ """
90
+ n = self.cartan_type.rank()
91
+ if self.cartan_type.series == "A":
92
+ return "S"+str(n+1) + ": the symmetric group acting on " + str(n+1) + " elements."
93
+
94
+ if self.cartan_type.series in ("B", "C"):
95
+ return "The hyperoctahedral group acting on " + str(2*n) + " elements."
96
+
97
+ if self.cartan_type.series == "D":
98
+ return "The symmetry group of the " + str(n) + "-dimensional demihypercube."
99
+
100
+ if self.cartan_type.series == "E":
101
+ if n == 6:
102
+ return "The symmetry group of the 6-polytope."
103
+
104
+ if n == 7:
105
+ return "The symmetry group of the 7-polytope."
106
+
107
+ if n == 8:
108
+ return "The symmetry group of the 8-polytope."
109
+
110
+ if self.cartan_type.series == "F":
111
+ return "The symmetry group of the 24-cell, or icositetrachoron."
112
+
113
+ if self.cartan_type.series == "G":
114
+ return "D6, the dihedral group of order 12, and symmetry group of the hexagon."
115
+
116
+ def element_order(self, weylelt):
117
+ """
118
+ This method returns the order of a given Weyl group element, which should
119
+ be specified by the user in the form of products of the generating
120
+ reflections, i.e. of the form r1*r2 etc.
121
+
122
+ For types A-F, this method current works by taking the matrix form of
123
+ the specified element, and then finding what power of the matrix is the
124
+ identity. It then returns this power.
125
+
126
+ Examples
127
+ ========
128
+
129
+ >>> from sympy.liealgebras.weyl_group import WeylGroup
130
+ >>> b = WeylGroup("B4")
131
+ >>> b.element_order('r1*r4*r2')
132
+ 4
133
+ """
134
+ n = self.cartan_type.rank()
135
+ if self.cartan_type.series == "A":
136
+ a = self.matrix_form(weylelt)
137
+ order = 1
138
+ while a != eye(n+1):
139
+ a *= self.matrix_form(weylelt)
140
+ order += 1
141
+ return order
142
+
143
+ if self.cartan_type.series == "D":
144
+ a = self.matrix_form(weylelt)
145
+ order = 1
146
+ while a != eye(n):
147
+ a *= self.matrix_form(weylelt)
148
+ order += 1
149
+ return order
150
+
151
+ if self.cartan_type.series == "E":
152
+ a = self.matrix_form(weylelt)
153
+ order = 1
154
+ while a != eye(8):
155
+ a *= self.matrix_form(weylelt)
156
+ order += 1
157
+ return order
158
+
159
+ if self.cartan_type.series == "G":
160
+ elts = list(weylelt)
161
+ reflections = elts[1::3]
162
+ m = self.delete_doubles(reflections)
163
+ while self.delete_doubles(m) != m:
164
+ m = self.delete_doubles(m)
165
+ reflections = m
166
+ if len(reflections) % 2 == 1:
167
+ return 2
168
+
169
+ elif len(reflections) == 0:
170
+ return 1
171
+
172
+ else:
173
+ if len(reflections) == 1:
174
+ return 2
175
+ else:
176
+ m = len(reflections) // 2
177
+ lcm = (6 * m)/ igcd(m, 6)
178
+ order = lcm / m
179
+ return order
180
+
181
+
182
+ if self.cartan_type.series == 'F':
183
+ a = self.matrix_form(weylelt)
184
+ order = 1
185
+ while a != eye(4):
186
+ a *= self.matrix_form(weylelt)
187
+ order += 1
188
+ return order
189
+
190
+
191
+ if self.cartan_type.series in ("B", "C"):
192
+ a = self.matrix_form(weylelt)
193
+ order = 1
194
+ while a != eye(n):
195
+ a *= self.matrix_form(weylelt)
196
+ order += 1
197
+ return order
198
+
199
+ def delete_doubles(self, reflections):
200
+ """
201
+ This is a helper method for determining the order of an element in the
202
+ Weyl group of G2. It takes a Weyl element and if repeated simple reflections
203
+ in it, it deletes them.
204
+ """
205
+ counter = 0
206
+ copy = list(reflections)
207
+ for elt in copy:
208
+ if counter < len(copy)-1:
209
+ if copy[counter + 1] == elt:
210
+ del copy[counter]
211
+ del copy[counter]
212
+ counter += 1
213
+
214
+
215
+ return copy
216
+
217
+
218
+ def matrix_form(self, weylelt):
219
+ """
220
+ This method takes input from the user in the form of products of the
221
+ generating reflections, and returns the matrix corresponding to the
222
+ element of the Weyl group. Since each element of the Weyl group is
223
+ a reflection of some type, there is a corresponding matrix representation.
224
+ This method uses the standard representation for all the generating
225
+ reflections.
226
+
227
+ Examples
228
+ ========
229
+
230
+ >>> from sympy.liealgebras.weyl_group import WeylGroup
231
+ >>> f = WeylGroup("F4")
232
+ >>> f.matrix_form('r2*r3')
233
+ Matrix([
234
+ [1, 0, 0, 0],
235
+ [0, 1, 0, 0],
236
+ [0, 0, 0, -1],
237
+ [0, 0, 1, 0]])
238
+
239
+ """
240
+ elts = list(weylelt)
241
+ reflections = elts[1::3]
242
+ n = self.cartan_type.rank()
243
+ if self.cartan_type.series == 'A':
244
+ matrixform = eye(n+1)
245
+ for elt in reflections:
246
+ a = int(elt)
247
+ mat = eye(n+1)
248
+ mat[a-1, a-1] = 0
249
+ mat[a-1, a] = 1
250
+ mat[a, a-1] = 1
251
+ mat[a, a] = 0
252
+ matrixform *= mat
253
+ return matrixform
254
+
255
+ if self.cartan_type.series == 'D':
256
+ matrixform = eye(n)
257
+ for elt in reflections:
258
+ a = int(elt)
259
+ mat = eye(n)
260
+ if a < n:
261
+ mat[a-1, a-1] = 0
262
+ mat[a-1, a] = 1
263
+ mat[a, a-1] = 1
264
+ mat[a, a] = 0
265
+ matrixform *= mat
266
+ else:
267
+ mat[n-2, n-1] = -1
268
+ mat[n-2, n-2] = 0
269
+ mat[n-1, n-2] = -1
270
+ mat[n-1, n-1] = 0
271
+ matrixform *= mat
272
+ return matrixform
273
+
274
+ if self.cartan_type.series == 'G':
275
+ matrixform = eye(3)
276
+ for elt in reflections:
277
+ a = int(elt)
278
+ if a == 1:
279
+ gen1 = Matrix([[1, 0, 0], [0, 0, 1], [0, 1, 0]])
280
+ matrixform *= gen1
281
+ else:
282
+ gen2 = Matrix([[Rational(2, 3), Rational(2, 3), Rational(-1, 3)],
283
+ [Rational(2, 3), Rational(-1, 3), Rational(2, 3)],
284
+ [Rational(-1, 3), Rational(2, 3), Rational(2, 3)]])
285
+ matrixform *= gen2
286
+ return matrixform
287
+
288
+ if self.cartan_type.series == 'F':
289
+ matrixform = eye(4)
290
+ for elt in reflections:
291
+ a = int(elt)
292
+ if a == 1:
293
+ mat = Matrix([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]])
294
+ matrixform *= mat
295
+ elif a == 2:
296
+ mat = Matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])
297
+ matrixform *= mat
298
+ elif a == 3:
299
+ mat = Matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]])
300
+ matrixform *= mat
301
+ else:
302
+
303
+ mat = Matrix([[Rational(1, 2), Rational(1, 2), Rational(1, 2), Rational(1, 2)],
304
+ [Rational(1, 2), Rational(1, 2), Rational(-1, 2), Rational(-1, 2)],
305
+ [Rational(1, 2), Rational(-1, 2), Rational(1, 2), Rational(-1, 2)],
306
+ [Rational(1, 2), Rational(-1, 2), Rational(-1, 2), Rational(1, 2)]])
307
+ matrixform *= mat
308
+ return matrixform
309
+
310
+ if self.cartan_type.series == 'E':
311
+ matrixform = eye(8)
312
+ for elt in reflections:
313
+ a = int(elt)
314
+ if a == 1:
315
+ mat = Matrix([[Rational(3, 4), Rational(1, 4), Rational(1, 4), Rational(1, 4),
316
+ Rational(1, 4), Rational(1, 4), Rational(1, 4), Rational(-1, 4)],
317
+ [Rational(1, 4), Rational(3, 4), Rational(-1, 4), Rational(-1, 4),
318
+ Rational(-1, 4), Rational(-1, 4), Rational(1, 4), Rational(-1, 4)],
319
+ [Rational(1, 4), Rational(-1, 4), Rational(3, 4), Rational(-1, 4),
320
+ Rational(-1, 4), Rational(-1, 4), Rational(-1, 4), Rational(1, 4)],
321
+ [Rational(1, 4), Rational(-1, 4), Rational(-1, 4), Rational(3, 4),
322
+ Rational(-1, 4), Rational(-1, 4), Rational(-1, 4), Rational(1, 4)],
323
+ [Rational(1, 4), Rational(-1, 4), Rational(-1, 4), Rational(-1, 4),
324
+ Rational(3, 4), Rational(-1, 4), Rational(-1, 4), Rational(1, 4)],
325
+ [Rational(1, 4), Rational(-1, 4), Rational(-1, 4), Rational(-1, 4),
326
+ Rational(-1, 4), Rational(3, 4), Rational(-1, 4), Rational(1, 4)],
327
+ [Rational(1, 4), Rational(-1, 4), Rational(-1, 4), Rational(-1, 4),
328
+ Rational(-1, 4), Rational(-1, 4), Rational(-3, 4), Rational(1, 4)],
329
+ [Rational(1, 4), Rational(-1, 4), Rational(-1, 4), Rational(-1, 4),
330
+ Rational(-1, 4), Rational(-1, 4), Rational(-1, 4), Rational(3, 4)]])
331
+ matrixform *= mat
332
+ elif a == 2:
333
+ mat = eye(8)
334
+ mat[0, 0] = 0
335
+ mat[0, 1] = -1
336
+ mat[1, 0] = -1
337
+ mat[1, 1] = 0
338
+ matrixform *= mat
339
+ else:
340
+ mat = eye(8)
341
+ mat[a-3, a-3] = 0
342
+ mat[a-3, a-2] = 1
343
+ mat[a-2, a-3] = 1
344
+ mat[a-2, a-2] = 0
345
+ matrixform *= mat
346
+ return matrixform
347
+
348
+
349
+ if self.cartan_type.series in ("B", "C"):
350
+ matrixform = eye(n)
351
+ for elt in reflections:
352
+ a = int(elt)
353
+ mat = eye(n)
354
+ if a == 1:
355
+ mat[0, 0] = -1
356
+ matrixform *= mat
357
+ else:
358
+ mat[a - 2, a - 2] = 0
359
+ mat[a-2, a-1] = 1
360
+ mat[a - 1, a - 2] = 1
361
+ mat[a -1, a - 1] = 0
362
+ matrixform *= mat
363
+ return matrixform
364
+
365
+
366
+
367
+ def coxeter_diagram(self):
368
+ """
369
+ This method returns the Coxeter diagram corresponding to a Weyl group.
370
+ The Coxeter diagram can be obtained from a Lie algebra's Dynkin diagram
371
+ by deleting all arrows; the Coxeter diagram is the undirected graph.
372
+ The vertices of the Coxeter diagram represent the generating reflections
373
+ of the Weyl group, $s_i$. An edge is drawn between $s_i$ and $s_j$ if the order
374
+ $m(i, j)$ of $s_is_j$ is greater than two. If there is one edge, the order
375
+ $m(i, j)$ is 3. If there are two edges, the order $m(i, j)$ is 4, and if there
376
+ are three edges, the order $m(i, j)$ is 6.
377
+
378
+ Examples
379
+ ========
380
+
381
+ >>> from sympy.liealgebras.weyl_group import WeylGroup
382
+ >>> c = WeylGroup("B3")
383
+ >>> print(c.coxeter_diagram())
384
+ 0---0===0
385
+ 1 2 3
386
+ """
387
+ n = self.cartan_type.rank()
388
+ if self.cartan_type.series in ("A", "D", "E"):
389
+ return self.cartan_type.dynkin_diagram()
390
+
391
+ if self.cartan_type.series in ("B", "C"):
392
+ diag = "---".join("0" for i in range(1, n)) + "===0\n"
393
+ diag += " ".join(str(i) for i in range(1, n+1))
394
+ return diag
395
+
396
+ if self.cartan_type.series == "F":
397
+ diag = "0---0===0---0\n"
398
+ diag += " ".join(str(i) for i in range(1, 5))
399
+ return diag
400
+
401
+ if self.cartan_type.series == "G":
402
+ diag = "0≡≡≡0\n1 2"
403
+ return diag
lib/python3.12/site-packages/sympy/simplify/__pycache__/cse_main.cpython-312.pyc ADDED
Binary file (35.8 kB). View file
 
lib/python3.12/site-packages/sympy/simplify/__pycache__/epathtools.cpython-312.pyc ADDED
Binary file (11.2 kB). View file
 
lib/python3.12/site-packages/sympy/simplify/__pycache__/fu.cpython-312.pyc ADDED
Binary file (81.4 kB). View file