# Array Arithmetic¶

After creating a FieldArray subclass and one or two arrays, nearly any arithmetic operation can be performed using normal NumPy ufuncs or Python operators.

In the sections below, the finite field $$\mathrm{GF}(3^5)$$ and arrays $$x$$ and $$y$$ are used.

In [1]: GF = galois.GF(3**5)

In [2]: x = GF([184, 25, 157, 31]); x
Out[2]: GF([184,  25, 157,  31], order=3^5)

In [3]: y = GF([179, 9, 139, 27]); y
Out[3]: GF([179,   9, 139,  27], order=3^5)


## Standard arithmetic¶

NumPy ufuncs are universal functions that operate on scalars. Unary ufuncs operate on a single scalar and binary ufuncs operate on two scalars. NumPy extends the scalar operation of ufuncs to operate on arrays in various ways. This extensibility enables NumPy broadcasting.

Expand any section for more details.

Addition: x + y == np.add(x, y)
In [4]: x
Out[4]: GF([184,  25, 157,  31], order=3^5)

In [5]: y
Out[5]: GF([179,   9, 139,  27], order=3^5)

In [6]: x + y
Out[6]: GF([ 81,   7, 215,  58], order=3^5)

Out[7]: GF([ 81,   7, 215,  58], order=3^5)

In [8]: x
Out[8]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [9]: y
Out[9]:
GF([2α^4 + α^2 + 2α + 2,                 α^2,  α^4 + 2α^3 + α + 1,
α^3], order=3^5)

In [10]: x + y
Out[10]:
GF([                       α^4,                     2α + 1,
2α^4 + α^3 + 2α^2 + 2α + 2,               2α^3 + α + 1], order=3^5)

Out[11]:
GF([                       α^4,                     2α + 1,
2α^4 + α^3 + 2α^2 + 2α + 2,               2α^3 + α + 1], order=3^5)

In [12]: x
Out[12]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [13]: y
Out[13]: GF([α^60,  α^2, α^59,  α^3], order=3^5)

In [14]: x + y
Out[14]: GF([  α^4, α^126, α^175,  α^28], order=3^5)

Out[15]: GF([  α^4, α^126, α^175,  α^28], order=3^5)

Additive inverse: -x == np.negative(x)
In [16]: x
Out[16]: GF([184,  25, 157,  31], order=3^5)

In [17]: -x
Out[17]: GF([ 98,  14, 206,  62], order=3^5)

In [18]: np.negative(x)
Out[18]: GF([ 98,  14, 206,  62], order=3^5)

In [19]: x
Out[19]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [20]: -x
Out[20]:
GF([       α^4 + α^2 + 2α + 2,               α^2 + α + 2,
2α^4 + α^3 + α^2 + 2α + 2,             2α^3 + 2α + 2], order=3^5)

In [21]: np.negative(x)
Out[21]:
GF([       α^4 + α^2 + 2α + 2,               α^2 + α + 2,
2α^4 + α^3 + α^2 + 2α + 2,             2α^3 + 2α + 2], order=3^5)

In [22]: x
Out[22]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [23]: -x
Out[23]: GF([ α^35, α^209,  α^55,  α^93], order=3^5)

In [24]: np.negative(x)
Out[24]: GF([ α^35, α^209,  α^55,  α^93], order=3^5)


In [25]: x
Out[25]: GF([184,  25, 157,  31], order=3^5)

In [26]: x + np.negative(x)
Out[26]: GF([0, 0, 0, 0], order=3^5)

In [27]: x
Out[27]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [28]: x + np.negative(x)
Out[28]: GF([0, 0, 0, 0], order=3^5)

In [29]: x
Out[29]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [30]: x + np.negative(x)
Out[30]: GF([0, 0, 0, 0], order=3^5)

Subtraction: x - y == np.subtract(x, y)
In [31]: x
Out[31]: GF([184,  25, 157,  31], order=3^5)

In [32]: y
Out[32]: GF([179,   9, 139,  27], order=3^5)

In [33]: x - y
Out[33]: GF([17, 16, 18,  4], order=3^5)

In [34]: np.subtract(x, y)
Out[34]: GF([17, 16, 18,  4], order=3^5)

In [35]: x
Out[35]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [36]: y
Out[36]:
GF([2α^4 + α^2 + 2α + 2,                 α^2,  α^4 + 2α^3 + α + 1,
α^3], order=3^5)

In [37]: x - y
Out[37]: GF([α^2 + 2α + 2, α^2 + 2α + 1,         2α^2,        α + 1], order=3^5)

In [38]: np.subtract(x, y)
Out[38]: GF([α^2 + 2α + 2, α^2 + 2α + 1,         2α^2,        α + 1], order=3^5)

In [39]: x
Out[39]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [40]: y
Out[40]: GF([α^60,  α^2, α^59,  α^3], order=3^5)

In [41]: x - y
Out[41]: GF([α^222, α^138, α^123,  α^69], order=3^5)

In [42]: np.subtract(x, y)
Out[42]: GF([α^222, α^138, α^123,  α^69], order=3^5)

Multiplication: x * y == np.multiply(x, y)
In [43]: x
Out[43]: GF([184,  25, 157,  31], order=3^5)

In [44]: y
Out[44]: GF([179,   9, 139,  27], order=3^5)

In [45]: x * y
Out[45]: GF([ 41, 225, 106, 123], order=3^5)

In [46]: np.multiply(x, y)
Out[46]: GF([ 41, 225, 106, 123], order=3^5)

In [47]: x
Out[47]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [48]: y
Out[48]:
GF([2α^4 + α^2 + 2α + 2,                 α^2,  α^4 + 2α^3 + α + 1,
α^3], order=3^5)

In [49]: x * y
Out[49]:
GF([   α^3 + α^2 + α + 2,    2α^4 + 2α^3 + α^2,  α^4 + 2α^2 + 2α + 1,
α^4 + α^3 + α^2 + 2α], order=3^5)

In [50]: np.multiply(x, y)
Out[50]:
GF([   α^3 + α^2 + α + 2,    2α^4 + 2α^3 + α^2,  α^4 + 2α^2 + 2α + 1,
α^4 + α^3 + α^2 + 2α], order=3^5)

In [51]: x
Out[51]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [52]: y
Out[52]: GF([α^60,  α^2, α^59,  α^3], order=3^5)

In [53]: x * y
Out[53]: GF([α^216,  α^90, α^235, α^217], order=3^5)

In [54]: np.multiply(x, y)
Out[54]: GF([α^216,  α^90, α^235, α^217], order=3^5)

Scalar multiplication: x * 4 == np.multiply(x, 4)

Scalar multiplication is essentially repeated addition. It is the “multiplication” of finite field elements and integers. The integer value indicates how many additions of the field element to sum.

In [55]: x
Out[55]: GF([184,  25, 157,  31], order=3^5)

In [56]: x * 4
Out[56]: GF([184,  25, 157,  31], order=3^5)

In [57]: np.multiply(x, 4)
Out[57]: GF([184,  25, 157,  31], order=3^5)

In [58]: x + x + x + x
Out[58]: GF([184,  25, 157,  31], order=3^5)

In [59]: x
Out[59]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [60]: x * 4
Out[60]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [61]: np.multiply(x, 4)
Out[61]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [62]: x + x + x + x
Out[62]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [63]: x
Out[63]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [64]: x * 4
Out[64]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [65]: np.multiply(x, 4)
Out[65]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [66]: x + x + x + x
Out[66]: GF([α^156,  α^88, α^176, α^214], order=3^5)


In finite fields $$\mathrm{GF}(p^m)$$, the characteristic $$p$$ is the smallest value when multiplied by any non-zero field element that results in 0.

In [67]: p = GF.characteristic; p
Out[67]: 3

In [68]: x * p
Out[68]: GF([0, 0, 0, 0], order=3^5)

In [69]: p = GF.characteristic; p
Out[69]: 3

In [70]: x * p
Out[70]: GF([0, 0, 0, 0], order=3^5)

In [71]: p = GF.characteristic; p
Out[71]: 3

In [72]: x * p
Out[72]: GF([0, 0, 0, 0], order=3^5)

Multiplicative inverse: y ** -1 == np.reciprocal(y)
In [73]: y
Out[73]: GF([179,   9, 139,  27], order=3^5)

In [74]: y ** -1
Out[74]: GF([ 71, 217, 213, 235], order=3^5)

In [75]: GF(1) / y
Out[75]: GF([ 71, 217, 213, 235], order=3^5)

In [76]: np.reciprocal(y)
Out[76]: GF([ 71, 217, 213, 235], order=3^5)

In [77]: y
Out[77]:
GF([2α^4 + α^2 + 2α + 2,                 α^2,  α^4 + 2α^3 + α + 1,
α^3], order=3^5)

In [78]: y ** -1
Out[78]:
GF([   2α^3 + α^2 + 2α + 2,        2α^4 + 2α^3 + 1,
2α^4 + α^3 + 2α^2 + 2α, 2α^4 + 2α^3 + 2α^2 + 1], order=3^5)

In [79]: GF(1) / y
Out[79]:
GF([   2α^3 + α^2 + 2α + 2,        2α^4 + 2α^3 + 1,
2α^4 + α^3 + 2α^2 + 2α, 2α^4 + 2α^3 + 2α^2 + 1], order=3^5)

In [80]: np.reciprocal(y)
Out[80]:
GF([   2α^3 + α^2 + 2α + 2,        2α^4 + 2α^3 + 1,
2α^4 + α^3 + 2α^2 + 2α, 2α^4 + 2α^3 + 2α^2 + 1], order=3^5)

In [81]: y
Out[81]: GF([α^60,  α^2, α^59,  α^3], order=3^5)

In [82]: y ** -1
Out[82]: GF([α^182, α^240, α^183, α^239], order=3^5)

In [83]: GF(1) / y
Out[83]: GF([α^182, α^240, α^183, α^239], order=3^5)

In [84]: np.reciprocal(y)
Out[84]: GF([α^182, α^240, α^183, α^239], order=3^5)


Any array multiplied by its multiplicative inverse results in one.

In [85]: y * np.reciprocal(y)
Out[85]: GF([1, 1, 1, 1], order=3^5)

In [86]: y * np.reciprocal(y)
Out[86]: GF([1, 1, 1, 1], order=3^5)

In [87]: y * np.reciprocal(y)
Out[87]: GF([1, 1, 1, 1], order=3^5)

Division: x / y == x // y == np.divide(x, y)
In [88]: x
Out[88]: GF([184,  25, 157,  31], order=3^5)

In [89]: y
Out[89]: GF([179,   9, 139,  27], order=3^5)

In [90]: x / y
Out[90]: GF([237,  56, 122, 126], order=3^5)

In [91]: x // y
Out[91]: GF([237,  56, 122, 126], order=3^5)

In [92]: np.divide(x, y)
Out[92]: GF([237,  56, 122, 126], order=3^5)

In [93]: x
Out[93]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [94]: y
Out[94]:
GF([2α^4 + α^2 + 2α + 2,                 α^2,  α^4 + 2α^3 + α + 1,
α^3], order=3^5)

In [95]: x / y
Out[95]:
GF([ 2α^4 + 2α^3 + 2α^2 + α,                2α^3 + 2,
α^4 + α^3 + α^2 + α + 2,        α^4 + α^3 + 2α^2], order=3^5)

In [96]: x // y
Out[96]:
GF([ 2α^4 + 2α^3 + 2α^2 + α,                2α^3 + 2,
α^4 + α^3 + α^2 + α + 2,        α^4 + α^3 + 2α^2], order=3^5)

In [97]: np.divide(x, y)
Out[97]:
GF([ 2α^4 + 2α^3 + 2α^2 + α,                2α^3 + 2,
α^4 + α^3 + α^2 + α + 2,        α^4 + α^3 + 2α^2], order=3^5)

In [98]: x
Out[98]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [99]: y
Out[99]: GF([α^60,  α^2, α^59,  α^3], order=3^5)

In [100]: x / y
Out[100]: GF([ α^96,  α^86, α^117, α^211], order=3^5)

In [101]: x // y
Out[101]: GF([ α^96,  α^86, α^117, α^211], order=3^5)

In [102]: np.divide(x, y)
Out[102]: GF([ α^96,  α^86, α^117, α^211], order=3^5)

Remainder: x % y == np.remainder(x, y)
In [103]: x
Out[103]: GF([184,  25, 157,  31], order=3^5)

In [104]: y
Out[104]: GF([179,   9, 139,  27], order=3^5)

In [105]: x % y
Out[105]: GF([0, 0, 0, 0], order=3^5)

In [106]: np.remainder(x, y)
Out[106]: GF([0, 0, 0, 0], order=3^5)

In [107]: x
Out[107]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [108]: y
Out[108]:
GF([2α^4 + α^2 + 2α + 2,                 α^2,  α^4 + 2α^3 + α + 1,
α^3], order=3^5)

In [109]: x % y
Out[109]: GF([0, 0, 0, 0], order=3^5)

In [110]: np.remainder(x, y)
Out[110]: GF([0, 0, 0, 0], order=3^5)

In [111]: x
Out[111]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [112]: y
Out[112]: GF([α^60,  α^2, α^59,  α^3], order=3^5)

In [113]: x % y
Out[113]: GF([0, 0, 0, 0], order=3^5)

In [114]: np.remainder(x, y)
Out[114]: GF([0, 0, 0, 0], order=3^5)

Divmod: divmod(x, y) == np.divmod(x, y)
In [115]: x
Out[115]: GF([184,  25, 157,  31], order=3^5)

In [116]: y
Out[116]: GF([179,   9, 139,  27], order=3^5)

In [117]: x // y, x % y
Out[117]: (GF([237,  56, 122, 126], order=3^5), GF([0, 0, 0, 0], order=3^5))

In [118]: divmod(x, y)
Out[118]: (GF([237,  56, 122, 126], order=3^5), GF([0, 0, 0, 0], order=3^5))

In [119]: np.divmod(x, y)
Out[119]: (GF([237,  56, 122, 126], order=3^5), GF([0, 0, 0, 0], order=3^5))

In [120]: x
Out[120]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [121]: y
Out[121]:
GF([2α^4 + α^2 + 2α + 2,                 α^2,  α^4 + 2α^3 + α + 1,
α^3], order=3^5)

In [122]: x // y, x % y
Out[122]:
(GF([ 2α^4 + 2α^3 + 2α^2 + α,                2α^3 + 2,
α^4 + α^3 + α^2 + α + 2,        α^4 + α^3 + 2α^2], order=3^5),
GF([0, 0, 0, 0], order=3^5))

In [123]: divmod(x, y)
Out[123]:
(GF([ 2α^4 + 2α^3 + 2α^2 + α,                2α^3 + 2,
α^4 + α^3 + α^2 + α + 2,        α^4 + α^3 + 2α^2], order=3^5),
GF([0, 0, 0, 0], order=3^5))

In [124]: np.divmod(x, y)
Out[124]:
(GF([ 2α^4 + 2α^3 + 2α^2 + α,                2α^3 + 2,
α^4 + α^3 + α^2 + α + 2,        α^4 + α^3 + 2α^2], order=3^5),
GF([0, 0, 0, 0], order=3^5))

In [125]: x
Out[125]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [126]: y
Out[126]: GF([α^60,  α^2, α^59,  α^3], order=3^5)

In [127]: x // y, x % y
Out[127]: (GF([ α^96,  α^86, α^117, α^211], order=3^5), GF([0, 0, 0, 0], order=3^5))

In [128]: divmod(x, y)
Out[128]: (GF([ α^96,  α^86, α^117, α^211], order=3^5), GF([0, 0, 0, 0], order=3^5))

In [129]: np.divmod(x, y)
Out[129]: (GF([ α^96,  α^86, α^117, α^211], order=3^5), GF([0, 0, 0, 0], order=3^5))

In [130]: q, r = divmod(x, y)

In [131]: q*y + r == x
Out[131]: array([ True,  True,  True,  True])

In [132]: q, r = divmod(x, y)

In [133]: q*y + r == x
Out[133]: array([ True,  True,  True,  True])

In [134]: q, r = divmod(x, y)

In [135]: q*y + r == x
Out[135]: array([ True,  True,  True,  True])

Exponentiation: x ** 3 == np.power(x, 3)
In [136]: x
Out[136]: GF([184,  25, 157,  31], order=3^5)

In [137]: x ** 3
Out[137]: GF([175,  76, 218, 192], order=3^5)

In [138]: np.power(x, 3)
Out[138]: GF([175,  76, 218, 192], order=3^5)

In [139]: x * x * x
Out[139]: GF([175,  76, 218, 192], order=3^5)

In [140]: x
Out[140]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [141]: x ** 3
Out[141]:
GF([ 2α^4 + α^2 + α + 1, 2α^3 + 2α^2 + α + 1,     2α^4 + 2α^3 + 2,
2α^4 + α^3 + α], order=3^5)

In [142]: np.power(x, 3)
Out[142]:
GF([ 2α^4 + α^2 + α + 1, 2α^3 + 2α^2 + α + 1,     2α^4 + 2α^3 + 2,
2α^4 + α^3 + α], order=3^5)

In [143]: x * x * x
Out[143]:
GF([ 2α^4 + α^2 + α + 1, 2α^3 + 2α^2 + α + 1,     2α^4 + 2α^3 + 2,
2α^4 + α^3 + α], order=3^5)

In [144]: x
Out[144]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [145]: x ** 3
Out[145]: GF([α^226,  α^22,  α^44, α^158], order=3^5)

In [146]: np.power(x, 3)
Out[146]: GF([α^226,  α^22,  α^44, α^158], order=3^5)

In [147]: x * x * x
Out[147]: GF([α^226,  α^22,  α^44, α^158], order=3^5)

Square root: np.sqrt(x)
In [148]: x
Out[148]: GF([184,  25, 157,  31], order=3^5)

In [149]: x.is_square()
Out[149]: array([ True,  True,  True,  True])

In [150]: z = np.sqrt(x); z
Out[150]: GF([102, 109,  14, 111], order=3^5)

In [151]: z ** 2 == x
Out[151]: array([ True,  True,  True,  True])

In [152]: x
Out[152]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [153]: x.is_square()
Out[153]: array([ True,  True,  True,  True])

In [154]: z = np.sqrt(x); z
Out[154]:
GF([α^4 + 2α^2 + α,  α^4 + α^3 + 1,    α^2 + α + 2,  α^4 + α^3 + α],
order=3^5)

In [155]: z ** 2 == x
Out[155]: array([ True,  True,  True,  True])

In [156]: x
Out[156]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [157]: x.is_square()
Out[157]: array([ True,  True,  True,  True])

In [158]: z = np.sqrt(x); z
Out[158]: GF([α^199, α^165, α^209, α^228], order=3^5)

In [159]: z ** 2 == x
Out[159]: array([ True,  True,  True,  True])

Logarithm: np.log(x) or x.log()

Compute the logarithm base $$\alpha$$, the primitive element of the field.

In [160]: y
Out[160]: GF([179,   9, 139,  27], order=3^5)

In [161]: z = np.log(y); z
Out[161]: array([60,  2, 59,  3])

In [162]: alpha = GF.primitive_element; alpha
Out[162]: GF(3, order=3^5)

In [163]: alpha ** z == y
Out[163]: array([ True,  True,  True,  True])

In [164]: y
Out[164]:
GF([2α^4 + α^2 + 2α + 2,                 α^2,  α^4 + 2α^3 + α + 1,
α^3], order=3^5)

In [165]: z = np.log(y); z
Out[165]: array([60,  2, 59,  3])

In [166]: alpha = GF.primitive_element; alpha
Out[166]: GF(α, order=3^5)

In [167]: alpha ** z == y
Out[167]: array([ True,  True,  True,  True])

In [168]: y
Out[168]: GF([α^60,  α^2, α^59,  α^3], order=3^5)

In [169]: z = np.log(y); z
Out[169]: array([60,  2, 59,  3])

In [170]: alpha = GF.primitive_element; alpha
Out[170]: GF(α, order=3^5)

In [171]: alpha ** z == y
Out[171]: array([ True,  True,  True,  True])


Compute the logarithm base $$\beta$$, a different primitive element of the field. See FieldArray.log() for more details.

In [172]: y
Out[172]: GF([179,   9, 139,  27], order=3^5)

In [173]: beta = GF.primitive_elements[-1]; beta
Out[173]: GF(242, order=3^5)

In [174]: z = y.log(beta); z
Out[174]: array([190, 208, 207, 191])

In [175]: beta ** z == y
Out[175]: array([ True,  True,  True,  True])

In [176]: y
Out[176]:
GF([2α^4 + α^2 + 2α + 2,                 α^2,  α^4 + 2α^3 + α + 1,
α^3], order=3^5)

In [177]: beta = GF.primitive_elements[-1]; beta
Out[177]: GF(2α^4 + 2α^3 + 2α^2 + 2α + 2, order=3^5)

In [178]: z = y.log(beta); z
Out[178]: array([190, 208, 207, 191])

In [179]: beta ** z == y
Out[179]: array([ True,  True,  True,  True])

In [180]: y
Out[180]: GF([α^60,  α^2, α^59,  α^3], order=3^5)

In [181]: beta = GF.primitive_elements[-1]; beta
Out[181]: GF(α^185, order=3^5)

In [182]: z = y.log(beta); z
Out[182]: array([190, 208, 207, 191])

In [183]: beta ** z == y
Out[183]: array([ True,  True,  True,  True])


## Ufunc methods¶

FieldArray instances support NumPy ufunc methods. Ufunc methods allow a user to apply a NumPy ufunc in a unique way across the target array. All arithmetic ufuncs are supported.

Expand any section for more details.

reduce()

The reduce methods reduce the input array’s dimension by one, applying the ufunc across one axis.

In [184]: x
Out[184]: GF([184,  25, 157,  31], order=3^5)

Out[185]: GF(7, order=3^5)

In [186]: x[0] + x[1] + x[2] + x[3]
Out[186]: GF(7, order=3^5)

In [187]: x
Out[187]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

Out[188]: GF(2α + 1, order=3^5)

In [189]: x[0] + x[1] + x[2] + x[3]
Out[189]: GF(2α + 1, order=3^5)

In [190]: x
Out[190]: GF([α^156,  α^88, α^176, α^214], order=3^5)

Out[191]: GF(α^126, order=3^5)

In [192]: x[0] + x[1] + x[2] + x[3]
Out[192]: GF(α^126, order=3^5)

In [193]: np.multiply.reduce(x)
Out[193]: GF(105, order=3^5)

In [194]: x[0] * x[1] * x[2] * x[3]
Out[194]: GF(105, order=3^5)

In [195]: np.multiply.reduce(x)
Out[195]: GF(α^4 + 2α^2 + 2α, order=3^5)

In [196]: x[0] * x[1] * x[2] * x[3]
Out[196]: GF(α^4 + 2α^2 + 2α, order=3^5)

In [197]: np.multiply.reduce(x)
Out[197]: GF(α^150, order=3^5)

In [198]: x[0] * x[1] * x[2] * x[3]
Out[198]: GF(α^150, order=3^5)

accumulate()

The accumulate methods accumulate the result of the ufunc across a specified axis.

In [199]: x
Out[199]: GF([184,  25, 157,  31], order=3^5)

Out[200]: GF([184, 173,  57,   7], order=3^5)

In [201]: GF([x[0], x[0] + x[1], x[0] + x[1] + x[2], x[0] + x[1] + x[2] + x[3]])
Out[201]: GF([184, 173,  57,   7], order=3^5)

In [202]: x
Out[202]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

Out[203]:
GF([2α^4 + 2α^2 + α + 1,      2α^4 + α^2 + 2,            2α^3 + α,
2α + 1], order=3^5)

In [204]: GF([x[0], x[0] + x[1], x[0] + x[1] + x[2], x[0] + x[1] + x[2] + x[3]])
Out[204]:
GF([2α^4 + 2α^2 + α + 1,      2α^4 + α^2 + 2,            2α^3 + α,
2α + 1], order=3^5)

In [205]: x
Out[205]: GF([α^156,  α^88, α^176, α^214], order=3^5)

Out[206]: GF([α^156, α^213, α^196, α^126], order=3^5)

In [207]: GF([x[0], x[0] + x[1], x[0] + x[1] + x[2], x[0] + x[1] + x[2] + x[3]])
Out[207]: GF([α^156, α^213, α^196, α^126], order=3^5)

In [208]: np.multiply.accumulate(x)
Out[208]: GF([184,   9, 211, 105], order=3^5)

In [209]: GF([x[0], x[0] * x[1], x[0] * x[1] * x[2], x[0] * x[1] * x[2] * x[3]])
Out[209]: GF([184,   9, 211, 105], order=3^5)

In [210]: np.multiply.accumulate(x)
Out[210]:
GF([      2α^4 + 2α^2 + α + 1,                       α^2,
2α^4 + α^3 + 2α^2 + α + 1,           α^4 + 2α^2 + 2α], order=3^5)

In [211]: GF([x[0], x[0] * x[1], x[0] * x[1] * x[2], x[0] * x[1] * x[2] * x[3]])
Out[211]:
GF([      2α^4 + 2α^2 + α + 1,                       α^2,
2α^4 + α^3 + 2α^2 + α + 1,           α^4 + 2α^2 + 2α], order=3^5)

In [212]: np.multiply.accumulate(x)
Out[212]: GF([α^156,   α^2, α^178, α^150], order=3^5)

In [213]: GF([x[0], x[0] * x[1], x[0] * x[1] * x[2], x[0] * x[1] * x[2] * x[3]])
Out[213]: GF([α^156,   α^2, α^178, α^150], order=3^5)

reduceat()

The reduceat methods reduces the input array’s dimension by one, applying the ufunc across one axis in-between certain indices.

In [214]: x
Out[214]: GF([184,  25, 157,  31], order=3^5)

Out[215]: GF([57, 31], order=3^5)

In [216]: GF([x[0] + x[1] + x[2], x[3]])
Out[216]: GF([57, 31], order=3^5)

In [217]: x
Out[217]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

Out[218]: GF([   2α^3 + α, α^3 + α + 1], order=3^5)

In [219]: GF([x[0] + x[1] + x[2], x[3]])
Out[219]: GF([   2α^3 + α, α^3 + α + 1], order=3^5)

In [220]: x
Out[220]: GF([α^156,  α^88, α^176, α^214], order=3^5)

Out[221]: GF([α^196, α^214], order=3^5)

In [222]: GF([x[0] + x[1] + x[2], x[3]])
Out[222]: GF([α^196, α^214], order=3^5)

In [223]: np.multiply.reduceat(x, [0, 3])
Out[223]: GF([211,  31], order=3^5)

In [224]: GF([x[0] * x[1] * x[2], x[3]])
Out[224]: GF([211,  31], order=3^5)

In [225]: np.multiply.reduceat(x, [0, 3])
Out[225]: GF([2α^4 + α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [226]: GF([x[0] * x[1] * x[2], x[3]])
Out[226]: GF([2α^4 + α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [227]: np.multiply.reduceat(x, [0, 3])
Out[227]: GF([α^178, α^214], order=3^5)

In [228]: GF([x[0] * x[1] * x[2], x[3]])
Out[228]: GF([α^178, α^214], order=3^5)

outer()

The outer methods applies the ufunc to each pair of inputs.

In [229]: x
Out[229]: GF([184,  25, 157,  31], order=3^5)

In [230]: y
Out[230]: GF([179,   9, 139,  27], order=3^5)

Out[231]:
GF([[ 81, 166,  80, 211],
[165,   7, 155,  52],
[ 54, 139, 215, 103],
[198,  40,  89,  58]], order=3^5)

In [232]: x
Out[232]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [233]: y
Out[233]:
GF([2α^4 + α^2 + 2α + 2,                 α^2,  α^4 + 2α^3 + α + 1,
α^3], order=3^5)

Out[234]:
GF([[                       α^4,               2α^4 + α + 1,
2α^3 + 2α^2 + 2α + 2,  2α^4 + α^3 + 2α^2 + α + 1],
[                  2α^4 + α,                     2α + 1,
α^4 + 2α^3 + 2α^2 + 2,        α^3 + 2α^2 + 2α + 1],
[                      2α^3,         α^4 + 2α^3 + α + 1,
2α^4 + α^3 + 2α^2 + 2α + 2,         α^4 + 2α^2 + α + 1],
[          2α^4 + α^3 + α^2,          α^3 + α^2 + α + 1,
α^4 + 2α + 2,               2α^3 + α + 1]], order=3^5)

In [235]: x
Out[235]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [236]: y
Out[236]: GF([α^60,  α^2, α^59,  α^3], order=3^5)

Out[237]:
GF([[  α^4,  α^45, α^236, α^178],
[α^137, α^126,  α^43,  α^79],
[α^124,  α^59, α^175, α^181],
[α^103, α^115, α^166,  α^28]], order=3^5)

In [238]: np.multiply.outer(x, y)
Out[238]:
GF([[ 41, 192,  93,  97],
[ 91, 225, 193, 196],
[ 80, 211, 106, 145],
[149,  41, 129, 123]], order=3^5)

In [239]: np.multiply.outer(x, y)
Out[239]:
GF([[        α^3 + α^2 + α + 2,            2α^4 + α^3 + α,
α^4 + α^2 + α,        α^4 + α^2 + 2α + 1],
[            α^4 + α^2 + 1,         2α^4 + 2α^3 + α^2,
2α^4 + α^3 + α + 1,       2α^4 + α^3 + 2α + 1],
[     2α^3 + 2α^2 + 2α + 2, 2α^4 + α^3 + 2α^2 + α + 1,
α^4 + 2α^2 + 2α + 1,      α^4 + 2α^3 + α^2 + 1],
[ α^4 + 2α^3 + α^2 + α + 2,         α^3 + α^2 + α + 2,
α^4 + α^3 + 2α^2 + α,      α^4 + α^3 + α^2 + 2α]], order=3^5)

In [240]: np.multiply.outer(x, y)
Out[240]:
GF([[α^216, α^158, α^215, α^159],
[α^148,  α^90, α^147,  α^91],
[α^236, α^178, α^235, α^179],
[ α^32, α^216,  α^31, α^217]], order=3^5)

at()

The at methods performs the ufunc in-place at the specified indices.

In [241]: x
Out[241]: GF([184,  25, 157,  31], order=3^5)

In [242]: z = x.copy()

# Negate indices 0 and 1 in-place
In [243]: np.negative.at(x, [0, 1]); x
Out[243]: GF([ 98,  14, 157,  31], order=3^5)

In [244]: z[0:1] *= -1; z
Out[244]: GF([ 98,  25, 157,  31], order=3^5)

In [245]: x
Out[245]:
GF([       α^4 + α^2 + 2α + 2,               α^2 + α + 2,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [246]: z = x.copy()

# Negate indices 0 and 1 in-place
In [247]: np.negative.at(x, [0, 1]); x
Out[247]:
GF([      2α^4 + 2α^2 + α + 1,             2α^2 + 2α + 1,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [248]: z[0:1] *= -1; z
Out[248]:
GF([      2α^4 + 2α^2 + α + 1,               α^2 + α + 2,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [249]: x
Out[249]: GF([α^156,  α^88, α^176, α^214], order=3^5)

In [250]: z = x.copy()

# Negate indices 0 and 1 in-place
In [251]: np.negative.at(x, [0, 1]); x
Out[251]: GF([ α^35, α^209, α^176, α^214], order=3^5)

In [252]: z[0:1] *= -1; z
Out[252]: GF([ α^35,  α^88, α^176, α^214], order=3^5)


Convolution: np.convolve(x, y)
In [253]: x
Out[253]: GF([ 98,  14, 157,  31], order=3^5)

In [254]: y
Out[254]: GF([179,   9, 139,  27], order=3^5)

In [255]: np.convolve(x, y)
Out[255]: GF([ 79,  80,   5,  79, 167, 166, 123], order=3^5)

In [256]: x
Out[256]:
GF([       α^4 + α^2 + 2α + 2,               α^2 + α + 2,
α^4 + 2α^3 + 2α^2 + α + 1,               α^3 + α + 1], order=3^5)

In [257]: y
Out[257]:
GF([2α^4 + α^2 + 2α + 2,                 α^2,  α^4 + 2α^3 + α + 1,
α^3], order=3^5)

In [258]: np.convolve(x, y)
Out[258]:
GF([2α^3 + 2α^2 + 2α + 1, 2α^3 + 2α^2 + 2α + 2,                α + 2,
2α^3 + 2α^2 + 2α + 1,         2α^4 + α + 2,         2α^4 + α + 1,
α^4 + α^3 + α^2 + 2α], order=3^5)

In [259]: x
Out[259]: GF([ α^35, α^209, α^176, α^214], order=3^5)

In [260]: y
Out[260]: GF([α^60,  α^2, α^59,  α^3], order=3^5)

In [261]: np.convolve(x, y)
Out[261]: GF([ α^95, α^236,   α^5,  α^95,   α^9,  α^45, α^217], order=3^5)

FFT: np.fft.fft(x)

The Discrete Fourier Transform (DFT) of size $$n$$ over the finite field $$\mathrm{GF}(p^m)$$ exists when there exists a primitive $$n$$-th root of unity. This occurs when $n\ |p^m - 1$.

In [262]: GF = galois.GF(7**5)

In [263]: n = 6

# n divides p^m - 1
In [264]: (GF.order - 1) % n
Out[264]: 0

In [265]: x = GF.Random(n, seed=1); x
Out[265]: GF([ 7952, 12470,  8601, 11055, 12691,  9895], order=7^5)

In [266]: X = np.fft.fft(x); X
Out[266]: GF([ 9387, 10789, 14695, 13079, 14025,  5694], order=7^5)

In [267]: np.fft.ifft(X)
Out[267]: GF([ 7952, 12470,  8601, 11055, 12691,  9895], order=7^5)

In [268]: GF = galois.GF(7**5, repr="poly")

In [269]: n = 6

# n divides p^m - 1
In [270]: (GF.order - 1) % n
Out[270]: 0

In [271]: x = GF.Random(n, seed=1); x
Out[271]:
GF([    3α^4 + 2α^3 + α^2 + 2α, 5α^4 + α^3 + 2α^2 + 3α + 3,
3α^4 + 4α^3 + 3α + 5, 4α^4 + 4α^3 + α^2 + 4α + 2,
5α^4 + 2α^3,       4α^4 + 5α^2 + 6α + 4], order=7^5)

In [272]: X = np.fft.fft(x); X
Out[272]:
GF([   3α^4 + 6α^3 + 2α^2 + 4α, 4α^4 + 3α^3 + 3α^2 + α + 2,
6α^4 + 5α^2 + 6α + 2,       5α^4 + 3α^3 + 6α + 3,
5α^4 + 5α^3 + 6α^2 + α + 4, 2α^4 + 2α^3 + 4α^2 + α + 3], order=7^5)

In [273]: np.fft.ifft(X)
Out[273]:
GF([    3α^4 + 2α^3 + α^2 + 2α, 5α^4 + α^3 + 2α^2 + 3α + 3,
3α^4 + 4α^3 + 3α + 5, 4α^4 + 4α^3 + α^2 + 4α + 2,
5α^4 + 2α^3,       4α^4 + 5α^2 + 6α + 4], order=7^5)

In [274]: GF = galois.GF(7**5, repr="power")

In [275]: n = 6

# n divides p^m - 1
In [276]: (GF.order - 1) % n
Out[276]: 0

In [277]: x = GF.Random(n, seed=1); x
Out[277]: GF([α^11363,  α^2127, α^15189,  α^5863,  α^1240,   α^255], order=7^5)

In [278]: X = np.fft.fft(x); X
Out[278]: GF([ α^7664, α^14905, α^15266, α^13358,  α^9822, α^16312], order=7^5)

In [279]: np.fft.ifft(X)
Out[279]: GF([α^11363,  α^2127, α^15189,  α^5863,  α^1240,   α^255], order=7^5)


See also ntt() and primitive_root_of_unity.

Inverse FFT: np.fft.ifft(X)

The inverse Discrete Fourier Transform (DFT) of size $$n$$ over the finite field $$\mathrm{GF}(p^m)$$ exists when there exists a primitive $$n$$-th root of unity. This occurs when $n\ |p^m - 1$.

In [280]: GF = galois.GF(7**5)

In [281]: n = 6

# n divides p^m - 1
In [282]: (GF.order - 1) % n
Out[282]: 0

In [283]: x = GF.Random(n, seed=1); x
Out[283]: GF([ 7952, 12470,  8601, 11055, 12691,  9895], order=7^5)

In [284]: X = np.fft.fft(x); X
Out[284]: GF([ 9387, 10789, 14695, 13079, 14025,  5694], order=7^5)

In [285]: np.fft.ifft(X)
Out[285]: GF([ 7952, 12470,  8601, 11055, 12691,  9895], order=7^5)

In [286]: GF = galois.GF(7**5, repr="poly")

In [287]: n = 6

# n divides p^m - 1
In [288]: (GF.order - 1) % n
Out[288]: 0

In [289]: x = GF.Random(n, seed=1); x
Out[289]:
GF([    3α^4 + 2α^3 + α^2 + 2α, 5α^4 + α^3 + 2α^2 + 3α + 3,
3α^4 + 4α^3 + 3α + 5, 4α^4 + 4α^3 + α^2 + 4α + 2,
5α^4 + 2α^3,       4α^4 + 5α^2 + 6α + 4], order=7^5)

In [290]: X = np.fft.fft(x); X
Out[290]:
GF([   3α^4 + 6α^3 + 2α^2 + 4α, 4α^4 + 3α^3 + 3α^2 + α + 2,
6α^4 + 5α^2 + 6α + 2,       5α^4 + 3α^3 + 6α + 3,
5α^4 + 5α^3 + 6α^2 + α + 4, 2α^4 + 2α^3 + 4α^2 + α + 3], order=7^5)

In [291]: np.fft.ifft(X)
Out[291]:
GF([    3α^4 + 2α^3 + α^2 + 2α, 5α^4 + α^3 + 2α^2 + 3α + 3,
3α^4 + 4α^3 + 3α + 5, 4α^4 + 4α^3 + α^2 + 4α + 2,
5α^4 + 2α^3,       4α^4 + 5α^2 + 6α + 4], order=7^5)

In [292]: GF = galois.GF(7**5, repr="power")

In [293]: n = 6

# n divides p^m - 1
In [294]: (GF.order - 1) % n
Out[294]: 0

In [295]: x = GF.Random(n, seed=1); x
Out[295]: GF([α^11363,  α^2127, α^15189,  α^5863,  α^1240,   α^255], order=7^5)

In [296]: X = np.fft.fft(x); X
Out[296]: GF([ α^7664, α^14905, α^15266, α^13358,  α^9822, α^16312], order=7^5)

In [297]: np.fft.ifft(X)
Out[297]: GF([α^11363,  α^2127, α^15189,  α^5863,  α^1240,   α^255], order=7^5)


See also ntt() and primitive_root_of_unity.

## Linear algebra¶

Linear algebra on FieldArray arrays/matrices is supported through both native NumPy linear algebra functions in numpy.linalg and additional linear algebra methods not included in NumPy.

Expand any section for more details.

Dot product: np.dot(a, b)
In [298]: GF = galois.GF(31)

In [299]: a = GF([29, 0, 2, 21]); a
Out[299]: GF([29,  0,  2, 21], order=31)

In [300]: b = GF([23, 5, 15, 12]); b
Out[300]: GF([23,  5, 15, 12], order=31)

In [301]: np.dot(a, b)
Out[301]: GF(19, order=31)

Vector dot product: np.vdot(a, b)
In [302]: GF = galois.GF(31)

In [303]: a = GF([29, 0, 2, 21]); a
Out[303]: GF([29,  0,  2, 21], order=31)

In [304]: b = GF([23, 5, 15, 12]); b
Out[304]: GF([23,  5, 15, 12], order=31)

In [305]: np.vdot(a, b)
Out[305]: GF(19, order=31)

Inner product: np.inner(a, b)
In [306]: GF = galois.GF(31)

In [307]: a = GF([29, 0, 2, 21]); a
Out[307]: GF([29,  0,  2, 21], order=31)

In [308]: b = GF([23, 5, 15, 12]); b
Out[308]: GF([23,  5, 15, 12], order=31)

In [309]: np.inner(a, b)
Out[309]: GF(19, order=31)

Outer product: np.outer(a, b)
In [310]: GF = galois.GF(31)

In [311]: a = GF([29, 0, 2, 21]); a
Out[311]: GF([29,  0,  2, 21], order=31)

In [312]: b = GF([23, 5, 15, 12]); b
Out[312]: GF([23,  5, 15, 12], order=31)

In [313]: np.outer(a, b)
Out[313]:
GF([[16, 21,  1,  7],
[ 0,  0,  0,  0],
[15, 10, 30, 24],
[18, 12,  5,  4]], order=31)

Matrix multiplication: A @ B == np.matmul(A, B)
In [314]: GF = galois.GF(31)

In [315]: A = GF([[17, 25, 18, 8], [7, 9, 21, 15], [6, 16, 6, 30]]); A
Out[315]:
GF([[17, 25, 18,  8],
[ 7,  9, 21, 15],
[ 6, 16,  6, 30]], order=31)

In [316]: B = GF([[8, 18], [22, 0], [7, 8], [20, 24]]); B
Out[316]:
GF([[ 8, 18],
[22,  0],
[ 7,  8],
[20, 24]], order=31)

In [317]: A @ B
Out[317]:
GF([[11, 22],
[19,  3],
[19,  8]], order=31)

In [318]: np.matmul(A, B)
Out[318]:
GF([[11, 22],
[19,  3],
[19,  8]], order=31)

Matrix exponentiation: np.linalg.matrix_power(A, 3)
In [319]: GF = galois.GF(31)

In [320]: A = GF([[14, 1, 5], [3, 23, 6], [24, 27, 4]]); A
Out[320]:
GF([[14,  1,  5],
[ 3, 23,  6],
[24, 27,  4]], order=31)

In [321]: np.linalg.matrix_power(A, 3)
Out[321]:
GF([[ 1, 16,  4],
[11,  9,  9],
[ 8, 24, 29]], order=31)

In [322]: A @ A @ A
Out[322]:
GF([[ 1, 16,  4],
[11,  9,  9],
[ 8, 24, 29]], order=31)

Matrix determinant: np.linalg.det(A)
In [323]: GF = galois.GF(31)

In [324]: A = GF([[23, 11, 3, 3], [13, 6, 16, 4], [12, 10, 5, 3], [17, 23, 15, 28]]); A
Out[324]:
GF([[23, 11,  3,  3],
[13,  6, 16,  4],
[12, 10,  5,  3],
[17, 23, 15, 28]], order=31)

In [325]: np.linalg.det(A)
Out[325]: GF(0, order=31)

Matrix rank: np.linalg.matrix_rank(A)
In [326]: GF = galois.GF(31)

In [327]: A = GF([[23, 11, 3, 3], [13, 6, 16, 4], [12, 10, 5, 3], [17, 23, 15, 28]]); A
Out[327]:
GF([[23, 11,  3,  3],
[13,  6, 16,  4],
[12, 10,  5,  3],
[17, 23, 15, 28]], order=31)

In [328]: np.linalg.matrix_rank(A)
Out[328]: 3

In [329]: A.row_reduce()
Out[329]:
GF([[ 1,  0,  0, 11],
[ 0,  1,  0, 25],
[ 0,  0,  1, 11],
[ 0,  0,  0,  0]], order=31)

Matrix trace: np.trace(A)
In [330]: GF = galois.GF(31)

In [331]: A = GF([[23, 11, 3, 3], [13, 6, 16, 4], [12, 10, 5, 3], [17, 23, 15, 28]]); A
Out[331]:
GF([[23, 11,  3,  3],
[13,  6, 16,  4],
[12, 10,  5,  3],
[17, 23, 15, 28]], order=31)

In [332]: np.trace(A)
Out[332]: GF(0, order=31)

In [333]: A[0,0] + A[1,1] + A[2,2] + A[3,3]
Out[333]: GF(0, order=31)

Solve a system of equations: np.linalg.solve(A, b)
In [334]: GF = galois.GF(31)

In [335]: A = GF([[14, 21, 14, 28], [24, 22, 23, 23], [16, 30, 26, 18], [4, 23, 18, 3]]); A
Out[335]:
GF([[14, 21, 14, 28],
[24, 22, 23, 23],
[16, 30, 26, 18],
[ 4, 23, 18,  3]], order=31)

In [336]: b = GF([15, 11, 6, 29]); b
Out[336]: GF([15, 11,  6, 29], order=31)

In [337]: x = np.linalg.solve(A, b)

In [338]: np.array_equal(A @ x, b)
Out[338]: True

Matrix inverse: np.linalg.inv(A)
In [339]: GF = galois.GF(31)

In [340]: A = GF([[14, 21, 14, 28], [24, 22, 23, 23], [16, 30, 26, 18], [4, 23, 18, 3]]); A
Out[340]:
GF([[14, 21, 14, 28],
[24, 22, 23, 23],
[16, 30, 26, 18],
[ 4, 23, 18,  3]], order=31)

In [341]: A_inv = np.linalg.inv(A); A_inv
Out[341]:
GF([[27, 17,  9,  8],
[20, 21, 12,  4],
[30, 10, 23, 22],
[13, 25,  6, 13]], order=31)

In [342]: A @ A_inv
Out[342]:
GF([[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]], order=31)


Last update: Aug 06, 2023