# Secure MPC: Recursive Least Squares

## 39 days ago by ktjell12@student.aau.dk

##################################################################################################### # This cell implementes most of the secure MPC funtions, used in the masters thesis # # by Katrine Tjell. # # To name a few: # # - Shamir's Secret sharing scheme # # - Multiplication protocol # # - Beavers triplet creation # # - Integer division # # - Comparison protocol # ##################################################################################################### import numpy as np import matplotlib.pylab as plt import csv import sys #Creates the "recombination"-vector used to reconstruct a secret from its shares. def basispoly(F, n): r = [] C = range(1,n+1) for i in range(1,n+1): c = [k for k in C if k != i] p = 1 for j in range(n-1): p *= -F(c[j]) / (F(i)-F(c[j])) r.append(F(p)); return r #Creates shares of secrets using Shamir's secret sharing scheme. def secretsharing(F, x, t, n): shares = [] c = [F.random_element() for i in range(t)] for i in range(1, n+1): s = x for j in range(1,t+1): s = F(s) + F(c[j-1]) * F(i)**F(j) shares.append(s) return np.array(shares) #Secure mulitplication of two secrets. -Resulting polynomial is still degree t. def mult(a,b): r = basispoly(F,n) h = np.array(a)*np.array(b) hs = [] for i in range(n): hs.append(secretsharing(F,h[i],t,n)) s = secretsharing(F,0,t,n) for i in range(n): s+= np.array(r[i]) * np.array(hs[i]) return np.array(s) #Create a Beaver's triplet. def triplet(F, n, t): r = basispoly(F, n) matrixA = np.zeros((n,n)) matrixB = np.zeros((n,n)) for i in range(n): a = F.random_element() b = F.random_element() matrixA[:,i] = secretsharing(F, a, t, n) matrixB[:,i] = secretsharing(F, b, t, n) sharesA = np.sum(matrixA, 1) sharesA = [F(i) for i in sharesA] sharesB = np.sum(matrixB, 1) sharesB = [F(i) for i in sharesB] sharesC = mult(sharesA,sharesB) #[F(i*j) for (i,j) in zip(sharesA, sharesB)] return [sharesA, sharesB, sharesC] # Shoul return a random bit unknown to all parties. Does not seem to work for huge field. def randomBit(F, n, t): #basis = basispoly(F,n) #rsq = 0 #while rsq == 0: #If r**2 = 0, the parties retry # r_shares = triplet(F,n,t)[0] #The parties compute an unknown random number, r, and computes r**2 # salpha, sbeta, sgamma = triplet(F, n, t) # d = dot(F, basis, [F(i-j) for (i,j) in zip(r_shares, salpha)]) # e = dot(F, basis, [F(i-j) for (i,j) in zip(r_shares, sbeta)]) # rsq_shares = [F(d*e + d*i + e*j + k) for (i,j,k) in zip(sbeta, salpha, sgamma)] # rsq = dot(F, rsq_shares, basis) #r**2 is revealed #r_prime = sqrt(rsq) #print 'rprime', r_prime r = np.random.choice(2,1)[0] return list(secretsharing(F,r,t,n))#list(F(1/2) * (1/r_prime * np.array(r_shares) + 1)) #Return both sharings of a random number and the bitwise sharing of the same random number. def randomBitNumber(F, n, t, l): basis = basispoly(F,n) sharings = [] for i in range(l): sharings.append(randomBit(F,n,t)) sharings_reverse = sharings[::-1] r_shares = [] for i in range(n): s = 0 for j in range(l): s += 2**j * dot(F, basis, sharings_reverse[j]) r_shares.append(s) return sharings, r_shares #Multiplication using Beavers' triple, does not seem to work for huge field. def multB(sx1,sx2): salpha, sbeta, sgamma = triplet(F, n, t) r = basispoly(F,len(sx1)) d = dot(F, r, [F(i-j) for (i,j) in zip(sx1, salpha)]) e = dot(F, r, [F(i-j) for (i,j) in zip(sx2, sbeta)]) return np.array([F(d*e + d*i + e*j + k) for (i,j,k) in zip(sbeta, salpha, sgamma)]) #Finding pre-or of an integer def PREor(a, lam = 2): x = [] for i in range(lam): x.append(OR(a[lam*i:lam*(i+1)])) z = [] for i in range(lam): z.append(OR(x[0:i+1])) f = [x[0]] for i in range(1,lam): f.append(z[i] - z[i-1]) g = [] for i in range(lam): for j in range(lam): g.append(mult(f[i],a[j+i*lam])) c = [] for i in range(lam): c.append(sum(a[lam*i:lam*(i+1)])) b = [] for i in range(lam): b.append(OR(c[0:i+1])) s = [] for i in range(lam): for j in range(lam): s.append(mult(f[i],b[j])) print s y = [] for i in range(lam): for j in range(lam): y.append(s[j+(lam*i)] + z[i] - f[i]) return y #Computing the truth of whether a < b. def lessthan(a,b): e = [] for i in range(len(a)): e.append(xor(a[i],b[i])) f = [] for i in range(len(e)): f.append(OR(e[0:i+1])) g = [f[0]] for i in range(0,len(f)-1): g.append(f[i+1] - f[i]) h = [] for i in range(len(g)): h.append(mult(g[i], b[i])) y = [] for i in range(len(h[0])): s = 0 for j in range(len(h)): s += h[j][i] y.append(s) return y #Computing the inverse of a field element. def inverse(x, r): b = basispoly(F,len(x)) w_s = [i*j for i,j in zip(x,r)] w = dot(F,b,w_s) return w**(-1) * np.array(r) #Dividing a secret with a public integer def down(F, q, k,t,n): basis = basispoly(F,n) #while True: # r = F.random_element() # if r < 10*dot(F,basis,q): # break r = F(900)# r_T = np.trunc(np.array(r,dtype = float) *k**(-1)) r_s = secretsharing(F,r,t,n) r_Ts = secretsharing(F,F(r_T),t,n) z_s = q + r_s z = dot(F,basis,z_s) z_T = np.trunc(np.array(z, dtype = float) * k**(-1)) z_Ts = secretsharing(F,F(z_T),t,n) return z_Ts - r_Ts #Bitwise AND operation def AND(a): p = a[0] for i in range(1,len(a)): p = mult(p, a[i]) return p #Bitwise XOR operation def xor(a1,b1): d = np.array(a1) - np.array(b1) return mult(d,d) #Bitwise OR operation def OR(a): p = 1 - a[0] for i in range(1,len(a)): p = mult(p, (1 - a[i])) return 1 - p #Converting bitwise sharing's of a secret to ordinary sharings. def dec(a): s = 0 for i in range(len(a)): s+= a[i] * 2**i return s #Computing PRE-OR more efficiently. def pre(F, d, r, rb, n, t, l): ### ADD RANDOMNESS basis = basispoly(F,n) rb.insert(0, list(secretsharing(F,0,t,n))) es = np.array(d) + np.array(r) e = dot(F, basis, es) ebit = list(bin(e)[2:].rjust(l+1,'0')) ebs = [] for b in ebit: ebs.append(secretsharing(F,b,t,n)) ### Create H ep = [xor(i,j) for i,j in zip(ebs,rb)] E = [] for i in range(len(ep)): E.append(OR(ep[0:i+1])) h = [i - mult(i,j) for i,j in zip (rb, ebs)] hp = [] for i in range(len(h)): if i ==0: Es = 0 else: Es = E[i-1] hp.append(h[i] + 1 - Es) H = [] for i in range(len(hp)): H.append(AND(hp[0:i+1])) ### Calc Hld Hp = [] for i in range(len(H)): Hp.append(xor(H[i],1)) e0 = [] r0 = [] for i in range(len(Hp)): e0.append(mult(Hp[i],ebs[i])) r0.append(mult(Hp[i], rb[i])) B1 = lessthan(e0,r0) H_aim = [1-i for i in B1] ### Trans H into D D_ = [] H2 = H[1:] H2.append(secretsharing(F,0,t,n)) N = [1-i for i in H_aim] for i in range(len(H)): D_.append(mult(H_aim, H2[i]) + mult(N,H[i])) D = [1-i for i in D_] D = D[::-1] y = [] for i in range(len(D[0])): b = [] for j in range(len(D)): b.append(D[j][i]) y.append(dec(b)) return y #Integer division def integerDiv(F, d, nom, n, t, l, k): r1,r2,c = triplet(F, n, t) rr = 0 while rr == 0: rbits, r = randomBitNumber(F, n, t, l) rr = dot(F,basispoly(F,n),r) D = pre(F, d, r, rbits, n, t, l) shift = [1+i for i in D] shiftInv = inverse(shift, r) a1 = [i-j for i,j in zip(shift, d)] p = mult(a1,shiftInv) sum_s = [] prod = secretsharing(F,1,t,n) s = np.array(n*[0]) for i in range(1,l): prod = mult(prod, p) s = s + np.array(prod) s = [1+i for i in s] a_hat = [2**k*i for i in shiftInv] a_hat = mult(a_hat,s) q_hat = mult(nom,a_hat) q = down(F, q_hat, 2**k,t,n) r = nom - mult(d,q) dd = dot(F,basispoly(F,n), d+d) rd = dot(F,basispoly(F,n), r+d) ep = int(dd <= rd) em = int(dot(F, basispoly(F,n),d) > rd) y = q + ep - em return y # dot product of two vectors. def dot(F, x, y): res = 0 for i in range(len(x)): res += F(x[i] * y[i]) return res ###############################MAIN ########################################## ##### CONSTANTS ################################ set_random_seed(1) np.random.seed(1) m = 1363005552434666078217421284621279933627102780881053358473 # Prime number, large enough that the probability of chossing a certain number is negeligable F = GF(m) # Finite field with m elements n = 4 # Number of parties t = 1 # Degree of polynomial l = 13 # bit length of values r = basispoly(F,n) # Lagrange basispolynomial coefficients #Tjeck that multiplication works: s1 = secretsharing(F,128,t,n) s2 = secretsharing(F,3,t,n) y = mult(s1,s2) print dot(F, r, y)
 384 384
########################################################################### #This cells defines functions that are more speciffically used to # #compute the recursive least squares equations securely. # ########################################################################### #Should and could, be implemented securely, see the report. def less(a,b): return int( dot(F,basispoly(F,n),a) < dot(F,basispoly(F,n),b) ) def reVector(b, n): r = basispoly(F,n) Nvector = [] for i in range(2): liste = [] for j in range(n): liste.append(b[j][i][0]) Nvector.append(F(dot(F,r,liste)))#[b[0][1][i][0],b[1][1][i][0],b[2][1][i][0],b[3][1][i][0]])) return matrix(2,1,Nvector) def ILS(F, P, x, w, y, C, t, n, i): g = matVec(P,x) e0 = vecVec(x,w) e0 = down(F,e0,C,t,n) e = [k-j for k,j in zip(y, e0)] a = [mult(k,e) for k in g] w = [i+j for i,j in zip(w,a)] return w def estP(F,P, x, C,t,n): xP = vecMat(x,P) a1 = vecVec(xP, x) denom = C + a1 Px = matVec(P,x) Part = VecVec(Px, xP) return denom, Part #Computing securely with coorect degree polynomial: x.tranpose * y def vecVec(x,y): p = len(x) s = secretsharing(F,0,t,n) for i in range(p): s+= mult(x[i], y[i]) return s #Computing securely with correct degree polynomial: x.transpose * A def vecMat(x,A): p = len(x) res = [] for i in range(p): s = secretsharing(F,0,t,n) for j in range(p): s+= mult(x[j], A[i+j*p]) res.append(s) return res #Computing securely with correct degree polynomial: A * x def matVec(A,x): res = [] p = len(x) for i in range(p): s = secretsharing(F,0,t,n) for j in range(p): s += mult(x[j], A[j+(i*p)]) res.append(s) return res #Computing securely with correct degree polynomial: x * x.transpose def VecVec(x,y): res = [] for i in range(len(y)): for j in range(len(x)): res.append(mult(y[i],x[j])) return res
########################################################################################### # This cells computes SECURELY the recursice least square equations for # # 1 and 2 dimensions - using the functions from cell 1 and 2 # #(thus cell 1 and 2 must run before attempting to run this cell). # ########################################################################################### np.random.seed(1) obs = 10 x1 = np.random.randint(0,10,obs) #Observations of input x2 = np.random.randint(0,5,obs) #Observations of input beta1 = 2 #Parameters beta2 = 5 #Parameters #y = [beta1 + beta2*i for i in x1] #Observations of output for 1 dimensional case y = [beta1*i + beta2*j for i,j in zip(x1,x2)] #Observations of output for 2 dimensional case #noice = np.random.normal(0,2,obs) #Noice #y = y+noice #Add noice #y = np.array(y,dtype = int) #Truncate Y #SETTINGS:######## n = 4 #Numbers of parties t = 1 #Corrupted parties p = 2 #Dimension C = 2**7 #Scaling constant P = C*np.eye(p) #Initialize P Ps = [] #List for holding shares of P l = 13 #Maximum number of bits for any secret nominator and denominator k=l**2+l #Scaling for 1/d basis = basispoly(F,n) #Reconstruction - Lagrange basispolynomials #The P matrix, will be a list of the secret entries. --> Ps = [P[1,1], P[1,2], P[2,1], P[2,2]]. for i in range(p): for j in range(p): Ps.append(secretsharing(F,P[i][j],t,n)) w = [secretsharing(F,0,t,n), secretsharing(F,0,t,n), secretsharing(F,0,t,n)] #Initialization of the parameter estimate w1 = [] w2 = [] for i in range(obs): #x = [1, x1[i]] #Observation vector for 1 dimension x = [x1[i], x2[i]] #Observation vector for 2 dimensions X = [list(secretsharing(F,j,t,n)) for j in x] #Secret sharing of observations denom, Ppart = estP(F,Ps, X,C,t,n) # TJECK FOR WRAP-AROUND ZERO b = less(Ppart[1]*-1, Ppart[1]) if b: Ppart = [Ppart[0], -Ppart[1], -Ppart[2], Ppart[3]] P2 = [integerDiv(F, denom, np.array(j), n, t, l, k) for j in Ppart] if b: P2 = [P2[0], -P2[1], -P2[2], P2[3]] Ps = [h-j for h,j in zip(Ps, P2)] y2 = secretsharing(F,y[i],t,n) w = ILS(F, Ps, X, w, y2, C,t,n,i) w_open = [] for j in w: w_open.append(np.array(dot(F,basis,j),dtype = float)/C) w_open = np.array(w_open) print w_open w1.append(w_open[0]) w2.append(w_open[1]) #print '({},{})'.format(i, 1/2 * ( (w_open[0] - beta1) **2 + (w_open[1] - beta2)**2 ) ) e = 1/2 *( (np.array(w1) - beta1)**2 + (np.array(w2)-beta2)**2) list_plot(e)
 [ 4.0625 1.875 ] [ 3.9375 1. ] [ 2.6015625 3.8828125] [ 2.3984375 4.3359375] [ 2.2109375 4.7109375] [ 2.0859375 4.9609375] [ 2.0859375 4.9609375] [ 2.0859375 4.9609375] [ 2.0859375 4.9609375] [ 2.0859375 4.9609375] [ 4.0625 1.875 ] [ 3.9375 1. ] [ 2.6015625 3.8828125] [ 2.3984375 4.3359375] [ 2.2109375 4.7109375] [ 2.0859375 4.9609375] [ 2.0859375 4.9609375] [ 2.0859375 4.9609375] [ 2.0859375 4.9609375] [ 2.0859375 4.9609375]
########################################################################################### # This cells computes SECURELY the recursice least square equations for # # 3 dimensions - using the functions from cell 1 and 2 # #(thus cell 1 and 2 must run before attempting to run this cell). # ########################################################################################### np.random.seed(1) obs = 10 x1 = np.random.randint(0,5,obs) #Observations of input x2 = np.random.randint(0,5,obs) #Observations of input x3 = np.random.randint(0,5,obs) #Observations of input beta1 = 2 #Parameter beta2 = 5 #Parameter beta3 = 7 #Parameter y = [beta1*i + beta2*j + beta3*w for i,j,w in zip(x1,x2,x3)] #Observations of output #noice = np.random.normal(0,2,obs) #Noice for 2 #y = y+noice #Add noice for 2 #y = np.array(y,dtype = int) #Truncate Y for 2 n = 4 #Numbers of parties t = 1 #Corrupted parties p = 3 #Dimension C = 2**7 #Scaling constant P = C*np.eye(p) #Initialize P Ps = [] #List for holding shares of P l = 13 #Maximum number of bits for any secret nominator and denominator k=l**2+l #Scaling for 1/d basis = basispoly(F,n) #Reconstruction Lagrange basispolynomials #The P matrix, will be a list of the secret entries. --> Ps = [P[1,1], P[1,2], P[2,1], P[2,2]]. for i in range(p): for j in range(p): Ps.append(secretsharing(F,P[i][j],t,n)) w = [secretsharing(F,0,t,n), secretsharing(F,0,t,n), secretsharing(F,0,t,n)] #Initialization of the parameter estimate w1 = [] w2 = [] w3 = [] for i in range(obs): x = [x1[i], x2[i], x3[i]] #Observation vector for 3 X = [list(secretsharing(F,j,t,n)) for j in x] #Secret sharing of observations denom, Ppart = estP(F,Ps, X,C,t,n) # TJECK FOR WRAP-AROUND ZERO b = [less(Ppart[1]*-1, Ppart[1]) , less(Ppart[2]*-1, Ppart[2]), less(Ppart[5]*-1, Ppart[5]) ] if b[0]: Ppart[1] = -Ppart[1] Ppart[3] = -Ppart[3] if b[1]: Ppart[2] = -Ppart[2] Ppart[6] = -Ppart[6] if b[2]: Ppart[5] = -Ppart[5] Ppart[7] = -Ppart[7] P2 = [integerDiv(F, denom, np.array(j), n, t, l, k) for j in Ppart] if b[0]: P2[1] = -P2[1] P2[3] = -P2[3] if b[1]: P2[2] = -P2[2] P2[6] = -P2[6] if b[2]: P2[5] = -P2[5] P2[7] = -P2[7] Ps = [h-j for h,j in zip(Ps, P2)] y2 = secretsharing(F,y[i],t,n) w = ILS(F, Ps, X, w, y2, C,t,n,i) w_open = [] for j in w: w_open.append(np.array(dot(F,basis,j),dtype = float)/C) w_open = np.array(w_open) print w_open w1.append(w_open[0]) w2.append(w_open[1]) w3.append(w_open[2]) e = 1/3 *( (np.array(w1) - beta1)**2 + (np.array(w2)-beta2)**2 + (np.array(w3)-beta3)**2) list_plot(e)
 [ 6.09375 3.046875 7.921875] [ 3.1484375 1.1171875 10.0546875] [ 1.421875 4.46875 10.8671875] [ 1.546875 4.6875 10.6640625] [ 1.4453125 4.578125 10.734375 ] [ 1.6484375 4.375 10.546875 ] [ 1.734375 4.265625 10.46875 ] [ 1.734375 4.2265625 10.4140625] [ 2.359375 4.5390625 9.6328125] [ 2.015625 4.8828125 8.0859375] [ 6.09375 3.046875 7.921875] [ 3.1484375 1.1171875 10.0546875] [ 1.421875 4.46875 10.8671875] [ 1.546875 4.6875 10.6640625] [ 1.4453125 4.578125 10.734375 ] [ 1.6484375 4.375 10.546875 ] [ 1.734375 4.265625 10.46875 ] [ 1.734375 4.2265625 10.4140625] [ 2.359375 4.5390625 9.6328125] [ 2.015625 4.8828125 8.0859375]
########################################################################################### # This cells computes NON-SECURELY the recursice least square equations. # # Thus FLOATING-point numbers are used in these equations. Can be used as a comparison # # for the results from cell 3 and 4. # ########################################################################################### np.random.seed(1) obs = 50 #x1 = np.arange(0,obs) #Test data x1 =np.random.randint(0,10,obs) #Test data x2 = np.random.randint(0,5,obs) #Test data x3 = np.random.randint(0,5,obs) #Test data beta1 = 2 #Parameters beta2 = 5 #Parameters beta3 = 7 p = 2 #Problem dimension y = [beta1 + beta2*i for i in x1] #Y-data 1 dimension #y = [beta1*i + beta2*j for i,j in zip(x1,x2)] #Y-data 2 dimensions #y = [beta1*i + beta2*j + beta3*q for i,j,q in zip(x1,x2,x3)] #Y-data 3 dimensions #noice = np.random.normal(0,2,obs) #Noice #y = y+noice #Add noice P = matrix.identity(p) w = matrix(p,1,p*[0]) w1 = [] w2 = [] for i in range(obs): x = matrix(p,1,[1, x1[i]]) #Observation vector for 1 dimension #x = matrix(p,1,[x1[i], x2[i]])#, x3[i]]) #Observation vector for 2 and 3 dimensions denom = (1 + x.transpose()* P * x) P = P - matrix(denom[0][0]**(-1) *( P * x * x.transpose() * P)) g = P * x e = y[i] - x.transpose() * w w = matrix(w + g * e) #print np.array(w).transpose() w3 = np.array(w) print w3.transpose() #print '({},{})'.format(i, float(1/2 * ( (w3[0] - beta1) **2 + (w3[1] - beta2)**2 )) ) ##print '({},{:.10f})'.format(i, float(1/3 * ( (w3[0] - beta1) **2 + (w3[1] - beta2)**2 + (w3[2] -beta3)**2 )) ) w1.append(w3[0]) w2.append(w3[1]) e = 1/2 *( (np.array(w1) - beta1)**2 + (np.array(w2)-beta2)**2) list_plot(e)
 [[1 5]] [[ 0.86138614 5.10891089]] [[ 0.84 5.12]] [[ 0.97609562 5.11553785]] [[ 1.42505593 5.05369128]] [[ 1.60031104 5.02954899]] [[ 1.67929293 5.02020202]] [[ 1.67947422 5.02527806]] [[ 1.68481124 5.02809482]] [[ 1.68117883 5.03014066]] [[ 1.72117647 5.02588235]] [[ 1.73629951 5.0255019 ]] [[ 1.74334505 5.02611753]] [[ 1.76978743 5.02306649]] [[ 1.7791437 5.02289106]] [[ 1.79907085 5.02051878]] [[ 1.80585009 5.02047532]] [[ 1.8047979 5.0213605]] [[ 1.80393912 5.02208296]] [[ 1.80410023 5.02201974]] [[ 1.82505106 5.01906059]] [[ 1.82460568 5.01955836]] [[ 1.84646539 5.01620029]] [[ 1.8469869 5.01659389]] [[ 1.84683017 5.01665895]] [[ 1.84670626 5.0167104 ]] [[ 1.84660651 5.01700013]] [[ 1.84734263 5.01721322]] [[ 1.84742268 5.01718213]] [[ 1.86011999 5.01547206]] [[ 1.8739448 5.01346865]] [[ 1.88281043 5.01228458]] [[ 1.88253863 5.01246054]] [[ 1.88229763 5.01261657]] [[ 1.88632072 5.01223657]] [[ 1.88623132 5.01227467]] [[ 1.88604558 5.01239504]] [[ 1.88606131 5.01253133]] [[ 1.88979896 5.01216183]] [[ 1.89023728 5.01225623]] [[ 1.89137738 5.01225909]] [[ 1.89797616 5.01136679]] [[ 1.89798259 5.01136423]] [[ 1.90096509 5.01106564]] [[ 1.90271337 5.01095215]] [[ 1.90259587 5.01101707]] [[ 1.90793537 5.01029405]] [[ 1.90944106 5.01019828]] [[ 1.91547857 5.00931411]] [[ 1.9175389 5.0091137]] [[1 5]] [[ 0.86138614 5.10891089]] [[ 0.84 5.12]] [[ 0.97609562 5.11553785]] [[ 1.42505593 5.05369128]] [[ 1.60031104 5.02954899]] [[ 1.67929293 5.02020202]] [[ 1.67947422 5.02527806]] [[ 1.68481124 5.02809482]] [[ 1.68117883 5.03014066]] [[ 1.72117647 5.02588235]] [[ 1.73629951 5.0255019 ]] [[ 1.74334505 5.02611753]] [[ 1.76978743 5.02306649]] [[ 1.7791437 5.02289106]] [[ 1.79907085 5.02051878]] [[ 1.80585009 5.02047532]] [[ 1.8047979 5.0213605]] [[ 1.80393912 5.02208296]] [[ 1.80410023 5.02201974]] [[ 1.82505106 5.01906059]] [[ 1.82460568 5.01955836]] [[ 1.84646539 5.01620029]] [[ 1.8469869 5.01659389]] [[ 1.84683017 5.01665895]] [[ 1.84670626 5.0167104 ]] [[ 1.84660651 5.01700013]] [[ 1.84734263 5.01721322]] [[ 1.84742268 5.01718213]] [[ 1.86011999 5.01547206]] [[ 1.8739448 5.01346865]] [[ 1.88281043 5.01228458]] [[ 1.88253863 5.01246054]] [[ 1.88229763 5.01261657]] [[ 1.88632072 5.01223657]] [[ 1.88623132 5.01227467]] [[ 1.88604558 5.01239504]] [[ 1.88606131 5.01253133]] [[ 1.88979896 5.01216183]] [[ 1.89023728 5.01225623]] [[ 1.89137738 5.01225909]] [[ 1.89797616 5.01136679]] [[ 1.89798259 5.01136423]] [[ 1.90096509 5.01106564]] [[ 1.90271337 5.01095215]] [[ 1.90259587 5.01101707]] [[ 1.90793537 5.01029405]] [[ 1.90944106 5.01019828]] [[ 1.91547857 5.00931411]] [[ 1.9175389 5.0091137]]