Commutation 1.5.0__tar.gz → 1.5.1__tar.gz
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-1.5.0/src/Commutation.egg-info → commutation-1.5.1}/PKG-INFO +3 -1
- {commutation-1.5.0 → commutation-1.5.1}/pyproject.toml +10 -1
- {commutation-1.5.0 → commutation-1.5.1/src/Commutation.egg-info}/PKG-INFO +3 -1
- {commutation-1.5.0 → commutation-1.5.1}/src/Commutation.egg-info/SOURCES.txt +6 -1
- commutation-1.5.1/src/Commutation.egg-info/requires.txt +4 -0
- {commutation-1.5.0 → commutation-1.5.1}/src/commutation/commutatoralgebra.py +8 -4
- commutation-1.5.1/tests/test_anticommutator.py +54 -0
- commutation-1.5.1/tests/test_commutator.py +132 -0
- commutation-1.5.1/tests/test_expression.py +149 -0
- commutation-1.5.1/tests/test_operator.py +85 -0
- commutation-1.5.1/tests/test_term.py +114 -0
- commutation-1.5.0/src/Commutation.egg-info/requires.txt +0 -1
- {commutation-1.5.0 → commutation-1.5.1}/LICENSE +0 -0
- {commutation-1.5.0 → commutation-1.5.1}/README.md +0 -0
- {commutation-1.5.0 → commutation-1.5.1}/setup.cfg +0 -0
- {commutation-1.5.0 → commutation-1.5.1}/src/Commutation.egg-info/dependency_links.txt +0 -0
- {commutation-1.5.0 → commutation-1.5.1}/src/Commutation.egg-info/top_level.txt +0 -0
- {commutation-1.5.0 → commutation-1.5.1}/src/commutation/__init__.py +0 -0
- {commutation-1.5.0 → commutation-1.5.1}/src/commutation/expression.py +0 -0
- {commutation-1.5.0 → commutation-1.5.1}/src/commutation/style.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Commutation
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.1
|
|
4
4
|
Summary: A small package for doing commutator algebra
|
|
5
5
|
Author-email: Alaric Sanders <thathatatt@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/Spuriosity1/Commutation
|
|
@@ -12,6 +12,8 @@ Requires-Python: >=3.8
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: ipython
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest; extra == "test"
|
|
15
17
|
Dynamic: license-file
|
|
16
18
|
|
|
17
19
|
# Commutation Station for Operator Elimination
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "Commutation"
|
|
7
|
-
version = "1.5.
|
|
7
|
+
version = "1.5.1"
|
|
8
8
|
requires-python = ">= 3.8"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Alaric Sanders", email="thathatatt@gmail.com"}
|
|
@@ -23,6 +23,9 @@ dependencies = [
|
|
|
23
23
|
"ipython",
|
|
24
24
|
]
|
|
25
25
|
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
test = ["pytest"]
|
|
28
|
+
|
|
26
29
|
[project.urls]
|
|
27
30
|
Homepage = "https://github.com/Spuriosity1/Commutation"
|
|
28
31
|
Issues = "https://github.com/Spuriosity1/Commutation/issues"
|
|
@@ -33,3 +36,9 @@ package-dir = {"" = "src"}
|
|
|
33
36
|
|
|
34
37
|
[tool.setuptools.packages.find]
|
|
35
38
|
where = ["src"]
|
|
39
|
+
|
|
40
|
+
[tool.pytest.ini_options]
|
|
41
|
+
# allows `pytest` from the repo root without installing the package first
|
|
42
|
+
# (tox installs it, but this keeps the local dev loop simple)
|
|
43
|
+
pythonpath = ["src"]
|
|
44
|
+
testpaths = ["tests"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Commutation
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.1
|
|
4
4
|
Summary: A small package for doing commutator algebra
|
|
5
5
|
Author-email: Alaric Sanders <thathatatt@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/Spuriosity1/Commutation
|
|
@@ -12,6 +12,8 @@ Requires-Python: >=3.8
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: ipython
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest; extra == "test"
|
|
15
17
|
Dynamic: license-file
|
|
16
18
|
|
|
17
19
|
# Commutation Station for Operator Elimination
|
|
@@ -9,4 +9,9 @@ src/Commutation.egg-info/top_level.txt
|
|
|
9
9
|
src/commutation/__init__.py
|
|
10
10
|
src/commutation/commutatoralgebra.py
|
|
11
11
|
src/commutation/expression.py
|
|
12
|
-
src/commutation/style.py
|
|
12
|
+
src/commutation/style.py
|
|
13
|
+
tests/test_anticommutator.py
|
|
14
|
+
tests/test_commutator.py
|
|
15
|
+
tests/test_expression.py
|
|
16
|
+
tests/test_operator.py
|
|
17
|
+
tests/test_term.py
|
|
@@ -156,8 +156,9 @@ class CommutatorAlgebra(AbstractCommutatorAlgebra):
|
|
|
156
156
|
B = term.ops[i-1]
|
|
157
157
|
front = Term(*term.ops[:i-1])
|
|
158
158
|
back = Term(*term.ops[i+1:])
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
|
|
160
|
+
# reordering B*A -> A*B picks up [B, A] = -[A, B]
|
|
161
|
+
c = self.get_commutator(B, A)
|
|
161
162
|
extra = front * c * back * term.multiplier
|
|
162
163
|
|
|
163
164
|
term.ops[i-1], term.ops[i] = term.ops[i], term.ops[i-1]
|
|
@@ -215,8 +216,11 @@ class AntiCommutatorAlgebra(AbstractCommutatorAlgebra):
|
|
|
215
216
|
front = Term(*term.ops[:i])
|
|
216
217
|
back = Term(*term.ops[i+2:])
|
|
217
218
|
|
|
218
|
-
|
|
219
|
-
|
|
219
|
+
A = term.ops[i]
|
|
220
|
+
B = term.ops[i+1]
|
|
221
|
+
c = self.get_anticommutator(A, B)
|
|
222
|
+
if c is None:
|
|
223
|
+
raise AntiCommutatorUnknownException(A, B)
|
|
220
224
|
extra = Expression(front) * \
|
|
221
225
|
c * Expression(back)*term.multiplier
|
|
222
226
|
term.multiplier *= -1
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Tests for AntiCommutatorAlgebra.
|
|
2
|
+
|
|
3
|
+
Anticommutator support is newer than the commutator path (see TODO.md), so
|
|
4
|
+
these focus on the basics plus two documented rough edges.
|
|
5
|
+
"""
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from commutation import (
|
|
9
|
+
Operator,
|
|
10
|
+
Expression,
|
|
11
|
+
AntiCommutatorAlgebra,
|
|
12
|
+
AntiCommutatorUnknownException,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _algebra():
|
|
17
|
+
"""Two fermion-like modes with {f1, f2} = 0."""
|
|
18
|
+
f1, f2 = Operator("f1"), Operator("f2")
|
|
19
|
+
ac = AntiCommutatorAlgebra()
|
|
20
|
+
ac.set_anticommutator(f1, f2)(0)
|
|
21
|
+
return ac, f1, f2
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_set_anticommutator_is_symmetric():
|
|
25
|
+
ac, f1, f2 = _algebra()
|
|
26
|
+
assert ac.relations["f1"]["f2"] == ac.relations["f2"]["f1"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_diagonal_defaults_to_twice_the_operator():
|
|
30
|
+
ac, f1, f2 = _algebra()
|
|
31
|
+
assert ac.relations["f1"]["f1"] == 2 * f1
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_move_right_flips_sign_on_swap():
|
|
35
|
+
# f1*f2 -> -f2*f1 + {f1, f2} = -f2*f1 (since {f1, f2} = 0)
|
|
36
|
+
ac, f1, f2 = _algebra()
|
|
37
|
+
e = Expression(f1 * f2)
|
|
38
|
+
ac.move_right(e, f1)
|
|
39
|
+
e.collect()
|
|
40
|
+
assert e == -1 * (f2 * f1)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_move_left_unknown_raises_friendly_exception():
|
|
44
|
+
ac, f1, f2 = _algebra()
|
|
45
|
+
g = Operator("g") # unregistered
|
|
46
|
+
with pytest.raises(AntiCommutatorUnknownException):
|
|
47
|
+
ac.move_left(Expression(g * f1), f1)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_move_right_unknown_raises_friendly_exception():
|
|
51
|
+
ac, f1, f2 = _algebra()
|
|
52
|
+
g = Operator("g") # unregistered
|
|
53
|
+
with pytest.raises(AntiCommutatorUnknownException):
|
|
54
|
+
ac.move_right(Expression(f1 * g), f1)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Tests for CommutatorAlgebra: relation storage and operator reordering.
|
|
2
|
+
|
|
3
|
+
These cover the TODO's "some well known commutator reductions" item using the
|
|
4
|
+
SU(2) spin algebra [az, ap] = ap, [az, am] = -am, [ap, am] = 2 az.
|
|
5
|
+
"""
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from commutation import (
|
|
11
|
+
Operator,
|
|
12
|
+
Scalar,
|
|
13
|
+
Expression,
|
|
14
|
+
CommutatorAlgebra,
|
|
15
|
+
CommutatorUnknownException,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# --------------------------------------------------------------------------
|
|
20
|
+
# relation storage
|
|
21
|
+
# --------------------------------------------------------------------------
|
|
22
|
+
def test_set_commutator_stores_antisymmetric_partner(spin):
|
|
23
|
+
ca, az, ap, am = spin
|
|
24
|
+
assert ca.relations["ap"]["am"] == 2 * az
|
|
25
|
+
assert ca.relations["am"]["ap"] == -2 * az
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_get_commutator_scalar_is_zero(spin):
|
|
29
|
+
ca, az, ap, am = spin
|
|
30
|
+
K = Scalar("K")
|
|
31
|
+
assert ca.get_commutator(K, az) == 0
|
|
32
|
+
assert ca.get_commutator(az, K) == 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_get_commutator_unknown_warns_and_assumes_commute(spin):
|
|
36
|
+
ca, az, ap, am = spin
|
|
37
|
+
unknown = Operator("unknown")
|
|
38
|
+
with warnings.catch_warnings(record=True) as w:
|
|
39
|
+
warnings.simplefilter("always")
|
|
40
|
+
result = ca.get_commutator(unknown, az)
|
|
41
|
+
assert result == 0
|
|
42
|
+
assert any(issubclass(x.category, UserWarning) for x in w)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_strict_mode_raises_for_unknown():
|
|
46
|
+
az, ap = Operator("az"), Operator("ap")
|
|
47
|
+
ca = CommutatorAlgebra(strict=True)
|
|
48
|
+
ca.set_commutator(az, ap)(ap)
|
|
49
|
+
am = Operator("am") # never registered
|
|
50
|
+
with pytest.raises(CommutatorUnknownException):
|
|
51
|
+
ca.move_right(Expression(az * ap * am), az)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# --------------------------------------------------------------------------
|
|
55
|
+
# elementary reductions
|
|
56
|
+
# --------------------------------------------------------------------------
|
|
57
|
+
def test_move_right_single_swap(spin):
|
|
58
|
+
# ap*az -> az*ap + [ap, az] = az*ap - ap
|
|
59
|
+
ca, az, ap, am = spin
|
|
60
|
+
e = Expression(ap * az)
|
|
61
|
+
ca.move_right(e, ap)
|
|
62
|
+
e.collect()
|
|
63
|
+
assert e == az * ap - ap
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_move_right_noop_when_already_rightmost(spin):
|
|
67
|
+
# az is already at the right end; nothing to move past.
|
|
68
|
+
ca, az, ap, am = spin
|
|
69
|
+
e = Expression(ap * az)
|
|
70
|
+
ca.move_right(e, az)
|
|
71
|
+
e.collect()
|
|
72
|
+
assert e == ap * az
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_move_right_readme_example(spin):
|
|
76
|
+
ca, az, ap, am = spin
|
|
77
|
+
e = Expression(az * ap * az * az * am)
|
|
78
|
+
ca.move_right(e, az)
|
|
79
|
+
e.collect()
|
|
80
|
+
assert e == ap * am * az * az * az - 2 * ap * am * az * az + ap * am * az
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_scalars_commute_through(spin):
|
|
84
|
+
ca, az, ap, am = spin
|
|
85
|
+
KA = Scalar("KA")
|
|
86
|
+
e = Expression(az * ap * am * KA * am)
|
|
87
|
+
ca.move_right(e, az)
|
|
88
|
+
e.collect()
|
|
89
|
+
assert e == ap * am * KA * am * az - ap * am * KA * am
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_unknown_operator_during_move_warns(spin):
|
|
93
|
+
ca, az, ap, am = spin
|
|
94
|
+
c = Operator("c") # not in the database
|
|
95
|
+
e = Expression(az * ap * am * c * am)
|
|
96
|
+
with warnings.catch_warnings(record=True) as w:
|
|
97
|
+
warnings.simplefilter("always")
|
|
98
|
+
ca.move_right(e, az)
|
|
99
|
+
e.collect()
|
|
100
|
+
assert any(issubclass(x.category, UserWarning) for x in w)
|
|
101
|
+
assert e == ap * am * c * am * az - ap * am * c * am
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# --------------------------------------------------------------------------
|
|
105
|
+
# move_left
|
|
106
|
+
# --------------------------------------------------------------------------
|
|
107
|
+
def test_move_left_single_swap(spin):
|
|
108
|
+
# ap*az -> az*ap + [ap, az] = az*ap - ap
|
|
109
|
+
ca, az, ap, am = spin
|
|
110
|
+
e = Expression(ap * az)
|
|
111
|
+
ca.move_left(e, az)
|
|
112
|
+
e.collect()
|
|
113
|
+
assert e == az * ap - ap
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_move_left_readme_example(spin):
|
|
117
|
+
ca, az, ap, am = spin
|
|
118
|
+
e = Expression(az * ap * az * az * am)
|
|
119
|
+
ca.move_left(e, am)
|
|
120
|
+
e.collect()
|
|
121
|
+
expected = (
|
|
122
|
+
am * az * ap * az * az
|
|
123
|
+
- 2 * am * az * ap * az
|
|
124
|
+
+ 2 * az * az * az * az
|
|
125
|
+
- am * ap * az * az
|
|
126
|
+
+ am * az * ap
|
|
127
|
+
- 4 * az * az * az
|
|
128
|
+
+ 2 * am * ap * az
|
|
129
|
+
+ 2 * az * az
|
|
130
|
+
- am * ap
|
|
131
|
+
)
|
|
132
|
+
assert e == expected
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Tests for the Expression abelian-group / CAS operations."""
|
|
2
|
+
from fractions import Fraction
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from commutation import Operator, Term, Expression
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_construction_drops_zeros():
|
|
10
|
+
a = Operator("a")
|
|
11
|
+
e = Expression(a, 0, Term(a))
|
|
12
|
+
assert len(e.terms) == 2
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_empty_expression():
|
|
16
|
+
assert Expression().terms == []
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_add_sub_neg():
|
|
20
|
+
a, b = Operator("a"), Operator("b")
|
|
21
|
+
assert (a + b) - b == a
|
|
22
|
+
assert -(a + b) == Expression(-Term(a), -Term(b))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_mul_distributes():
|
|
26
|
+
a, b, c = Operator("a"), Operator("b"), Operator("c")
|
|
27
|
+
e = (a + b) * c
|
|
28
|
+
assert e == a * c + b * c
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_scalar_mul():
|
|
32
|
+
a, b = Operator("a"), Operator("b")
|
|
33
|
+
assert 2 * (a + b) == (2 * a) + (2 * b)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_collect_folds_like_terms():
|
|
37
|
+
a = Operator("a")
|
|
38
|
+
e = Expression(a) + Expression(a) - Expression(a)
|
|
39
|
+
e.collect()
|
|
40
|
+
assert len(e.terms) == 1
|
|
41
|
+
assert e.terms[0].multiplier == 1
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_collect_drops_zero_terms():
|
|
45
|
+
a = Operator("a")
|
|
46
|
+
e = a - a
|
|
47
|
+
e.collect()
|
|
48
|
+
assert e.terms == []
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_sort_strategies():
|
|
52
|
+
a, b, c = Operator("a"), Operator("b"), Operator("c")
|
|
53
|
+
e = Expression(c) + a + b
|
|
54
|
+
e.sort("first")
|
|
55
|
+
assert [str(t.ops[0]) for t in e.terms] == ["a", "b", "c"]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_operators_property():
|
|
59
|
+
a, b, c = Operator("a"), Operator("b"), Operator("c")
|
|
60
|
+
assert (a * b + c).operators == {"a", "b", "c"}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_order_property():
|
|
64
|
+
a, b, c = Operator("a"), Operator("b"), Operator("c")
|
|
65
|
+
assert (a * b * c + a).order == 3
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_is_scalar_property():
|
|
69
|
+
from commutation import Scalar
|
|
70
|
+
a = Operator("a")
|
|
71
|
+
K = Scalar("K")
|
|
72
|
+
assert (K + K).is_scalar is True
|
|
73
|
+
assert (a + K).is_scalar is False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_eq_is_algebraic():
|
|
77
|
+
a, b = Operator("a"), Operator("b")
|
|
78
|
+
assert (a + b) == (b + a) # collect ignores term order for matching keys
|
|
79
|
+
assert (a + a) == (2 * a)
|
|
80
|
+
assert (a + b) != (a + a)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_substitute_readme_example():
|
|
84
|
+
a, b, c, d = (Operator(x) for x in "abcd")
|
|
85
|
+
x = a * b * b * b * a * c * a * a * c * a * b + 1
|
|
86
|
+
y = x.substitute(a * b, c + d)
|
|
87
|
+
expected = (
|
|
88
|
+
c * b * b * a * c * a * a * c * c
|
|
89
|
+
+ c * b * b * a * c * a * a * c * d
|
|
90
|
+
+ d * b * b * a * c * a * a * c * c
|
|
91
|
+
+ d * b * b * a * c * a * a * c * d
|
|
92
|
+
+ 1
|
|
93
|
+
)
|
|
94
|
+
assert y == expected
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_substitute_preserves_multiplier():
|
|
98
|
+
a, b, c = Operator("a"), Operator("b"), Operator("c")
|
|
99
|
+
assert Expression(3 * a * b).substitute(a * b, c) == 3 * c
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_sub_is_alias_for_substitute():
|
|
103
|
+
a, b, c = Operator("a"), Operator("b"), Operator("c")
|
|
104
|
+
e = Expression(a * b)
|
|
105
|
+
assert e.sub(a * b, c) == e.substitute(a * b, c)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_replaceall():
|
|
109
|
+
a, b, c = Operator("a"), Operator("b"), Operator("c")
|
|
110
|
+
assert Expression(a * b * a * b).replaceall((a * b, c)) == c * c
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_factor_no_x_right():
|
|
114
|
+
a, b, c, d = (Operator(x) for x in "abcd")
|
|
115
|
+
xx = 7 * a * b * b + Fraction(4, 5) * a * c * b * b + a * d * b * b
|
|
116
|
+
front, back = xx.factor("right")
|
|
117
|
+
assert front * back == xx
|
|
118
|
+
assert front == Term(a)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_factor_no_x_left():
|
|
122
|
+
a, b, c, d = (Operator(x) for x in "abcd")
|
|
123
|
+
xx = 7 * a * b * b + Fraction(4, 5) * a * c * b * b + a * d * b * b
|
|
124
|
+
front, back = xx.factor() # default side = left
|
|
125
|
+
assert front * back == xx
|
|
126
|
+
assert back == Term(b, b)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_factor_explicit_x():
|
|
130
|
+
a, b, c = Operator("a"), Operator("b"), Operator("c")
|
|
131
|
+
front, back = (a * b + a * c).factor("right", a)
|
|
132
|
+
assert front == Term(a)
|
|
133
|
+
assert back == b + c
|
|
134
|
+
assert front * back == (a * b + a * c)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_coefficient():
|
|
138
|
+
a, b, c = Operator("a"), Operator("b"), Operator("c")
|
|
139
|
+
assert (a * b + a * c).coefficient(a, "right") == b + c
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_as_latex_fraction():
|
|
143
|
+
a = Operator("a")
|
|
144
|
+
assert (Fraction(4, 5) * Term(a)).as_latex() == r"+\frac{4}{5} a"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_from_str_not_implemented():
|
|
148
|
+
with pytest.raises(NotImplementedError):
|
|
149
|
+
Expression().from_str("a + b")
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Tests for the Operator / Scalar primitives."""
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from commutation import Operator, Scalar, Term, Expression
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_name_must_be_str():
|
|
8
|
+
with pytest.raises(TypeError):
|
|
9
|
+
Operator(123)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_latex_must_be_str():
|
|
13
|
+
with pytest.raises(TypeError):
|
|
14
|
+
Operator("a", 123)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_latex_defaults_to_name():
|
|
18
|
+
a = Operator("a")
|
|
19
|
+
assert a.as_latex() == "a"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_latex_string_used_when_given():
|
|
23
|
+
a = Operator("a", "S^z_a")
|
|
24
|
+
assert a.as_latex() == "S^z_a"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_str_and_repr_are_name():
|
|
28
|
+
a = Operator("a")
|
|
29
|
+
assert str(a) == "a"
|
|
30
|
+
assert repr(a) == "a"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_mul_by_int_gives_term():
|
|
34
|
+
a = Operator("a")
|
|
35
|
+
t = a * 3
|
|
36
|
+
assert isinstance(t, Term)
|
|
37
|
+
assert t.multiplier == 3
|
|
38
|
+
assert t.ops == [a]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_rmul_by_int_gives_term():
|
|
42
|
+
a = Operator("a")
|
|
43
|
+
t = 3 * a
|
|
44
|
+
assert isinstance(t, Term)
|
|
45
|
+
assert t.multiplier == 3
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_mul_by_operator_gives_term():
|
|
49
|
+
a, b = Operator("a"), Operator("b")
|
|
50
|
+
t = a * b
|
|
51
|
+
assert isinstance(t, Term)
|
|
52
|
+
assert t.ops == [a, b]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_add_gives_expression():
|
|
56
|
+
a, b = Operator("a"), Operator("b")
|
|
57
|
+
e = a + b
|
|
58
|
+
assert isinstance(e, Expression)
|
|
59
|
+
assert len(e.terms) == 2
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_neg_gives_term_with_negative_multiplier():
|
|
63
|
+
a = Operator("a")
|
|
64
|
+
t = -a
|
|
65
|
+
assert isinstance(t, Term)
|
|
66
|
+
assert t.multiplier == -1
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_eq_is_algebraic():
|
|
70
|
+
a, b = Operator("a"), Operator("b")
|
|
71
|
+
assert a == a
|
|
72
|
+
assert a != b
|
|
73
|
+
assert a == Term(a)
|
|
74
|
+
assert (a - a) == 0
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_scalar_sets_flag():
|
|
78
|
+
assert Scalar("K").is_scalar is True
|
|
79
|
+
assert Operator("a").is_scalar is False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_subtraction_of_operators():
|
|
83
|
+
a, b = Operator("a"), Operator("b")
|
|
84
|
+
e = a - b
|
|
85
|
+
assert e == Expression(Term(a), -Term(b))
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Tests for the Term monoid."""
|
|
2
|
+
from fractions import Fraction
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from commutation import Operator, Scalar, Term
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_construction_products_operators_and_scalars():
|
|
10
|
+
a, b = Operator("a"), Operator("b")
|
|
11
|
+
t = Term(2, a, 3, b)
|
|
12
|
+
assert t.multiplier == 6
|
|
13
|
+
assert t.ops == [a, b]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_identity_term_is_empty():
|
|
17
|
+
t = Term()
|
|
18
|
+
assert t.multiplier == 1
|
|
19
|
+
assert t.ops == []
|
|
20
|
+
assert len(t) == 0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_nested_term_flattens():
|
|
24
|
+
a, b = Operator("a"), Operator("b")
|
|
25
|
+
t = Term(Term(2, a), Term(3, b))
|
|
26
|
+
assert t.multiplier == 6
|
|
27
|
+
assert t.ops == [a, b]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_bad_type_raises():
|
|
31
|
+
with pytest.raises(TypeError):
|
|
32
|
+
Term(1.5) # float is not allowed, only int / Fraction / Operator
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_is_scalar():
|
|
36
|
+
a = Operator("a")
|
|
37
|
+
assert Term(Scalar("K"), Scalar("J")).is_scalar is True
|
|
38
|
+
assert Term(a).is_scalar is False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_order_sign_len():
|
|
42
|
+
a, b = Operator("a"), Operator("b")
|
|
43
|
+
t = -2 * Term(a, b)
|
|
44
|
+
assert t.order == 2
|
|
45
|
+
assert len(t) == 2
|
|
46
|
+
assert t.sign == -1
|
|
47
|
+
assert (2 * Term(a)).sign == 1
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_mul_rmul_truediv():
|
|
51
|
+
a, b = Operator("a"), Operator("b")
|
|
52
|
+
assert (Term(a) * b).ops == [a, b]
|
|
53
|
+
assert (b * Term(a)).ops == [b, a]
|
|
54
|
+
assert (Term(a) * 4).multiplier == 4
|
|
55
|
+
assert (Term(4, a) / 2).multiplier == Fraction(2, 1)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_neg_copies():
|
|
59
|
+
a = Operator("a")
|
|
60
|
+
t = Term(a)
|
|
61
|
+
n = -t
|
|
62
|
+
assert n.multiplier == -1
|
|
63
|
+
assert t.multiplier == 1 # original untouched
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_factor_scalars():
|
|
67
|
+
a = Operator("a")
|
|
68
|
+
K = Scalar("K")
|
|
69
|
+
scal, ops = Term(3, K, a).factor_scalars()
|
|
70
|
+
assert scal == Term(3, K)
|
|
71
|
+
assert ops == Term(a)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_move_scalars_left_and_right():
|
|
75
|
+
a, b = Operator("a"), Operator("b")
|
|
76
|
+
K = Scalar("K")
|
|
77
|
+
|
|
78
|
+
left = Term(a, K, b)
|
|
79
|
+
left.move_scalars("left")
|
|
80
|
+
assert [str(o) for o in left.ops] == ["K", "a", "b"]
|
|
81
|
+
|
|
82
|
+
right = Term(a, K, b)
|
|
83
|
+
right.move_scalars("right")
|
|
84
|
+
assert [str(o) for o in right.ops] == ["a", "b", "K"]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_move_scalars_bad_side():
|
|
88
|
+
a = Operator("a")
|
|
89
|
+
with pytest.raises(IndexError):
|
|
90
|
+
Term(a).move_scalars("sideways")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_findall_non_overlapping():
|
|
94
|
+
a, b = Operator("a"), Operator("b")
|
|
95
|
+
assert Term(a, a, a).findall(Term(a, a)) == [0]
|
|
96
|
+
assert Term(a, a, a, a).findall(Term(a, a)) == [0, 2]
|
|
97
|
+
assert Term(a, b, a, b).findall(Term(a, b)) == [0, 2]
|
|
98
|
+
assert Term(a, b).findall(Term(b, a)) == []
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_eq():
|
|
102
|
+
a, b = Operator("a"), Operator("b")
|
|
103
|
+
assert Term(2, a, b) == Term(2, a, b)
|
|
104
|
+
assert Term(2, a, b) != Term(3, a, b)
|
|
105
|
+
assert Term(a, b) != Term(b, a)
|
|
106
|
+
assert Term(a) != "a"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_copy_is_independent():
|
|
110
|
+
a = Operator("a")
|
|
111
|
+
t = Term(a)
|
|
112
|
+
c = t.copy()
|
|
113
|
+
c.multiplier = 5
|
|
114
|
+
assert t.multiplier == 1
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
ipython
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|