Python given implicit equation, find points on that equation? - python

Context: Convert an .iges to .vtk.
I have the following equation Ax^2+Bxy+Cy^2+Dx+Ey+F=0 representing a conic section.
The parameters A~F are given. I want to find points on the conic section, so that I can connect them with lines, and make a mesh.
The reason I need the points instead of just using matplotlib Ellipse is because I'm creating a mesh not a plot.
It is in 3 dimension space, but I first get points on xy plane, and use affine transformation to send it to 3 dim.
Question: How do I find points given an implicit equation?

To avoid spending too much time on this, I wrote some code that seems to handle general ellipses. It can be expanded for other conics, depending on what is needed.
The code takes in the coefficients of a general quadratic equation of an ellipse and a number of desired points to be generated on the ellipse and generates a set of points on the ellipse.
import numpy as np
def equation(conic, points):
'''
equation of a conic with coefficients 'conic'
applied to a matrix number_of_points x 3 whose each row is the coordinates
of each point
'''
c = np.array(conic)
x = np.array([points[:,0]**2, points[:, 0]*points[:,1], points[:,1]**2, points[:,0], points[:,1], np.ones(points.shape[0])])
return c.dot(x)
def equation_to_matrix(eq):
'''
eq[0]*x**2 + eq[1]*x*y + eq[2]*y**2 + eq[3]*x + eq[4]*y + eq[5] = 0
'''
return np.array([[2*eq[0], eq[1], eq[3]],
[ eq[1], 2*eq[2], eq[4]],
[ eq[3], eq[4], 2*eq[5]]]) / 2
def solve_quadratic(a, b, c):
'''
solves
ax^2 + bx + c = 0
'''
D = b**2 - 4*a*c
D = np.sqrt(D)
return (-b-D)/(2*a), (-b+D)/(2*a)
def eigen2(S):
'''
solves the eigen-decomposition problem
for a 2x2 symmetric matrix
'''
k1, k2 = solve_quadratic(1, -S[0,0]-S[1,1], S[0,0]*S[1,1] - S[0,1]*S[1,0])
u1 = np.array([-S[0,1], S[0,0]-k1, 0])
u1 = u1 / np.sqrt(u1.dot(u1))
u2 = np.array([-u1[1], u1[0], 0])
return np.array([k1, k2]), np.array([u1, u2, np.array([0,0,1])]).T
def center(conic_matrix):
center = np.linalg.solve(conic_matrix, np.array([0,0,1]))
return center/center[2]
def find_rotation_and_translation(conic_matrix):
'''
conic = c[0]x^2 + c[1]*xy + c[2]*y^2 + c[3]*x + c[4]*y + c[5] = 0
the result is rotation U such that U.T C U = diag
'''
k, U = eigen2(conic_matrix)
U[:,2] = center(conic_matrix)
return U, k
def find_transform(conic):
C = equation_to_matrix(conic)
U, k = find_rotation_and_translation(C)
C = (U.T).dot(C.dot(U))
C = - C / C[2,2]
k = np.array([1/np.sqrt(C[0,0]), 1/np.sqrt(C[1,1]), 1])
return U.dot(np.diag(k))
def generate_points_on(conic, num_points):
'''
conic = [c[0], c[1], c[2], c[3], c[4], c[5]]
coefficients of the qudaratic equation:
conic: c[0]x^2 + c[1]*xy + c[2]*y^2 + c[3]*x + c[4]*y + c[5] = 0
result is the affine transformation (scaling, rotation, translation)
that maps the unit circle to the ellipse defined by the coefficients
'conic'
'''
cos_ = np.cos(2*np.pi* np.arange(0, num_points)/ num_points)
sin_ = np.sin(2*np.pi* np.arange(0, num_points)/ num_points)
U = find_transform(conic)
points = np.array([cos_, sin_, np.ones(num_points)])
return ((U.dot(points)).T)[:,[0,1]]
'''
Test:
'''
'''
Ellipse with equation whose coefficients are in the list E.
The ellipse has semi-major axes 2 and 1,
it is rotated 60 deg from the horizontal,
and its center is at (1, 4)
'''
E = [ 3.25, -2.59807621, 1.75, -23.40192379, 6.89230485, 39.35769515]
'''
U maps points from unit circle to points on E
'''
U = find_transform(E)
print(U)
'''
the set of points on the ellipse E
'''
p = generate_points_on(E, num_points = 20)
print(p)
'''
check that the points p lie on the ellipse E
'''
print(equation(E, p).round(10))
'''
plot
'''
fig = plt.figure()
ax = fig.add_subplot()
ax.plot(p[:,0], p[:,1], 'ro')
ax.set_aspect('equal')
plt.show()

The code below handles the case of a hyperbola. It largely adapts the code from here
import numpy as np
import matplotlib.pyplot as plt
def equation_to_matrix(eq):
'''
eq[0]*x**2 + eq[1]*x*y + eq[2]*y**2 + eq[3]*x + eq[4]*y + eq[5] = 0
'''
return np.array([[2*eq[0], eq[1], eq[3]],
[ eq[1], 2*eq[2], eq[4]],
[ eq[3], eq[4], 2*eq[5]]]) / 2
def hyp_params_from_general(coeffs):
# get the matrix of the quadratic equation
Aq = equation_to_matrix(coeffs)
# get the matrix of the quadratic form A33
A33 = Aq[:2, :2]
# determinant of A33
detA33 = np.linalg.det(A33)
if detA33 > 0:
raise ValueError('coeffs do not represent a hyperbola: det A33 must be negative!')
# get the center
x0 = -np.linalg.det(np.array([Aq[:2, 2], Aq[:2, 1]]).T) / detA33
y0 = -np.linalg.det(np.array([Aq[:2, 0], Aq[:2, 2]]).T) / detA33
# The semi-major and semi-minor axis lengths (these are not sorted).
# get discriminant of the conic section
delta = np.linalg.det(Aq)
# get the eigenvalues
k1, k2 = np.linalg.eigvals(A33)
k1isk2 = np.isclose(k1/k2, -1)
ap = np.sqrt(abs(delta/k1/detA33))
bp = np.sqrt(abs(delta/k2/detA33))
# Eccentricity.
fac = np.sqrt((Aq[0, 0] - Aq[1, 1])**2 + Aq[0, 1]**2)
if delta < 0:
nu = 1
else:
nu = -1
e = np.sqrt(2*fac/(nu*(Aq[0, 0] - Aq[1, 1]) + fac))
# slope of the asymptotes
if Aq[0, 0] == Aq[1, 1] and k1isk2:
m1 = 0.
m2 = np.nan
else:
m1 = Aq[0, 0]/(-Aq[0, 1] - np.sqrt(-detA33))
m2 = Aq[0, 0]/(-Aq[0, 1] + np.sqrt(-detA33))
# Sort the semi-major and semi-minor axis lengths but keep track of
# the original relative magnitudes of width and height.
width_gt_height = True
if ap < bp and not k1isk2:
width_gt_height = False
ap, bp = bp, ap
# The angle of anticlockwise rotation of the major-axis from x-axis.
if Aq[0, 1] == 0:
phi = 0 if Aq[0, 0] < Aq[1, 1] else np.pi/2
elif Aq[0, 0] == Aq[1, 1]:
phi = np.pi/4 # would divide by zero and arctan(inf) -> pi/4
if m1 > 0 and m2 > 0:
width_gt_height = True
else:# Aq[0, 0] > Aq[1, 1]:
phi = np.arctan(2*Aq[0, 1]/(Aq[0, 0] - Aq[1, 1])) / 2
if not width_gt_height:
# Ensure that phi is the angle to rotate to the semi-major axis.
phi += np.pi/2
phi = phi % np.pi
return x0, y0, ap, bp, phi, e, m1, m2, width_gt_height
def get_hyperbola_pts(params, npts=100, tmin=-1, tmax=1):
x0, y0, ap, bp, phi, m1, m2 = params
# A grid of the parametric variable, t.
t = np.linspace(tmin, tmax, npts)
# points
x = x0 + ap * np.cosh(t) * np.cos(phi) - bp * np.sinh(t) * np.sin(phi)
y = y0 + ap * np.cosh(t) * np.sin(phi) + bp * np.sinh(t) * np.cos(phi)
# asymptotes
ya1 = y0 + m1*(x - x0)
ya2 = y0 + m2*(x - x0)
return x, y, ya1, ya2
if __name__ == '__main__':
coeffs = [1., 6., -2., 3., 0., 0.]
x0, y0, ap, bp, phi, e, m1, m2, width_gt_height = hyp_params_from_general(coeffs)
print('x0, y0, ap, bp, phi, e, m1, m2, width_gt_height = ', x0, y0, ap, bp, phi, e, m1, m2)
x_, y_, ya1, ya2 = get_hyperbola_pts((x0, y0, ap, bp, phi, m1, m2), npts=250, tmin=-2, tmax=3)
fig, ax = plt.subplots(figsize=(16, 9))
ax.plot(x_, y_, marker='.', linewidth=0.5, c='r')
ax.plot(x_, ya1, marker='.', linewidth=0.2, c='b')
ax.plot(x_, ya2, marker='.', linewidth=0.2, c='b')
ax.grid(True, linestyle='--')

Related

Odeint problem in model: lsoda-- repeated occurrences of illegal input && lsoda-- at start of problem, too much accuracy

I need help to solve a ODE system using odeint or any workaround that works. I`m trying to model a ODE system descriving the beheaviour of a cilinder on a mechanichal arm, using Lagrangian mechanics I got the following 2nd order ODE system:
$$\ddot{x} = x*\dot{\phi}^2 - g*(sin(\phi) +cos(\phi))$$
$$\ddot{\phi} = \frac{1}{2*\dot{x}*m_c}*(\frac{k*i}{x}-\frac{m_b*L*\dot{\phi}}{2}-g*cos(\phi)*(m_c*x-m_b*L/2))$$
Then I transformed it to a 4x4 1st order ODE system y linearized 2 equations aroun initial condition, to make it more "solveable".
In python I`wrote the folllowing:
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
# source: https://apmonitor.com/pdc/index.php/Main/SolveDifferentialEquations
g, mc, mb, k, L = (9.8, 0.5, 1.3, 2, 1.2)
def model(z, t, i, z0):
global g, mc, mb, k, L
#ODE variables
x1 = z[0]
y1 = z[1]
x2 = z[2]
y2 = z[3]
#ODE initial conditions
zx1 = z0[0]
zy1 = z0[1]
zx2 = z0[2]
zy2 = z0[3]
#Diferential for linealization around I.C.
dx1 = z[0]-z0[0]
dy1 = z[1]-z0[1]
dx2 = z[2]-z0[2]
dy2 = z[3]-z0[3]
def w1(zx1, zy1, zx2, zy2):
global g, mc, mb, k, L
return zx2*zy1**2-g*(np.sin(zy2)+np.cos(zy2))
def w2(zx1, zy1, zx2, zy2):
global g, mc, mb, k, L
return 1/(2*zx1*mc)*((k*i)/(zx2)-mb*zy2*L/2-g*np.cos(zy2*(mc*zx2-mb*L/2)))
dx2dt = x1
#dx1dt = x2*y1**2-g*(np.sin(y2)+np.cos(y2))
dx1dt = w1(zx1, zy1, zx2, zy2) + zy1**2*dx2+2*zx2 * \
zy1*dy1-g*(np.cos(zy2)-np.sin(zy2))*dy2
dy2dt = y1
#dy1dt = 1/(2*x1*mc)*((k*i)/(x2)-mb*y2*L/2-g*np.cos(y2*(mc*x2-mb*L/2)))
dy1dt = w2(zx1, zy1, zx2, zy2) - 1/(4*zx1**2*mc) * \
((k*i)/(zx2)-mb*zy2*L/2-g*np.cos(zy2*(mc*zx2-mb*L/2)))*dx1
+(k*i*np.log(abs(zx2))/(2*zx1*mc)-g*np.cos(zy2)*mc)*dx2-(mb*L) / \
(4*zx1*mc)*dy1+g*np.sin(zy2)*(mc*zx2-mb*L/2)*(2*zx1*mc)*dy2
dzdt = [dx1dt, dy1dt, dx2dt, dy2dt]
return dzdt
# initial condition
z0 = [0.01, 0.01, 0.8, np.pi/8]
# number of time points
n = 30
# time points
t = np.linspace(0, 1, n)
# step input
u = np.zeros(n)
# change to 2.0 at time = 5.0
u[:] = 1
# store solution
x1 = np.zeros(n)
y1 = np.zeros(n)
x2 = np.zeros(n)
y2 = np.zeros(n)
# record initial conditions
x1[0] = z0[0]
y1[0] = z0[1]
x2[0] = z0[2]
y2[0] = z0[3]
# solve ODE
for i in range(1, n):
# span for next time step
tspan = [t[i-1], t[i]]
# solve for next step
z = odeint(model, z0, tspan, args=((u[i], z0)) )
# store solution for plotting
if -np.pi/4 <= z[1][3] <= np.pi/4: # Angulo +/- Pi/4
u[i] = u[i-1] + 1
else:
u[i] = u[i-1]
if 0 <= z[1][2] <= L: # Se mantiene en el brazo
print("Se saliĆ³")
x1[i] = z[1][0]
y1[i] = z[1][1]
x1[i] = z[1][2]
y1[i] = z[1][3]
# Siguientes CI
z0 = z[1]
# plot results
plt.plot(t, u, 'g:', label='u(t)')
plt.plot(t, x2, 'b-', label='x(t)')
plt.plot(t, y2, 'r--', label='phi(t)')
plt.ylabel('Cilindro(x,phi)')
plt.xlabel('Tiempo(s)')
plt.legend(loc='best')
plt.show()
Then I`ve got the following error, telling me than too much accuracy is needed to solve my system:
lsoda-- at start of problem, too much accuracy
requested for precision of machine.. see tolsf (=r1)
in above message, r1 = NaN
lsoda-- repeated occurrences of illegal input
lsoda-- at start of problem, too much accuracy
requested for precision of machine.. see tolsf (=r1)
in above message, r1 = NaN
dy1dt = w2(zx1, zy1, zx2, zy2) - 1/(4*zx1**2*mc) * \
lsoda-- warning..internal t (=r1) and h (=r2) are
such that in the machine, t + h = t on the next step
(h = step size). solver will continue anyway
in above, r1 = 0.3448275862069D+00 r2 = 0.1555030227910D-28

How can I make a 3D plot in matplotlib of an ellipsoid defined by a quadratic equation?

I have the general formula of an ellipsoid:
A*x**2 + C*y**2 + D*x + E*y + B*x*y + F + G*z**2 = 0
where A,B,C,D,E,F,G are constant factors.
How can I plot this equation as a 3D plot in matplotlib? (A wireframe would be best.)
I saw this example but it is in parametric form and I am not sure how to put the z-coordinates in this code. Is there a way to keep the general form to plot this without the parametric form?
I started to put this in some kind of code like this:
from mpl_toolkits import mplot3d
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
def f(x, y):
return ((A*x**2 + C*y**2 + D*x + E*y + B*x*y + F))
def f(z):
return G*z**2
x = np.linspace(-2200, 1850, 30)
y = np.linspace(-100, 60, 30)
z = np.linspace(-100, 60, 30)
X, Y, Z = np.meshgrid(x, y, z)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z');
I got this error:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-1-95b1296ae6a4> in <module>()
18 fig = plt.figure()
19 ax = fig.add_subplot(111, projection='3d')
---> 20 ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
21 ax.set_xlabel('x')
22 ax.set_ylabel('y')
C:\Program Files (x86)\Microsoft Visual Studio\Shared\Anaconda3_64\lib\site-packages\mpl_toolkits\mplot3d\axes3d.py in plot_wireframe(self, X, Y, Z, *args, **kwargs)
1847 had_data = self.has_data()
1848 if Z.ndim != 2:
-> 1849 raise ValueError("Argument Z must be 2-dimensional.")
1850 # FIXME: Support masked arrays
1851 X, Y, Z = np.broadcast_arrays(X, Y, Z)
ValueError: Argument Z must be 2-dimensional.
Side note, but what you have is not the most general equation for a 3d ellipsoid. Your equation can be rewritten as
A*x**2 + C*y**2 + D*x + E*y + B*x*y = - G*z**2 - F,
which means that in effect for each value of z you get a different level of a 2d ellipse, and the slices are symmetric with respect to the z = 0 plane. This shows how your ellipsoid is not general, and it helps check the results to make sure that what we get makes sense.
Assuming we take a general point r0 = [x0, y0, z0], you have
r0 # M # r0 + b0 # r0 + c0 == 0
where
M = [ A B/2 0
B/2 C 0
0 0 G],
b0 = [D, E, 0],
c0 = F
where # stands for matrix-vector or vector-vector product.
You could take your function and plot its isosurface, but that would be suboptimal: you would need a gridded approximation for your function which is very expensive to do to sufficient resolution, and you'd have to choose the domain for this sampling wisely.
Instead you can perform a principal axis transformation on your data to generalize the parametric plot of a canonical ellipsoid that you yourself linked.
The first step is to diagonalize M as M = V # D # V.T, where D is diagonal. Since it's a real symmetric matrix this is always possible and V is orthogonal. Then we have
r0 # V # D # V.T # r0 + b0 # r0 + c0 == 0
which we can regroup as
(V.T # r0) # D # (V.T # r0) + b0 # V # (V.T # r0) + c0 == 0
which motivates the definition of the auxiliary coordinates r1 = V.T # r0 and vector b1 = b0 # V, for which we get
r1 # D # r1 + b1 # r1 + c0 == 0.
Since D is a symmetric matrix with the eigenvalues d1, d2, d3 in its diagonal, the above is the equation
d1 * x1**2 + d2 * x2**2 + d3 * x3**3 + b11 * x1 + b12 * x2 + b13 * x3 + c0 == 0
where r1 = [x1, x2, x3] and b1 = [b11, b12, b13].
What's left is to switch from r1 to r2 such that we remove the linear terms:
d1 * (x1 + b11/(2*d1))**2 + d2 * (x2 + b12/(2*d2))**2 + d3 * (x3 + b13/(2*d3))**2 - b11**2/(4*d1) - b12**2/(4*d2) - b13**2/(4*d3) + c0 == 0
So we define
r2 = [x2, y2, z2]
x2 = x1 + b11/(2*d1)
y2 = y1 + b12/(2*d2)
z2 = z1 + b13/(2*d3)
c2 = b11**2/(4*d1) b12**2/(4*d2) b13**2/(4*d3) - c0.
For these we finally have
d1 * x2**2 + d2 * y2**2 + d3 * z2**2 == c2,
d1/c2 * x2**2 + d2/c2 * y2**2 + d3/c2 * z2**2 == 1
which is the canonical form of a second-order surface. In order for this to meaningfully correspond to an ellipsoid we must ensure that d1, d2, d3 and c2 are all strictly positive. If this is guaranteed then the semi-major axes of the canonical form are sqrt(c2/d1), sqrt(c2/d2) and sqrt(c2/d3).
So here's what we do:
ensure that the parameters correspond to an ellipsoid
generate a theta and phi mesh for polar and azimuthal angles
compute the transformed coordinates [x2, y2, z2]
shift them back (by r2 - r1) to get [x1, y1, z1]
transform the coordinates back by V to get r0, the actual [x, y, z] coordinates we're interested in.
Here's how I'd implement this:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def get_transforms(A, B, C, D, E, F, G):
""" Get transformation matrix and shift for a 3d ellipsoid
Assume A*x**2 + C*y**2 + D*x + E*y + B*x*y + F + G*z**2 = 0,
use principal axis transformation and verify that the inputs
correspond to an ellipsoid.
Returns: (d, V, s) tuple of arrays
d: shape (3,) of semi-major axes in the canonical form
(X/d1)**2 + (Y/d2)**2 + (Z/d3)**2 = 1
V: shape (3,3) of the eigensystem
s: shape (3,) shift from the linear terms
"""
# construct original matrix
M = np.array([[A, B/2, 0],
[B/2, C, 0],
[0, 0, G]])
# construct original linear coefficient vector
b0 = np.array([D, E, 0])
# constant term
c0 = F
# compute eigensystem
D, V = np.linalg.eig(M)
if (D <= 0).any():
raise ValueError("Parameter matrix is not positive definite!")
# transform the shift
b1 = b0 # V
# compute the final shift vector
s = b1 / (2 * D)
# compute the final constant term, also has to be positive
c2 = (b1**2 / (4 * D)).sum() - c0
if c2 <= 0:
print(b1, D, c0, c2)
raise ValueError("Constant in the canonical form is not positive!")
# compute the semi-major axes
d = np.sqrt(c2 / D)
return d, V, s
def get_ellipsoid_coordinates(A, B, C, D, E, F, G, n_theta=20, n_phi=40):
"""Compute coordinates of an ellipsoid on an ellipsoidal grid
Returns: x, y, z arrays of shape (n_theta, n_phi)
"""
# get canonical grid
theta,phi = np.mgrid[0:np.pi:n_theta*1j, 0:2*np.pi:n_phi*1j]
r2 = np.array([np.sin(theta) * np.cos(phi),
np.sin(theta) * np.sin(phi),
np.cos(theta)]) # shape (3, n_theta, n_phi)
# get transformation data
d, V, s = get_transforms(A, B, C, D, E, F, G) # could be *args I guess
# shift and transform back the coordinates
r1 = d[:,None,None]*r2 - s[:,None,None] # broadcast along first of three axes
r0 = (V # r1.reshape(3, -1)).reshape(r1.shape) # shape (3, n_theta, n_phi)
return r0 # unpackable to x, y, z of shape (n_theta, n_phi)
Here's an example with an ellipsoid and proof that it works:
A,B,C,D,E,F,G = args = 2, -1, 2, 3, -4, -3, 4
x,y,z = get_ellipsoid_coordinates(*args)
print(np.allclose(A*x**2 + C*y**2 + D*x + E*y + B*x*y + F + G*z**2, 0)) # True
The actual plotting from here is trivial. Using the 3d scaling hack from this answer to preserve equal axes:
# create 3d axes
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# plot the data
ax.plot_wireframe(x, y, z)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
# scaling hack
bbox_min = np.min([x, y, z])
bbox_max = np.max([x, y, z])
ax.auto_scale_xyz([bbox_min, bbox_max], [bbox_min, bbox_max], [bbox_min, bbox_max])
plt.show()
Here's how the result looks:
Rotating it around it's nicely visible that the surface is indeed reflection symmetric with respect to the z = 0 plane, which was evident from the equation.
You can change the n_theta and n_phi keyword arguments to the function to generate a grid with a different mesh. The fun thing is that you can take any scattered points lying on the unit sphere and plug it into the definition of r2 in the function get_ellipsoid_coordinates (as long as this array has a first dimension of size 3), and the output coordinates will have the same shape, but they will be transformed onto the actual ellipsoid.
You can also use other libraries to visualize the surface, for instance mayavi where you can either plot the surface we just computed, or compare it with an isosurface which is built-in there.

Best-Fit axis of points on a cylindrical surface

I would like to find the best-fit axis of points that are on a cylindrical surface, using python.
Seems that scipy.linalg.svd is the function to look for.
So to test out, I decide to generate some points, function makeCylinder, from this thread How to generate regular points on cylindrical surface, and estimate the axis.
This is the code:
def rotMatrixAxisAngle(axis, theta, theta2deg=False):
# Load
from math import radians, cos, sin
from numpy import array
# Convert to radians
if theta2deg:
theta = radians(theta)
#
a = cos(theta/2.0)
b, c, d = -array(axis)*sin(theta/2.0)
# Rotation matrix
R = array([ [a*a+b*b-c*c-d*d, 2.0*(b*c-a*d), 2.0*(b*d+a*c)],
[2.0*(b*c+a*d), a*a+c*c-b*b-d*d, 2.0*(c*d-a*b)],
[2.0*(b*d-a*c), 2.0*(c*d+a*b), a*a+d*d-b*b-c*c] ])
return R
def makeCylinder(radius, length, nlength, alpha, nalpha, center, orientation):
# Load
from numpy import array, allclose, linspace, tile, vstack
from numpy import pi, cos, sin, arccos, cross
from numpy.linalg import norm
# Create the length array
I = linspace(0, length, nlength)
# Create alpha array avoid duplication of endpoints
if int(alpha) == 360:
A = linspace(0, alpha, num=nalpha, endpoint=False)/180.0*pi
else:
A = linspace(0, alpha, num=nalpha)/180.0*pi
# Calculate X and Y
X = radius * cos(A)
Y = radius * sin(A)
# Tile/repeat indices so all unique pairs are present
pz = tile(I, nalpha)
px = X.repeat(nlength)
py = Y.repeat(nlength)
# Points
points = vstack(( pz, px, py )).T
## Shift to center
points += array(center) - points.mean(axis=0)
# Orient tube to new vector
ovec = orientation / norm(orientation)
cylvec = array([1,0,0])
if allclose(cylvec, ovec):
return points
# Get orthogonal axis and rotation
oaxis = cross(ovec, cylvec)
rot = arccos(ovec.dot(cylvec))
R = rotMatrixAxisAngle(oaxis, rot)
return points.dot(R)
from numpy.linalg import norm
from numpy.random import rand
from scipy.linalg import svd
for i in xrange(100):
orientation = rand(3)
orientation[0] = 0
orientation /= norm(orientation)
# Generate sample points
points = makeCylinder(radius = 3.0,
length = 20.0, nlength = 20,
alpha = 360, nalpha = 30,
center = [0,0,0],
orientation = orientation)
# Least Square
uu, dd, vv = svd(points - points.mean(axis=0))
asse = vv[0]
assert abs( abs(orientation.dot(asse)) - 1) <= 1e-4, orientation.dot(asse)
As you can see, I generate multiple cylinder whose axis is random (rand(3)).
The funny thing is that svd returns an axis that is absolutely perfect if the first component of orientation is zero (orientation[0] = 0).
If I comment this line the estimated axis is way off.
Update 1:
Even using leastsq on a cylinder equation returns the same behavior:
def bestLSQ1(points):
from numpy import array, sqrt
from scipy.optimize import leastsq
# Expand
points = array(points)
x = points[:,0]
y = points[:,1]
z = points[:,2]
# Calculate the distance of each points from the center (xc, yc, zc)
# http://geometry.puzzles.narkive.com/2HaVJ3XF/geometry-equation-of-an-arbitrary-orientated-cylinder
def calc_R(xc, yc, zc, u1, u2, u3):
return sqrt( (x-xc)**2 + (y-yc)**2 + (z-zc)**2 - ( (x-xc)*u1 + (y-yc)*u2 + (z-zc)*u3 )**2 )
# Calculate the algebraic distance between the data points and the mean circle centered at c=(xc, yc, zc)
def dist(c):
Ri = calc_R(*c)
return Ri - Ri.mean()
# Axes - Minimize residu
xM, yM, zM = points.mean(axis=0)
# Calculate the center
center, ier = leastsq(dist, (xM, yM, zM, 0, 0, 1))
xc, yc, zc, u1, u2, u3 = center
asse = u1, u2, u3
return asse
Despite your interesting approach using svd, you could also do a more intuitive approach with scipy.optimize.leastsq.
This would need a function to calculate distance between the axis and your cloud of points in order to find the best-fitting axis.
The code could be something like shown below (distance_axis_points adapted from alg3dpy):
from numpy.linalg import norm
from numpy.random import rand
from scipy.optimize import leastsq
for i in range(100):
orientation = rand(3)
orientation[0] = 0
orientation /= norm(orientation)
# Generate sample points
points = makeCylinder(radius = 3.0,
length = 20.0, nlength = 20,
alpha = 360, nalpha = 30,
center = [0,0,0],
orientation = orientation)
def dist_axis_points(axis, points):
axis_pt0 = points.mean(axis=0)
axis = np.asarray(axis)
x1 = axis_pt0[0]
y1 = axis_pt0[1]
z1 = axis_pt0[2]
x2 = axis[0]
y2 = axis[1]
z2 = axis[2]
x3 = points[:, 0]
y3 = points[:, 1]
z3 = points[:, 2]
den = ((x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2)
t = ((x1**2 + x2 * x3 - x1 * x3 - x1 * x2 +
y1**2 + y2 * y3 - y1 * y3 - y1 * y2 +
z1**2 + z2 * z3 - z1 * z3 - z1 * z2)/den)
projected_pt = t[:, None]*(axis[None, :] - axis_pt0[None, :]) + axis_pt0[None, :]
return np.sqrt(((points - projected_pt)**2).sum(axis=-1))
popt, pconv = leastsq(dist_axis_points, x0=[1, 1, 1], args=(points,))
popt /= norm(popt)
assert abs(abs(orientation.dot(popt)) - 1) <= 1e-4, orientation.dot(popt)

Plotting projectile motion of 1 y-position values vs. 2 x-position values using matplotlib and numpy

Hi i'm trying to get a plot of the trajectory of a mass under projectile motion. One with a force acting on the horizontal axis and one without (basically 2 sets of x values plotted against a 1 set of y-values). Here's what i have so far.. I'm new to programming and i can't seem to figure out where this went wrong. Hope you guys can help me. Thank you!
import numpy as np
import matplotlib.pyplot as pl
def position(y0, v0, theta, g, t):
y= y0 + v0*np.sin(theta)*t + (g*t**2)/2
return y
def position2(x0, v0, theta, c, e, alpha, t):
x1 = x0 + v0*(np.cos(theta))*t + c*(t*(e-1)+(2-2*e)/alpha)
return x1
def position3(x0, v0, theta, t):
x2 = x0 + v0*(np.cos(theta))*t
return x2
t = np.linspace(0,10,1000)
#part1
m = 1
theta = 45
y0 = 2
x0 = 0
v0 = 3
k = 1
alpha = 0.5
g = -9.8
c = (-k/m)*(1/alpha**2)
e = -(np.e**(-alpha*t))
x1 = []
x2 = []
y = []
for a in t:
x1_data = position2(x0, v0, theta, c, e, alpha, t)
x1.append(x1_data)
x2_data = position3(x0, v0, theta, t)
x2.append(x2_data)
y_data = position(y0, v0, theta, g, t)
y.append(y_data)
print x1_data
print x2_data
print y_data
pl.title('Constant and Time-Dependent Forces')
pl.xlabel(b'x-position')
pl.ylabel(b'y-position')
x1label = 'projectile 1'
x2label = "'normal' projectile"
plot1 = pl.plot(x1_data, y, 'r')
plot2 = pl.plot(x2_data, y, 'b')
pl.legend()
pl.show()
I went through your code since i am new to matplotlib and wanted to play a bit with it. The only mistake i found is in the for loop where you do for a in t: but end up passing t to the functions instead of a.
import numpy as np
import matplotlib.pyplot as pl
sin = np.sin
cos = np.cos
pi = np.pi
def y_position(y0, v0, phi, g, t):
y_t = y0 + v0 * sin(phi) * t + (g * t**2) / 2
return y_t
def x_position_force(x0, v0, phi, k, m, alpha, t):
term1 = (-k / m) * (1 / alpha ** 2)
term2 = -np.e ** (-alpha * t)
x_t = x0 + v0 * cos(phi) * t + term1 * (t * (term2 - 1) + (2 - 2 * term2) / alpha)
return x_t
def x_position_no_force(x0, v0, phi, t):
x_t = x0 + v0 * cos(phi) * t
return x_t
time = np.linspace(0, 10, 100)
#------------- I N P U T -------------#
x_init = 0
y_init = 2
v_init = 3
theta = 45
gravity = -9.8
m = 1
k = 1
alpha = 0.5
#------------- I N P U T -------------#
x_with_force = []
x_with_no_force = []
y = []
for time_i in time:
x_with_force.append(x_position_force(x_init, v_init, theta, k, m, alpha, time_i))
x_with_no_force.append(x_position_no_force(x_init, v_init, theta, time_i))
y.append(y_position(y_init, v_init, theta, gravity, time_i))
# print(x1_data)
# print(x2_data)
# print(y_data)
pl.subplot(211)
pl.title('Constant and Time-Dependent Forces')
pl.xlabel('time')
plot1 = pl.plot(time, x_with_force, 'r', label='x_coord_dynamicF')
plot2 = pl.plot(time, x_with_no_force, 'g', label='x_coord_staticF')
plot3 = pl.plot(time, y, 'b', label='y_coord')
pl.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, ncol=2, mode="expand", borderaxespad=0.)
pl.subplot(212)
pl.title('Trajectory (x,y)')
pl.xlabel('X')
pl.ylabel('Y')
plot4 = pl.plot(x_with_force, y, 'r^')
plot5 = pl.plot(x_with_no_force, y, 'b*')
pl.show()
I changed a number of things though to make the code inline with PEP8. In my opinion it is the use of bad variable names that lead you to the mistake you did. So i would recommend taking the time to type those few extra characters that ultimately help you and the people reading your code.

Procrustes Analysis with NumPy?

Is there something like Matlab's procrustes function in NumPy/SciPy or related libraries?
For reference. Procrustes analysis aims to align 2 sets of points (in other words, 2 shapes) to minimize square distance between them by removing scale, translation and rotation warp components.
Example in Matlab:
X = [0 1; 2 3; 4 5; 6 7; 8 9]; % first shape
R = [1 2; 2 1]; % rotation matrix
t = [3 5]; % translation vector
Y = X * R + repmat(t, 5, 1); % warped shape, no scale and no distortion
[d Z] = procrustes(X, Y); % Z is Y aligned back to X
Z
Z =
0.0000 1.0000
2.0000 3.0000
4.0000 5.0000
6.0000 7.0000
8.0000 9.0000
Same task in NumPy:
X = arange(10).reshape((5, 2))
R = array([[1, 2], [2, 1]])
t = array([3, 5])
Y = dot(X, R) + t
Z = ???
Note: I'm only interested in aligned shape, since square error (variable d in Matlab code) is easily computed from 2 shapes.
I'm not aware of any pre-existing implementation in Python, but it's easy to take a look at the MATLAB code using edit procrustes.m and port it to Numpy:
def procrustes(X, Y, scaling=True, reflection='best'):
"""
A port of MATLAB's `procrustes` function to Numpy.
Procrustes analysis determines a linear transformation (translation,
reflection, orthogonal rotation and scaling) of the points in Y to best
conform them to the points in matrix X, using the sum of squared errors
as the goodness of fit criterion.
d, Z, [tform] = procrustes(X, Y)
Inputs:
------------
X, Y
matrices of target and input coordinates. they must have equal
numbers of points (rows), but Y may have fewer dimensions
(columns) than X.
scaling
if False, the scaling component of the transformation is forced
to 1
reflection
if 'best' (default), the transformation solution may or may not
include a reflection component, depending on which fits the data
best. setting reflection to True or False forces a solution with
reflection or no reflection respectively.
Outputs
------------
d
the residual sum of squared errors, normalized according to a
measure of the scale of X, ((X - X.mean(0))**2).sum()
Z
the matrix of transformed Y-values
tform
a dict specifying the rotation, translation and scaling that
maps X --> Y
"""
n,m = X.shape
ny,my = Y.shape
muX = X.mean(0)
muY = Y.mean(0)
X0 = X - muX
Y0 = Y - muY
ssX = (X0**2.).sum()
ssY = (Y0**2.).sum()
# centred Frobenius norm
normX = np.sqrt(ssX)
normY = np.sqrt(ssY)
# scale to equal (unit) norm
X0 /= normX
Y0 /= normY
if my < m:
Y0 = np.concatenate((Y0, np.zeros(n, m-my)),0)
# optimum rotation matrix of Y
A = np.dot(X0.T, Y0)
U,s,Vt = np.linalg.svd(A,full_matrices=False)
V = Vt.T
T = np.dot(V, U.T)
if reflection != 'best':
# does the current solution use a reflection?
have_reflection = np.linalg.det(T) < 0
# if that's not what was specified, force another reflection
if reflection != have_reflection:
V[:,-1] *= -1
s[-1] *= -1
T = np.dot(V, U.T)
traceTA = s.sum()
if scaling:
# optimum scaling of Y
b = traceTA * normX / normY
# standarised distance between X and b*Y*T + c
d = 1 - traceTA**2
# transformed coords
Z = normX*traceTA*np.dot(Y0, T) + muX
else:
b = 1
d = 1 + ssY/ssX - 2 * traceTA * normY / normX
Z = normY*np.dot(Y0, T) + muX
# transformation matrix
if my < m:
T = T[:my,:]
c = muX - b*np.dot(muY, T)
#transformation values
tform = {'rotation':T, 'scale':b, 'translation':c}
return d, Z, tform
There is a Scipy function for it: scipy.spatial.procrustes
I'm just posting its example here:
>>> import numpy as np
>>> from scipy.spatial import procrustes
>>> a = np.array([[1, 3], [1, 2], [1, 1], [2, 1]], 'd')
>>> b = np.array([[4, -2], [4, -4], [4, -6], [2, -6]], 'd')
>>> mtx1, mtx2, disparity = procrustes(a, b)
>>> round(disparity)
0.0
You can have both Ordinary Procrustes Analysis and Generalized Procrustes Analysis in python with something like this:
import numpy as np
def opa(a, b):
aT = a.mean(0)
bT = b.mean(0)
A = a - aT
B = b - bT
aS = np.sum(A * A)**.5
bS = np.sum(B * B)**.5
A /= aS
B /= bS
U, _, V = np.linalg.svd(np.dot(B.T, A))
aR = np.dot(U, V)
if np.linalg.det(aR) < 0:
V[1] *= -1
aR = np.dot(U, V)
aS = aS / bS
aT-= (bT.dot(aR) * aS)
aD = (np.sum((A - B.dot(aR))**2) / len(a))**.5
return aR, aS, aT, aD
def gpa(v, n=-1):
if n < 0:
p = avg(v)
else:
p = v[n]
l = len(v)
r, s, t, d = np.ndarray((4, l), object)
for i in range(l):
r[i], s[i], t[i], d[i] = opa(p, v[i])
return r, s, t, d
def avg(v):
v_= np.copy(v)
l = len(v_)
R, S, T = [list(np.zeros(l)) for _ in range(3)]
for i, j in np.ndindex(l, l):
r, s, t, _ = opa(v_[i], v_[j])
R[j] += np.arccos(min(1, max(-1, np.trace(r[:1])))) * np.sign(r[1][0])
S[j] += s
T[j] += t
for i in range(l):
a = R[i] / l
r = [np.cos(a), -np.sin(a)], [np.sin(a), np.cos(a)]
v_[i] = v_[i].dot(r) * (S[i] / l) + (T[i] / l)
return v_.mean(0)
For testing purposes, the output of each algorithm can be visualized as follows:
import matplotlib.pyplot as p; p.rcParams['toolbar'] = 'None';
def plt(o, e, b):
p.figure(figsize=(10, 10), dpi=72, facecolor='w').add_axes([0.05, 0.05, 0.9, 0.9], aspect='equal')
p.plot(0, 0, marker='x', mew=1, ms=10, c='g', zorder=2, clip_on=False)
p.gcf().canvas.set_window_title('%f' % e)
x = np.ravel(o[0].T[0])
y = np.ravel(o[0].T[1])
p.xlim(min(x), max(x))
p.ylim(min(y), max(y))
a = []
for i, j in np.ndindex(len(o), 2):
a.append(o[i].T[j])
O = p.plot(*a, marker='x', mew=1, ms=10, lw=.25, c='b', zorder=0, clip_on=False)
O[0].set(c='r', zorder=1)
if not b:
O[2].set_color('b')
O[2].set_alpha(0.4)
p.axis('off')
p.show()
# Fly wings example (Klingenberg, 2015 | https://en.wikipedia.org/wiki/Procrustes_analysis)
arr1 = np.array([[588.0, 443.0], [178.0, 443.0], [56.0, 436.0], [50.0, 376.0], [129.0, 360.0], [15.0, 342.0], [92.0, 293.0], [79.0, 269.0], [276.0, 295.0], [281.0, 331.0], [785.0, 260.0], [754.0, 174.0], [405.0, 233.0], [386.0, 167.0], [466.0, 59.0]])
arr2 = np.array([[477.0, 557.0], [130.129, 374.307], [52.0, 334.0], [67.662, 306.953], [111.916, 323.0], [55.119, 275.854], [107.935, 277.723], [101.899, 259.73], [175.0, 329.0], [171.0, 345.0], [589.0, 527.0], [591.0, 468.0], [299.0, 363.0], [306.0, 317.0], [406.0, 288.0]])
def opa_out(a):
r, s, t, d = opa(a[0], a[1])
a[1] = a[1].dot(r) * s + t
return a, d, False
plt(*opa_out([arr1, arr2, np.matrix.copy(arr2)]))
def gpa_out(a):
g = gpa(a, -1)
D = [avg(a)]
for i in range(len(a)):
D.append(a[i].dot(g[0][i]) * g[1][i] + g[2][i])
return D, sum(g[3])/len(a), True
plt(*gpa_out([arr1, arr2]))
Probably you want to try this package with various flavors of different Procrustes methods, https://github.com/theochem/procrustes.

Categories