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)
In [7]: np.add(x, y)
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)
In [11]: np.add(x, y)
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)
In [15]: np.add(x, y)
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)
Any array added to its additive inverse results in zero.
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])
See also is_square()
, squares()
, and
non_squares()
.
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)
In [185]: np.add.reduce(x)
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)
In [188]: np.add.reduce(x)
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)
In [191]: np.add.reduce(x)
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)
In [200]: np.add.accumulate(x)
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)
In [203]: np.add.accumulate(x)
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)
In [206]: np.add.accumulate(x)
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)
In [215]: np.add.reduceat(x, [0, 3])
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)
In [218]: np.add.reduceat(x, [0, 3])
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)
In [221]: np.add.reduceat(x, [0, 3])
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)
In [231]: np.add.outer(x, y)
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)
In [234]: np.add.outer(x, y)
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)
In [237]: np.add.outer(x, y)
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)
Advanced arithmetic¶
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 \mid 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 \mid 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)