kryptools 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kryptools/Zmod.py +115 -0
- kryptools/__init__.py +13 -0
- kryptools/dlp.py +76 -0
- kryptools/dlp_bsgs.py +29 -0
- kryptools/dlp_ic.py +136 -0
- kryptools/dlp_qs.py +297 -0
- kryptools/dlp_rho.py +62 -0
- kryptools/ec.py +383 -0
- kryptools/factor.py +138 -0
- kryptools/factor_ecm.py +140 -0
- kryptools/factor_pm1.py +58 -0
- kryptools/factor_qs.py +174 -0
- kryptools/la.py +223 -0
- kryptools/lat.py +168 -0
- kryptools/nt.py +271 -0
- kryptools/poly.py +232 -0
- kryptools/primes.py +123 -0
- kryptools-0.1.dist-info/LICENSE +21 -0
- kryptools-0.1.dist-info/METADATA +35 -0
- kryptools-0.1.dist-info/RECORD +22 -0
- kryptools-0.1.dist-info/WHEEL +5 -0
- kryptools-0.1.dist-info/top_level.txt +1 -0
kryptools/factor_ecm.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integer factorization: Lentra's ECM
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from math import gcd, isqrt, log
|
|
6
|
+
from random import randint, seed
|
|
7
|
+
from .primes import sieve_eratosthenes
|
|
8
|
+
|
|
9
|
+
# Crandall and Pomerance: Primes (doi=10.1007/0-387-28979-8)
|
|
10
|
+
# Algorithm 7.2.7
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def dbl(P, c2, p):
|
|
14
|
+
# c2 = c - 2
|
|
15
|
+
t1 = (P[0] + P[1]) % p
|
|
16
|
+
t2 = (P[0] - P[1]) % p
|
|
17
|
+
t3 = P[0] * P[1] % p
|
|
18
|
+
return pow(t1 * t2, 2, p), ((t1 * t1 + c2 * t3) % p) * 4 * t3 % p
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def add(P1, P2, P3, p):
|
|
22
|
+
t1 = pow(P1[0] * P2[0] - P1[1] * P2[1], 2, p)
|
|
23
|
+
t2 = pow(P1[0] * P2[1] - P2[0] * P1[1], 2, p)
|
|
24
|
+
return P3[1] * t1 % p, P3[0] * t2 % p
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def mult(k, P, c2, p):
|
|
28
|
+
if k == 1:
|
|
29
|
+
return P
|
|
30
|
+
if k == 2:
|
|
31
|
+
return dbl(P, c2, p)
|
|
32
|
+
Q = dbl(P, c2, p)
|
|
33
|
+
R = P
|
|
34
|
+
for i in bin(k)[3:]:
|
|
35
|
+
if i == "1":
|
|
36
|
+
R = add(Q, R, P, p)
|
|
37
|
+
Q = dbl(Q, c2, p)
|
|
38
|
+
else:
|
|
39
|
+
Q = add(R, Q, P, p)
|
|
40
|
+
R = dbl(R, c2, p)
|
|
41
|
+
return R
|
|
42
|
+
|
|
43
|
+
# Crandall and Pomerance: Primes (doi=10.1007/0-387-28979-8)
|
|
44
|
+
# Algorithm 7.4.4 (Inversionless ECM)
|
|
45
|
+
|
|
46
|
+
def _ecm_parameters(B1: int, B2: int = None, D: int = None, primes: tuple = None):
|
|
47
|
+
"Precompute parameters for the ECM method."
|
|
48
|
+
|
|
49
|
+
# Stage-one/two limits must be even
|
|
50
|
+
B1 += B1 & 1
|
|
51
|
+
if not B2:
|
|
52
|
+
B2 = 100 * B1
|
|
53
|
+
else:
|
|
54
|
+
B2 += B2 & 1
|
|
55
|
+
if not D:
|
|
56
|
+
D = isqrt(B2)
|
|
57
|
+
if not primes:
|
|
58
|
+
primes = sieve_eratosthenes(B1 - 1 + ((B2 - B1 + 1) // (2 * D) + 1) * 2 * D)
|
|
59
|
+
|
|
60
|
+
stage_one = 1
|
|
61
|
+
for p in primes:
|
|
62
|
+
if p > B1:
|
|
63
|
+
break
|
|
64
|
+
stage_one *= p**int(log(B1, p))
|
|
65
|
+
|
|
66
|
+
stage_two_deltas = {}
|
|
67
|
+
r = B1 - 1
|
|
68
|
+
stage_two_deltas[r] = []
|
|
69
|
+
for p in primes:
|
|
70
|
+
if p <= r:
|
|
71
|
+
continue
|
|
72
|
+
if p <= r + 2 * D:
|
|
73
|
+
stage_two_deltas[r].append((p - r) // 2)
|
|
74
|
+
else:
|
|
75
|
+
r += 2 * D
|
|
76
|
+
while p > r + 2 * D:
|
|
77
|
+
stage_two_deltas[r] = []
|
|
78
|
+
r += 2 * D
|
|
79
|
+
stage_two_deltas[r] = [(p - r) // 2]
|
|
80
|
+
return D, stage_one, stage_two_deltas
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def factor_ecm(n: int, B1: int = 11000, B2: int = 1900000, curves: int = 74, ecm_parameters: tuple = None):
|
|
84
|
+
"Factors a number n using Lentsta's ECM method."
|
|
85
|
+
|
|
86
|
+
if ecm_parameters:
|
|
87
|
+
curves, D, stage_one, stage_two_deltas = ecm_parameters
|
|
88
|
+
else:
|
|
89
|
+
D, stage_one, stage_two_deltas = _ecm_parameters(B1, B2)
|
|
90
|
+
|
|
91
|
+
for _ in range(curves):
|
|
92
|
+
# find a random curve
|
|
93
|
+
sigma = randint(6, n - 1)
|
|
94
|
+
u = (sigma**2 - 5) % n
|
|
95
|
+
v = (4 * sigma) % n
|
|
96
|
+
try:
|
|
97
|
+
c = (pow(v - u, 3, n) * (3 * u + v) * pow(4 * u**3 * v, -1, n) - 2) % n
|
|
98
|
+
except:
|
|
99
|
+
m = gcd(4 * u**3 * v, n)
|
|
100
|
+
return m
|
|
101
|
+
c2 = c - 2 % n
|
|
102
|
+
# and a point on the curve (or its twist)
|
|
103
|
+
Q = (pow(u, 3, n), pow(v, 3, n))
|
|
104
|
+
# Stage one
|
|
105
|
+
Q = mult(stage_one, Q, c2, n)
|
|
106
|
+
if Q[1] == 0:
|
|
107
|
+
continue
|
|
108
|
+
g = gcd(Q[1], n)
|
|
109
|
+
if g > 1:
|
|
110
|
+
return g
|
|
111
|
+
|
|
112
|
+
# Stage two
|
|
113
|
+
S = [dbl(Q, c2, n)]
|
|
114
|
+
S.append(dbl(S[0], c2, n))
|
|
115
|
+
|
|
116
|
+
for i in range(2, D):
|
|
117
|
+
S.append(add(S[i - 1], S[0], S[i - 2], n))
|
|
118
|
+
|
|
119
|
+
beta = []
|
|
120
|
+
for i in range(D):
|
|
121
|
+
beta.append((S[i][0] * S[i][1]) % n)
|
|
122
|
+
g = 1
|
|
123
|
+
T = mult(B1 - 2 * D - 1, Q, c2, n)
|
|
124
|
+
R = mult(B1 - 1, Q, c2, n)
|
|
125
|
+
for r in range(B1 - 1, B2, 2 * D):
|
|
126
|
+
alpha = R[0] * R[1] % n
|
|
127
|
+
for delta in stage_two_deltas[r]:
|
|
128
|
+
g = g * ((R[0] - S[delta - 1][0]) * (R[1] + S[delta - 1][1]) - alpha + beta[delta - 1]) % n
|
|
129
|
+
if g == 0:
|
|
130
|
+
break
|
|
131
|
+
if g == 0:
|
|
132
|
+
break
|
|
133
|
+
g =gcd(g,n)
|
|
134
|
+
if 1 < g:
|
|
135
|
+
return g
|
|
136
|
+
R, T = add(R, S[D - 1], T, n), R
|
|
137
|
+
|
|
138
|
+
g = gcd(g, n)
|
|
139
|
+
if 1 < g < n:
|
|
140
|
+
return g
|
kryptools/factor_pm1.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integer factorization: Pollard's p-1 method
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from math import gcd, log
|
|
6
|
+
from .primes import sieve_eratosthenes
|
|
7
|
+
|
|
8
|
+
def _pm1_parameters(B1: int, B2: int = None, primes: tuple = None):
|
|
9
|
+
"Precompute parameters for the P-1 method."
|
|
10
|
+
if not B2:
|
|
11
|
+
B2 = 100 * B1
|
|
12
|
+
if not primes:
|
|
13
|
+
primes = sieve_eratosthenes(B2)
|
|
14
|
+
|
|
15
|
+
stage_one = 1
|
|
16
|
+
for p in primes:
|
|
17
|
+
if p > B1:
|
|
18
|
+
break
|
|
19
|
+
stage_one *= p**int(log(B1, p))
|
|
20
|
+
|
|
21
|
+
stage_two_deltas = []
|
|
22
|
+
for q in primes:
|
|
23
|
+
if q <= B1:
|
|
24
|
+
continue
|
|
25
|
+
if q > B2:
|
|
26
|
+
break
|
|
27
|
+
stage_two_deltas.append(q - p)
|
|
28
|
+
|
|
29
|
+
return stage_one, stage_two_deltas
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def factor_pm1(n: int, B1: int = 11000, B2: int = 1900000, x: int = 2, pm1_parameters: tuple = None):
|
|
33
|
+
"Factors a number n using Pollard's P-1 method."
|
|
34
|
+
|
|
35
|
+
if pm1_parameters:
|
|
36
|
+
stage_one, stage_two_deltas = pm1_parameters
|
|
37
|
+
else:
|
|
38
|
+
stage_one, stage_two_deltas = _pm1_parameters(B1, B2)
|
|
39
|
+
|
|
40
|
+
g = gcd(pow(x, stage_one, n) - 1, n)
|
|
41
|
+
if 1 < g < n:
|
|
42
|
+
return g
|
|
43
|
+
k = 0
|
|
44
|
+
for d in stage_two_deltas:
|
|
45
|
+
saved = {}
|
|
46
|
+
if not d in saved:
|
|
47
|
+
y = pow(x, d, n)
|
|
48
|
+
saved[d] = y
|
|
49
|
+
x = (x * saved[d]) % n
|
|
50
|
+
k += 1
|
|
51
|
+
if k % 20 == 0:
|
|
52
|
+
g = gcd(x-1, n)
|
|
53
|
+
if 1 < g < n:
|
|
54
|
+
return g
|
|
55
|
+
g = gcd(x-1, n)
|
|
56
|
+
if 1 < g < n:
|
|
57
|
+
return g
|
|
58
|
+
return None
|
kryptools/factor_qs.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integer factorization: Quadratic sieve
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from math import isqrt, gcd, sqrt, log, exp, ceil
|
|
6
|
+
from .primes import sieve_eratosthenes
|
|
7
|
+
from .nt import legendre_symbol, sqrt_mod
|
|
8
|
+
|
|
9
|
+
def bytexor(a: bytearray, b: bytes) -> bytes:
|
|
10
|
+
"""Xor the bytestring b to the bytearry a."""
|
|
11
|
+
for i in range(len(a)):
|
|
12
|
+
a[i] ^= b[i]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def byteset(a: bytearray, i: int) -> bytes:
|
|
16
|
+
"""Set the i'th bit in the bytearray a to 1."""
|
|
17
|
+
i1, i0 = divmod(i, 8)
|
|
18
|
+
a[i1] |= 2**i0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def bytetest(a: bytes, i: int) -> bytes:
|
|
22
|
+
"""Test if the i'th bit in the bytestring a is 1."""
|
|
23
|
+
i1, i0 = divmod(i, 8)
|
|
24
|
+
return (a[i1] & 2**i0) != 0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def factor_qs(n: int) -> list:
|
|
28
|
+
"""Find factors of n using the quadratic sieve."""
|
|
29
|
+
# first determine the bound B for the factorbase: Choosing B=p^(1/u) Canfield-Erdös-Pomerance gives us
|
|
30
|
+
# the expected running time |B|^2 u^u = u^(u+2) p^(2/u)/log(n). There is no explicit expression for the optimum, hence
|
|
31
|
+
# we use Newton
|
|
32
|
+
u = 2 * sqrt(log(n) / log(log(n))) # asymptotic value
|
|
33
|
+
for _ in range(3):
|
|
34
|
+
u = (2 * log(n) + u * u * (2 + log(u))) / (
|
|
35
|
+
2 + 3 * u + 2 * u * log(u)
|
|
36
|
+
) # Newton iteration
|
|
37
|
+
B = int(exp(log(n) / u))
|
|
38
|
+
# B = int(exp(0.5 * sqrt( log(n) * log(log(n)) )*( 1 + 1/log(log(n)) )))
|
|
39
|
+
|
|
40
|
+
factorbase = []
|
|
41
|
+
for p in sieve_eratosthenes(B): # compute the factorbase
|
|
42
|
+
ls = legendre_symbol(n, p)
|
|
43
|
+
if ls == 0: # we already found a factor;-)
|
|
44
|
+
if n == p:
|
|
45
|
+
return n
|
|
46
|
+
return p
|
|
47
|
+
if ls == 1: # we only take primes such that n is quadratic residue
|
|
48
|
+
factorbase.append(p)
|
|
49
|
+
lf = len(factorbase) # length of the factorbase (including -1)
|
|
50
|
+
lfb = ceil((lf+1) / 8) # the number of bytes we need to store a relation
|
|
51
|
+
|
|
52
|
+
factorbase_log = [log(p) for p in factorbase] # we add these up to test if a number will probably factor
|
|
53
|
+
factorbase_root = [0] * lf # compute the roots of n mod p
|
|
54
|
+
for i, p in enumerate(factorbase):
|
|
55
|
+
r1 = sqrt_mod(n, p)
|
|
56
|
+
assert r1 is not None # only quadratic residues
|
|
57
|
+
r2 = -r1 % p
|
|
58
|
+
if r1 == r2:
|
|
59
|
+
factorbase_root[i] = [ r1 ]
|
|
60
|
+
else:
|
|
61
|
+
factorbase_root[i] = [ r1, -r1 % p ]
|
|
62
|
+
|
|
63
|
+
m = isqrt(n - 1) + 1
|
|
64
|
+
d = m**2 - n
|
|
65
|
+
if d == 0: # Our number is a square
|
|
66
|
+
return m
|
|
67
|
+
m2 = 2 * m
|
|
68
|
+
|
|
69
|
+
relation_no = -1
|
|
70
|
+
relations = [None] * (lf + 1) # here we will store the relations in case we found a B-smooth number
|
|
71
|
+
values = [None] * (lf + 1) # here we will store the values leading to the B-smooth numbers
|
|
72
|
+
|
|
73
|
+
# set up the sieve
|
|
74
|
+
sieve_step = 100
|
|
75
|
+
sieve_bound = sieve_step
|
|
76
|
+
# start values for the iterators in the positive/negative direction
|
|
77
|
+
iterator_p = [ [0, 0] for _ in range(lf) ]
|
|
78
|
+
iterator_m = [ [0, 0] for _ in range(lf) ]
|
|
79
|
+
for i, p in enumerate(factorbase):
|
|
80
|
+
for j, r in enumerate(factorbase_root[i]):
|
|
81
|
+
iterator_p[i][j] = (r - m) % p
|
|
82
|
+
iterator_m[i][j] = iterator_p[i][j] - p # one step in the negative direction
|
|
83
|
+
|
|
84
|
+
def do_sieve():
|
|
85
|
+
nonlocal factorbase, factorbase_root, sieve_bound, factors
|
|
86
|
+
for i, p in enumerate(factorbase):
|
|
87
|
+
for r in range(len(factorbase_root[i])):
|
|
88
|
+
j = iterator_p[i][r]
|
|
89
|
+
while j <= sieve_bound:
|
|
90
|
+
if j in factors:
|
|
91
|
+
factors[j][0].append(i)
|
|
92
|
+
factors[j][1] += factorbase_log[i]
|
|
93
|
+
else:
|
|
94
|
+
factors[j] = [[i], factorbase_log[i]]
|
|
95
|
+
j += p
|
|
96
|
+
iterator_p[i][r] = j # store as start value for the next step
|
|
97
|
+
j = iterator_m[i][r]
|
|
98
|
+
while j > -sieve_bound:
|
|
99
|
+
if j in factors:
|
|
100
|
+
factors[j][0].append(i)
|
|
101
|
+
factors[j][1] += factorbase_log[i]
|
|
102
|
+
else:
|
|
103
|
+
factors[j] = [[i], factorbase_log[i]]
|
|
104
|
+
j -= p
|
|
105
|
+
iterator_m[i][r] = j # store as start value for the next step
|
|
106
|
+
|
|
107
|
+
def process_relation(j: int, relation: bytes):
|
|
108
|
+
nonlocal relation_no, values, relations
|
|
109
|
+
relation_no += 1
|
|
110
|
+
rhs = bytearray(b"\x00") * lfb # construct the k'th row of the identity matrix
|
|
111
|
+
byteset(rhs, relation_no)
|
|
112
|
+
relation += rhs # extend the relation with this row
|
|
113
|
+
values[relation_no] = j # store the value which lead to the relation
|
|
114
|
+
# do the Gauss elimination
|
|
115
|
+
index = lf # this will be the index of the first nonzero entry
|
|
116
|
+
# print(f'{j:3}', ' '.join(f'{b:08b}' for b in reversed(relation)))
|
|
117
|
+
for i in range(lf):
|
|
118
|
+
if bytetest(relation, i) and relations[i] is not None: # make this entry zero if we can (Gauss elimination)
|
|
119
|
+
bytexor(relation, relations[i])
|
|
120
|
+
if bytetest(relation, i) and index == lf: # is this the index of the first nonzero entry?
|
|
121
|
+
index = i
|
|
122
|
+
# print(f'{j:3}', ' '.join(f'{b:08b}' for b in reversed(relation)))
|
|
123
|
+
if index == lf: # the new relation is linearly dependent: we have found a linear combination of the 0 vector
|
|
124
|
+
# now we need to determine the factors
|
|
125
|
+
u = 1 # product over all values m - j such that f(m-j) is B-smooth
|
|
126
|
+
v = 1 # sqrt of the product over all f(m-j) which are B-smooth
|
|
127
|
+
w = 1 # save nonsquare terms for the next round
|
|
128
|
+
for i in range(lf):
|
|
129
|
+
if bytetest(relation, lfb * 8 + i): # select the relations which sum to the 0 vector
|
|
130
|
+
ui = m + values[i]
|
|
131
|
+
u = (u * ui) % n
|
|
132
|
+
vi = ui ** 2 - n
|
|
133
|
+
d = gcd(w, vi)
|
|
134
|
+
v = (v * d) % n # sqrt of the part which is already square
|
|
135
|
+
w = (w // d) * (vi // d) # this part is not square yet
|
|
136
|
+
v = v * isqrt(w) % n
|
|
137
|
+
res = gcd(u - v, n)
|
|
138
|
+
if 1 < res < n:
|
|
139
|
+
return res
|
|
140
|
+
relation_no -= 1 # this one did not work, try again
|
|
141
|
+
else:
|
|
142
|
+
relations[index] = relation
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
while True:
|
|
146
|
+
factors = {} # store the index of the primes dividing j
|
|
147
|
+
|
|
148
|
+
do_sieve()
|
|
149
|
+
|
|
150
|
+
for j in sorted(factors.keys(),key=abs):
|
|
151
|
+
v = d + (m2 + j) * j # (j + m) ** 2 - n
|
|
152
|
+
if factors[j][1] > 0.49 * log(abs(v)):
|
|
153
|
+
primes = factors[j][0]
|
|
154
|
+
mask = bytearray(b"\x00") * lfb
|
|
155
|
+
if v < 0:
|
|
156
|
+
byteset(mask, 0)
|
|
157
|
+
v *= -1
|
|
158
|
+
for i in primes:
|
|
159
|
+
p = factorbase[i]
|
|
160
|
+
k = 1 # per our sieve we already know that p devides v
|
|
161
|
+
v //= p
|
|
162
|
+
while v % p == 0: # divide by p as many times as possible
|
|
163
|
+
k = (k + 1) % 2 # we only want to know if the exponent is even or odd
|
|
164
|
+
v //= p
|
|
165
|
+
if k:
|
|
166
|
+
byteset(mask, i + 1)
|
|
167
|
+
if v == 1:
|
|
168
|
+
res = process_relation(j, mask)
|
|
169
|
+
if res:
|
|
170
|
+
return res
|
|
171
|
+
del(factors[j])
|
|
172
|
+
else:
|
|
173
|
+
del(factors[j]) # the number is unlikely to factor
|
|
174
|
+
sieve_bound += sieve_step # increase the sieve and continue
|
kryptools/la.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Linear algebra
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from math import sqrt, prod
|
|
6
|
+
|
|
7
|
+
class Matrix:
|
|
8
|
+
"""
|
|
9
|
+
Matrix class.
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
|
|
13
|
+
To define a matrix use
|
|
14
|
+
>>> Matrix([[1, 2], [3, 4]])
|
|
15
|
+
[1, 2]
|
|
16
|
+
[3, 4]
|
|
17
|
+
"""
|
|
18
|
+
def __init__(self, matrix, ring = None):
|
|
19
|
+
if not isinstance(matrix[0], list|tuple):
|
|
20
|
+
matrix = [ [x] for x in matrix ]
|
|
21
|
+
self.matrix = matrix
|
|
22
|
+
self.cols = len(matrix[0])
|
|
23
|
+
self.rows = len(matrix)
|
|
24
|
+
if ring:
|
|
25
|
+
self.map(ring)
|
|
26
|
+
|
|
27
|
+
def __repr__(self) -> str:
|
|
28
|
+
out = ["[ "] * self.rows
|
|
29
|
+
for j in range(self.cols):
|
|
30
|
+
tmp = [ "" ] * self.rows
|
|
31
|
+
max_len = 0
|
|
32
|
+
for i in range(self.rows):
|
|
33
|
+
tmp[i] = str(self.matrix[i][j])
|
|
34
|
+
max_len = max(max_len, len(tmp[i]))
|
|
35
|
+
for i in range(self.rows):
|
|
36
|
+
out[i] += " " * (max_len - len(tmp[i])) + tmp[i]
|
|
37
|
+
if j < self.cols - 1:
|
|
38
|
+
out[i] += ", "
|
|
39
|
+
for i in range(self.rows):
|
|
40
|
+
out[i] += " ]"
|
|
41
|
+
return '\n'.join(out)
|
|
42
|
+
|
|
43
|
+
def __len__(self):
|
|
44
|
+
return self.cols * self.rows
|
|
45
|
+
|
|
46
|
+
def __getitem__(self, item):
|
|
47
|
+
if isinstance(item, tuple):
|
|
48
|
+
i, j = item
|
|
49
|
+
if isinstance(i, int) and isinstance(j, int):
|
|
50
|
+
return self.matrix[i][j]
|
|
51
|
+
rows = range(self.rows)[i]
|
|
52
|
+
if isinstance(i, int):
|
|
53
|
+
rows = [ rows ]
|
|
54
|
+
cols = range(self.cols)[j]
|
|
55
|
+
if isinstance(j, int):
|
|
56
|
+
cols = [ cols ]
|
|
57
|
+
return Matrix([[self.matrix[i][j] for j in cols] for i in rows])
|
|
58
|
+
i, j = divmod(item, self.cols)
|
|
59
|
+
return self.matrix[i][j]
|
|
60
|
+
|
|
61
|
+
def __setitem__(self, item, value):
|
|
62
|
+
if isinstance(item, tuple):
|
|
63
|
+
i, j = item
|
|
64
|
+
if isinstance(i, int) and isinstance(j, int):
|
|
65
|
+
self.matrix[i][j] = value
|
|
66
|
+
return
|
|
67
|
+
rows = range(self.rows)[i]
|
|
68
|
+
if isinstance(i, int):
|
|
69
|
+
rows = [ rows ]
|
|
70
|
+
cols = range(self.cols)[j]
|
|
71
|
+
if isinstance(j, int):
|
|
72
|
+
cols = [ cols ]
|
|
73
|
+
for i, ii in zip(cols,range(len(cols))):
|
|
74
|
+
for j, jj in zip(rows,range(len(rows))):
|
|
75
|
+
self.matrix[j][i] = value[jj,ii]
|
|
76
|
+
return
|
|
77
|
+
i, j = divmod(item, self.cols)
|
|
78
|
+
self.matrix[i][j] = value
|
|
79
|
+
|
|
80
|
+
def __eq__(self, other):
|
|
81
|
+
if not isinstance(other, self.__class__):
|
|
82
|
+
return False
|
|
83
|
+
return self.matrix == other.matrix
|
|
84
|
+
|
|
85
|
+
def map(self, func):
|
|
86
|
+
"Apply a function to all elements in place"
|
|
87
|
+
for row in self.matrix:
|
|
88
|
+
row[:] = map(func, row)
|
|
89
|
+
|
|
90
|
+
def applyfunc(self, func):
|
|
91
|
+
"Apply a function to all elements"
|
|
92
|
+
tmp = self[:,:]
|
|
93
|
+
tmp.map(func)
|
|
94
|
+
return tmp
|
|
95
|
+
|
|
96
|
+
def norm2(self) -> float:
|
|
97
|
+
"Squared Frobenius/Euclidean norm."
|
|
98
|
+
return sum( sum(x*x for x in row) for row in self.matrix )
|
|
99
|
+
|
|
100
|
+
def norm(self) -> float:
|
|
101
|
+
"Frobenius/Euclidean norm."
|
|
102
|
+
return sqrt(self.norm2())
|
|
103
|
+
|
|
104
|
+
def dot(self, other) -> int:
|
|
105
|
+
if self.rows == 1 and other.rows == 1 and self.cols == other.cols:
|
|
106
|
+
return sum(x * y for x, y in zip(self.matrix[0], other.matrix[0]))
|
|
107
|
+
if self.cols == 1 and other.cols == 1 and self.rows == other.rows:
|
|
108
|
+
return sum(x[0] * y[0] for x, y in zip(self.matrix, other.matrix))
|
|
109
|
+
return NotImplemented
|
|
110
|
+
|
|
111
|
+
def transpose(self) -> "Matrix":
|
|
112
|
+
return Matrix([list(i) for i in zip(*self.matrix)])
|
|
113
|
+
|
|
114
|
+
def multiply(self, other) -> "Matrix":
|
|
115
|
+
if not isinstance(other, Matrix) or self.cols != other.rows:
|
|
116
|
+
return NotImplemented
|
|
117
|
+
result = [[0 for j in range(other.cols)] for i in range(self.rows)]
|
|
118
|
+
for i in range(self.rows):
|
|
119
|
+
for j in range(other.cols):
|
|
120
|
+
for k in range(other.rows):
|
|
121
|
+
result[i][j] += self.matrix[i][k] * other.matrix[k][j]
|
|
122
|
+
return Matrix(result)
|
|
123
|
+
|
|
124
|
+
def __add__(self, other) -> "Matrix":
|
|
125
|
+
if isinstance(other, Matrix) and other.cols == self.cols and other.rows == self.rows:
|
|
126
|
+
return Matrix([ [ x1 + y1 for x1, y1 in zip(x,y)] for x, y in zip(self.matrix, other.matrix)])
|
|
127
|
+
return NotImplemented
|
|
128
|
+
|
|
129
|
+
def __sub__(self, other) -> "Matrix":
|
|
130
|
+
if isinstance(other, Matrix) and other.cols == self.cols and other.rows == self.rows:
|
|
131
|
+
return Matrix([ [ x1 - y1 for x1, y1 in zip(x,y)] for x, y in zip(self.matrix, other.matrix)])
|
|
132
|
+
return NotImplemented
|
|
133
|
+
|
|
134
|
+
def __neg__(self) -> "Matrix":
|
|
135
|
+
return -1 * self
|
|
136
|
+
|
|
137
|
+
def __mul__(self, other) -> "Matrix":
|
|
138
|
+
if isinstance(other, Matrix):
|
|
139
|
+
return self.multiply(other)
|
|
140
|
+
return NotImplemented
|
|
141
|
+
|
|
142
|
+
def __rmul__(self, other) -> "Matrix":
|
|
143
|
+
if isinstance(other, Matrix):
|
|
144
|
+
return self.multiply(other)
|
|
145
|
+
return Matrix([ [item * other for item in row] for row in self.matrix ])
|
|
146
|
+
|
|
147
|
+
def rref(self) -> "Matrix":
|
|
148
|
+
"Compute the reduced echelon form of a matrix M."
|
|
149
|
+
n, m = self.cols, self.rows
|
|
150
|
+
R = self[:, :]
|
|
151
|
+
i = 0
|
|
152
|
+
for j in range(n):
|
|
153
|
+
if not R[i, j]: # search for am nonzero entry in the present column
|
|
154
|
+
for ii in range(i+1,m):
|
|
155
|
+
if R[ii, j]:
|
|
156
|
+
R[i, :], R[ii, :] = R[ii, :], R[i, :] # swap rows
|
|
157
|
+
break
|
|
158
|
+
else:
|
|
159
|
+
continue # all entries are zero
|
|
160
|
+
if R[i, j] != 1:
|
|
161
|
+
R[i, :] = 1/ R[i, j] * R[i, :] # make the pivot one
|
|
162
|
+
for ii in range(m): # remove the column entries above/below the pivot
|
|
163
|
+
if i == ii:
|
|
164
|
+
continue
|
|
165
|
+
tmp = R[ii, j]
|
|
166
|
+
R[ii, ::] -= tmp * R[i, :]
|
|
167
|
+
i += 1
|
|
168
|
+
if i == m:
|
|
169
|
+
break
|
|
170
|
+
return R
|
|
171
|
+
|
|
172
|
+
def det(self) -> int:
|
|
173
|
+
"Compute the determinant of a matrix M."
|
|
174
|
+
if self.rows != self.rows:
|
|
175
|
+
raise ValueError("Matrix must be square!")
|
|
176
|
+
n = self.cols
|
|
177
|
+
R = self[:, :]
|
|
178
|
+
D = 1
|
|
179
|
+
i = 0
|
|
180
|
+
for j in range(n):
|
|
181
|
+
if not R[i, j]: # search for am nonzero entry in the present column
|
|
182
|
+
for ii in range(i+1,m):
|
|
183
|
+
if R[ii, j]:
|
|
184
|
+
D *= -1
|
|
185
|
+
R[i, :], R[ii, :] = R[ii, :], R[i, :] # swap rows
|
|
186
|
+
break
|
|
187
|
+
else:
|
|
188
|
+
return 0 # all entries are zero
|
|
189
|
+
if R[i, j] != 1:
|
|
190
|
+
D *= R[i, j]
|
|
191
|
+
R[i, :] = 1/ R[i, j] * R[i, :] # make the pivot one
|
|
192
|
+
for ii in range(i+1,n): # remove the column entries below the pivot
|
|
193
|
+
if i == ii:
|
|
194
|
+
continue
|
|
195
|
+
tmp = R[ii, j]
|
|
196
|
+
R[ii, ::] -= tmp * R[i, :]
|
|
197
|
+
i += 1
|
|
198
|
+
return D
|
|
199
|
+
|
|
200
|
+
def inv(self) -> "Matrix":
|
|
201
|
+
"Compute the inverse of a square matrix M."
|
|
202
|
+
if self.rows != self.rows:
|
|
203
|
+
raise ValueError("Matrix must be square!")
|
|
204
|
+
n = self.cols
|
|
205
|
+
MM = Matrix([[0 for _ in range(2*n)] for _ in range(n)])
|
|
206
|
+
for i in range(n):
|
|
207
|
+
MM[i,n+i] = 1
|
|
208
|
+
MM[:,0:n] = self
|
|
209
|
+
MM = MM.rref()
|
|
210
|
+
if not prod(MM[i, i] for i in range(n)):
|
|
211
|
+
raise ValueError("Matrix is not invertible!")
|
|
212
|
+
return MM[:,n:]
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def zeros(m:int, n: int) -> "Matrix":
|
|
216
|
+
return Matrix([[ 0 for j in range(n)] for i in range(m) ])
|
|
217
|
+
|
|
218
|
+
def eye(m:int, n: int) -> "Matrix":
|
|
219
|
+
def delta(i, j):
|
|
220
|
+
if i == j:
|
|
221
|
+
return 1
|
|
222
|
+
return 0
|
|
223
|
+
return Matrix([[ delta(i, j) for j in range(n) ] for i in range(m) ])
|