Mesh Generation for Computational Science in Python - python

I have a need for a Python module/package that provides a mesh on which I can do computational science? I am not doing graphics, so I don't think the blender package is what I want.
Does anyone know of a good package?

The most useful packages out there are perhaps
mshr,
pygalmesh,
dmsh,
pygmsh, and
MeshPy,
meshzoo.
In addition, there is optimesh for improving the quality of any mesh.
(Disclaimer: I'm the author of pygmsh, pygalmesh, dmsh, meshzoo, and optimesh.)

If you're trying to solve FE or CFD style equations on a mesh you can use MeshPy in 2 and 3 dimensions. Meshpy is a nice wrapper around the existing tools tetgen and triangle.
If you're looking for more typical graphics style meshes, there was an interesting talk at PyCon 2011 "Algorithmic Generation of OpenGL Geometry", which described a pragmatic approach to procedural mesh generation. The code from the presentation is available online
If you're interested in reconstruction of surfaces from data, you can't go past the Standford 3D Scanning Repository, home of the Stanford Bunny
Edit:
A dependancy free alternative may be to use something like gmsh, which is platform independent, and uses similar tools to meshpy in its back-end.

I recommend using NumPy (especially if you've used MATLAB before). Many computational scientists / mechanical engineers working in python might agree, but I'm biased as it found it's way into much of the last year of my research. It's part of SciPy: http://numpy.scipy.org/
I was fond of numpy.linspace(a,b,N) which makes an N length vector of equally spaced values from a to b. You can use numpy.ndarray to make a N x M matrix, or if you want 2D arrays use numpy.meshgrid.

Here is code adapted from Kardontchik's port,
import numpy as np
from numpy import pi as pi
from scipy.spatial import Delaunay
import matplotlib.pylab as plt
from scipy.optimize import fmin
import matplotlib.pylab as plt
def ktrimesh(p,bars,pflag=0):
# create the (x,y) data for the plot
xx1 = p[bars[:,0],0]; yy1 = p[bars[:,0],1]
xx2 = p[bars[:,1],0]; yy2 = p[bars[:,1],1]
xmin = np.min(p[:,0])
xmax = np.max(p[:,0])
ymin = np.min(p[:,1])
ymax = np.max(p[:,1])
xmin = xmin - 0.05*(xmax - xmin)
xmax = xmax + 0.05*(xmax - xmin)
ymin = ymin - 0.05*(ymax - ymin)
ymax = ymax + 0.05*(ymax - ymin)
plt.figure()
for i in range(len(xx1)):
xp = np.array([xx1[i],xx2[i]])
yp = np.array([yy1[i],yy2[i]])
plt.plot(xmin,ymin,'.',xmax,ymax,'.',markersize=0.1)
plt.plot(xp,yp,'k')
plt.axis('equal')
if pflag == 0:
stitle = 'Triangular Mesh'
if pflag == 1:
stitle = 'Visual Boundary Integrity Check'
#plt.title('Triangular Mesh')
plt.title(stitle)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
return 1
def ccw_tri(p,t):
"""
orients all the triangles counterclockwise
"""
# vector A from vertex 0 to vertex 1
# vector B from vertex 0 to vertex 2
A01x = p[t[:,1],0] - p[t[:,0],0]
A01y = p[t[:,1],1] - p[t[:,0],1]
B02x = p[t[:,2],0] - p[t[:,0],0]
B02y = p[t[:,2],1] - p[t[:,0],1]
# if vertex 2 lies to the left of vector A the component z of
# their vectorial product A^B is positive
Cz = A01x*B02y - A01y*B02x
a = t[np.where(Cz<0)]
b = t[np.where(Cz>=0)]
a[:,[1,2]] = a[:,[2,1]]
t = np.concatenate((a, b))
return t
def triqual_flag(p,t):
# a(1,0), b(2,0), c(2,1)
a = np.sqrt((p[t[:,1],0] - p[t[:,0],0])**2 + (p[t[:,1],1] - p[t[:,0],1])**2)
b = np.sqrt((p[t[:,2],0] - p[t[:,0],0])**2 + (p[t[:,2],1] - p[t[:,0],1])**2)
c = np.sqrt((p[t[:,2],0] - p[t[:,1],0])**2 + (p[t[:,2],1] - p[t[:,1],1])**2)
A = 0.25*np.sqrt((a+b+c)*(b+c-a)*(a+c-b)*(a+b-c))
R = 0.25*(a*b*c)/A
r = 0.5*np.sqrt( (a+b-c)*(b+c-a)*(a+c-b)/(a+b+c) )
q = 2.0*(r/R)
min_edge = np.minimum(np.minimum(a,b),c)
min_angle_deg = (180.0/np.pi)*np.arcsin(0.5*min_edge/R)
min_q = np.min(q)
min_ang = np.min(min_angle_deg)
return min_q, min_ang
def triqual(p,t,fh,qlim=0.2):
# a(1,0), b(2,0), c(2,1)
a = np.sqrt((p[t[:,1],0] - p[t[:,0],0])**2 + (p[t[:,1],1] - p[t[:,0],1])**2)
b = np.sqrt((p[t[:,2],0] - p[t[:,0],0])**2 + (p[t[:,2],1] - p[t[:,0],1])**2)
c = np.sqrt((p[t[:,2],0] - p[t[:,1],0])**2 + (p[t[:,2],1] - p[t[:,1],1])**2)
A = 0.25*np.sqrt((a+b+c)*(b+c-a)*(a+c-b)*(a+b-c))
R = 0.25*(a*b*c)/A
r = 0.5*np.sqrt( (a+b-c)*(b+c-a)*(a+c-b)/(a+b+c) )
q = 2.0*(r/R)
pmid = (p[t[:,0]] + p[t[:,1]] + p[t[:,2]])/3.0
hmid = fh(pmid)
Ah = A/hmid
Anorm = Ah/np.mean(Ah)
min_edge = np.minimum(np.minimum(a,b),c)
min_angle_deg = (180.0/np.pi)*np.arcsin(0.5*min_edge/R)
plt.figure()
plt.subplot(3,1,1)
plt.hist(q)
plt.title('Histogram;Triangle Statistics:q-factor,Minimum Angle and Area')
plt.subplot(3,1,2)
plt.hist(min_angle_deg)
plt.ylabel('Number of Triangles')
plt.subplot(3,1,3)
plt.hist(Anorm)
plt.xlabel('Note: for equilateral triangles q = 1 and angle = 60 deg')
plt.show()
indq = np.where(q < qlim) # indq is a tuple: len(indq) = 1
if list(indq[0]) != []:
print ('List of triangles with q < %5.3f and the (x,y) location of their nodes' % qlim)
print ('')
print ('q t[i] t[nodes] [x,y][0] [x,y][1] [x,y][2]')
for i in indq[0]:
print ('%.2f %4d [%4d,%4d,%4d] [%+.2f,%+.2f] [%+.2f,%+.2f] [%+.2f,%+.2f]' % \
(q[i],i,t[i,0],t[i,1],t[i,2],p[t[i,0],0],p[t[i,0],1],p[t[i,1],0],p[t[i,1],1],p[t[i,2],0],p[t[i,2],1]))
print ('')
# end of detailed data on worst offenders
return q,min_angle_deg,Anorm
class Circle:
def __init__(self,xc,yc,r):
self.xc, self.yc, self.r = xc, yc, r
def __call__(self,p):
xc, yc, r = self.xc, self.yc, self.r
d = np.sqrt((p[:,0] - xc)**2 + (p[:,1] - yc)**2) - r
return d
class Rectangle:
def __init__(self,x1,x2,y1,y2):
self.x1, self.x2, self.y1, self.y2 = x1,x2,y1,y2
def __call__(self,p):
x1,x2,y1,y2 = self.x1, self.x2, self.y1, self.y2
d1 = p[:,1] - y1 # if p inside d1 > 0
d2 = y2 - p[:,1] # if p inside d2 > 0
d3 = p[:,0] - x1 # if p inside d3 > 0
d4 = x2 - p[:,0] # if p inside d4 > 0
d = -np.minimum(np.minimum(np.minimum(d1,d2),d3),d4)
return d
class Polygon:
def __init__(self,verts):
self.verts = verts
def __call__(self,p):
verts = self.verts
# close the polygon
cverts = np.zeros((len(verts)+1,2))
cverts[0:-1] = verts
cverts[-1] = verts[0]
# initialize
inside = np.zeros(len(p))
dist = np.zeros(len(p))
Cz = np.zeros(len(verts)) # z-components of the vectorial products
dist_to_edge = np.zeros(len(verts))
in_ref = np.ones(len(verts))
# if np.sign(Cz) == in_ref then point is inside
for j in range(len(p)):
Cz = (cverts[1:,0] - cverts[0:-1,0])*(p[j,1] - cverts[0:-1,1]) - \
(cverts[1:,1] - cverts[0:-1,1])*(p[j,0] - cverts[0:-1,0])
dist_to_edge = Cz/np.sqrt( \
(cverts[1:,0] - cverts[0:-1,0])**2 + \
(cverts[1:,1] - cverts[0:-1,1])**2)
inside[j] = int(np.array_equal(np.sign(Cz),in_ref))
dist[j] = (1 - 2*inside[j])*np.min(np.abs(dist_to_edge))
return dist
class Union:
def __init__(self,fd1,fd2):
self.fd1, self.fd2 = fd1, fd2
def __call__(self,p):
fd1,fd2 = self.fd1, self.fd2
d = np.minimum(fd1(p),fd2(p))
return d
class Diff:
def __init__(self,fd1,fd2):
self.fd1, self.fd2 = fd1, fd2
def __call__(self,p):
fd1,fd2 = self.fd1, self.fd2
d = np.maximum(fd1(p),-fd2(p))
return d
class Intersect:
def __init__(self,fd1,fd2):
self.fd1, self.fd2 = fd1, fd2
def __call__(self,p):
fd1,fd2 = self.fd1, self.fd2
d = np.maximum(fd1(p),fd2(p))
return d
class Protate:
def __init__(self,phi):
self.phi = phi
def __call__(self,p):
phi = self.phi
c = np.cos(phi)
s = np.sin(phi)
temp = np.copy(p[:,0])
rp = np.copy(p)
rp[:,0] = c*p[:,0] - s*p[:,1]
rp[:,1] = s*temp + c*p[:,1]
return rp
class Pshift:
def __init__(self,x0,y0):
self.x0, self.y0 = x0,y0
def __call__(self,p):
x0, y0 = self.x0, self.y0
p[:,0] = p[:,0] + x0
p[:,1] = p[:,1] + y0
return p
def Ellipse_dist_to_minimize(t,p,xc,yc,a,b):
x = xc + a*np.cos(t) # coord x of the point on the ellipse
y = yc + b*np.sin(t) # coord y of the point on the ellipse
dist = (p[0] - x)**2 + (p[1] - y)**2
return dist
class Ellipse:
def __init__(self,xc,yc,a,b):
self.xc, self.yc, self.a, self.b = xc, yc, a, b
self.t, self.verts = self.pick_points_on_shape()
def pick_points_on_shape(self):
xc, yc, a, b = self.xc, self.yc, self.a, self.b
c = np.array([xc,yc])
t = np.linspace(0,(7.0/4.0)*pi,8)
verts = np.zeros((8,2))
verts[:,0] = c[0] + a*np.cos(t)
verts[:,1] = c[1] + b*np.sin(t)
return t, verts
def inside_ellipse(self,p):
xc, yc, a, b = self.xc, self.yc, self.a, self.b
c = np.array([xc,yc])
r, phase = self.rect_to_polar(p-c)
r_ellipse = self.rellipse(phase)
in_ref = np.ones(len(p))
inside = 0.5 + 0.5*np.sign(r_ellipse-r)
return inside
def rect_to_polar(self,p):
r = np.sqrt(p[:,0]**2 + p[:,1]**2)
phase = np.arctan2(p[:,1],p[:,0])
# note: np.arctan2(y,x) order; phase in +/- pi (+/- 180deg)
return r, phase
def rellipse(self,phi):
a, b = self.a, self.b
r = a*b/np.sqrt((b*np.cos(phi))**2 + (a*np.sin(phi))**2)
return r
def find_closest_vertex(self,point):
t, verts = self.t, self.verts
dist = np.zeros(len(t))
for i in range(len(t)):
dist[i] = (point[0] - verts[i,0])**2 + (point[1] - verts[i,1])**2
ind = np.argmin(dist)
t0 = t[ind]
return t0
def __call__(self,p):
xc, yc, a, b = self.xc, self.yc, self.a, self.b
t, verts = self.t, self.verts
dist = np.zeros(len(p))
inside = self.inside_ellipse(p)
for j in range(len(p)):
t0 = self.find_closest_vertex(p[j]) # initial guess to minimizer
opt = fmin(Ellipse_dist_to_minimize,t0, \
args=(p[j],xc,yc,a,b),full_output=1,disp=0)
# add full_output=1 so we can retrieve the min dist(squared)
# (2nd argument of opt array, 1st argument is the optimum t)
min_dist = np.sqrt(opt[1])
dist[j] = min_dist*(1 - 2*inside[j])
return dist
def distmesh(fd,fh,h0,xmin,ymin,xmax,ymax,pfix,ttol=0.1,dptol=0.001,Iflag=1,qmin=1.0):
geps = 0.001*h0; deltat = 0.2; Fscale = 1.2
deps = h0 * np.sqrt(np.spacing(1))
random_seed = 17
h0x = h0; h0y = h0*np.sqrt(3)/2 # to obtain equilateral triangles
Nx = int(np.floor((xmax - xmin)/h0x))
Ny = int(np.floor((ymax - ymin)/h0y))
x = np.linspace(xmin,xmax,Nx)
y = np.linspace(ymin,ymax,Ny)
# create the grid in the (x,y) plane
xx,yy = np.meshgrid(x,y)
xx[1::2] = xx[1::2] + h0x/2.0 # shifts even rows by h0x/2
p = np.zeros((np.size(xx),2))
p[:,0] = np.reshape(xx,np.size(xx))
p[:,1] = np.reshape(yy,np.size(yy))
p = np.delete(p,np.where(fd(p) > geps),axis=0)
np.random.seed(random_seed)
r0 = 1.0/fh(p)**2
p = np.concatenate((pfix,p[np.random.rand(len(p))<r0/max(r0),:]))
pold = np.inf
Num_of_Delaunay_triangulations = 0
Num_of_Node_movements = 0 # dp = F*dt
while (1):
Num_of_Node_movements += 1
if Iflag == 1 or Iflag == 3: # Newton flag
print ('Num_of_Node_movements = %3d' % (Num_of_Node_movements))
if np.max(np.sqrt(np.sum((p - pold)**2,axis = 1))) > ttol:
Num_of_Delaunay_triangulations += 1
if Iflag == 1 or Iflag == 3: # Delaunay flag
print ('Num_of_Delaunay_triangulations = %3d' % \
(Num_of_Delaunay_triangulations))
pold = p
tri = Delaunay(p) # instantiate a class
t = tri.vertices
pmid = (p[t[:,0]] + p[t[:,1]] + p[t[:,2]])/3.0
t = t[np.where(fd(pmid) < -geps)]
bars = np.concatenate((t[:,[0,1]],t[:,[0,2]], t[:,[1,2]]))
bars = np.unique(np.sort(bars),axis=0)
if Iflag == 4:
min_q, min_angle_deg = triqual_flag(p,t)
print ('Del iter: %3d, min q = %5.2f, min angle = %3.0f deg' \
% (Num_of_Delaunay_triangulations, min_q, min_angle_deg))
if min_q > qmin:
break
if Iflag == 2 or Iflag == 3:
ktrimesh(p,bars)
# move mesh points based on bar lengths L and forces F
barvec = p[bars[:,0],:] - p[bars[:,1],:]
L = np.sqrt(np.sum(barvec**2,axis=1))
hbars = 0.5*(fh(p[bars[:,0],:]) + fh(p[bars[:,1],:]))
L0 = hbars*Fscale*np.sqrt(np.sum(L**2)/np.sum(hbars**2))
F = np.maximum(L0-L,0)
Fvec = np.column_stack((F,F))*(barvec/np.column_stack((L,L)))
Ftot = np.zeros((len(p),2))
n = len(bars)
for j in range(n):
Ftot[bars[j,0],:] += Fvec[j,:] # the : for the (x,y) components
Ftot[bars[j,1],:] -= Fvec[j,:]
# force = 0 at fixed points, so they do not move:
Ftot[0: len(pfix),:] = 0
# update the node positions
p = p + deltat*Ftot
# bring outside points back to the boundary
d = fd(p); ix = d > 0 # find points outside (d > 0)
dpx = np.column_stack((p[ix,0] + deps,p[ix,1]))
dgradx = (fd(dpx) - d[ix])/deps
dpy = np.column_stack((p[ix,0], p[ix,1] + deps))
dgrady = (fd(dpy) - d[ix])/deps
p[ix,:] = p[ix,:] - np.column_stack((dgradx*d[ix], dgrady*d[ix]))
# termination criterium: all interior nodes move less than dptol:
if max(np.sqrt(np.sum(deltat*Ftot[d<-geps,:]**2,axis=1))/h0) < dptol:
break
final_tri = Delaunay(p) # another instantiation of the class
t = final_tri.vertices
pmid = (p[t[:,0]] + p[t[:,1]] + p[t[:,2]])/3.0
# keep the triangles whose geometrical center is inside the shape
t = t[np.where(fd(pmid) < -geps)]
bars = np.concatenate((t[:,[0,1]],t[:,[0,2]], t[:,[1,2]]))
# delete repeated bars
#bars = unique_rows(np.sort(bars))
bars = np.unique(np.sort(bars),axis=0)
# orient all the triangles counterclockwise (ccw)
t = ccw_tri(p,t)
# graphical output of the current mesh
ktrimesh(p,bars)
triqual(p,t,fh)
return p,t,bars
def boundary_bars(t):
# create the bars (edges) of every triangle
bars = np.concatenate((t[:,[0,1]],t[:,[0,2]], t[:,[1,2]]))
# sort all the bars
data = np.sort(bars)
# find the bars that are not repeated
Delaunay_bars = dict()
for row in data:
row = tuple(row)
if row in Delaunay_bars:
Delaunay_bars[row] += 1
else:
Delaunay_bars[row] = 1
# return the keys of Delaunay_bars whose value is 1 (non-repeated bars)
bbars = []
for key in Delaunay_bars:
if Delaunay_bars[key] == 1:
bbars.append(key)
bbars = np.asarray(bbars)
return bbars
def plot_shapes(xc,yc,r):
# circle for plotting
t_cir = np.linspace(0,2*pi)
x_cir = xc + r*np.cos(t_cir)
y_cir = yc + r*np.sin(t_cir)
plt.figure()
plt.plot(x_cir,y_cir)
plt.grid()
plt.title('Shapes')
plt.xlabel('x')
plt.ylabel('y')
plt.axis('equal')
#plt.show()
return
plt.close('all')
xc = 0; yc = 0; r = 1.0
x1,y1 = -1.0,-2.0
x2,y2 = 2.0,3.0
plot_shapes(xc,yc,r)
xmin = -1.5; ymin = -1.5
xmax = 1.5; ymax = 1.5
h0 = 0.4
pfix = np.zeros((0,2)) # null 2D array, no fixed points provided
fd = Circle(xc,yc,r)
fh = lambda p: np.ones(len(p))
p,t,bars = distmesh(fd,fh,h0,xmin,ymin,xmax,ymax,pfix,Iflag=4)

Related

Can not use numba in the class

I recently want to use #njit(parallel=True) in package numba to speed up my nbodysimulation code, but when I separate the original function out of the class, my code can not work anymore. How to fix this problem?
The following block is the original code to calculate acceleration.
def _calculate_acceleration(self, mass, pos, rsoft):
"""
Calculate the acceleration.
"""
# TODO:
N = self.particles
rsoft = self.rsoft
posx = pos[:,0]
posy = pos[:,1]
posz = pos[:,2]
G = self.G
npts = self.nparticles
acc = np.zeros((npts, 3))
for i in prange(npts):
for j in prange(npts):
if (j>i):
x = (posx[i]-posx[j])
y = (posy[i]-posy[j])
z = (posz[i]-posz[j])
rsq = x**2 + y**2 + z**2
req = np.sqrt(x**2 + y**2)
f = -G*mass[i,0]*mass[j,0]/rsq
theta = np.arctan2(y, x)
phi = np.arctan2(z, req)
fx = f*np.cos(theta)*np.cos(phi)
fy = f*np.sin(theta)*np.cos(phi)
fz = f*np.sin(phi)
acc[i,0] += fx/mass[i]
acc[i,1] += fy/mass[i]
acc[i,2] += fz/mass[i]
acc[j,0] -= fx/mass[j]
acc[j,1] -= fy/mass[j]
acc[j,2] -= fz/mass[j]
return acc
def initialRandomParticles(N = 100, total_mass = 10):
"""
Initial particles
"""
particles = Particles(N)
masses = particles.masses
mass = total_mass/particles.nparticles
particles.masses = (masses*mass)
positions = np.random.randn(N,3)
velocities = np.random.randn(N,3)
accelerations = np.random.randn(N,3)
particles.positions = positions
particles.velocities = velocities
particles.accelerations = accelerations
return particles
particles = initialRandomParticles(N = 10**5, total_mass = 20)
sim = NbodySimulation(particles)
sim.setup(G=G,method="RK4",io_freq=200,io_title=problem_name,io_screen=True,visualized=False, rsoft=0.01)
sim.evolve(dt=0.01,tmax=10)
# Particles and NbodySimulation are defined class.
I spearate the original function out of the class, and define another function to call it in the class, but it's still can not work.
The following is new code which is out of the class.
#njit(nopython=True, parallel=True)
def _calculate_acceleration(n, npts, G, mass, pos, rsoft):
"""
Calculate the acceleration. This function is out of the class.
"""
# TODO:
posx = pos[:,0]
posy = pos[:,1]
posz = pos[:,2]
acc = np.zeros((n, 3))
sqrt = np.sqrt
for i in prange(npts):
for j in prange(npts):
if (j>i):
x = (posx[i]-posx[j])
y = (posy[i]-posy[j])
z = (posz[i]-posz[j])
rsq = x**2 + y**2 + z**2
req = sqrt(x**2 + y**2 + z**2)
f = -G*mass[i,0]*mass[j,0]/(req + rsoft)**2
fx = f*x**2/rsq
fy = f*y**2/rsq
fz = f*z**2/rsq
acc[i,0] = fx/mass[i] + acc[i,0]
acc[i,1] = fy/mass[i] + acc[i,1]
acc[i,2] = fz/mass[i] + acc[i,2]
acc[j,0] = fx/mass[j] - acc[j,0]
acc[j,1] = fy/mass[j] - acc[j,1]
acc[j,2] = fz/mass[j] - acc[j,2]
return acc
Here is the error:
TypingError Traceback (most recent call last)
:428, in NbodySimulation._update_particles_rk4(self, dt, particles)
426 position = particles.positions # y0[0]
427 velocity = particles.velocities # y0[1], k1[0]
--> 428 acceleration = self._calculate_acceleration_inclass() # k1[1]
430 position2 = position + 0.5*velocity * dt # y1[0]
431 velocity2 = velocity + 0.5*acceleration * dt # y1[1], k2[0]
:381, in NbodySimulation._calculate_acceleration_inclass(self)
377 def _calculate_acceleration_inclass(self):
378 """
379 Calculate the acceleration.
...
<source elided>
acc[i,0] = fx/mass[i] + acc[i,0]
^

Ellipse construction

I would like to construct an ellipse given the major/minor axes (or radii) and two points. I would like the line between the two points to be on the major axis. This just means that I would like the two points to lie on the major axis, and then construct the ellipse around the major axis. I originally constructed the ellipse at the origin and attempted to rotate and translate the ellipse, which didn't work. The unfinished code I have is listed below. How can I go about constructing an ellipse in this manner? Ideally, this would just return a list. Any insights would be greatly appreciated, and if you have any code for this please feel free to share.
import numpy
import matplotlib.pyplot as plt
import random
import math
from math import sin, cos
# Returns theta in [-pi/2, 3pi/2]
def generate_theta(a, b):
u = random.random() / 4.0
theta = numpy.arctan(b/a * numpy.tan(2*numpy.pi*u))
v = random.random()
if v < 0.25:
return theta
elif v < 0.5:
return numpy.pi - theta
elif v < 0.75:
return numpy.pi + theta
else:
return -theta
def radius(a, b, theta):
return a * b / numpy.sqrt((b*numpy.cos(theta))**2 + (a*numpy.sin(theta))**2)
def random_point(a, b, third_point, center=(0, 0)):
angle = None
if a > b:
random_theta = generate_theta(a, b)
max_radius = radius(a, b, random_theta)
random_radius = max_radius * numpy.sqrt(random.random())
f = round(random_radius * numpy.cos(random_theta))
s = round(random_radius * numpy.sin(random_theta))
angle = math.atan2(third_point[1], third_point[0]) - math.atan2(center[1], center[0])
else:
random_theta = generate_theta(b, a)
max_radius = radius(b, a, random_theta)
random_radius = max_radius * numpy.sqrt(random.random())
f = round(random_radius * numpy.cos(random_theta))
s = round(random_radius * numpy.sin(random_theta))
angle = math.atan2(third_point[1], third_point[0]) - math.atan2(center[1], center[0])
lio = rotate(center, (f, s), angle)
lio = (int(lio[0]), int(lio[1]))
return numpy.array([third, ward])
def rotate(origin, point, angle):
#Rotate a point counterclockwise by a given angle around a given origin.
#The angle should be given in radians.
x = origin[0] + cos(angle) * (point[0] - origin[0]) - sin(angle) * (point[1] - origin[1])
y = origin[1] + sin(angle) * (point[0] - origin[0]) + cos(angle) * (point[1] - origin[1])
return (x, y)
#height
a = 95
#length
b = 25
#rand_p = (-random.randint(300, 400), -random.randint(100, 300))
rand_p = (0, 0)
points = numpy.array([random_point(a, b, (100, 100), (-25, 0)) for _ in range(200)])
#rando = rotate((0, 0), right_most_point, angle)
iopoints = []
# t = x[0] - (int(centroid[0]) - 100)
# t2 = x[1] - (int(centroid[1]) - 100)
centroid = numpy.mean(points, axis=0)
print(centroid)
#plt.plot(rando[0], rando[1], 'ro')
plt.plot(rand_p[0], rand_p[1], 'ro')
plt.scatter(points[:,0], points[:,1])
plt.show()
class ELLIPSE:
def __init__(self, a, b, num_points, start, end):
self.a = a
self.b = b
self.num_points = num_points
self.start = start
self.end = end
self.angle_gen = math.atan2(self.end[1]-self.start[1], self.end[0]-self.start[0])
def generate_theta(self, a, b):
u = random.random() / 4.0
theta = np.arctan(self.b/self.a * np.tan(2*np.pi*u))
v = random.random()
if v < 0.25:
return theta
elif v < 0.5:
return np.pi - theta
elif v < 0.75:
return np.pi + theta
else:
return -theta
def radius(self, a, b, theta):
return self.a * self.b / np.sqrt((b*np.cos(theta))**2 + (a*np.sin(theta))**2)
def random_point(self, major_axis, minor_axis, center, qa):
random_theta = self.generate_theta(self.a, self.b)
max_radius = self.radius(self.a, self.b, random_theta)
random_radius = max_radius * np.sqrt(random.random())
f = round(random_radius * np.cos(random_theta))
s = round(random_radius * np.sin(random_theta))
lio = self.rotate((0, 0), (f, s), self.angle_gen)
return (int(lio[0]+center[0]), int(lio[1]+center[1]))
def rotate(self, origin, point, angle):
"""
Rotate a point counterclockwise by a given angle around a given origin.
The angle should be given in radians.
"""
ox, oy = origin
px, py = point
qx = ox + math.cos(angle) * (px - ox) - math.sin(angle) * (py - oy)
qy = oy + math.sin(angle) * (px - ox) + math.cos(angle) * (py - oy)
return qx, qy
def midpoint(self, p1, p2):
return ((p1[0]+p2[0])/2, (p1[1]+p2[1])/2)
def ret_list(self):
points = [self.random_point(self.a, self.b, self.midpoint(self.start, self.end), self.angle_gen) for _ in range(self.num_points)]
return points

Propagated Solution of Lambert Solver Leads to Wrong Orbit

Excuse me for the length of the title please but this is a pretty specific question. I'm currently simulating a launch of a rocket to mars in the 2022 launch window and I noticed that my rocket is a far distance away from Mars, even though it's traveling in the right direction. After simplifying my code to narrow down the problem, I simply plotted the orbits of the Earth and Mars (Using data from NASA's SPICE library) and propagated the position and velocity given to me by the lambert solver I implemented (Universal variables) to plot the final orbit of the rocket.
I'm only letting the Sun's gravity effect the rocket, not the Earth or Mars, to minimize my problem space. Yet even though I've simplified my problem so far, the intersection between Mars' and my rocket's orbits happens well before the time of flight has been simulated all the way, and the minimum distance between the two bodies is more than a million kilometers at all times.
That being said, something must be wrong but I cannot find the problem. I've made sure the lambert solver code I copied is correct by comparing it to Dario Izzo's method and both gave the same results. Furthermore, I've also checked that my orbit propagator works by propagating Mars' and the Earth's orbits and comparing those ellipses to the data from SPICE.
In conclusion, I assume this must be a stupid little mistake I made somewhere, but cannot find because I lack experience in this field. Thank you for any help! :)
This is the JupyterLab notebook I used:
import numpy as np
import matplotlib.pyplot as plt
import json
import math
import spiceypy as spice
# Physics
G = 6.6741e-11
class Entity:
def __init__(self, x, v, m, do_gravity):
self.x = x
self.v = v
self.a = np.array([0,0,0])
self.m = m
self.do_gravity = do_gravity
def apply_force(self, f):
self.a = np.add(self.a, f / self.m);
def step(self, dt):
self.v = np.add(self.v, self.a * dt)
self.x = np.add(self.x, self.v * dt)
self.a = np.array([0,0,0])
class StaticEntity(Entity):
def __init__(self, body, t, do_gravity):
super().__init__(self.get_state(body, t)[:3], self.get_state(body, t)[3:], self.get_mass(body), do_gravity)
self.body = body
self.t = t
def step(self, dt):
self.t += dt
state = self.get_state(self.body, self.t)
self.x = state[:3]
self.v = state[3:]
#classmethod
def get_state(self, body, t):
[state, lt] = spice.spkezr(body, t, "J2000", "NONE", "SSB")
return state * 1000
#classmethod
def get_mass(self, body):
[dim, gm] = spice.bodvrd(body, "GM", 1)
return gm * 1e9 / G
def get_position(self, t):
return self.get_state(self.body, t)[:3]
def get_velocity(self, t):
return self.get_state(self.body, t)[3:]
class Propagator:
def __init__(self, entities):
self.entities = entities
def step(self, dt):
for e1 in self.entities:
for e2 in self.entities:
if (e1 is e2) or (not e1.do_gravity) or isinstance(e2, StaticEntity):
continue
diff = np.subtract(e1.x, e2.x)
fg = G * e1.m * e2.m / np.dot(diff, diff)
force = fg * diff / np.linalg.norm(diff)
e2.apply_force(force)
for entity in self.entities:
entity.step(dt)
# Lambert solver
def C2(psi):
if psi >= 0.0:
sp = math.sqrt(psi)
return (1 - math.cos(sp)) / psi
else:
sp = math.sqrt(-psi)
return (1 - math.cosh(sp)) / psi
def C3(psi):
if psi >= 0.0:
sp = math.sqrt(psi)
return (sp - math.sin(sp)) / (psi * sp)
else:
sp = math.sqrt(-psi)
return (sp - math.sinh(sp)) / (psi * sp)
def lambert_solve(r1, r2, tof, mu, iterations, tolerance):
R1 = np.linalg.norm(r1)
R2 = np.linalg.norm(r2)
cos_a = np.dot(r1, r2) / (R1 * R2)
A = math.sqrt(R1 * R2 * (1.0 + cos_a))
sqrt_mu = math.sqrt(mu)
if A == 0.0:
return None
psi = 0.0
psi_lower = -4.0 * math.pi * math.pi
psi_upper = 4.0 * math.pi * math.pi
c2 = 1.0 / 2.0
c3 = 1.0 / 6.0
for i in range(iterations):
B = R1 + R2 + A * (psi * c3 - 1.0) / math.sqrt(c2)
if A > 0.0 and B < 0.0:
psi_lower += math.pi
B = -B
chi = math.sqrt(B / c2)
chi3 = chi * chi * chi
tof_new = (chi3 * c3 + A * math.sqrt(B)) / sqrt_mu
if math.fabs(tof_new - tof) < tolerance:
f = 1.0 - B / R1
g = A * math.sqrt(B / mu)
g_dot = 1.0 - B / R2
v1 = (r2 - f * r1) / g
v2 = (g_dot * r2 - r1) / g
return (v1, v2)
if tof_new <= tof:
psi_lower = psi
else:
psi_upper = psi
psi = (psi_lower + psi_upper) * 0.5
c2 = C2(psi)
c3 = C3(psi)
return None
# Set up solar system
spice.furnsh('solar_system.tm')
inject_time = spice.str2et('2022 Sep 28 00:00:00')
exit_time = spice.str2et('2023 Jun 1 00:00:00')
sun = StaticEntity("Sun", inject_time, True)
earth = StaticEntity("Earth", inject_time, False)
mars = StaticEntity("Mars Barycenter", inject_time, False)
(v1, v2) = lambert_solve(earth.get_position(inject_time), mars.get_position(exit_time), exit_time - inject_time, G * sun.m, 1000, 1e-4)
rocket = Entity(earth.x, v1, 100000, False)
propagator = Propagator([sun, earth, mars, rocket])
# Generate data
earth_pos = [[], [], []]
mars_pos = [[], [], []]
rocket_pos = [[], [], []]
t = inject_time
dt = 3600 # seconds
while t < exit_time:
propagator.step(dt)
earth_pos[0].append(earth.x[0])
earth_pos[1].append(earth.x[1])
earth_pos[2].append(earth.x[2])
mars_pos[0].append(mars.x[0])
mars_pos[1].append(mars.x[1])
mars_pos[2].append(mars.x[2])
rocket_pos[0].append(rocket.x[0])
rocket_pos[1].append(rocket.x[1])
rocket_pos[2].append(rocket.x[2])
t += dt
# Plot data
plt.figure()
plt.title('Transfer orbit')
plt.xlabel('x-axis')
plt.ylabel('y-axis')
plt.plot(earth_pos[0], earth_pos[1], color='blue')
plt.plot(mars_pos[0], mars_pos[1], color='orange')
plt.plot(rocket_pos[0], rocket_pos[1], color='green')
EDIT:
I recently remodeled my code so that it uses orbit class to represent the entities. This actually gave me acceptable results, even though the code is, in theory, not doing anything differently (as far as I can tell; obviously something must be different)
def norm(a):
return np.dot(a, a)**0.5
def fabs(a):
return -a if a < 0 else a
def newton_raphson(f, f_dot, x0, n):
res = x0
for i in range(n):
res = res - f(res) / f_dot(res)
return res
def get_ephemeris(body, time):
state, _ = sp.spkezr(body, time, "J2000", "NONE", "SSB")
return np.array(state[:3]) * ap.units.km, np.array(state[3:]) * ap.units.km / ap.units.s
def get_mu(body):
_, mu = sp.bodvrd(body, "GM", 1)
return mu * ap.units.km**3 / ap.units.s**2
class orbit:
def __init__(self, position, velocity, mu):
self.position = position
self.velocity = velocity
self.mu = mu
#staticmethod
def from_body(name, center, time):
return static_orbit(name, center, time)
def get_ephemerides(self, t, dt):
time = 0
positions = []
velocities = []
#M = self.M
position = self.position
velocity = self.velocity
delta_t = dt * ap.units.s
t1 = t * ap.units.s
while time < t1:
g = self.mu / np.dot(position, position)
g_vec = g * -position / norm(position)
velocity = np.add(velocity, g_vec * delta_t)
position = np.add(position, velocity * delta_t)
positions.append(position)
velocities.append(velocity)
time = time + delta_t
return positions, velocities
class static_orbit(orbit):
def __init__(self, name, center, time):
p, v = get_ephemeris(name, time)
pc, vc = get_ephemeris(center, time)
super().__init__(p - pc, v - vc, get_mu(center))
self.name = name
self.center = center
self.time = time
def get_ephemerides(self, t, dt):
time = 0
positions = []
velocities = []
while time < t:
p, v = get_ephemeris(self.name, time + self.time)
pc, vc = get_ephemeris(self.center, time + self.time)
positions.append(p - pc)
velocities.append(v - vc)
time += dt
return positions, velocities
sp.furnsh('solar_system.tm')
t1 = sp.str2et('2022 Sep 28 00:00:00')
t2 = sp.str2et('2023 Jun 10 00:00:00')
eo = orbit.from_body("Earth", "Sun", t1)
mo = orbit.from_body("Mars Barycenter", "Sun", t1)
earth_x, earth_v = eo.get_ephemerides(t2 - t1, 3600)
mars_x, mars_v = mo.get_ephemerides(t2 - t1, 3600)
l = lambert(earth_x[0], mars_x[-1], t2 - t1, get_mu("Sun"), 1000, 1e-6)
ro = orbit(earth_x[0], l.v1, get_mu("Sun"))
rocket_x, rocket_v = ro.get_ephemerides(t2 - t1, 60)
earth_x = np.array(earth_x)
mars_x = np.array(mars_x)
rocket_x = np.array(rocket_x)
fig = go.Figure()
fig.add_trace(go.Scatter3d(x=earth_x[:,0], y=earth_x[:,1], z=earth_x[:,2], marker_size=1, marker_color='blue'))
fig.add_trace(go.Scatter3d(x=mars_x[:,0], y=mars_x[:,1], z=mars_x[:,2], marker_size=1, marker_color='orange'))
fig.add_trace(go.Scatter3d(x=rocket_x[:,0], y=rocket_x[:,1], z=rocket_x[:,2], marker_size=1, marker_color='green'))
fig.show()
This method generated following plot:
Also, before this is mentioned again, I have varied my integration step size and lambert solver tolerance to no avail, the result was qualitatively different.
So, I managed to figure out what the problem was after much head-scratching. I was simply not taking into account that the Sun is not located at (0,0,0) in my coordinate system. I thought this was negligible, but that is what made the difference. In the end, I simply passed the difference between the Earth and Mars's and the Sun's position vectors and passed those into the Lambert solver. This finally gave me the desired results.
The reason that the error ended up being so "small" (It didn't seem like an obvious bug at first) was because my coordinates are centered at the solar system barycenter which is a few million kilometers away from the Sun, as one would expect.
Thanks for the comments!

I cannot get my 3 DOF bicycle model to work

I'm trying to simulate a vehicle using a dynamic bicycle model but I cannot seem to get it working. If I set a constant steering angle the lateral velocity grows exponentially and creates impossible results.
a = 0.34284
b = 0.40716
m = 155
I = 37.29
def f_DynBkMdl(x, y, delta, theta, dt, states):
dtheta = states[0]
vlat = states[1]
vlon = states[2]
if delta > math.radians(180):
delta = delta - math.radians(360)
if delta<0:
j = 1
else:
j = 0
if dtheta<0:
q = 1
else:
q = 0
dtheta = abs(dtheta)
delta = abs(delta)
sf = delta - (a*dtheta)/vlon
ff = 30.77*math.degrees(sf)
pf = 0
sr = (b*dtheta)/vlon
fr = 30.77*math.degrees(sr)
pr = 0
if j == 1:
fr = -fr
ff = -ff
if q == 1:
dtheta = -dtheta
theta = theta%math.radians(360)
ddtheta = (a*pf*delta + a*ff - b*fr)/I
dvlat = (pf*delta + ff + fr)/m - vlon*dtheta
dvlon = (pf + pr - ff*delta)/m - vlat*dtheta
dx = -vlat*np.sin(theta) + vlon*np.cos(theta)
dy = vlat*np.cos(theta) + vlon*np.sin(theta)
theta = theta + dtheta*dt + (1/2)*ddtheta*dt**2
dtheta = dtheta + ddtheta*dt
vlat = vlat + dvlat*dt
vlon = vlon + dvlon*dt
vabs = np.sqrt(vlat**2 + vlon**2)
x = x + dx*dt
y = y + dy*dt
states = [dtheta, vlat, vlon]
array = np.array([x, y, theta, vabs, states])
return array
With a and b being the distance between the front and rear axle to the vehicle's centre of gravity, m being the mass and I the inertia. x and y are the global position and theta is the heading with delta being the steering angle. I got these equations from this document. Please note I used a simplified tyre model to calculate the lateral forces and I assumed infinite friction so the friction circle was not needed.
Is there something I am missing to make this work?

What is wrong with my Implementation of 4th Order runge kutta in python for nonholonomic constraints?

I am trying to implement 4th order Runge Kutta for nonholonomic motion for car-like robots.
I don't know what I am doing wrong,essentially I am passing +-Pi/4 to calculate hard left and right turns to get different trajectories.
But no matter if I pass +pi/4 or -pi/4 to it, I get the same answer.
I cannot figure out what I am doing wrong.
The constraint equations that I am using are:
thetadot = (s/L)*tan(phi)
xdot = s*cos(theta)
ydot = s*sin(theta)
Where s is the speed and L is the length of the car like robot.
#! /usr/bin/env python
import sys, random, math, pygame
from pygame.locals import *
from math import sqrt,cos,sin,atan2,tan
import numpy as np
import matplotlib.pyplot as plt
XDIM = 640
YDIM = 480
WINSIZE = [XDIM, YDIM]
PHI = 45
s = 0.5
white = 255, 240, 200
black = 20, 20, 40
red = 255, 0, 0
green = 0, 255, 0
blue = 0, 0, 255
cyan = 0,255,255
pygame.init()
screen = pygame.display.set_mode(WINSIZE)
X = XDIM/2
Y = YDIM/2
THETA = 45
def main():
nodes = []
nodes.append(Node(XDIM/2.0,YDIM/2.0,0.0))
plt.plot(runge_kutta(nodes[0], (3.14/4))) #Hard Left turn
plt.plot(runge_kutta(nodes[0], 0)) #Straight ahead
plt.plot(runge_kutta(nodes[0], -(3.14/4))) #Hard Right turn
plt.show()
class Node:
x = 0
y = 0
theta = 0
distance=0
parent=None
def __init__(self,xcoord, ycoord, theta):
self.x = xcoord
self.y = ycoord
self.theta = theta
def rk4(f, x, y, n):
x0 = y0 = 0
vx = [0]*(n + 1)
vy = [0]*(n + 1)
h = 0.8
vx[0] = x = x0
vy[0] = y = y0
for i in range(1, n + 1):
k1 = h*f(x, y)
k2 = h*f(x + 0.5*h, y + 0.5*k1)
k3 = h*f(x + 0.5*h, y + 0.5*k2)
k4 = h*f(x + h, y + k3)
vx[i] = x = x0 + i*h
vy[i] = y = y + (k1 + k2 + k2 + k3 + k3 + k4)/6
print "1"
print vy
return vy
def fun1(x,y):
x = (0.5/2)*tan(y)
print "2"
print x
return x
def fun2(x,y):
x = 0.5*cos(y)
print "3"
print x
return x
def fun3(x,y):
x = 0.5*sin(y)
print "4"
print x
return x
def runge_kutta(p, phi):
x1 = p.x
y1 = p.y
theta1 = p.theta
fi = phi
for i in range(0,5):
x2 = rk4(fun2, x1, theta1, 5)
y2 = rk4(fun3, y1, theta1, 5)
theta2 = rk4(fun1, theta1 ,fi, 5)
theta1 = theta2
print "5"
print zip(x2,y2)
return zip(x2,y2)
# if python says run, then we should run
if __name__ == '__main__':
main()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
I can't really say much about the algorithm, but the way you set up our rk4 function, the x, and y arguments will never have any effect:
def rk4(f, x, y, n):
x0 = y0 = 0 # x0 and y0 will both be 0 after this
vx = [0]*(n + 1)
vy = [0]*(n + 1)
h = 0.8
vx[0] = x = x0 # now x will be 0
vy[0] = y = y0 # and y will be 0 too
...
The rest of the function will use x=0 and y=0 in any case.
Also, I don't know if that's intentional, but the other functions fun1, fun2 and fun3 don't ever use the parameter passed as x, they only use y. They change x locally, but that won't reflect outside the function.

Categories

Resources