Skip to content

Commit 6468e68

Browse files
committed
Release 0.1.1
multivectors.py: * Small fix in module docstring + Blade.__(eq|ne|[lg][te])__ * __eq__ and __ne__ defined for all objects * __[lg][te]__ defined only for scalar blades * Small fix in Blade.__rpow__ docstring: cannot use isclose on Blade + MultiVector.__(eq|ne)__ - doctest.testmod is done elsewhere README.md: * Follow module docstring +docs.md: * More formal documentation, mostly of geometric algebra * Contains code too, which is doctested +run_tests.py: * Exits with a 1 status if doctesting the module or any MD has failures * Used for workflow
1 parent 401d02c commit 6468e68

File tree

4 files changed

+305
-8
lines changed

4 files changed

+305
-8
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pip install multivectors
1313
>>> v = 2*x + 3*y + 4*z
1414
>>> print(v.rotate(math.pi/2, x * y))
1515
(-3.00x + 2.00y + 4.00z)
16+
1617
```
1718

1819
For more see [the docs](https://github.com/Kenny2github/MultiVectors/wiki)

docs.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
Welcome to MultiVectors documentation!
2+
3+
- [Concepts](#concepts)
4+
- [Bases and geometric products](#bases-and-geometric-products)
5+
- [Blades and multivectors](#blades-and-multivectors)
6+
- [The choose operator, inner (dot) and outer (wedge) products](#the-choose-operator-inner-dot-and-outer-wedge-products)
7+
- [Euler's formula applied to multivectors](#eulers-formula-applied-to-multivectors)
8+
- [Applied](#applied)
9+
- [Blade](#blade)
10+
- [MultiVector](#multivector)
11+
12+
## Concepts
13+
Here are some concepts to bear in mind. This is a *very* brief introduction to geometric algebra; a longer one is [here](https://www.youtube.com/watch?v=60z_hpEAtD8).
14+
15+
### Bases and geometric products
16+
* Every dimension of space comes with a *basis vector*: an arrow of length 1 unit pointed towards the positive end of the axis.
17+
* In our 3-dimensional world, there are the basis vectors **, **, and **, which are pointed towards the positive ends of the *x*, *y*, and *z* axes respectively.
18+
* The fourth dimensional basis vector is **. In higher dimensions, usually all bases are numbered instead of lettered: the fifth dimensional basis vectors are *ê₁*, *ê₂*, *ê₃*, *ê₄*, and *ê₅*.
19+
* The *geometric product* of two basis vectors is their simple multiplication - not the dot or cross product! The geometric product of ** and ** is simply *x̂ŷ*.
20+
* The geometric product of two basis vectors is a *basis plane*. *x̂ŷ* is the basis plane of the *x-y* plane. The other basis planes are *ŷẑ* and *x̂ẑ*.
21+
* The geometric product of three basis vectors is a *basis volume*. *x̂ŷẑ* is the basis volume of 3D space, which only has one basis volume, but also one of the four basis volumes of 4D space.
22+
* The geometric product of a basis vector with itself is 1. That is, *x̂x̂* = **² = *ŷŷ* = **² = *ẑẑ* = **² = 1
23+
* The geometric product of different basis vectors *anticommutes*: *x̂ŷ* = -*ŷx̂* and *x̂ŷẑ* = -*x̂ẑŷ* = *ẑx̂ŷ* = -*ẑŷx̂*
24+
25+
### Blades and multivectors
26+
* A *blade* is a *scaled basis*: a *scalar* (regular real number) multiplied by a *basis*. For example, 3*x̂ŷ* is a blade. Note that this means all bases are blades scaled by 1.
27+
* A *k-blade* is a blade of *grade k*: the geometric product of a scalar and *k* different basis vectors. 3*x̂ŷ* has grade 2; it is a 2-blade.
28+
* Scalars are 0-blades - blades consisting of *no* basis vectors.
29+
* A *multivector* is a sum of multiple blades. For example, 1 + 2** - 3*ŷẑ* is a multivector.
30+
* The sum of multiple (and only) 1-blades is usually called a simple *vector*. For example, 3** + 2** is a vector.
31+
* The sum of multiple (and only) 2-blades is a *bivector*. Basis planes are also known as *basis bivectors*. For example, 3*x̂ŷ* is a bivector.
32+
* The rules of linearity, associativity and distributivity in multiplication apply, as long as order of arguments is maintained:
33+
* (**)(*aŷ*) = *ax̂ŷ* (linearity, for scalar *a*)
34+
* (*x̂ŷ*)** = **(*ŷẑ*) (associativity)
35+
* **(** + **) = *x̂ŷ* + *x̂ẑ* (distributivity)
36+
* (** + **)(*ax̂*) = *a*(** + **)(**) (linearity) = a(*ŷx̂* + *ẑx̂*) (distributivity) = a(-*x̂ŷ* - *x̂ẑ*) (anticommutativity)
37+
* However, some things which require commutativity break down, such as the binomial theorem.
38+
39+
### The choose operator, inner (dot) and outer (wedge) products
40+
**V*⟩ₙ *chooses* all *n*-blades from the multivector *V*. For example, if *V* = 1 + 2** + 3** + 4*x̂ŷ* + 5*ŷẑ*, then ⟨*V*⟩₀ = 1 and ⟨*V*⟩₁ = 2** + 3** and ⟨*V*⟩₂ = 4*x̂ŷ* + 5*ŷẑ*
41+
* *U* · *V* = ⟨*UV*⟩ₙ where *U* is of grade *r*, *V* is of grade *s*, and *n* = |*r - s*|. This is the *inner* or *dot product*.
42+
* The dot product associates and distributes the same way the geometric product does.
43+
* From this, for arbitrary vectors *ax̂* + *bŷ* and *cx̂* + *dŷ*, we recover the typical meaning of the dot product:<br/>![Derivation of normal vector dot product](https://cdn.discordapp.com/attachments/417244106876780544/859663520747356170/dot_product.png)
44+
* *U**V* = ⟨*UV*⟩ₙ where *U* is of grade *r*, *V* is of grade *s*, and *n* = *r + s*. This is the *outer* or *wedge product*.
45+
* The outer product associates and distributes the same way the geometric product does.
46+
* From this, for arbitrary vectors *ax̂* + *bŷ* + *cẑ* and *dx̂* + *eŷ* + *fẑ*, we recover something that looks very much like a cross product:<br/>![Derivation of normal vector cross product](https://cdn.discordapp.com/attachments/417244106876780544/859663658762108968/wedge_product.png)
47+
48+
### Euler's formula applied to multivectors
49+
* *e* to the power (*θB*) = cos(*θ*) + *B* sin(*θ*) where θ is a scalar in radians and *B* is a basis multivector.
50+
* For reasons that are beyond my power to explain, the rotation of a multivector *V* by *θ* through the plane *B* is e\*\*(-*θB*/2) * V * e\*\*(*θB*/2)
51+
52+
## Applied
53+
All of the above concepts are applied in this library.
54+
55+
### Blade
56+
* `multivectors.Blade(*bases, scalar=a)` represents a blade with **0-indexed bases** `bases` multiplied by a real scalar `a`. For example, `Blade(0, 1, 3)` represents the basis volume *x̂ŷŵ*, with 0 meaning **.
57+
* Basis names can be *swizzled* on the `Blade` class itself: the above could have been done with `Blade.xyw`. For basis vectors beyond **, use **ₙ: `Blade.e1e3e4e5` is a basis 4-vector in 5D space. `Blade._` is a 0-blade: a scalar, but with `Blade` type.
58+
* Basis indices can also be used: `Blade[:4]` = `Blade[0, 1, 2, 3]` = `Blade(0, 1, 2, 3)` = `Blade.xyzw`
59+
* To take it one step further, basis names can be swizzled on the module itself: `multivector.xyz` returns `multivectors.Blade.xyz`. This also works when importing: `from multivectors import x, y, z, xy, xz, yz` will work just fine.
60+
* The rules of arithmetic with blades as described above apply:
61+
```python
62+
>>> from multivectors import x, y, z
63+
>>> x * 2 + x * 3
64+
5.0 * Blade.x
65+
>>> x * 5 * y
66+
5.0 * Blade.xy
67+
>>> (x * y) * z == x * (y * z)
68+
True
69+
70+
```
71+
* You can query the grade of a blade: `Blade.xy.grade` is 2.
72+
73+
### MultiVector
74+
* `multivectors.MultiVector.from_terms(*terms)` represents a **sum of `terms`**. You normally should not be constructing this class; it is created from summation involving `Blade`s.
75+
* Basis names can be swizzled on class **instances** to get the coefficient of that basis:
76+
```python
77+
>>> from multivectors import x, y
78+
>>> (x + 2*y).x
79+
1.0
80+
81+
```
82+
* Basis indices can also be used:
83+
```python
84+
>>> from multivectors import xy, yz
85+
>>> (xy + 2*yz)[0, 1]
86+
1.0
87+
88+
```
89+
* Choosing by grade is supported:
90+
```python
91+
>>> from multivectors import x, y, xy, yz
92+
>>> V = 1 + 2*x + 3*y + 4*xy + 5*yz
93+
>>> V % 0
94+
1.0
95+
>>> V % 1
96+
(2.0 * Blade.x + 3.0 * Blade.y)
97+
>>> V % 2
98+
(4.0 * Blade.xy + 5.0 * Blade.yz)
99+
100+
```
101+
* The rules of arithmetic with multivectors as described above apply:
102+
```python
103+
>>> from multivectors import x, y, z
104+
>>> x * (y + z)
105+
(1.0 * Blade.xy + 1.0 * Blade.xz)
106+
>>> (y + z) * (2 * x)
107+
(-2.0 * Blade.xy + -2.0 * Blade.xz)
108+
>>> (1*x + 2*y) * (3*x + 4*y)
109+
(11.0 + -2.0 * Blade.xy)
110+
111+
```
112+
* The extra products apply too:
113+
```python
114+
>>> from multivectors import x, y, z
115+
>>> 1*3 + 2*4
116+
11
117+
>>> (1*x + 2*y) @ (3*x + 4*y)
118+
11.0
119+
>>> (1*5 - 2*4, 1*6 - 3*4, 2*6 - 3*5)
120+
(-3, -6, -3)
121+
>>> (1*x + 2*y + 3*z) ^ (4*x + 5*y + 6*z)
122+
(-3.0 * Blade.xy + -6.0 * Blade.xz + -3.0 * Blade.yz)
123+
124+
```
125+
* A convenience method is provided to rotate multivectors:
126+
```python
127+
>>> from math import radians
128+
>>> from multivectors import x, y, z, xz
129+
>>> round((3*x + 2*y + 4*z).rotate(radians(90), xz), 2)
130+
(-4.0 * Blade.x + 2.0 * Blade.y + 3.0 * Blade.z)
131+
132+
```

multivectors.py

Lines changed: 163 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
>>> v = 2*x + 3*y + 4*z
1515
>>> print(v.rotate(math.pi/2, x * y))
1616
(-3.00x + 2.00y + 4.00z)
17+
1718
```
1819
1920
For more see [the docs](https://github.com/Kenny2github/MultiVectors/wiki)
@@ -34,7 +35,7 @@
3435
'w'
3536
]
3637

37-
__version__ = '0.1.0'
38+
__version__ = '0.1.1'
3839

3940
NAMES = 'xyzw'
4041

@@ -328,6 +329,129 @@ def __str__(self) -> str:
328329
r = ''.join(NAMES[i] for i in self.bases)
329330
return '%.2f%s' % (self.scalar, r)
330331

332+
# Relational operators
333+
334+
def __eq__(self, other: Simple) -> bool:
335+
"""Compare equality of two objects.
336+
337+
Returns: True if this is a scalar blade equal to the real.
338+
Returns: True if this blade's bases and scalar equal the other's.
339+
Returns: False for all other cases or types.
340+
341+
```python
342+
>>> Blade._ * 1 == 1
343+
True
344+
>>> Blade.xy == Blade.xy
345+
True
346+
>>> Blade.xy == Blade.x + Blade.y
347+
False
348+
349+
```
350+
"""
351+
if isinstance(other, Real):
352+
if self.bases != ():
353+
return False
354+
return self.scalar == other
355+
if isinstance(other, Blade):
356+
return (self.bases, self.scalar) == (other.bases, other.scalar)
357+
return False
358+
359+
def __ne__(self, other: Simple) -> bool:
360+
"""Compare inequality of two objects.
361+
362+
Returns: False if this is a scalar blade equal to the real.
363+
Returns: False if this blade's bases and scalar equal the other's.
364+
Returns: True for all other cases or types.
365+
366+
```python
367+
>>> Blade._ * 1 != 2
368+
True
369+
>>> Blade.xy * 1 != Blade.xy * 2
370+
True
371+
>>> Blade.xy != Blade.x + Blade.y
372+
True
373+
374+
```
375+
"""
376+
return not (self == other)
377+
378+
def __lt__(self, other: Real) -> bool:
379+
"""Compare this blade less than an object.
380+
381+
Returns: True if this is a scalar blade less than the real.
382+
Returns: NotImplemented for all other types.
383+
384+
```python
385+
>>> Blade._ * 1 < 2
386+
True
387+
>>> Blade.x * 1 < 2
388+
Traceback (most recent call last):
389+
...
390+
TypeError: '<' not supported between instances of 'Blade' and 'int'
391+
392+
```
393+
"""
394+
if not (isinstance(other, Real) and self.bases == ()):
395+
return NotImplemented
396+
return self.scalar < other
397+
398+
def __gt__(self, other: Real) -> bool:
399+
"""Compare this blade greater than an object.
400+
401+
Returns: True if this is a scalar blade greater than the real.
402+
Returns: NotImplemented for all other types.
403+
404+
```python
405+
>>> Blade._ * 2 > 1
406+
True
407+
>>> Blade.x * 2 > 1
408+
Traceback (most recent call last):
409+
...
410+
TypeError: '>' not supported between instances of 'Blade' and 'int'
411+
412+
"""
413+
if not (isinstance(other, Real) and self.bases == ()):
414+
return NotImplemented
415+
return self != other and not (self < other)
416+
417+
def __le__(self, other: Real) -> bool:
418+
"""Compare this blade less than or equal to an object.
419+
420+
Returns: True if this is a scalar blade not greater than the real.
421+
Returns: NotImplemented for all other types.
422+
423+
```python
424+
>>> Blade._ * 1 <= 2
425+
True
426+
>>> Blade.x * 1 <= 2
427+
Traceback (most recent call last):
428+
...
429+
TypeError: '<=' not supported between instances of 'Blade' and 'int'
430+
431+
"""
432+
if not (isinstance(other, Real) and self.bases == ()):
433+
return NotImplemented
434+
return self < other or self == other
435+
436+
def __ge__(self, other: Real) -> bool:
437+
"""Compare this blade greater than or equal to an object.
438+
439+
Returns: True if this is a scalar blade not less than the real.
440+
Returns: NotImplemented for all other types.
441+
442+
```python
443+
>>> Blade._ * 2 >= 1
444+
True
445+
>>> Blade.x * 2 >= 1
446+
Traceback (most recent call last):
447+
...
448+
TypeError: '>=' not supported between instances of 'Blade' and 'int'
449+
450+
"""
451+
if not (isinstance(other, Real) and self.bases == ()):
452+
return NotImplemented
453+
return not (self < other)
454+
331455
# Binary operators
332456

333457
def __add__(self, other: MVV) -> MV:
@@ -571,12 +695,11 @@ def __rpow__(self, other: Real) -> Simple:
571695
= cos(a ln x) + sin(a ln x) * I
572696
573697
```python
574-
>>> from math import e, pi
575-
>>> from cmath import isclose
698+
>>> from cmath import e, pi, isclose
576699
>>> round(e ** (pi / 4 * Blade.xy), 2)
577700
(0.71 + 0.71 * Blade.xy)
578701
>>> # in 2D, xy is isomorphic to i
579-
>>> isclose(e ** (pi * Blade.xy), -1)
702+
>>> round(e ** (pi * Blade.xy), 9) == -1
580703
True
581704
>>> isclose(e ** (pi * 1j), -1)
582705
True
@@ -925,6 +1048,42 @@ def __str__(self) -> str:
9251048
"""
9261049
return '(' + ' + '.join(map(str, self.terms)) + ')'
9271050

1051+
# Relational operators
1052+
1053+
def __eq__(self, other: MultiVector) -> bool:
1054+
"""Compare equality of two objects.
1055+
1056+
Returns: True if all terms of this multivector are equal to the other.
1057+
Returns: False for all other cases or types.
1058+
1059+
```python
1060+
>>> Blade.x + Blade.y == Blade.y + Blade.x
1061+
True
1062+
>>> Blade.x + 2 * Blade.y == 2 * Blade.x + Blade.y
1063+
False
1064+
1065+
```
1066+
"""
1067+
if not isinstance(other, MultiVector):
1068+
return False
1069+
return self.termdict == other.termdict
1070+
1071+
def __ne__(self, other: MultiVector) -> bool:
1072+
"""Compare inequality of two objects.
1073+
1074+
Returns: False if all terms of this multivector are equal to the other.
1075+
Returns: True for all other cases or types.
1076+
1077+
```python
1078+
>>> Blade.x + Blade.y != Blade.y + Blade.x
1079+
False
1080+
>>> Blade.x + 2 * Blade.y != 2 * Blade.x + Blade.y
1081+
True
1082+
1083+
```
1084+
"""
1085+
return not (self == other)
1086+
9281087
# Binary operators
9291088

9301089
def __add__(self, other: MVV) -> MV:
@@ -1361,7 +1520,3 @@ def __getattr__(name: str) -> Blade:
13611520
Unlike Blade.__getattr__, this rejects invalid characters in the name.
13621521
"""
13631522
return Blade(*names_to_idxs(name, True))
1364-
1365-
if __name__ == "__main__":
1366-
import doctest
1367-
doctest.testmod()

run_tests.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import sys
2+
import doctest
3+
import multivectors
4+
5+
fails = doctest.testmod(multivectors)[0]
6+
fails += doctest.testfile('docs.md')[0]
7+
fails += doctest.testfile('README.md')[0]
8+
if fails > 0:
9+
sys.exit(1)

0 commit comments

Comments
 (0)