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.
@@ -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):