Skip to content

Commit d2341a6

Browse files
committed
add some function
1 parent b81fa4e commit d2341a6

File tree

4 files changed

+687
-0
lines changed

4 files changed

+687
-0
lines changed

README.md

+96
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,98 @@
11
# clifford_algebra_pytorch
22
Simple pytorch implement to compute basic Clifford algebra (Geometric algebra) operation.
3+
4+
## Usage
5+
6+
### Use ga.GA
7+
8+
```python
9+
import torch
10+
from ga import GA
11+
12+
ga = GA(3)
13+
x = 3 + ga[1] + 2 * ga[2] - 2 * ga[3] + ga[1, 2] - ga[2, 3] + 2.5 * ga[1, 3] - ga[1, 2, 3]
14+
print(x)
15+
r = x * (~x) * ga.I
16+
print(r)
17+
q = ga.tensor_to_mv(torch.tensor([1, 1, 1, 1]))
18+
print(q)
19+
20+
# output
21+
# 3.0 + 1.0*[e1] + 2.0*[e2] + 1.0*[e1,e2] + -2.0*[e3] + 2.5*[e1,e3] + -1.0*[e2,e3] + -1.0*[e1,e2,e3]
22+
# -15.0*[e1,e2] + -19.0*[e1,e3] + 2.0*[e2,e3] + 27.25*[e1,e2,e3]
23+
# 1 + 1*[e1] + 1*[e2] + 1*[e1,e2]
24+
25+
```
26+
27+
### Use mv.MultiVector
28+
29+
```python
30+
from mv import MultiVector
31+
a = MultiVector(4, device='cpu')
32+
a.set_values(torch.tensor([1., 1, 2, 3, 4]), torch.tensor([0, 1, 2, 3, 4]))
33+
b = MultiVector(4, device='cpu')
34+
b.set_values(torch.tensor([1., -1]), torch.tensor([1, 2]))
35+
c = MultiVector(4, device='cpu')
36+
c.set_values(torch.ones(16, dtype=torch.float32), torch.arange(16))
37+
print(a + b)
38+
print(a - b)
39+
print(a * b)
40+
print(a / 2)
41+
42+
# output
43+
# 1.0 + 2.0*[e1] + 1.0*[e2] + 3.0*[e1,e2] + 4.0*[e3]
44+
# 1.0 + 3.0*[e2] + 3.0*[e1,e2] + 4.0*[e3]
45+
# -1.0 + -2.0*[e1] + -4.0*[e2] + -3.0*[e1,e2] + -4.0*[e1,e3] + 4.0*[e2,e3]
46+
# 0.5 + 0.5*[e1] + 1.0*[e2] + 1.5*[e1,e2] + 2.0*[e3]
47+
48+
```
49+
50+
### A simple Linear layer using geometry product
51+
52+
```python
53+
from layers import CFLiner
54+
bs = 2
55+
shape = 8
56+
module = CFLiner(shape, out_channels=4, bias=True)
57+
input = torch.ones((bs, shape))
58+
print('\n input \n', input, '\n input shape \n', input.shape)
59+
output = module(input)
60+
print('\n output \n', output, '\n output.shape \n', output.shape)
61+
62+
# output
63+
# input
64+
# tensor([[1., 1., 1., 1., 1., 1., 1., 1.],
65+
# [1., 1., 1., 1., 1., 1., 1., 1.]])
66+
# input shape
67+
# torch.Size([2, 8])
68+
69+
# output
70+
# tensor([[[ 0.0007, 0.0007, 0.0165, 0.0165, -0.0502, -0.0502, -0.0285,
71+
# -0.0285],
72+
# [-0.0107, -0.0107, -0.0236, -0.0236, -0.0301, -0.0301, 0.0165,
73+
# 0.0165],
74+
# [-0.0160, -0.0160, 0.0046, 0.0046, -0.0592, -0.0592, 0.0104,
75+
# 0.0104],
76+
# [ 0.0799, 0.0799, -0.0691, -0.0691, 0.0050, 0.0050, 0.0024,
77+
# 0.0024]],
78+
79+
# [[ 0.0007, 0.0007, 0.0165, 0.0165, -0.0502, -0.0502, -0.0285,
80+
# -0.0285],
81+
# [-0.0107, -0.0107, -0.0236, -0.0236, -0.0301, -0.0301, 0.0165,
82+
# 0.0165],
83+
# [-0.0160, -0.0160, 0.0046, 0.0046, -0.0592, -0.0592, 0.0104,
84+
# 0.0104],
85+
# [ 0.0799, 0.0799, -0.0691, -0.0691, 0.0050, 0.0050, 0.0024,
86+
# 0.0024]]], grad_fn=<AddBackward0>)
87+
# output.shape
88+
# torch.Size([2, 4, 8])
89+
90+
```
91+
92+
## Other
93+
94+
95+
## Reference
96+
97+
Original vanilla python implement: https://github.com/tdvance/GeometricAlgebra
98+

ga.py

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from numbers import Number
2+
from mv import MultiVector
3+
import math
4+
5+
6+
class GA:
7+
def __init__(self, n):
8+
self._dim = n
9+
10+
@property
11+
def n(self):
12+
return self._dim
13+
14+
def scalar(self, s=0.):
15+
x = MultiVector(self.n)
16+
x[0] = s
17+
return x
18+
19+
def blade(self, coef, *indices):
20+
x = self.scalar(coef)
21+
for i in indices:
22+
y = MultiVector(self.n)
23+
y[2 ** (i - 1)] = 1.
24+
x *= y
25+
return x
26+
27+
@property
28+
def I(self):
29+
x = MultiVector(self.n)
30+
x[-1] = 1.
31+
return x
32+
33+
def __getitem__(self, index):
34+
if isinstance(index, tuple):
35+
return self.blade(1., *index)
36+
return self.blade(1., index)
37+
38+
@classmethod
39+
def tensor_to_mv(cls, t, dim=None):
40+
if dim:
41+
shape = dim
42+
else:
43+
shape = math.ceil(math.sqrt(len(t)))
44+
x = MultiVector(dim=shape, data=t)
45+
return x
46+
47+
@classmethod
48+
def mv_to_tensor(cls, mv):
49+
return mv._data
50+
51+
52+
if __name__ == '__main__':
53+
import torch
54+
55+
ga = GA(3)
56+
x = 3 + ga[1] + 2 * ga[2] - 2 * ga[3] + ga[1, 2] - ga[2, 3] + 2.5 * ga[1, 3] - ga[1, 2, 3]
57+
print(x)
58+
r = x * (~x) * ga.I
59+
print(r)
60+
61+
q = ga.tensor_to_mv(torch.tensor([1, 1, 1, 1]))
62+
print(q)

layers.py

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import sys
2+
import torch
3+
4+
5+
def _precalculate_reversion_sign(dim=256):
6+
r = []
7+
for i in range(dim):
8+
a = bin(i).count('1')
9+
a = a % 4
10+
if a == 2 or a == 3:
11+
r.append(-1.)
12+
else:
13+
r.append(1.)
14+
return torch.tensor(r)
15+
16+
17+
def _blade_combine(a, b):
18+
if a == 0:
19+
return b, 1
20+
if b == 0:
21+
return a, 1
22+
c = a ^ b
23+
s = 1
24+
p = max(a, b)
25+
# d = MultiVector._rank(a)
26+
d = bin(a).count('1')
27+
e = 1
28+
while e <= p:
29+
if e & a:
30+
d -= 1
31+
if (d & 1) and (e & b):
32+
s = -s
33+
e *= 2
34+
return c, s
35+
36+
37+
def _precalculate_combine(dim=256):
38+
rs = torch.zeros((dim, dim), dtype=int)
39+
rk = torch.zeros((dim, dim), dtype=int)
40+
for i in range(dim):
41+
for j in range(dim):
42+
k, s = _blade_combine(i, j)
43+
rs[i, j] = s
44+
rk[i, j] = k
45+
return rs, rk
46+
47+
48+
class CFLiner(Module):
49+
def __init__(self, in_channels=8, out_channels=4, bias=True):
50+
'''
51+
input shape = [B, SHAPE]
52+
if out_channels = N, the output shape will be [B, N, SHAPE]
53+
'''
54+
super(CFLiner, self).__init__()
55+
self.shape = in_channels
56+
self.out_channels = out_channels
57+
self.use_bias = bias
58+
self.weight = torch.nn.Parameter(torch.randn(out_channels, self.shape))
59+
60+
if self.use_bias:
61+
self.bias = torch.nn.Parameter(torch.randn(self.shape))
62+
63+
self._init_params()
64+
65+
def _init_params(self):
66+
torch.nn.init.normal_(self.weight, 0, 0.01)
67+
if self.use_bias:
68+
torch.nn.init.zeros_(self.bias)
69+
70+
# pre-calculate some used tensor
71+
rs, rk = _precalculate_combine(self.shape)
72+
signs = _precalculate_reversion_sign(self.shape)
73+
self.register_buffer('rs', rs)
74+
self.register_buffer('rk', rk)
75+
self.register_buffer('signs', signs)
76+
77+
def A_geoProduct_B(self, A, B):
78+
# b is weight
79+
bs, shape = A.shape
80+
R = torch.zeros((bs, self.out_channels, shape), device=A.device)
81+
for k in range(self.out_channels):
82+
for i in range(self.shape):
83+
for j in range(self.shape):
84+
R[:, k, self.rk[i, j]] += A[:, i] * B[k, j] * self.rs[i, j]
85+
return R
86+
87+
def forward(self, input):
88+
b, s = input.shape
89+
if s != self.shape:
90+
sys.exit(f'Wrong Input Features. Please use tensor with {self.shape} Input Features')
91+
output = self.A_geoProduct_B(input, self.weight)
92+
if self.use_bias:
93+
output += self.bias
94+
return output
95+
96+
97+
if __name__ == '__main__':
98+
bs = 2
99+
shape = 8
100+
module = CFLiner(shape, out_channels=4, bias=True)
101+
input = torch.ones((bs, shape))
102+
print('\n input \n', input, '\n input shape \n', input.shape)
103+
output = module(input)
104+
print('\n output \n', output, '\n output.shape \n', output.shape)
105+
106+
# from torch.autograd.gradcheck import gradcheck
107+
# moduleConv = CFLiner(shape, 3)
108+
# input = torch.randn((bs, shape), dtype=torch.double, requires_grad=True)
109+
# test = gradcheck(moduleConv, input, eps=1e-2, atol=1e-2)
110+
# print("Are the gradients correct: ", test)

0 commit comments

Comments
 (0)