Commutation 1.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- commutation/__init__.py +5 -0
- commutation/commutatoralgebra.py +248 -0
- commutation/expression.py +592 -0
- commutation/style.py +30 -0
- commutation-1.5.0.dist-info/METADATA +101 -0
- commutation-1.5.0.dist-info/RECORD +9 -0
- commutation-1.5.0.dist-info/WHEEL +5 -0
- commutation-1.5.0.dist-info/licenses/LICENSE +675 -0
- commutation-1.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
from fractions import Fraction
|
|
2
|
+
import copy as cp
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Operator(object):
|
|
6
|
+
"""Represents (non-commutative) symbols.
|
|
7
|
+
Terms are built from lists of Operators.
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, name, latex_string=None, scalar=False):
|
|
12
|
+
"""Operator constructor
|
|
13
|
+
name -> display name, result of str(Operator),
|
|
14
|
+
used for keys in CommutatorAlgebra
|
|
15
|
+
latex_string -> allows fancier formatting for as_latex() methods in
|
|
16
|
+
enclosing classes, defaults to name if not provided.
|
|
17
|
+
is_scalar -> Flags whether CommutatorAlgebra should treat this as a scalar
|
|
18
|
+
"""
|
|
19
|
+
self.is_scalar = scalar
|
|
20
|
+
if type(name) is not str:
|
|
21
|
+
raise TypeError('Names must be str')
|
|
22
|
+
if latex_string is None:
|
|
23
|
+
latex_string = name
|
|
24
|
+
elif type(latex_string) is not str:
|
|
25
|
+
raise TypeError('Latex strings must be str')
|
|
26
|
+
self.name = name
|
|
27
|
+
self.latex_string = latex_string
|
|
28
|
+
|
|
29
|
+
def __mul__(self, other):
|
|
30
|
+
if isinstance(other, (int, Fraction, Operator)):
|
|
31
|
+
return Term(self, other)
|
|
32
|
+
else:
|
|
33
|
+
return NotImplemented
|
|
34
|
+
|
|
35
|
+
def __rmul__(self, other):
|
|
36
|
+
if type(other) in (int, Fraction):
|
|
37
|
+
return Term(other, self)
|
|
38
|
+
return NotImplemented
|
|
39
|
+
|
|
40
|
+
def __add__(self, other):
|
|
41
|
+
return Expression(self, other)
|
|
42
|
+
|
|
43
|
+
def __radd__(self, other):
|
|
44
|
+
return Expression(other, self)
|
|
45
|
+
|
|
46
|
+
def __sub__(self, other):
|
|
47
|
+
return self + (other*-1)
|
|
48
|
+
|
|
49
|
+
def __rsub__(self, other):
|
|
50
|
+
return other + (-1*self)
|
|
51
|
+
|
|
52
|
+
def __str__(self):
|
|
53
|
+
return self.name
|
|
54
|
+
|
|
55
|
+
def __repr__(self):
|
|
56
|
+
return self.name
|
|
57
|
+
|
|
58
|
+
def as_latex(self):
|
|
59
|
+
return self.latex_string
|
|
60
|
+
|
|
61
|
+
def __eq__(self, other):
|
|
62
|
+
if isinstance(other, (Operator, Term, Expression)):
|
|
63
|
+
return self + other*-1 == 0
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
def __neg__(self):
|
|
67
|
+
t = Term(self)
|
|
68
|
+
t.multiplier = -t.multiplier
|
|
69
|
+
return t
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# trivial overload
|
|
74
|
+
class Scalar(Operator):
|
|
75
|
+
def __init__(self, name, latex_string=None):
|
|
76
|
+
super().__init__(name, latex_string, scalar=True)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Term(object):
|
|
80
|
+
"""Terms should be read as (Fraction * Term1*Terms2*...).
|
|
81
|
+
These implement a noncommutative monoid structure for Term.
|
|
82
|
+
|
|
83
|
+
Term.multiplier -> a Fraction, which absorbs scalar multiples of Terms.
|
|
84
|
+
Term.ops -> a list of Operators, which are understood as producted together.
|
|
85
|
+
"1" is written as [].
|
|
86
|
+
These should only ever be shallow-copied: want to retain ability to tune Operator objects on the fly
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, *variables):
|
|
90
|
+
"""Term constructor
|
|
91
|
+
Usage: Term(x1, x2, x3, ...)
|
|
92
|
+
xi can be Operator, Fraction or int. These are all producted together.
|
|
93
|
+
"""
|
|
94
|
+
self.multiplier = Fraction(1, 1) # rational numbers!
|
|
95
|
+
self.ops = []
|
|
96
|
+
|
|
97
|
+
for t in variables:
|
|
98
|
+
if isinstance(t, Term):
|
|
99
|
+
self.ops += t.ops
|
|
100
|
+
self.multiplier *= t.multiplier
|
|
101
|
+
elif isinstance(t, Operator):
|
|
102
|
+
self.ops.append(t)
|
|
103
|
+
elif type(t) in [int, Fraction]:
|
|
104
|
+
self.multiplier *= t
|
|
105
|
+
else:
|
|
106
|
+
raise TypeError('cannot initialise Term from ' + str(type(t)))
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def is_scalar(self):
|
|
110
|
+
for o in self.ops:
|
|
111
|
+
if not o.is_scalar:
|
|
112
|
+
return False
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
def from_str(self, s):
|
|
116
|
+
# cursed parser code, a problem for another day!
|
|
117
|
+
raise NotImplementedError
|
|
118
|
+
|
|
119
|
+
def factor_scalars(self):
|
|
120
|
+
scalars = []
|
|
121
|
+
ops = []
|
|
122
|
+
for o in self.ops:
|
|
123
|
+
if o.is_scalar:
|
|
124
|
+
scalars.append(o)
|
|
125
|
+
else:
|
|
126
|
+
ops.append(o)
|
|
127
|
+
return Term(*scalars)*self.multiplier, Term(*ops)
|
|
128
|
+
|
|
129
|
+
def move_scalars(self, side='left'):
|
|
130
|
+
scalars = []
|
|
131
|
+
ops = []
|
|
132
|
+
for o in self.ops:
|
|
133
|
+
if o.is_scalar:
|
|
134
|
+
scalars.append(o)
|
|
135
|
+
else:
|
|
136
|
+
ops.append(o)
|
|
137
|
+
|
|
138
|
+
if side in ['l', 'left']:
|
|
139
|
+
self.ops = scalars + ops
|
|
140
|
+
elif side in ['r', 'right']:
|
|
141
|
+
self.ops = ops + scalars
|
|
142
|
+
else:
|
|
143
|
+
raise IndexError("Side must be one of 'l', 'r', 'left', 'right'")
|
|
144
|
+
|
|
145
|
+
def __len__(self):
|
|
146
|
+
return len(self.ops)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def __repr__(self):
|
|
150
|
+
if self.multiplier > 0:
|
|
151
|
+
s = '+'+str(self.multiplier)
|
|
152
|
+
else:
|
|
153
|
+
s = str(self.multiplier)
|
|
154
|
+
for op in self.ops:
|
|
155
|
+
s += ' '+str(op)
|
|
156
|
+
return s
|
|
157
|
+
|
|
158
|
+
def __str__(self):
|
|
159
|
+
return self.__repr__()
|
|
160
|
+
|
|
161
|
+
def as_latex(self):
|
|
162
|
+
if self.multiplier.denominator == 1:
|
|
163
|
+
s = '%+d' % self.multiplier.numerator
|
|
164
|
+
else:
|
|
165
|
+
s = '+' if self.multiplier >= 0 else '-'
|
|
166
|
+
s += r'\frac{%d}{%d}' % (abs(self.multiplier.numerator),
|
|
167
|
+
self.multiplier.denominator)
|
|
168
|
+
|
|
169
|
+
for o in self.ops:
|
|
170
|
+
s += ' ' + o.latex_string
|
|
171
|
+
return s
|
|
172
|
+
|
|
173
|
+
def __neg__(self):
|
|
174
|
+
t = cp.copy(self)
|
|
175
|
+
t.multiplier = -t.multiplier
|
|
176
|
+
return t
|
|
177
|
+
|
|
178
|
+
def __add__(self, other):
|
|
179
|
+
retval = Expression()
|
|
180
|
+
retval += self
|
|
181
|
+
retval += other
|
|
182
|
+
return retval
|
|
183
|
+
|
|
184
|
+
def __radd__(self, other):
|
|
185
|
+
retval = Expression()
|
|
186
|
+
retval += other
|
|
187
|
+
retval += self
|
|
188
|
+
return retval
|
|
189
|
+
|
|
190
|
+
def __sub__(self, other):
|
|
191
|
+
return self + (other*-1)
|
|
192
|
+
|
|
193
|
+
def __rsub__(self, other):
|
|
194
|
+
return (other*-1) + self
|
|
195
|
+
|
|
196
|
+
def __mul__(self, other):
|
|
197
|
+
copy = cp.copy(self)
|
|
198
|
+
if type(other) in (int, Fraction):
|
|
199
|
+
copy.multiplier *= Fraction(other)
|
|
200
|
+
return copy
|
|
201
|
+
elif isinstance(other, Operator):
|
|
202
|
+
copy.ops = copy.ops + [other]
|
|
203
|
+
return copy
|
|
204
|
+
elif isinstance(other, Term):
|
|
205
|
+
copy.multiplier *= other.multiplier
|
|
206
|
+
copy.ops = copy.ops + other.ops
|
|
207
|
+
return copy
|
|
208
|
+
else:
|
|
209
|
+
return NotImplemented
|
|
210
|
+
|
|
211
|
+
def __truediv__(self, other):
|
|
212
|
+
copy = cp.copy(self)
|
|
213
|
+
if type(other) in (int, Fraction):
|
|
214
|
+
copy.multiplier /= Fraction(other)
|
|
215
|
+
return copy
|
|
216
|
+
else:
|
|
217
|
+
return NotImplemented
|
|
218
|
+
|
|
219
|
+
def __rmul__(self, other):
|
|
220
|
+
copy = cp.copy(self)
|
|
221
|
+
if type(other) in [int, Fraction]:
|
|
222
|
+
copy.multiplier *= other
|
|
223
|
+
return copy
|
|
224
|
+
elif isinstance(other, Operator):
|
|
225
|
+
copy.ops = [other] + copy.ops
|
|
226
|
+
return copy
|
|
227
|
+
else:
|
|
228
|
+
return NotImplemented
|
|
229
|
+
|
|
230
|
+
def findall(self, glob):
|
|
231
|
+
"""Finds all instances of subterm `glob` in the present operator product.
|
|
232
|
+
This returns a list of indices [i1, i2, ...] such that
|
|
233
|
+
self.ops[i1:i1+len(glob)] == glob.ops
|
|
234
|
+
Collisions in are ignored - e.g.
|
|
235
|
+
aaa.findall(aa) -> [0]
|
|
236
|
+
aaaa.findall(aa) -> [0,2]
|
|
237
|
+
"""
|
|
238
|
+
if not isinstance(glob, Term):
|
|
239
|
+
glob = Term(glob)
|
|
240
|
+
hits = []
|
|
241
|
+
i = 0
|
|
242
|
+
N = len(glob.ops)
|
|
243
|
+
while (i < len(self.ops)-N+1):
|
|
244
|
+
if self.ops[i:i+N] == glob.ops:
|
|
245
|
+
hits.append(i)
|
|
246
|
+
# skip duplicates when we have e.g. aaaaaa.find(aa)
|
|
247
|
+
i += N-1
|
|
248
|
+
i += 1
|
|
249
|
+
return hits
|
|
250
|
+
|
|
251
|
+
@property
|
|
252
|
+
def sign(self):
|
|
253
|
+
return 1 if self.multiplier > 0 else -1
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def order(self):
|
|
257
|
+
return len(self.ops)
|
|
258
|
+
|
|
259
|
+
def copy(self):
|
|
260
|
+
return Term(self)
|
|
261
|
+
|
|
262
|
+
def __eq__(self, other):
|
|
263
|
+
if not isinstance(other, Term):
|
|
264
|
+
return False
|
|
265
|
+
if self.multiplier != other.multiplier or len(self) != len(other):
|
|
266
|
+
return False
|
|
267
|
+
return all([t == o for (t, o) in zip(self.ops, other.ops)])
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class Expression(object):
|
|
271
|
+
"""Implements an Abelian group operation + on Term objects,
|
|
272
|
+
allowing for representation of arbitrary polynomials.
|
|
273
|
+
Expression.terms = [] has only Term elements, and should be read as x1 + x2 + ...
|
|
274
|
+
This list needs to be deep-copied.
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
def __init__(self, *termlist):
|
|
278
|
+
"""
|
|
279
|
+
Expression(x1,x2,x3,...)
|
|
280
|
+
Should be read as x1 + x2 + ...
|
|
281
|
+
xi can be Expression, Term, Operator, Fraction or int. All of these are summed
|
|
282
|
+
"""
|
|
283
|
+
self.terms = []
|
|
284
|
+
|
|
285
|
+
for term in termlist:
|
|
286
|
+
if isinstance(term, Expression):
|
|
287
|
+
# this ensures that we make new Term objets, but the
|
|
288
|
+
# underlying references to Operator are preserved
|
|
289
|
+
for t in term.terms:
|
|
290
|
+
self.terms.append(Term(t))
|
|
291
|
+
elif term != 0:
|
|
292
|
+
self.terms.append(Term(term))
|
|
293
|
+
|
|
294
|
+
def __repr__(self):
|
|
295
|
+
s = ''
|
|
296
|
+
for term in self.terms:
|
|
297
|
+
s += ' ' + str(term)
|
|
298
|
+
return s
|
|
299
|
+
|
|
300
|
+
def __str__(self):
|
|
301
|
+
return self.__repr__()
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def is_scalar(self):
|
|
306
|
+
for t in self.terms:
|
|
307
|
+
if not t.is_scalar:
|
|
308
|
+
return False
|
|
309
|
+
return True
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def order(self):
|
|
313
|
+
maxlen = 0
|
|
314
|
+
for t in self.terms:
|
|
315
|
+
l = t.order
|
|
316
|
+
maxlen = maxlen if l < maxlen else l
|
|
317
|
+
|
|
318
|
+
return maxlen
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
def operators(self):
|
|
322
|
+
s = set({})
|
|
323
|
+
for term in self.terms:
|
|
324
|
+
for op in term.ops:
|
|
325
|
+
s.add(op.name)
|
|
326
|
+
return s
|
|
327
|
+
|
|
328
|
+
def __neg__(self):
|
|
329
|
+
copy = Expression(self)
|
|
330
|
+
for t in copy.terms:
|
|
331
|
+
t.multiplier *= -1
|
|
332
|
+
return copy
|
|
333
|
+
|
|
334
|
+
def __sub__(self, other):
|
|
335
|
+
return self + other*-1
|
|
336
|
+
|
|
337
|
+
def __add__(self, other):
|
|
338
|
+
copy = Expression(self)
|
|
339
|
+
if isinstance(other, Expression):
|
|
340
|
+
for t in other.terms:
|
|
341
|
+
copy.terms.append(Term(t))
|
|
342
|
+
elif isinstance(other, Term):
|
|
343
|
+
if other.multiplier != 0:
|
|
344
|
+
copy.terms.append(Term(other))
|
|
345
|
+
elif isinstance(other, (Operator, int, Fraction)):
|
|
346
|
+
copy.terms.append(Term(other))
|
|
347
|
+
else:
|
|
348
|
+
return NotImplemented
|
|
349
|
+
return copy
|
|
350
|
+
|
|
351
|
+
def __radd__(self, other):
|
|
352
|
+
copy = Expression(self)
|
|
353
|
+
if isinstance(other, Operator) or type(other) in [int, Fraction]:
|
|
354
|
+
copy.terms = [Term(other)] + copy.terms
|
|
355
|
+
return copy
|
|
356
|
+
else:
|
|
357
|
+
return NotImplemented
|
|
358
|
+
|
|
359
|
+
def __mul__(self, other):
|
|
360
|
+
if isinstance(other, (Term, Operator, int, Fraction)):
|
|
361
|
+
copy = Expression(self)
|
|
362
|
+
# right-multiplying by Term
|
|
363
|
+
for i, t in enumerate(copy.terms):
|
|
364
|
+
copy.terms[i] = t * other
|
|
365
|
+
return copy
|
|
366
|
+
elif isinstance(other, Expression):
|
|
367
|
+
r = []
|
|
368
|
+
for left in self.terms:
|
|
369
|
+
for right in other.terms:
|
|
370
|
+
r.append(left * right)
|
|
371
|
+
return Expression(*r)
|
|
372
|
+
|
|
373
|
+
else:
|
|
374
|
+
return NotImplemented
|
|
375
|
+
|
|
376
|
+
def __rmul__(self, other):
|
|
377
|
+
if type(other) in (int, Fraction):
|
|
378
|
+
copy = Expression(self)
|
|
379
|
+
# scalar multiplicaiton
|
|
380
|
+
for i, t in enumerate(copy.terms):
|
|
381
|
+
copy.terms[i] = t * other
|
|
382
|
+
return copy
|
|
383
|
+
elif isinstance(other, (Term, Operator)):
|
|
384
|
+
copy = Expression(self)
|
|
385
|
+
# right-multiplying by Term
|
|
386
|
+
for i, t in enumerate(copy.terms):
|
|
387
|
+
copy.terms[i] = other * t
|
|
388
|
+
return copy
|
|
389
|
+
else:
|
|
390
|
+
return NotImplemented
|
|
391
|
+
|
|
392
|
+
def __eq__(self, other):
|
|
393
|
+
diff = self + -Expression(other)
|
|
394
|
+
diff.collect()
|
|
395
|
+
return diff.terms == []
|
|
396
|
+
|
|
397
|
+
# def to_operator(self):
|
|
398
|
+
# # Ensure that the expression is castable to Term
|
|
399
|
+
# assert len(self.terms) == 1
|
|
400
|
+
# # ensure that the Term only has one thing in it
|
|
401
|
+
# assert len(self.terms[0].ops) == 1
|
|
402
|
+
# if self.terms[0].multiplier != 1:
|
|
403
|
+
# warn("Ignoring multiplier")
|
|
404
|
+
# return self.terms[0].ops[0]
|
|
405
|
+
|
|
406
|
+
def replaceall(self, *rule_args):
|
|
407
|
+
"""Usage: term.replaceall((target, replacement),(target, replacement)...)
|
|
408
|
+
targets must be Terms, but replacemnts may be any expression_like.
|
|
409
|
+
Applies the rules in order specified.
|
|
410
|
+
"""
|
|
411
|
+
# make it mutable
|
|
412
|
+
rules = []
|
|
413
|
+
# promote to Term and Expression
|
|
414
|
+
for glob, sub in rule_args:
|
|
415
|
+
rules.append((Term(glob), Expression(sub)))
|
|
416
|
+
|
|
417
|
+
result = Expression()
|
|
418
|
+
for t in self.terms:
|
|
419
|
+
expression_product = []
|
|
420
|
+
i = 0
|
|
421
|
+
old_i = 0
|
|
422
|
+
while i < len(t):
|
|
423
|
+
step = True
|
|
424
|
+
|
|
425
|
+
for glob, sub in rules:
|
|
426
|
+
# print([str(o) for o in t.ops[i:i+len(glob)]], glob)
|
|
427
|
+
if t.ops[i:i+len(glob)] == glob.ops:
|
|
428
|
+
pre = Term(*t.ops[old_i:i])
|
|
429
|
+
expression_product.append(pre)
|
|
430
|
+
expression_product.append(sub)
|
|
431
|
+
i += len(glob)
|
|
432
|
+
old_i = i
|
|
433
|
+
step = False
|
|
434
|
+
if step:
|
|
435
|
+
i += 1
|
|
436
|
+
expression_product.append(Term(*t.ops[old_i:]))
|
|
437
|
+
|
|
438
|
+
x = Term(t.multiplier)
|
|
439
|
+
for fragment in expression_product:
|
|
440
|
+
x = x * fragment
|
|
441
|
+
result += x
|
|
442
|
+
|
|
443
|
+
return result
|
|
444
|
+
|
|
445
|
+
def from_str(self, s):
|
|
446
|
+
# cursed parser code, a problem for another day!
|
|
447
|
+
raise NotImplementedError
|
|
448
|
+
|
|
449
|
+
def as_latex(self):
|
|
450
|
+
s = ''
|
|
451
|
+
for term in self.terms:
|
|
452
|
+
s += term.as_latex() + ' '
|
|
453
|
+
return s
|
|
454
|
+
|
|
455
|
+
def move_scalars(self, side='left'):
|
|
456
|
+
for t in self.terms:
|
|
457
|
+
t.move_scalars(side)
|
|
458
|
+
|
|
459
|
+
def factor(self, side='left', x=None):
|
|
460
|
+
# usage: factor (ABC + ABD) ---> AB, C+D
|
|
461
|
+
# Does NOT factor subunits! That's too hard!
|
|
462
|
+
minorder = min([len(t.ops) for t in self.terms])
|
|
463
|
+
front = Term()
|
|
464
|
+
back = Expression(self)
|
|
465
|
+
|
|
466
|
+
if x is None:
|
|
467
|
+
# search through and find the longest forestring
|
|
468
|
+
if side in ['right', 'r']:
|
|
469
|
+
back = Expression(self)
|
|
470
|
+
front_arr = []
|
|
471
|
+
for n in range(minorder):
|
|
472
|
+
a = self.terms[0].ops[n]
|
|
473
|
+
if all([t.ops[n] == a for t in self.terms]):
|
|
474
|
+
front_arr.append(a)
|
|
475
|
+
for t in back.terms:
|
|
476
|
+
del t.ops[0]
|
|
477
|
+
else:
|
|
478
|
+
break
|
|
479
|
+
front = Term(*front_arr)
|
|
480
|
+
|
|
481
|
+
elif side in ['left', 'l']:
|
|
482
|
+
front = Expression(self)
|
|
483
|
+
back_arr = []
|
|
484
|
+
for n in range(minorder):
|
|
485
|
+
a = self.terms[0].ops[-n-1]
|
|
486
|
+
p = [t.ops[-n-1] == a for t in self.terms]
|
|
487
|
+
if all(p):
|
|
488
|
+
back_arr = [a] + back_arr
|
|
489
|
+
for t in front.terms:
|
|
490
|
+
del t.ops[-1]
|
|
491
|
+
else:
|
|
492
|
+
break
|
|
493
|
+
back = Term(*back_arr)
|
|
494
|
+
else:
|
|
495
|
+
raise ValueError(
|
|
496
|
+
"Side must be one of 'l', 'r', 'left', 'right'")
|
|
497
|
+
|
|
498
|
+
elif type(x) in [Term, Operator]:
|
|
499
|
+
rem = self.coefficient(x, side)
|
|
500
|
+
if len(rem.terms) == len(self.terms):
|
|
501
|
+
if side in ['right', 'r']:
|
|
502
|
+
front = Term(x)
|
|
503
|
+
back = rem
|
|
504
|
+
elif side in ['left', 'l']:
|
|
505
|
+
front = rem
|
|
506
|
+
back = Term(x)
|
|
507
|
+
else:
|
|
508
|
+
if side in ['right', 'r']:
|
|
509
|
+
front = Term()
|
|
510
|
+
back = Expression(self)
|
|
511
|
+
elif side in ['left', 'l']:
|
|
512
|
+
front = Expression(self)
|
|
513
|
+
back = Term()
|
|
514
|
+
|
|
515
|
+
assert front * back == self
|
|
516
|
+
return front, back
|
|
517
|
+
|
|
518
|
+
def collect(self):
|
|
519
|
+
agg = {}
|
|
520
|
+
|
|
521
|
+
for t in self.terms:
|
|
522
|
+
h = '*'.join([o.name for o in t.ops])
|
|
523
|
+
if h not in agg:
|
|
524
|
+
agg[h] = t.copy()
|
|
525
|
+
assert type(t.multiplier) is Fraction
|
|
526
|
+
else:
|
|
527
|
+
agg[h].multiplier += t.multiplier
|
|
528
|
+
self.terms = [t for t in agg.values() if t.multiplier != 0]
|
|
529
|
+
|
|
530
|
+
def sort(self, strategy='first'):
|
|
531
|
+
# order the elements
|
|
532
|
+
self.collect()
|
|
533
|
+
sorters = {
|
|
534
|
+
'first': lambda tup: ' '.join([str(o) for o in tup.ops]),
|
|
535
|
+
'last': lambda tup: ' '.join([str(o) for o in reversed(tup.ops)]),
|
|
536
|
+
'multiplier': lambda tup: tup.multiplier
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
self.terms.sort(key=sorters[strategy])
|
|
540
|
+
|
|
541
|
+
def coefficient(self, term, side='left'):
|
|
542
|
+
self.collect()
|
|
543
|
+
if type(term) in [Operator, int, Fraction]:
|
|
544
|
+
term = Term(term)
|
|
545
|
+
elif not isinstance(term, Term):
|
|
546
|
+
raise TypeError('Cannot factor type '+str(type(term)))
|
|
547
|
+
termstr = Expression()
|
|
548
|
+
M = len(term.ops)
|
|
549
|
+
if side == 'right' or side == 'r':
|
|
550
|
+
for t in self.terms:
|
|
551
|
+
if t.ops[:M] == term.ops:
|
|
552
|
+
termstr += Term(*t.ops[M:])*(t.multiplier/term.multiplier)
|
|
553
|
+
elif side == 'left' or side == 'l':
|
|
554
|
+
for t in self.terms:
|
|
555
|
+
if t.ops[-M:] == term.ops:
|
|
556
|
+
termstr += Term(*t.ops[:-M])*(t.multiplier/term.multiplier)
|
|
557
|
+
|
|
558
|
+
return termstr
|
|
559
|
+
|
|
560
|
+
def sub(self, glob, sub):
|
|
561
|
+
'''an alias for substitute'''
|
|
562
|
+
return self.substitute(glob, sub)
|
|
563
|
+
|
|
564
|
+
def substitute(self, glob, sub):
|
|
565
|
+
"""Searches through each Term, runs Term.findall to
|
|
566
|
+
find all non-overlapping occurrences of `glob`, and subs in Expression(sub)
|
|
567
|
+
Returns a different Expression, no changes are made to self
|
|
568
|
+
"""
|
|
569
|
+
# we cannot do any fancy multiterm substitutions... yet!
|
|
570
|
+
if not isinstance(glob, Term):
|
|
571
|
+
glob = Term(glob)
|
|
572
|
+
|
|
573
|
+
sub = Expression(sub)
|
|
574
|
+
|
|
575
|
+
retval = Expression()
|
|
576
|
+
for i, t in enumerate(self.terms):
|
|
577
|
+
idx = t.findall(glob)
|
|
578
|
+
N = len(glob.ops)
|
|
579
|
+
pieces = []
|
|
580
|
+
# Slice up the list
|
|
581
|
+
oldj = -N
|
|
582
|
+
for j in idx:
|
|
583
|
+
pieces.append(t.ops[oldj+N:j])
|
|
584
|
+
oldj = j
|
|
585
|
+
last = t.ops[oldj+N:]
|
|
586
|
+
|
|
587
|
+
# product the pieces together
|
|
588
|
+
x = Expression(1)
|
|
589
|
+
for p in pieces:
|
|
590
|
+
x = x*Term(*p)*sub
|
|
591
|
+
retval += x*Term(*last)*(t.multiplier/glob.multiplier)
|
|
592
|
+
return retval
|
commutation/style.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from .expression import Expression, Term, Operator
|
|
2
|
+
from fractions import Fraction
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
import IPython.display as ipdisp
|
|
6
|
+
except AttributeError:
|
|
7
|
+
ipdisp=None
|
|
8
|
+
|
|
9
|
+
def show(x, max_len=200):
|
|
10
|
+
if isinstance(x, Expression):
|
|
11
|
+
if len(x.terms) > max_len:
|
|
12
|
+
raise RuntimeError('Expression too long (override with show(x, n), n is number of terms)')
|
|
13
|
+
|
|
14
|
+
s = ''
|
|
15
|
+
if isinstance(x, (Expression, Term, Operator)):
|
|
16
|
+
s = x.as_latex()
|
|
17
|
+
elif isinstance(x, int):
|
|
18
|
+
s = str(x)
|
|
19
|
+
elif isinstance(x, Fraction):
|
|
20
|
+
s = '\\frac{%d}{%d}' % (x.numerator, x.denominator)
|
|
21
|
+
else:
|
|
22
|
+
raise TypeError("show may only be called on Expression, Operator, Term, Fraction or int")
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
ipdisp.display(ipdisp.Latex('$'+s+'$'))
|
|
26
|
+
except AttributeError:
|
|
27
|
+
raise AttributeError("LaTeX rendering is only possible in a jupyter notebook.")
|
|
28
|
+
|
|
29
|
+
## Converts Expression, Term or Operator into Mathematica
|
|
30
|
+
# def as_mathematica(x):
|