Distance between two lines in 3d - python

I'm writing a program for university on python. I have to find the shortest distance between two lines in 3d given by two points (A B and C D) and find the points on both of these lines with the shortest distance between them. I'm bad at math so I can't understand how to find the points, only managed to find the formula of the minimal distance between two lines.
tried to write program, but it finds only point of intersection between lines and doesn't work correctly
#line 1
A = [1, 3, 1]
B = [0, -1, 2]
#line 2
C = [0, -2, 3]
D = [1, 0, 2]
def line_intersection(a, b, c, d):
v1 = [a_i - b_i for a_i, b_i in zip(B, A)]
v2 = [a_i - b_i for a_i, b_i in zip(D, C)]
if (-v1[0] * v2[1] + v1[1] * v2[0]) == 0:
t = ((c[1]-a[1]) * (-v2[2]) + (v2[1]) * (c[2]-a[2]))/(-v1[1] * v2[2] + v1[2] * v2[1])
else:
t = ((c[0]-a[0]) * (-v2[1]) + (v2[0]) * (c[1]-a[1]))/(-v1[0] * v2[1] + v1[1] * v2[0])
x = a[0] + t * v1[0]
y = a[1] + t * v1[1]
z = a[2] + t * v1[2]
return x, y, z

Related

Python implementation of Francis double step QR iteration algorithm does not converge

I am implementing the Francis double step QR Iteration algorithm using the notes and psuedocode from lecture https://people.inf.ethz.ch/arbenz/ewp/Lnotes/chapter4.pdf - Algorithm 4.5
The psuedocode is provided in Matlab I believe.
Below is the implementation of my code.
# compute upper hessenberg form of matrix
def hessenberg(A):
m,n = A.shape
H = A.astype(np.float64)
for k in range(n-2):
x = H[k+1:, k]
v = np.concatenate([np.array([np.sign(x[0]) * np.linalg.norm(x)]), x[1:]])
v = v / np.linalg.norm(v)
H[k+1:, k:] -= 2 * np.outer(v, np.dot(v, H[k+1:, k:]))
H[:, k+1:] -= 2 * np.outer(np.dot(H[:, k+1:], v), v)
return(H)
# compute first three elements of M
def first_three_M(T,s,t):
x = T[0, 0]**2 + T[0, 1] * T[1, 0] - s * T[0, 0] + t
y = T[1, 0] * (T[0, 0] + T[1, 1] - s)
z = T[1, 0] * T[2, 1]
return(x,y,z)
# householder reflection
def householder_reflection_step(x_1):
v = x_1[0] + np.sign(x_1[0]) * np.linalg.norm(x_1)
v = v / np.linalg.norm(v)
P = np.eye(3) - 2 * np.outer(v, v)
return(P)
# update elements of M
def update_M(T,k,p):
x = T[k+1, k]
y = T[k+2, k]
if k < p - 3:
z = T[k+3, k]
else:
z = 0
return(x,y,z)
# givens rotation
def givens_step(T,x_2,x,y,p,q,n):
# calculate c and s
c = x / np.sqrt(x**2 + y**2)
s = -y / np.sqrt(x**2 + y**2)
P = np.array([[c, s], [-s, c]])
T[q-1:p, p-3:n] = P.T # T[q-1:p, p-3:n]
T[0:p, p-2:p] = T[0:p, p-2:p] # P
return(T)
# deflation step
def deflation_step(T,p,q,epsilon):
if abs(T[p-1, p-2]) < epsilon * (abs(T[p-2, p-2]) + abs(T[p-1, p-1])):
T[p-1, p-2] = 0
p = p - 1
q = p - 1
elif abs(T[p-2, p-3]) < epsilon * (abs(T[p-3, p-3]) + abs(T[p-2, p-2])):
T[p-2, p-3] = 0
p = p - 2
q = p - 1
return(T,p,q)
# francis qr step
def francis_step(H, epsilon=0.90):
n = H.shape[0]
T = H.copy().astype(np.float64)
p = n - 1
while p > 2:
q = p - 1
s = T[q, q] + T[p, p]
t = T[q, q] * T[p, p] - T[q, p] * T[p, q]
# Compute M
x,y,z = first_three_M(T,s,t)
x_1 = np.transpose([[x], [y], [z]])
# Bulge chasing
for k in range(p - 3):
# Compute Householder reflector
P = householder_reflection_step(x_1)
r = max(1, k-1)
T[k:k+3, r:] = P.T # T[k:k+3, r:]
r = min(k + 3, p)
T[0:r, k:k+3] = T[0:r, k:k+3] # P
# Update M
x,y,z = update_M(T,k,p)
x_2 = np.transpose([[x], [y]])
# Compute Givens rotation
T = givens_step(T,x_2,x,y,p,q,n)
# Check for convergence
T,p,q = deflation_step(T,p,q,epsilon)
return(T)
# francis qr iteration
def francis_qr_iteration(A):
m,n = A.shape
H = hessenberg(A)
eigvals = []
iters = 0
max_iters = 100
while iters<max_iters:
# Perform Francis step
T = francis_step(H)
eigvals.append(np.diag(T))
iters+=1
return(eigvals)
# for quick testing
A = np.array([[2, 2, 3, 4, 2],
[1, 2, 4, 2, 3],
[4, 1, 2, 1, 5],
[5, 2, 5, 2, 1],
[3, 6, 3, 1, 4]])
eigenvals = francis_qr_iteration(A)
#comparing our method to scipy - final eigvals obtained
print(len(eigenvals))
print(sorted(eigenvals[-1]))
print(sorted(scipy.linalg.eig(A)[0].real))
And this is the output I am getting.
100
[-4.421235127393854, -0.909209110641351, -0.8342390091346807, 3.7552499102751575, 8.215454029003958]
[-3.0411228516834217, -1.143605409373778, -1.143605409373778, 3.325396565009845, 14.002937105421134]
The matrix T is not changing and hence it does not converge to the Schur form through which I can obtain the eigenvalues by using np.diag(T). I believe the error is coming either from the Givens rotation step or the Householder reflection step. It could be an indexing issue since I tried to work in python using matlab psuedocode. Please let me know where I am going wrong so I can improve the code and make it converge.

How to find bezier coefficients without matrices?

The function get_cubic needs 4 points and i need to find b and c by calculation (a and d is given).
Here is my code and i need help specifically with get_bezier_coef
def get_bezier_coef(points):
# since the formulas work given that we have n+1 points
# then n must be this:
n = len(points) - 1
# build coefficents matrix
C = 4 * np.identity(n)
np.fill_diagonal(C[1:], 1)
np.fill_diagonal(C[:, 1:], 1)
C[0, 0] = 2
C[n - 1, n - 1] = 7
C[n - 1, n - 2] = 2
# build points vector
P = [2. * (2. * points[i] + points[i + 1]) for i in range(n)]
P[0] = points[0] + 2 * points[1]
P[n - 1] = 8 * points[n - 1] + points[n]
# solve system, find a & b
A = np.linalg.solve(C,P)
B = [0] * n
for i in range(n - 1):
B[i] = 2. * points[i + 1] - A[i + 1]
B[n - 1] = (A[n - 1] + points[n]) / 2.
return A, B
# returns the general Bezier cubic formula given 4 control points
def get_cubic(a, b, c, d):
return lambda t: np.power(1 - t, 3) * a + 3 * np.power(1 - t, 2) * t * b + 3 * (1 - t) * np.power(t,2) * c + np.power(t, 3) * d
# return one cubic curve for each consecutive points
def get_bezier_cubic(points):
A, B = get_bezier_coef(points)
return [
get_cubic(points[i], A[i], B[i], points[i + 1])
for i in range(len(points) - 1)
]
The function get_bezier_coef get list of points [(X0,Y0),(X1,Y1)....] and return the coefficients of the bezier (find the 2 control points between the start and end point). Is there anyway to calculate the coefficients without matrices? Or any other way that will reduce time.

How to create a Single Vector having 2 Dimensions?

I have used the Equation of Motion (Newtons Law) for a simple spring and mass scenario incorporating it into the given 2nd ODE equation y" + (k/m)x = 0; y(0) = 3; y'(0) = 0.
I have then been able to run a code that calculates and compares the Exact Solution with the Runge-Kutta Method Solution.
It works fine...however, I have recently been asked not to separate my values of 'x' and 'v', but use a single vector 'x' that has two dimensions ( i.e. 'x' and 'v' can be handled by x(1) and x(2) ).
MY CODE:
# Given is y" + (k/m)x = 0; y(0) = 3; y'(0) = 0
# Parameters
h = 0.01; #Step Size
t = 100.0; #Time(sec)
k = 1;
m = 1;
x0 = 3;
v0 = 0;
# Exact Analytical Solution
te = np.arange(0, t ,h);
N = len(te);
w = (k / m) ** 0.5;
x_exact = x0 * np.cos(w * te);
v_exact = -x0 * w * np.sin(w * te);
# Runge-kutta Method
x = np.empty(N);
v = np.empty(N);
x[0] = x0;
v[0] = v0;
def f1 (t, x, v):
x = v
return x
def f2 (t, x, v):
v = -(k / m) * x
return v
for i in range(N - 1): #MAIN LOOP
K1x = f1(te[i], x[i], v[i])
K1v = f2(te[i], x[i], v[i])
K2x = f1(te[i] + h / 2, x[i] + h * K1x / 2, v[i] + h * K1v / 2)
K2v = f2(te[i] + h / 2, x[i] + h * K1x / 2, v[i] + h * K1v / 2)
K3x = f1(te[i] + h / 2, x[i] + h * K2x / 2, v[i] + h * K2v / 2)
K3v = f2(te[i] + h / 2, x[i] + h * K2x / 2, v[i] + h * K2v / 2)
K4x = f1(te[i] + h, x[i] + h * K3x, v[i] + h * K3v)
K4v = f2(te[i] + h, x[i] + h * K3x, v[i] + h * K3v)
x[i + 1] = x[i] + h / 6 * (K1x + 2 * K2x + 2 * K3x + K4x)
v[i + 1] = v[i] + h / 6 * (K1v + 2 * K2v + 2 * K3v + K4v)
Can anyone help me understand how I can create this single vector having 2 dimensions, and how to fix my code up please?
You can use np.array() function, here is an example of what you're trying to do:
x = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
Unsure of your exact expectations of what you are wanting besides just having a 2 lists inside a single list. Though I do hope this link will help answer your issue.
https://www.tutorialspoint.com/python_data_structure/python_2darray.htm?

Is there any possibility to vectorize this?

Currently I'm working on a project that implements cubic spline interpolation. So far I have managed to calculate coefficients for my equations.
Now I'm trying to return an interpolating function that for any x returns y.
Let's assume that we have
x = [1, 3, 5]
y = [6, -2, 4]
The coefficients that we get are as follow:
[ 6, -5.75, 0, 0.4375, -2, -0.5, 2.625, -0.4375]
It is equal to
[ a<sub>0</sub>, b<sub>0</sub>, c<sub>0</sub>, d<sub>0</sub>, a<sub>1</sub>, b<sub>1</sub>, c<sub>1</sub>, d<sub>1</sub>]
The interpolating polynomials are
S<sub>0</sub>(x) = a<sub>0</sub> + b<sub>0</sub>*x + c<sub>0</sub>*x<sup>2</sup> + d<sub>0</sub>*x<sup>3</sup> x ∈ [1, 3]
S<sub>1</sub>(x) = a<sub>1</sub> + b<sub>1</sub>*x + c<sub>1</sub>*x<sup>2</sup> + d<sub>1</sub>*x<sup>3</sup> x ∈ (3, 5]
And so on - it can be calculated for more than only 3 points
Right now I have implemented a method that works only if one x is given as an input.
def interpolate_spline(x, x_array, coefficients):
i = 1
while x_array[i] < x:
i += 1
i = i - 1
a = coefficients[4 * i]
b = coefficients[4 * i + 1]
c = coefficients[4 * i + 2]
d = coefficients[4 * i + 3]
return a + b * x + c * (x ** 2) + d * (x ** 3)
And coming back to my question: Is there any possibility that it can be vectorized or at least take whole array as an input?
I don't know if that matters but assume that x_array is sorted

Find area of polygon from xyz coordinates

I'm trying to use the shapely.geometry.Polygon module to find the area of polygons but it performs all calculations on the xy plane. This is fine for some of my polygons but others have a z dimension too so it's not quite doing what I'd like.
Is there a package which will either give me the area of a planar polygon from xyz coordinates, or alternatively a package or algorithm to rotate the polygon to the xy plane so that i can use shapely.geometry.Polygon().area?
The polygons are represented as a list of tuples in the form [(x1,y1,z1),(x2,y2,z3),...(xn,yn,zn)].
Here is the derivation of a formula for calculating the area of a 3D planar polygon
Here is Python code that implements it:
#determinant of matrix a
def det(a):
return a[0][0]*a[1][1]*a[2][2] + a[0][1]*a[1][2]*a[2][0] + a[0][2]*a[1][0]*a[2][1] - a[0][2]*a[1][1]*a[2][0] - a[0][1]*a[1][0]*a[2][2] - a[0][0]*a[1][2]*a[2][1]
#unit normal vector of plane defined by points a, b, and c
def unit_normal(a, b, c):
x = det([[1,a[1],a[2]],
[1,b[1],b[2]],
[1,c[1],c[2]]])
y = det([[a[0],1,a[2]],
[b[0],1,b[2]],
[c[0],1,c[2]]])
z = det([[a[0],a[1],1],
[b[0],b[1],1],
[c[0],c[1],1]])
magnitude = (x**2 + y**2 + z**2)**.5
return (x/magnitude, y/magnitude, z/magnitude)
#dot product of vectors a and b
def dot(a, b):
return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
#cross product of vectors a and b
def cross(a, b):
x = a[1] * b[2] - a[2] * b[1]
y = a[2] * b[0] - a[0] * b[2]
z = a[0] * b[1] - a[1] * b[0]
return (x, y, z)
#area of polygon poly
def area(poly):
if len(poly) < 3: # not a plane - no area
return 0
total = [0, 0, 0]
for i in range(len(poly)):
vi1 = poly[i]
if i is len(poly)-1:
vi2 = poly[0]
else:
vi2 = poly[i+1]
prod = cross(vi1, vi2)
total[0] += prod[0]
total[1] += prod[1]
total[2] += prod[2]
result = dot(total, unit_normal(poly[0], poly[1], poly[2]))
return abs(result/2)
And to test it, here's a 10x5 square that leans over:
>>> poly = [[0, 0, 0], [10, 0, 0], [10, 3, 4], [0, 3, 4]]
>>> poly_translated = [[0+5, 0+5, 0+5], [10+5, 0+5, 0+5], [10+5, 3+5, 4+5], [0+5, 3+5, 4+5]]
>>> area(poly)
50.0
>>> area(poly_translated)
50.0
>>> area([[0,0,0],[1,1,1]])
0
The problem originally was that I had oversimplified. It needs to calculate the unit vector normal to the plane. The area is half of the dot product of that and the total of all the cross products, not half of the sum of all the magnitudes of the cross products.
This can be cleaned up a bit (matrix and vector classes would make it nicer, if you have them, or standard implementations of determinant/cross product/dot product), but it should be conceptually sound.
This is the final code I've used. It doesn't use shapely, but implements Stoke's theorem to calculate the area directly. It builds on #Tom Smilack's answer which shows how to do it without numpy.
import numpy as np
#unit normal vector of plane defined by points a, b, and c
def unit_normal(a, b, c):
x = np.linalg.det([[1,a[1],a[2]],
[1,b[1],b[2]],
[1,c[1],c[2]]])
y = np.linalg.det([[a[0],1,a[2]],
[b[0],1,b[2]],
[c[0],1,c[2]]])
z = np.linalg.det([[a[0],a[1],1],
[b[0],b[1],1],
[c[0],c[1],1]])
magnitude = (x**2 + y**2 + z**2)**.5
return (x/magnitude, y/magnitude, z/magnitude)
#area of polygon poly
def poly_area(poly):
if len(poly) < 3: # not a plane - no area
return 0
total = [0, 0, 0]
N = len(poly)
for i in range(N):
vi1 = poly[i]
vi2 = poly[(i+1) % N]
prod = np.cross(vi1, vi2)
total[0] += prod[0]
total[1] += prod[1]
total[2] += prod[2]
result = np.dot(total, unit_normal(poly[0], poly[1], poly[2]))
return abs(result/2)
#pythonn code for polygon area in 3D (optimised version)
def polygon_area(poly):
#shape (N, 3)
if isinstance(poly, list):
poly = np.array(poly)
#all edges
edges = poly[1:] - poly[0:1]
# row wise cross product
cross_product = np.cross(edges[:-1],edges[1:], axis=1)
#area of all triangles
area = np.linalg.norm(cross_product, axis=1)/2
return sum(area)
if __name__ == "__main__":
poly = [[0+5, 0+5, 0+5], [10+5, 0+5, 0+5], [10+5, 3+5, 4+5], [0+5, 3+5, 4+5]]
print(polygon_area(poly))
The area of a 2D polygon can be calculated using Numpy as a one-liner...
poly_Area(vertices) = np.sum( [0.5, -0.5] * vertices * np.roll( np.roll(vertices, 1, axis=0), 1, axis=1) )
Fyi, here is the same algorithm in Mathematica, with a baby unit test
ClearAll[vertexPairs, testPoly, area3D, planeUnitNormal, pairwise];
pairwise[list_, fn_] := MapThread[fn, {Drop[list, -1], Drop[list, 1]}];
vertexPairs[Polygon[{points___}]] := Append[{points}, First[{points}]];
testPoly = Polygon[{{20, -30, 0}, {40, -30, 0}, {40, -30, 20}, {20, -30, 20}}];
planeUnitNormal[Polygon[{points___}]] :=
With[{ps = Take[{points}, 3]},
With[{p0 = First[ps]},
With[{qs = (# - p0) & /# Rest[ps]},
Normalize[Cross ## qs]]]];
area3D[p : Polygon[{polys___}]] :=
With[{n = planeUnitNormal[p], vs = vertexPairs[p]},
With[{areas = (Dot[n, #]) & /# pairwise[vs, Cross]},
Plus ## areas/2]];
area3D[testPoly]
Same as #Tom Smilack's answer, but in javascript
//determinant of matrix a
function det(a) {
return a[0][0] * a[1][1] * a[2][2] + a[0][1] * a[1][2] * a[2][0] + a[0][2] * a[1][0] * a[2][1] - a[0][2] * a[1][1] * a[2][0] - a[0][1] * a[1][0] * a[2][2] - a[0][0] * a[1][2] * a[2][1];
}
//unit normal vector of plane defined by points a, b, and c
function unit_normal(a, b, c) {
let x = math.det([
[1, a[1], a[2]],
[1, b[1], b[2]],
[1, c[1], c[2]]
]);
let y = math.det([
[a[0], 1, a[2]],
[b[0], 1, b[2]],
[c[0], 1, c[2]]
]);
let z = math.det([
[a[0], a[1], 1],
[b[0], b[1], 1],
[c[0], c[1], 1]
]);
let magnitude = Math.pow(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2), 0.5);
return [x / magnitude, y / magnitude, z / magnitude];
}
// dot product of vectors a and b
function dot(a, b) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
// cross product of vectors a and b
function cross(a, b) {
let x = (a[1] * b[2]) - (a[2] * b[1]);
let y = (a[2] * b[0]) - (a[0] * b[2]);
let z = (a[0] * b[1]) - (a[1] * b[0]);
return [x, y, z];
}
// area of polygon poly
function area(poly) {
if (poly.length < 3) {
console.log("not a plane - no area");
return 0;
} else {
let total = [0, 0, 0]
for (let i = 0; i < poly.length; i++) {
var vi1 = poly[i];
if (i === poly.length - 1) {
var vi2 = poly[0];
} else {
var vi2 = poly[i + 1];
}
let prod = cross(vi1, vi2);
total[0] = total[0] + prod[0];
total[1] = total[1] + prod[1];
total[2] = total[2] + prod[2];
}
let result = dot(total, unit_normal(poly[0], poly[1], poly[2]));
return Math.abs(result/2);
}
}
Thanks for detailed answers, But I am little surprised there is no simple answer to get the area.
So, I am just posting a simplified approach for calculating area using 3d Coordinates of polygon or surface using pyny3d.
#Install pyny3d as:
pip install pyny3d
#Calculate area
import numpy as np
import pyny3d.geoms as pyny
coords_3d = np.array([[0, 0, 0],
[7, 0, 0],
[7, 10, 2],
[0, 10, 2]])
polygon = pyny.Polygon(coords_3d)
print(f'Area is : {polygon.get_area()}')

Categories