Calculate nolinear model of vibration control - python

I am studding automatic and have a matlab/SIMULINK model of nonlinear vibration. I am trying to resolve the same problem using python instead.
Here we have a schema. The black frame is a part that I have already. I also have a Model tłumnika MR (eng. Magnetorheological Damper). Just I don't know how to connect it together?
There are welcome any suggestion how to resolve this including modifying model.
Code for part 1 (black frame) [just pseudo code ]
def rms(v): # ROOT MEAN SQRT
return np.sqrt(np.mean(np.square(v)))
def transmissibility(_in, _out):
# Transmissibility (T) = output/input
return rms(_out) / rms(_in)
def q(ss, t, Ampituda, freqs):
y2 = []
for f in freqs:
u = Ampituda * np.sin(2 * np.pi * f * t) # wektor wejscia
t1, y1, x1 = signal.lsim(ss, u, t) # wyliczenie modelu w dziedzinie czasu
y2.append(transmissibility(u, y1))
return y2
vec_c = np.arange(1000, 6000 , 1000)
for c in vec_c:
A = [[0, 1], [-(k/m), -(c/m)]]
B = [[-c/m], [(k/m) - (c/m)**2]]
C = [1, 0]
D = 0
ss = signal.StateSpace(A,B,C,D) # State Space model
y2 = q(ss, t, Ampituda, freqs)
plt.plot(freqs, y2, label=r'$c = {} \frac{}{} $'.format(c, "{Ns}", "{m}"), linewidth=2.0)
plt.legend()
Code for MR (Model tłumnika MR) [copy paste example]
from scipy import signal
import numpy as np
def I(to_intagrate, dt, y0=0):
i = [y0]
for v in to_intagrate:
i.append(i[-1] + v * dt)
del i[0]
return i
def MR(v, i, dt):
""" #v -- prędkość (z' -w') wektor przyśpieszeń
#i -- natężenie prądu w amperach
return Siła generowan przez tłumnik w N
Slajd 18
http://home.agh.edu.pl/~jastrzeb/images/SSD/SSD_2DOF_v1.pdf
"""
b1 = 3415.7
b2 = 93.324
b3 = 74.487
F0 = b1 * (i**2) + b2 * i + b3
b4 = 2534.1
b5 = 19.55
b6 = 643.1
C1 = b4 * (i**2) + b5*i + b6
beta = 50
p1 = 4
p2 = 0.2
x = I(v, dt)
Ft = []
for x1, v1 in zip(x, v):
part1 = F0 * np.tanh( beta * (v1 + (p1 * x1)))
part2 = C1 * (v1 + (p2 * x1))
Ft.append(part1 + part2)
return Ft, x
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = (20, 16)
plt.rcParams['font.size'] = 18
for f in [2, 5]: # wybrane częstotliwości
# f = 5
i = 0.2
# x_sin wektor wartości x dla wymuszenia sinusoidalnego 201 pkt
# na każdy okres sinusa. Rozpoczęcie pkt pracy w -0.2
x_sin = np.linspace(-np.pi/2, (np.pi * f) - np.pi/2, num=201 * f)
u = np.sin(x_sin) * 0.2 # przeskalowanie przyśpieszenia
dt = 1/(f*201)
ft, x = MR(u, i, dt) # sila
plt.plot(u, ft, label='Freq = {}Hz, I={}A'.format(f, i))
plt.legend()
plt.grid()
plt.show()

Related

What is the best way to get double integral from discontinuous function?

I am using scipy.integrate.dblquad but got warning that says:
The maximum number of subdivisions (50) has been achieved.
If increasing the limit yields no improvement it is advised to analyze the integrand in order
to determine the difficulties.
If the position of a local difficulty can be determined (singularity, discontinuity) one
will probably gain from splitting up the interval and calling the integrator on the subranges.
Perhaps a special-purpose integrator should be use
Also tried nquad and set limit to 100 got the warning.
The integrand looks like THIS, where V-function is integrated over x and y.
IF NEEDED the code:
import numpy as np
import math
#from scipy.integrate import *
import scipy.optimize as optimize
from scipy.integrate import nquad
import matplotlib.pyplot as plt
def rotcoords(y, x, alpha):
X = x * np.cos(-alpha) + y * np.sin(-alpha)
Y = -y * np.sin(-alpha) + y * np.cos(-alpha)
return (X, Y)
def getcoords(y, x, dx, dy, alpha, r):
d = 2 * r
X, Y = rotcoords((y-dy), (x-dx), alpha)
#dX, dY = rotcoords(dy, dx, alpha)
#X = X - dX
#Y = Y - dY
X = np.abs(X - X // d * d) - r
Y = np.abs(Y - Y // d * d) - r
I = np.sqrt(X**2 + Y**2)
return I
def function_V(y, x, dx, dy, alpha, r):
v1=getcoords(y,x,0,0,0,r)
#v1=getcoords(y,x,dx,dy,alpha,r)
if v1>=r:
v1 = 0
v2=getcoords(y,x,dx,dy,alpha,r)
if v2>=r:
v2 = 0
V = np.max([v1, v2])
return V
def function_Al(y, x, dx, dy, alpha, r):
R1=getcoords(y,x,0,0,0,r)
#R1=getcoords(y,x,dx,dy,alpha,r)
if (R1<r/3) or (R1>r):
A1 = 1
else:
A1 = 0
R2=getcoords(y,x,dx,dy,alpha,r)
if (R2<r/3) or (R2>r):
A2 = 1
else:
A2 = 0
A = np.min([A1, A2])
return A
def function_Ah(y, x, dx, dy, alpha, r):
R1=getcoords(y,x,0,0,0,r)
#R1=getcoords(y,x,dx,dy,alpha,r)
if (R1<=r) and (R1>=2*r/3):
A1 = 1
else:
A1 = 0
R2=getcoords(y,x,dx,dy,alpha,r)
if (R2<=r) and (R2>=2*r/3):
A2 = 1
else:
A2 = 0
A = np.max([A1, A2])
return A
def intgrl_V(params):
dx, dy, alpha = params
r=0.5
d = 2 * r
w1 = 0
w2 = 3*d
h1 = 0
h2 = 3*d
# Integrate for V
#intgrl, abserr = dblquad(function_V, w1, w2, lambda x: h1, lambda x: h2, args=(dx, dy, alpha, r))
options = {'limit': 100}
intgrl, abserr = nquad(function_V, [[w1,w2],[h1,h2]], args=(dx, dy, alpha, r), opts=[options, options])
# Take average of the integral over the area
A = (w2 - w1) * (h2 - h1)
return -intgrl/A
def intgrl_Al(params):
dx, dy, alpha = params
r=0.5
d = 2 * r
w1 = 0
w2 = 3*d
h1 = 0
h2 = 3*d
# Integrate for A_low
#intgrl, abserr = dblquad(function_Al, w1, w2, lambda x: h1, lambda x: h2, args=(dx, dy, alpha, r))
options = {'limit': 100}
intgrl, abserr = nquad(function_Al, [[w1,w2],[h1,h2]], args=(dx, dy, alpha, r), opts=[options, options])
# Take average of the integral over the area
A = (w2 - w1) * (h2 - h1)
return intgrl/A
def intgrl_Ah(params):
dx, dy, alpha = params
r=0.5
d = 2 * r
w1 = 0
w2 = 3*d
h1 = 0
h2 = 3*d
# Integrate for A_low
#intgrl, abserr = dblquad(function_Ah, w1, w2, lambda x: h1, lambda x: h2, args=(dx, dy, alpha, r))
options = {'limit': 100}
intgrl, abserr = nquad(function_Ah, [[w1,w2],[h1,h2]], args=(dx, dy, alpha, r), opts=[options, options])
# Take average of the integral over the area
A = (w2 - w1) * (h2 - h1)
return -intgrl/A
def optfun(params):
return 1.9 * intgrl_V(params) + 7.6 * intgrl_Al(params) + intgrl_Ah(params)
initial_guess = [0.25, 0.4, 0]
result = optimize.minimize(optfun, initial_guess)
if result.success:
fitted_params = result.x
print(fitted_params)
else:
raise ValueError(result.message)

solve_ivp error : Required step size is less than spacing between numbers

I have been trying to implement a model of unstable glacier flow in Python, solving the ODEs in scipy, with the RK45 method.
The original model publication can be found here.
Now, I think I understand what is going on with the error but I cannot find a way to fix it.
I don't know if it comes from my implementation or from the ODEs themselves.
I've been through the units several times, checking that all times were in seconds, all distances in meters and so on.
I've tried with different t_eval and even different values of certain constants, but not been able to solve my problem.
I started by creating a class with all constants.
import numpy as np
import scipy.integrate
import matplotlib.pyplot as plt
import astropy.units as u
SECONDS_PER_YEAR = 3600*24*365.15
class Cst:
#Glenn's flow Law
A = 2.4e-25
n = 3.
#Standard physical constants
g = 10.#*(u.m)*(u.second**-2)
rho = 916#*(u.kilogram*(u.m**-3))
#Thermodynamics
cp = 2000#**(u.Joule)*(u.kilogram**-1)*(u.Kelvin**-1)
L = 3.3e5#*(u.Joule)*(u.kilogram**-1)
k = 2.1 #*(u.Watt)*(u.m**-1)*'(u.Kelvin**-1)'
DDF = 0.1/SECONDS_PER_YEAR #*(u.m)*(u.yr**-1)*'(u.Kelvin**-1)
K = 2.3e-47#*((3600*24*365.15)**9)#*((u.kilogram**-5)*(u.m**2)*(u.second**9))
C = 9.2e13#*((u.Pascal)*(u.Joule)*(u.m**-2))
#Weertman friction law
q = 1
p = 1/3
R = 15.7#*((u.m**(-1/3))*(u.second**(1/3)))
d = 10#*u.m
sin_theta = 0.05
Tm = 0+273.15 #*u.Kelvin
T_offset = -10+273.15#*u.Kelvin
w = 0.6 #u.m
Wc = 1000.#*u.m
#Velocities
u1 = 0/SECONDS_PER_YEAR #m/s
u2 = 100/SECONDS_PER_YEAR # m/s
#Dimensionless parameters
alpha = 5.
Then I declared the problem-specific parameters specified in the paper:
#All values are from Table 1
a0 = 1./SECONDS_PER_YEAR#* m/s (u.meter*((u.second)**-1))
l0 = 10000#*(u.meter)
E0 = 1.8e8#(Cst.g*Cst.sin_theta*a0*(l0**2))/(Cst.L*Cst.K))**(1/Cst.alpha)#*(u.Joule/u.m**2)
T0 = 10#E0/(Cst.rho*Cst.cp*Cst.d)#*u.Kelvin
w0 = 0.6#E0/(Cst.rho*Cst.L)#*u.m
N0 = 0.5#Cst.C/E0#*u.Pascal
H0 = 200 #((Cst.R*(Cst.C**Cst.q)*(a0**Cst.p)*(l0**Cst.p))/(Cst.rho*Cst.g*Cst.sin_theta*(E0**Cst.q)))**(1/(Cst.p+1))
t0 = 200 #H0/a0
u0 = 50/SECONDS_PER_YEAR#((Cst.rho*Cst.g*Cst.sin_theta*(E0**Cst.q)*a0*l0)/(Cst.R*(Cst.C**Cst.q)))**(1/(Cst.p+1))
Q0 = (Cst.g*Cst.sin_theta*a0*(l0**2))/Cst.L
S0 = ((Cst.g*Cst.sin_theta*a0*(l0**2)*Cst.Wc)/(Cst.L*Cst.K*((Cst.rho*Cst.g*Cst.sin_theta)**(1/2))))**(3/4)
lamb = ((2.*Cst.A*(Cst.rho*Cst.g*Cst.sin_theta)**Cst.n)*(H0**(Cst.n+1)))/((Cst.n+2)*u0)
chi = N0/(Cst.rho*Cst.g*H0)
gamma = 0.41
kappa = 0.7
phi = 0.2
delta = 66
mu = 0.2
Define the model :
def model(t, x):
#Initial values
H_hat = x[0]
E_hat = x[1]
#Thickness
H = H_hat*H0
#Enthalpy
E_hat_plus = max(E_hat, 0)
E_hat_minus = min(E_hat, 0)
E_plus = E_hat_plus*E0
E_minus = E_hat_minus*E0
a_hat = 1.
theta_hat = Cst.sin_theta/Cst.sin_theta
l_hat =l0/l0
T_a = 0+273.15
T = -10+273.15
# Equation 3
m_hat = (Cst.DDF*(T_a-Cst.T_offset))/a0
S_hat = 0.
T_a_hat = T_a/T0
#Equation A7
if E_plus > 0:
N = min(H/chi, 1./E_plus)
else:
N = H/chi
phi = min(1., E_plus/(H/chi))
#Equation 8
inv_p = 1./Cst.p
u = (Cst.rho*Cst.g*Cst.sin_theta/Cst.R * H * (N**(-Cst.q)))**inv_p
#Equation A7
beta = min(max(0, (u-Cst.u1)/(Cst.u2-Cst.u1)), 1)
#Equation A4
dHdt_hat = (
a_hat - m_hat
+ 1./l_hat*(
theta_hat**inv_p
* H_hat**(1.+inv_p)
* N**(-Cst.q*inv_p)
+ lamb*(theta_hat**Cst.n)
)
)
#Equation A5
dEdt_hat = 1./mu*(
theta_hat**(1+inv_p) * H_hat**(1.+inv_p) * N**(-Cst.q*inv_p)
+ gamma
+ kappa*(E_hat_minus - T_a_hat)/H_hat
- 1./l_hat * (
theta_hat * E_hat_plus**Cst.alpha
+ phi * theta_hat**(1./2) * S_hat**(4/3.)
)
+ delta * beta * m_hat
)
return [dHdt_hat, dEdt_hat]
And finally call it :
tmax = 200*SECONDS_PER_YEAR# *u.years
t = np.linspace(0, tmax, 10000)
sol = scipy.integrate.solve_ivp(model, t_span=[t[0], t[-1]], y0=[1, 1], t_eval=t, method='RK23')
print(sol)
Which yields
message: 'Required step size is less than spacing between numbers.'
nfev: 539
njev: 0
nlu: 0
sol: None
status: -1
success: False
t: array([0.])
t_events: None
y: array([[1.],
[1.]])
y_events: None

Rectifying the image

I have problem in remaping the image, I used my own millimeter sheet which contains coplanar set of points, I applied the method of direct radial alignment still the image is warped not rectified, can someone help me to find where my error is. Xw, Yw, Zw are the real world coordinates, Xf, Yf are the corresponding pixel coordinates. Cx, Cy are the coordinates of the center of distortion.
import cv2
import numpy as np
from scipy.optimize import minimize
Xd = dx*(Xf-Cx)/Sx
Yd = dy*(Yf-Cy)
n1=6
A=np.zeros((N, n1))
for i in range(N):
for j in range(n1):
A[:, 0] = Yd*Xw
A[:, 1] = Yd*Yw
A[:, 2] = Yd
A[:, 3] = -Xd*Xw
A[:, 4] = -Xd*Yw
A[:, 5] = -Xd
X = solution(A)
Sr = r1_prime**2 + r2_prime**2 + r4_prime**2 + r5_prime**2
Ty = np.sqrt(Sr-np.sqrt(Sr**2-4*(r1_prime*r5_prime-r2_prime*r4_prime)**2))/(2*(r1_prime*r5_prime-r2_prime*r4_prime)**2)
#compute the rotation matrix components:
r1 = (X[0]/X[5])*Ty
r2 = (X[1]/X[5])*Ty
r4 = (X[3]/X[5])*Ty
r5 = (X[5]/X[5])*Ty
Tx = (X[2]/X[5])*Ty
s = -np.sign(r1*r4+r2*r5)
r3 = np.sqrt(1-r1**2-r2**2)
r6 = s*np.sqrt(1-r4**2-r5**2)
r7 = np.sqrt(1-(r1**2+r4**2))
r8 = np.sqrt(1-(r2**2+r5**2))
r9 = np.sqrt(-1+Sr*Ty**2)
n11 = 2
A1=np.zeros((N, n11))
for i in range(N):
for j in range(n11):
A1[:, 0] = r4*Xw + r5*Yw +Ty
A1[:, 1] = -Yd
b1 = (r7*Xw + r8*Yw)*Yd
U1, S1, VT1 = np.linalg.svd(A1)
Sigma = np.zeros((A1.shape[0], A1.shape[1]))
Sigma[:A1.shape[1], :A1.shape[1]] = np.diag(S1)
J1 = np.zeros((A1.shape[0], A1.shape[1]))
J1[:A1.shape[1], :A1.shape[1]] = np.linalg.inv(np.diag(S1))
H1 = np.zeros((A1.shape[0], A1.shape[1]))
H1[:A1.shape[0], :A1.shape[0]] = np.linalg.multi_dot([U1, J1, VT1])
H1 = H1.T
x1 = np.dot(H1, b1)
f = x1[0]
Tz = x1[1]
R = np.array([[r1, r2, r3], [r4, r5, r6], [r7, r8, r9]])
def func(guess):
k1 = guess[0]
k2 = guess[1]
f = guess[2]
tz = guess[3]
sx = guess[4]
r = np.sqrt((dx*(Xf-Cx)/sx)**2 + (Yd)**2)
return np.sum((Yd*(1+k1*r**2 + k2*r**4)*(r7*Xw + r8*Yw + r9*Zw + tz)-f*(r4*Xw + r5*Yw + r6*Zw + Ty))**2)
x0 = np.array([0, 0, f, Tz, 1])
i = minimize(func, x0, method='COBYLA', options={'disp': True})
K1 = i.x[0]
K2 = i.x[1]
F = i.x[2]
Pz = i.x[3]
Sx = i.x[4]
dx_new = dx*Sx
nx, ny = img.shape[1], img.shape[0]
X, Y = np.meshgrid(np.arange(0, nx, 1), np.arange(0, ny, 1))
x = X.astype(np.float32)
y = Y.astype(np.float32)
rd = np.sqrt((y-Cy)**2 + (x-Cx)**2)
map_x = y*(1+K11*np.power(rd, 2)+K22*np.power(rd, 4))
map_y = x*(1+K11*np.power(rd, 2)+K22*np.power(rd, 4))
imgg = cv2.remap(img, map_x, map_y, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101)
enter image description here

Why my handmade numpy neural network doesn't learn?

As an exercise I was building a neural network in numpy from scratch.
For simplicity I wanted to use it to solve XOR problem. I derived all the equation and put everything together, but it looks like my network doesn't learn. I've spent some time trying to spot the mistake, but without success. Maybe you notice something I'm missing here?
X = [(0,0), (1,0), (0,1), (1,1)]
Y = [0, 1, 1, 0]
w1 = 2 * np.random.random(size=(2,3)) - 1
w2 = 2 * np.random.random(size=(3,1)) - 1
b1 = 2 * np.random.random(size=(1,3)) - 1
b2 = 2 * np.random.random(size=(1,1)) - 1
def sigmoid(x):
return 1./(1 + np.exp(-x))
def dsigmoid(y):
return y*(1-y)
N = 1000
error = np.zeros((N,1))
for n in range(N):
Dw_1 = np.zeros((2,3))
Dw_2 = np.zeros((3,1))
Db_1 = np.zeros((1,3))
Db_2 = np.zeros((1,1))
for i in range(len(X)): # iterate over all examples
x = np.array(X[i])
y = np.array(Y[i])
# Forward pass, 1st layer
act1 = np.dot(w1.T, x) + b1
lay1 = sigmoid(act1)
# Forward pass, 2nd layer
act2 = np.dot(w2.T, lay1.T) + b2
lay2 = sigmoid(act2)
# Computing error
E = 0.5*(lay2 - y)**2
error[n] += E[0]
# Backprop, 2nd layer
delta_l2 = (y-lay2) * dsigmoid(lay2)
corr_w2 = (delta_l2 * lay1).T
corr_b2 = delta_l2 * 1
# Backprop, 1st layer
delta_l1 = np.dot(w2, delta_l2) * dsigmoid(lay1).T
corr_w1 = np.outer(x, delta_l1)
corr_b1 = (delta_l1 * 1).T
Dw_2 += corr_w2
Dw_1 += corr_w1
Db_2 += corr_b2
Db_1 += corr_b1
if n % 1000 == 0:
print y, lay2,
if n % 1000 == 0:
print
w2 = w2 - eta * Dw_2
b2 = b2 - eta * Db_2
w1 = w1 - eta * Dw_1
b1 = b1 - eta * Db_1
error[n] /= len(X)
There were small mistakes in it, I hope this helps you
import numpy as np
import matplotlib.pyplot as plt
X = [(0, 0), (1, 0), (0, 1), (1, 1)]
Y = [0, 1, 1, 0]
eta = 0.7
w1 = 2 * np.random.random(size=(2, 3)) - 1
w2 = 2 * np.random.random(size=(3, 1)) - 1
b1 = 2 * np.random.random(size=(1, 3)) - 1
b2 = 2 * np.random.random(size=(1, 1)) - 1
def sigmoid(x):
return 1. / (1 + np.exp(-x))
def dsigmoid(y):
return y * (1 - y)
N = 2000
error = []
for n in range(N):
Dw_1 = np.zeros((2, 3))
Dw_2 = np.zeros((3, 1))
Db_1 = np.zeros((1, 3))
Db_2 = np.zeros((1, 1))
tmp_error = 0
for i in range(len(X)): # iterate over all examples
x = np.array(X[i]).reshape(1, 2)
y = np.array(Y[i])
layer1 = sigmoid(np.dot(x, w1) + b1)
output = sigmoid(np.dot(layer1, w2) + b2)
tmp_error += np.mean(np.abs(output - y))
d_w2 = np.dot(layer1.T, ((output - y) * dsigmoid(output)))
d_b2 = np.dot(1, ((output - y) * dsigmoid(output)))
d_w1 = np.dot(x.T, (np.dot((output - y) * dsigmoid(output), w2.T) * dsigmoid(layer1)))
d_b1 = np.dot(1, (np.dot((output - y) * dsigmoid(output), w2.T) * dsigmoid(layer1)))
Dw_2 += d_w2
Dw_1 += d_w1
Db_1 += d_b1
Db_2 += d_b2
w2 = w2 - eta * Dw_2
w1 = w1 - eta * Dw_1
b1 = b1 - eta * Db_1
b2 = b2 - eta * Db_2
error.append(tmp_error)
error = np.array(error)
print(error.shape)
plt.plot(error)
plt.show()

Intersection between bezier curve and a line segment

I am writing a game in Python (with pygame) that requires me to generate random but nice-looking "sea" for each new game. After a long search I settled on an algorithm that involves Bezier curves as defined in padlib.py. I now need to figure out when the curves generated by padlib intersect a line segment.
The brute force method would be to just use the set of approximating line segments produced by padlib to find the answer. However, I suspect that a better answer can be found analytically. I only have a few dozen spline segments - searching them should be faster than thousand of line segments.
A little search took me down this road: Bezier Curve -> Kochanek-Bartels Spline -> Cubic Hermite spline
On the last page, I found this function:
p(t) = h00(t)p0 + h10(t)m0 + h01(t)p1 + h11(t)m1
where p(t) is a actually a point (2-dimensional vector), hij(t) functions are cubic polynomials, p0, p1, m0 and m1 are points I can get from padlib code.
Now, I can see that the solution to my problem is p(t) = u + v * t1, where u and v are the end of my line segment.
However, working out the analytical solution is beyond me. Does anyone here know of an existing solution? Or can help me with solving the equations?
As a rough outline, rotate and translate the system so that the line segment lies on the X axis. Now the y coordinate is a cubic function of the parameter t. Find the 'zeros' (the analytic formulae will be found in good math texts or wikipedia). Now evaluate the x coordinates corresponding to those zero points and test against your line segment.
I've finally got to a working code to illustrate the method suggested by Mark Thornton. Below is the Python code for the intersection routine, together with pygame code to test it visually. The cubic roots solution can be written based on this question.
import pygame
from pygame.locals import *
import sys
import random
from math import sqrt, fabs, pow
from lines import X, Y
import itertools
import pygame
from pygame import draw, Color
import padlib
from roots_detailed import cubicRoots
def add_points(*points):
X = 0
Y = 0
for (x,y) in points:
X += x
Y += y
return (X,Y)
def diff_points(p2, p1):
# p2 - p1
return (X(p2)-X(p1), Y(p2)-Y(p1));
def scale_point(factor, p):
return (factor * X(p), factor*Y(p))
def between(v0, v, v1):
if v0 > v1: v0, v1 = v1, v0
return v >= v0 and v <= v1
# the point is guaranteed to be on the right line
def pointOnLineSegment(l1, l2, point):
return between(X(l1), X(point), X(l2)) and between(Y(l1), Y(point), Y(l2))
def rotate(x, y, R1, R2, R3, R4):
return (x*R1 + y*R2, x*R3 + y * R4);
def findIntersections(p0, p1, m0, m1, l1, l2):
# We're solving the equation of one segment of Kochanek-Bartels
# spline intersecting with a line segment
# The spline is described at http://en.wikipedia.org/wiki/Cubic_Hermite_spline
# The discussion on the adopted solution can be found at https://stackoverflow.com/questions/1813719/intersection-between-bezier-curve-and-a-line-segment
#
# The equation we're solving is
#
# h00(t) p0 + h10(t) m0 + h01(t) p1 + h11(t) m1 = u + v t1
#
# where
#
# h00(t) = 2t^3 - 3t^2 + 1
# h10(t) = t^3 - 2t^2 + t
# h01(t) = -2t^3 + 3t^2
# h11(t) = t^3 - t^2
# u = l1
# v = l2-l1
u = l1
v = diff_points(l2, l1);
# The first thing we do is to move u to the other side:
#
# h00(t) p0 + h10(t) m0 + h01(t) p1 + h11(t) m1 - u = v t1
#
# Then we're looking for matrix R that would turn (v t1) into
# ({|v|, 0} t1). This is rotation of coordinate system matrix,
# described at http://mathworld.wolfram.com/RotationMatrix.html
#
# R(h00(t) p0 + h10(t) m0 + h01(t) p1 + h11(t) m1 - u) = R(v t1) = {|v|, 0}t1
#
# We only care about R[1,0] and R[1,1] because it lets us solve
# the equation for y coordinate where y == 0 (intersecting the
# spline segment with the x axis of rotated coordinate
# system). I'll call R[1,0] = R3 and R[1,1] = R4 .
v_abs = sqrt(v[0] ** 2 + v[1] ** 2)
R1 = X(v) / v_abs
R2 = Y(v) / v_abs
R3 = -Y(v) / v_abs
R4 = X(v) / v_abs
# The letters x and y are denoting x and y components of vectors
# p0, p1, m0, m1, and u.
p0x = p0[0]; p0y = p0[1]
p1x = p1[0]; p1y = p1[1]
m0x = m0[0]; m0y = m0[1]
m1x = m1[0]; m1y = m1[1]
ux = X(u); uy = Y(u)
#
#
# R3(h00(t) p0x + h10(t) m0x + h01(t) p1x + h11(t) m1x - ux) +
# + R4(h00(t) p0y + h10(t) m0y + h01(t) p1y + h11(t) m1y - uy) = 0
#
# Opening all parentheses and simplifying for hxx we get:
#
# h00(t) p0x R3 + h10(t) m0x R3 + h01(t) p1x R3 + h11(t) m1x R3 - ux R3 +
# + h00(t) p0y R4 + h10(t) m0y R4 + h01(t) p1y R4 + h11(t) m1y R4 - uy R4 = 0
#
# h00(t) p0x R3 + h10(t) m0x R3 + h01(t) p1x R3 + h11(t) m1x R3 - ux R3 +
# + h00(t) p0y R4 + h10(t) m0y R4 + h01(t) p1y R4 + h11(t) m1y R4 - uy R4 = 0
#
# (1)
# h00(t) (p0x R3 + p0y R4) + h10(t) (m0x R3 + m0y R4) +
# h01(t) (p1x R3 + p1y R4) + h11(t) (m1x R3 + m1y R4) - (ux R3 + uy R4) = 0
#
# We now introduce new substitution
K00 = p0x * R3 + p0y * R4
K10 = m0x * R3 + m0y * R4
K01 = p1x * R3 + p1y * R4
K11 = m1x * R3 + m1y * R4
U = ux * R3 + uy * R4
# Expressed in those terms, equation (1) above becomes
#
# h00(t) K00 + h10(t) K10 + h01(t) K01 + h11(t) K11 - U = 0
#
# We will now substitute the expressions for hxx(t) functions
#
# (2t^3 - 3t^2 + 1) K00 + (t^3 - 2t^2 + t) K10 + (-2t^3 + 3t^2) K01 + (t^3 - t^2) K11 - U = 0
#
# 2 K00 t^3 - 3 K00 t^2 + K00 +
# + K10 t^3 - 2 K10 t^2 + K10 t -
# - 2 K01 t^3 + 3 K01 t^2 +
# + K11 t^3 - K11 t^2 - U = 0
#
# 2 K00 t^3 - 3 K00 t^2 + 0t + K00
# + K10 t^3 - 2 K10 t^2 + K10 t
# - 2 K01 t^3 + 3 K01 t^2
# + K11 t^3 - K11 t^2 + 0t - U = 0
#
# (2 K00 + K10 - 2K01 + K11) t^3
# +(-3 K00 - 2K10 + 3 K01 - K11) t^2
# + K10 t
# + K00 - U = 0
#
#
# (2 K00 + K10 - 2K01 + K11) t^3 + (-3 K00 - 2K10 + 3 K01 - K11) t^2 + K10 t + K00 - U = 0
#
# All we need now is to solwe a cubic equation
valuesOfT = cubicRoots((2 * K00 + K10 - 2 * K01 + K11),
(-3 * K00 - 2 * K10 + 3 * K01 - K11),
(K10),
K00 - U)
# We can then put the values of it into our original spline segment
# formula to find the potential intersection points. Any point
# that's on original line segment is an intersection
def h00(t): return 2 * t**3 - 3 * t**2 + 1
def h10(t): return t**3 - 2 * t**2 + t
def h01(t): return -2 * t**3 + 3 * t**2
def h11(t): return t**3 - t**2
intersections = []
for t in valuesOfT:
if t < 0 or t > 1.0: continue
# point = h00(t) * p0 + h10(t) * m0 + h01(t) * p1 + h11(t) * m1
point = add_points(
scale_point(h00(t), p0),
scale_point(h10(t), m0),
scale_point(h01(t), p1),
scale_point(h11(t), m1)
)
if pointOnLineSegment(l1, l2, point): intersections.append(point)
return intersections
def findIntersectionsManyCurves(p0_array, p1_array, m0_array, m1_array, u, v):
result = [];
for (p0, p1, m0, m1) in itertools.izip(p0_array, p1_array, m0_array, m1_array):
result.extend(findIntersections(p0, p1, m0, m1, u, v))
return result
def findIntersectionsManyCurvesManyLines(p0, p1, m0, m1, points):
result = [];
for (u,v) in itertools.izip(*[iter(points)]*2):
result.extend(findIntersectionsManyCurves(p0, p1, m0, m1, u, v))
return result
class EventsEmitter(object):
def __init__(self):
self.consumers = []
def emit(self, eventName, *params):
for method in self.consumers:
funcName = method.im_func.func_name if hasattr(method, "im_func") else method.func_name
if funcName == eventName:
method(*params)
def register(self, method):
self.consumers.append(method)
def unregister(self, method):
self.consumers.remove(method)
class BunchOfPointsModel(EventsEmitter):
def __init__(self):
EventsEmitter.__init__(self)
self.pts = []
def points(self):
return self.pts.__iter__()
def pointsSequence(self):
return tuple(self.pts)
def have(self, point):
return point in self.pts
def addPoint(self,p):
self.pts.append(p)
self.emit("pointsChanged", p)
def replacePoint(self, oldP, newP):
idx = self.pts.index(oldP)
self.pts[idx] = newP
self.emit("pointsChanged", newP)
def removePoint(self, p):
self.point.remove(p)
self.emit("pointsChanged", p)
class BunchOfPointsCompositeModel(object):
def __init__(self, m1, m2):
self.m1 = m1
self.m2 = m2
def points(self):
return itertools.chain(self.m1.points(), self.m2.points())
def have(self, point):
return self.m1.have(point) or self.m2.have(point)
def replacePoint(self, oldP, newP):
if self.m1.have(oldP):
self.m1.replacePoint(oldP, newP)
else:
self.m2.replacePoint(oldP, newP)
def removePoint(self, p):
if self.m1.have(p):
self.m1.removePoint(p)
else:
self.m2.removePoint(p)
def register(self, method):
self.m1.register(method)
self.m2.register(method)
def unregister(self, method):
self.m1.unregister(method)
self.m2.unregister(method)
class BunchOfPointsDragController(EventsEmitter):
def __init__(self, model):
EventsEmitter.__init__(self)
self.model = model
self.draggedPoint = None
def mouseMovedTo(self, x,y):
if self.draggedPoint != None:
newPoint = (x,y)
draggedPoint = self.draggedPoint
self.draggedPoint = newPoint
self.model.replacePoint(draggedPoint, newPoint)
def buttonDown(self, x,y):
if self.draggedPoint == None:
closePoint = self.getCloseEnoughPoint(x,y)
if closePoint != None:
self.draggedPoint = closePoint
self.emit("dragPointChanged",closePoint)
def buttonUp(self, x,y):
self.mouseMovedTo(x,y)
self.draggedPoint = None
self.emit("dragPointChanged", None)
def getCloseEnoughPoint(self, x,y):
minSquareDistance = 25
closestPoint = None
for point in self.model.points():
dx = X(point) - x
dy = Y(point) - y
distance = dx*dx + dy*dy
if minSquareDistance > distance:
closestPoint = point
minSquareDistance = distance
return closestPoint
def isDraggedPoint(self, p):
return p is self.draggedPoint
class CurvesLinesViewPointsView(object):
def __init__(self, screen, modelCurves, modelLines, model, controller):
self.screen = screen
self.modelLines = modelLines
self.modelCurves = modelCurves
self.controller = controller
controller.register(self.dragPointChanged)
model.register(self.pointsChanged)
def draw(self):
self.screen.fill(Color("black"))
pygame.draw.lines(self.screen, Color("cyan"), 0, self.modelLines.pointsSequence(), 3)
(p0, p1, m0, m1) = padlib.BezierCurve(screen,modelCurves.pointsSequence(),3,100,Color("magenta"))
self.drawPointSet(self.modelCurves.points(),
lambda(p):self.controller.isDraggedPoint(p),
Color("white"), Color("red"))
self.drawPointSet(self.modelLines.points(),
lambda(p):self.controller.isDraggedPoint(p),
Color("lightgray"), Color("red"))
self.drawSimplePointSet(findIntersectionsManyCurvesManyLines(p0, p1, m0, m1,self.modelLines.points()),
Color("blue"))
def drawSimplePointSet(self, points, normalColor):
self.drawPointSet(points, lambda(p):True, None, normalColor);
def drawPointSet(self, points, specialPoint, normalColor, specialColor):
for p in points:
if specialPoint(p):
draw.circle(self.screen, specialColor, p, 6)
else:
draw.circle(self.screen, normalColor, p, 2)
pygame.display.update()
def dragPointChanged(self, p): self.draw()
def pointsChanged(self, p): self.draw()
class PygameEventsDistributor(EventsEmitter):
def __init__(self):
EventsEmitter.__init__(self)
def processEvent(self, e):
if e.type == MOUSEMOTION:
self.emit("mouseMovedTo", e.pos[0], e.pos[1])
elif e.type == MOUSEBUTTONDOWN:
self.emit("buttonDown", e.pos[0], e.pos[1])
elif e.type == MOUSEBUTTONUP:
self.emit("buttonUp", e.pos[0], e.pos[1])
modelLines = BunchOfPointsModel()
modelCurves = BunchOfPointsModel()
model = BunchOfPointsCompositeModel(modelLines, modelCurves);
controller = BunchOfPointsDragController(model)
distributor = PygameEventsDistributor()
distributor.register(controller.mouseMovedTo)
distributor.register(controller.buttonUp)
distributor.register(controller.buttonDown)
pygame.init()
screen = pygame.display.set_mode((640, 480))
modelCurves.addPoint((29,34))
modelCurves.addPoint((98,56))
modelCurves.addPoint((200, 293))
modelCurves.addPoint((350, 293))
modelLines.addPoint((23,123))
modelLines.addPoint((78,212))
view = CurvesLinesViewPointsView(screen, modelCurves, modelLines, model, controller)
keepGoing = True
try:
while (keepGoing):
for event in pygame.event.get():
if event.type == QUIT:
keepGoing = False
break
distributor.processEvent(event)
pass
finally:
pygame.quit()

Categories

Resources