/ test / test_differential_ops.py
test_differential_ops.py
  1  import operator
  2  
  3  from sympy import symbols, S
  4  import pytest
  5  
  6  from galgebra.ga import Ga
  7  from galgebra.mv import Dop, Mv
  8  from galgebra.dop import Sdop, Pdop
  9  
 10  
 11  class TestDop(object):
 12  
 13      def test_associativity_and_distributivity(self):
 14          coords = x, y, z = symbols('x y z', real=True)
 15          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1], coords=coords)
 16          v = ga.mv('v', 'vector', f=True)
 17          laplacian = ga.grad * ga.grad
 18          rlaplacian = ga.rgrad * ga.rgrad
 19  
 20          # check addition distributes
 21          assert (laplacian + ga.grad) * v == laplacian * v + ga.grad * v != 0
 22          assert (laplacian + 1234567) * v == laplacian * v + 1234567 * v != 0
 23          assert (1234 * ex + ga.grad) * v == 1234 * ex * v + ga.grad * v != 0
 24          # check subtraction distributes
 25          assert (laplacian - ga.grad) * v == laplacian * v - ga.grad * v != 0
 26          assert (laplacian - 1234567) * v == laplacian * v - 1234567 * v != 0
 27          assert (1234 * ex - ga.grad) * v == 1234 * ex * v - ga.grad * v != 0
 28          # check unary subtraction distributes
 29          assert (-ga.grad) * v == -(ga.grad * v) != 0
 30          # check division is associative
 31          assert v * (ga.rgrad / 2) == (v * ga.rgrad) / 2 != 0
 32          # check multiplication is associative
 33          assert (ex * ga.grad) * v == ex * (ga.grad * v) != 0
 34          assert (20 * ga.grad) * v == 20 * (ga.grad * v) != 0
 35          assert v * (ga.rgrad * ex) == (v * ga.rgrad) * ex != 0
 36          assert v * (ga.rgrad * 20) == (v * ga.rgrad) * 20 != 0
 37          assert (laplacian * ga.grad) * v == laplacian * (ga.grad * v) != 0
 38          # check wedge is associative
 39          assert (ex ^ ga.grad) ^ v == ex ^ (ga.grad ^ v) != 0
 40          assert (20 ^ ga.grad) ^ v == 20 ^ (ga.grad ^ v) != 0
 41          assert v ^ (ga.rgrad ^ ex) == (v ^ ga.rgrad) ^ ex != 0
 42          assert v ^ (ga.rgrad ^ 20) == (v ^ ga.rgrad) ^ 20 != 0
 43  
 44      def test_empty_dop(self):
 45          """ Test that dop with zero terms is equivalent to multiplying by zero """
 46          coords = x, y, z = symbols('x y z', real=True)
 47          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1], coords=coords)
 48          v = ga.mv('v', 'vector', f=True)
 49  
 50          make_zero = ga.dop([])
 51          assert make_zero * v == 0
 52          assert make_zero * make_zero * v == 0
 53          assert (make_zero + make_zero) * v == 0
 54          assert (-make_zero) * v == 0
 55  
 56      def test_misc(self):
 57          """ Other miscellaneous tests """
 58          coords = x, y, z = symbols('x y z', real=True)
 59          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1], coords=coords)
 60          v = ga.mv('v', 'vector', f=True)
 61          laplacian = ga.grad * ga.grad
 62          rlaplacian = ga.rgrad * ga.rgrad
 63  
 64          # laplacian is a scalar operator, so applying it from either side
 65          # is the same
 66          assert laplacian * v == v * rlaplacian
 67  
 68          assert laplacian.is_scalar()
 69          assert not ga.grad.is_scalar()
 70  
 71          # test comparison
 72          assert ga.grad == ga.grad
 73          assert not (ga.grad != ga.grad)
 74          assert ga.grad != laplacian
 75          assert not (ga.grad == laplacian)
 76          assert ga.grad != object()
 77          assert not (ga.grad == object())
 78  
 79          # inconsistent cmpflg, not clear which side the operator goes on
 80          with pytest.raises(ValueError):
 81              ga.grad + ga.rgrad
 82          with pytest.raises(ValueError):
 83              ga.grad * ga.rgrad
 84  
 85      def test_mixed_algebras(self):
 86          coords = x, y, z = symbols('x y z', real=True)
 87          ga1, ex1, ey1, ez1 = Ga.build('e1*x|y|z', g=[1, 1, 1], coords=coords)
 88          ga2, ex2, ey2, ez2 = Ga.build('e2*x|y|z', g=[1, 1, 1], coords=coords)
 89          assert ga1 != ga2
 90  
 91          v1 = ga1.mv('v', 'vector', f=True)
 92          v2 = ga2.mv('v', 'vector', f=True)
 93  
 94          with pytest.raises(ValueError):
 95              ga1.grad * v2
 96          with pytest.raises(ValueError):
 97              v1 * ga2.rgrad
 98          with pytest.raises(ValueError):
 99              ga1.grad * ga2.grad
100  
101      def test_components(self):
102          coords = x, y, z = symbols('x y z', real=True)
103          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1], coords=coords)
104  
105          components = ga.grad.components()
106          assert components == (
107              ex * (ex | ga.grad),
108              ey * (ey | ga.grad),
109              ez * (ez | ga.grad),
110          )
111  
112      def test_constructor_errors(self):
113          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1])
114  
115          # list lengths must match
116          with pytest.raises(ValueError, match='same length'):
117              Dop([ex], [], ga=ga)
118  
119          # the two conventions can't be mixed
120          mixed_args = [
121              (ex, Pdop({})),
122              (Sdop([]), ex),
123          ]
124          with pytest.raises(TypeError, match='pairs'):
125              Dop(mixed_args, ga=ga)
126  
127          # ga must be non-none
128          with pytest.raises(ValueError, match='must not be None'):
129              Dop([], ga=None)
130  
131          # too few arguments
132          with pytest.raises(TypeError, match='0 were given'):
133              Dop(ga=ga)
134          # too many arguments
135          with pytest.raises(TypeError, match='3 were given'):
136              Dop(1, 2, 3, ga=ga)
137  
138  
139  class TestSdop(object):
140  
141      def test_deprecation(self):
142          coords = x, y, z = symbols('x y z', real=True)
143          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1], coords=coords)
144  
145          with pytest.warns(DeprecationWarning):
146              ga.sPds
147  
148      def test_shorthand(self):
149          coords = x, y, z = symbols('x y z', real=True)
150          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1], coords=coords)
151  
152          assert Sdop(x) == Sdop([(S(1), Pdop({x: 1}))])
153  
154      def test_empty_sdop(self):
155          """ Test that sdop with zero terms is equivalent to multiplying by zero """
156          coords = x, y, z = symbols('x y z', real=True)
157          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1], coords=coords)
158          v = ga.mv('v', 'vector', f=True)
159  
160          make_zero = Sdop([])
161          assert make_zero * v == 0
162          assert make_zero * make_zero * v == 0
163          assert (make_zero + make_zero) * v == 0
164          assert (-make_zero) * v == 0
165  
166      def test_associativity_and_distributivity(self):
167          coords = x, y, z = symbols('x y z', real=True)
168          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1], coords=coords)
169          v = ga.mv('v', 'vector', f=True)
170          laplacian = Sdop((ga.grad * ga.grad).terms)
171  
172          # check addition distributes
173          assert (laplacian + 20) * v == laplacian * v + 20 * v != 0
174          assert (20 + laplacian) * v == laplacian * v + 20 * v != 0
175          # check subtraction distributes
176          assert (laplacian - 20) * v == laplacian * v - 20 * v != 0
177          assert (20 - laplacian) * v == 20 * v - laplacian * v != 0
178          # check unary subtraction distributes
179          assert (-laplacian) * v == -(laplacian * v) != 0
180          # check multiplication is associative
181          assert (20 * laplacian) * v == 20 * (laplacian * v) != 0
182          assert (laplacian * laplacian) * v == laplacian * (laplacian * v) != 0
183  
184  
185      def test_misc(self):
186          """ Other miscellaneous tests """
187          coords = x, y, z = symbols('x y z', real=True)
188          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1], coords=coords)
189          laplacian = ga.sdop((ga.grad * ga.grad).terms)
190          lap2 = laplacian*laplacian
191  
192          # test comparison
193          assert lap2 == lap2
194          assert not (lap2 != lap2)
195          assert lap2 != laplacian
196          assert not (lap2 == laplacian)
197          assert lap2 != object()
198          assert not (lap2 == object())
199  
200      def chain_with_pdop(self):
201          x, y = symbols('x y', real=True)
202          s = Sdop([(x, Pdop(x)), (y, Pdop(y))])
203          # right-multiplication by Pdop chains only the pdiffs
204          sp = s * Pdop(x)
205          assert sp == Sdop([(x, Pdop(x) * Pdop(x)), (y, Pdop(y) * Pdop(x))])
206          # left-multiplcation by Pdop invokes the product rule
207          ps = Pdop(x) * s
208          assert ps == Sdop([(x, Pdop(x) * Pdop(x)), (1, Pdop(x)), (y, Pdop(y) * Pdop(x))])
209  
210          # implicit multiplication
211          assert ps == Pdop(x)(s)
212          assert sp == s(Pdop(x))
213  
214          # no-op pdop
215          assert s == Pdop({})(s)
216          assert s == s(Pdop({}))
217  
218      def chain_with_mv(self):
219          coords = x, y, z = symbols('x y z', real=True)
220          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1], coords=coords)
221          s = Sdop([(x, Pdop(x)), (y, Pdop(y))])
222  
223          assert type(ex * s) is Sdop
224          assert type(s * ex) is Mv
225  
226          # type should be preserved even when the result is 0
227          assert type(ex * Sdop([])) is Sdop
228          assert type(Sdop([]) * ex) is Mv
229  
230          # As discussed with brombo, these operations are not well defined - if
231          # you need them, you should be using `Dop` not `Sdop`.
232          for op in [operator.xor, operator.or_, operator.lt, operator.gt]:
233              with pytest.raises(TypeError):
234                  op(ex, s)
235              with pytest.raises(TypeError):
236                  op(s, ex)
237  
238      def test_constructor_errors(self):
239          coords = x, y, z = symbols('x y z', real=True)
240          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1], coords=coords)
241  
242          # list lengths must match
243          with pytest.raises(ValueError, match='same length'):
244              Sdop([ex], [])
245  
246          # not a symbol or list
247          with pytest.raises(TypeError, match='symbol or sequence is required'):
248              Sdop(1)
249  
250          # not a pair of lists
251          with pytest.raises(TypeError, match='must be lists'):
252              Sdop([], 1)
253  
254          # too few arguments
255          with pytest.raises(TypeError, match='0 were given'):
256              Sdop()
257          # too many arguments
258          with pytest.raises(TypeError, match='3 were given'):
259              Sdop(1, 2, 3)
260  
261  
262  class TestPdop(object):
263  
264      def test_deprecation(self):
265          coords = x, y, z = symbols('x y z', real=True)
266          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1], coords=coords)
267  
268          # passing `None` is a deprecate way to spell `{}`
269          with pytest.warns(DeprecationWarning):
270              p = Pdop(None)
271          assert p == Pdop({})
272  
273          with pytest.warns(DeprecationWarning):
274              ga.Pdop_identity
275          with pytest.warns(DeprecationWarning):
276              ga.Pdiffs
277  
278      def test_misc(self):
279          """ Other miscellaneous tests """
280          coords = x, y, z = symbols('x y z', real=True)
281          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1], coords=coords)
282  
283          pxa = ga.pdop({x: 1})
284          pxb = ga.pdop({x: 1})
285          p1 = ga.pdop({})
286  
287          # test comparison
288          assert pxa == pxb
289          assert not (pxa != pxb)
290          assert p1 != pxa
291          assert not (p1 == pxa)
292          assert pxa != object()
293          assert not (pxa == object())
294  
295      def test_multiply(self):
296          coords = x, y, z = symbols('x y z', real=True)
297          ga, ex, ey, ez = Ga.build('e*x|y|z', g=[1, 1, 1], coords=coords)
298  
299          p = Pdop(x)
300          assert x * p == Sdop([(x, p)])
301          assert ex * p == Sdop([(ex, p)])
302  
303          assert p * x == p(x) == S(1)
304          assert p * ex == p(ex) == S(0)
305          assert type(p(ex)) is Mv
306  
307          # These are not defined for consistency with Sdop
308          for op in [operator.xor, operator.or_, operator.lt, operator.gt]:
309              with pytest.raises(TypeError):
310                  op(ex, p)
311              with pytest.raises(TypeError):
312                  op(p, ex)
313  
314      def test_constructor_errors(self):
315          # not a symbol or dict
316          with pytest.raises(TypeError, match='dictionary or symbol is required'):
317              Pdop(1)