I was experimenting with the second option so far and have a follow up question.
In prime extension fields numbers are represented as polynomials and all calculations are done using these polynomials.
In GF(2^2):
0 = 0 -> (0*x + 0) -> [0, 0]
1 = 1 -> (0*x + 1) -> [0, 1]
2 = x -> (1*x + 0) -> [1, 0]
3 = x + 1 -> (1*x + 1) -> [1, 1]
Therefore 2 * 2
is
2 * 2
= (1*x + 0) * (1*x + 0) -> [1, 0] * [1, 0]
= x * x
= x^2 -> [1, 0, 0]
How could I achieve this behaviour with Nx functions?
There is element-wise multiplication, but I need that “overflow” into the next degree.
I found the operation Nx. outer(t1, t2)
, which can give me a matrix of element * row
results like so:
# [1,0] * [1,0]
Nx.outer(Nx.tensor([1,0]), Nx.tensor([1,0]))
#Nx.Tensor<
s64[2][2]
[
[1, 0], # 1 * [1,0]
[0, 0] # 0 * [1,0]
]
>
If I can pad and “shift” the first row to the left I can sum the tensor over the y axis:
# 2 * 2 = x * x = x^2 = [1,0,0]
Nx.outer(Nx.tensor([1,0]), Nx.tensor([1,0]))
# [1, 0],
# [0, 0]
|> Nx.pad(0, [{0,0,0}, {1,0,0}]) # insert single zero into all rows
# [0, 1, 0],
# [0, 0, 0]
|> shift([1,0]) # <-- made up: cycle first row by one to the left, second not at all
# [1, 0, 0],
# [0, 0, 0]
|> Nx.sum(axis: 1)
# [1, 0, 0]
another example:
# 3 * 2 = (x + 1) * x = x^2 + x = [1,1,0]
Nx.outer(Nx.tensor([1,1]), Nx.tensor([1,0]))
# [1, 0],
# [1, 0]
|> Nx.pad(0, [{0,0,0}, {1,0,0}]) # insert single zero into all rows
# [0, 1, 0],
# [0, 1, 0]
|> shift([1,0]) # <-- made up: cycle first row by one to the left, second not at all
# [1, 0, 0],
# [0, 1, 0]
|> Nx.sum(axis: 1)
# [1, 1, 0]
Is there a function that behaves like shift
?