Meshgrid/lattice/matrix of an ellipse area - python

I'm likely using the wrong terms but seeking some help.
I would like to generate an array of x,y values for a grid that sits within the perimeter of an ellipse shape.
There is code here: http://people.sc.fsu.edu/~jburkardt/c_src/ellipse_grid/ellipse_grid.html to accomplish this in Python.
However, for my purpose the ellipses have been rotated to a certain degree. The current equation does not account for this and need some help to account for this transformation, unsure how to change the code to do this?
I've been looking into np.meshrid function as well, so if there are better ways to do this, please say.
Many thanks.

Given an ellipse in the Euclidean plane in its most general form as quadratic curve in the form
f(x,y) = a x^2 + 2b x y + c y^2 + 2d x + 2f y + g,
one can compute the center (x0,y0) by
((cd-bf)/(b^2-ac), (af-bd)/(b^2-ac))
(see equations 19 and 20 at Ellipse on MathWorld). The length of the major axis a_m can be computed by equation 21 on the same page.
Now it suffices to find all grid points (x,y) inside the circle with center (x0,y0) and radius a_m with
sign(f(x,y)) = sign(f(x0,y0)).

To generate lattice points inside ellipse, we have to know where horizontal line intersects that ellipse.
Equation of zero-centered ellipse, rotated by angle Theta:
x = a * Cos(t) * Cos(theta) - b * Sin(t) * Sin(theta)
y = a * Cos(t) * Sin(theta) + b * Sin(t) * Cos(theta)
To simplify calculations, we can introduce pseudoangle Fi and magnitude M (constants for given ellipse)
Fi = atan2(a * Sin(theta), b * Cos(theta))
M = Sqrt((a * Sin(theta))^2 + (b * Cos(theta))^2)
so
y = M * Sin(Fi) * Cos(t) + M * Cos(Fi) * Sin(t)
y/M = Sin(Fi) * Cos(t) + Cos(Fi) * Sin(t)
y/M = Sin(Fi + t)
and solution for given horizontal line at position y are
Fi + t = ArcSin( y / M)
Fi + t = Pi - ArcSin( y / M)
t1 = ArcSin( y / M) - Fi //note two values
t2 = Pi - ArcSin( y / M) - Fi
Substitute both values of t in the first equation and get values of X for given Y, and generate one lattice point sequence
To get top and bottom coordinates, differentiate y
y' = M * Cos(Fi + t) = 0
th = Pi/2 - Fi
tl = -Pi/2 - Fi
find corresponding y's and use them as starting and ending Y-coordinates for lines.
import math
def ellipselattice(cx, cy, a, b, theta):
res = []
at = a * math.sin(theta)
bt = b * math.cos(theta)
Fi = math.atan2(at, bt)
M = math.hypot(at, bt)
ta = math.pi/2 - Fi
tb = -math.pi/2 - Fi
y0 = at * math.cos(ta) + bt *math.sin(ta)
y1 = at * math.cos(tb) + bt *math.sin(tb)
y0, y1 = math.ceil(cy + min(y0, y1)), math.floor(cy + max(y0, y1))
for y in range(y0, y1+1):
t1 = math.asin(y / M) - Fi
t2 = math.pi - math.asin(y / M) - Fi
x1 = a * math.cos(t1) * math.cos(theta) - b* math.sin(t1) * math.sin(theta)
x2 = a * math.cos(t2) * math.cos(theta) - b* math.sin(t2) * math.sin(theta)
x1, x2 = math.ceil(cx + min(x1, x2)), math.floor(cx + max(x1, x2))
line = [(x, y) for x in range(x1, x2 + 1)]
res.append(line)
return res
print(ellipselattice(0, 0, 4, 3, math.pi / 4))

Related

How to calculate the path of a particle subject to a vortex?

I'm trying to draw a path of a particle starting at (x0,y0) subject to a vortex located at (xv,yv). This is inspired by Lorena Barba's AeroPython. As stated in the lesson, the motion should be concentric circles about a given point.
I first calculate the velocity at point (x0,y0).
def get_velocity_vortex(strength, xv, yv, x0, y0):
u = +strength / (2 * np.pi) * (y0 - yv) / ((x0 - xv)**2 + (y0 - yv)**2)
v = -strength / (2 * np.pi) * (x0 - xv) / ((x0 - xv)**2 + (y0 - yv)**2)
return u, v
I then try to calculate the suceeding positions as follows.
def get_next_pos(x0,y0,u,v,dt):
x_next = x0 + u * dt
y_next = y0 + v * dt
return x_next, y_next
A complete example is as shown.
import numpy as np
import matplotlib.pyplot as plt
strength_vortex = 5.0
# coordinates of vortex
x_vortex = 0.0
y_vortex = 0.0
# coordinates of initial point
x0 = 0.1
y0 = 0.0
dt = 1e-3 # timestep
nt = 150 # number of iterations
# empty arrays for the positions
X = np.zeros(nt)
Y = np.zeros(nt)
# initial positions
X[0], Y[0] = x0, y0
# calculate the path
for i in range(1,nt):
u, v = get_velocity_vortex(
strength_vortex,
x_vortex,
y_vortex,
X[i-1],
Y[i-1]
)
X[i],Y[i] = get_next_pos(X[i-1], Y[i-1], u, v, dt)
# plot the path
plt.scatter(
x_vortex,
y_vortex,
color="red"
)
plt.scatter(
X,Y,
)
plt.xlim(-0.2,0.2)
plt.ylim(-0.2,0.2)
plt.grid()
However, my output does not result in a circle. I suspect that this is due to my get_next_pos function. How can I correct the path? Any help is appreciated. Thanks in advance.

Fitting curve with conditions

I'm trying to simulate an exoplanet transit and to determine its orbital characteristics with curve fitting. However, the intersection area between two circles needs to distinguish two cases: if the center of the smallest circle is in the biggest or not. This is a problem for scipy with the function curve_fit, calling an array in my function cacl_aire. The function transit simulates the smallest disc's evolution with time.
Here's my code:
import numpy as np
from matplotlib import pyplot as plt
from scipy.optimize import curve_fit
import xlrd
dt = 0.1
Vx = 0.08
Vy = 0
X0 = -5
Y0 = 0
R = 2
r = 0.7
X = X0
Y = Y0
doc = xlrd.open_workbook("transit data.xlsx")
feuille_1 = doc.sheet_by_index(0)
mag = [feuille_1.cell_value(rowx=k, colx=4) for k in range(115)]
T = [feuille_1.cell_value(rowx=k, colx=3) for k in range(115)]
def calc_aire(r, x, y):
D2 = x * x + y * y
if D2 >= (r + R)**2:
return 0
d = (r**2 - R**2 + D2) / (2 * (D2**0.5))
d2 = D2**0.5 - d
if abs(d) >= r:
return min([r * r * np.pi, R * R * np.pi])
H = (r * r - d * d)**0.5
As = np.arccos(d / r) * r * r - d * H
As2 = R * R * np.arccos(d2 / R) - d2 * H
return As + As2
def transit(t, r, X0, Y0, Vx, Vy):
return -calc_aire(r, X0 + Vx * t, Y0 + Vy * t)
best_vals = curve_fit(transit, T, mag)[0]
print('best_vals: {}'.format(best_vals))
plt.figure()
plt.plot(T, mag)
plt.draw()
I have the following error :
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all() with the line 28 :
if D2 >= (r + R)**2:
Here is my database:
https://drive.google.com/file/d/1SP12rrHGjjpHfKBQ0l3nVMJDIRCPlkuf/view?usp=sharing
I don't see any trick to solve my problem.

Rotating a point to be parallel to a direction vector delivers wrong vector. Why?

I would need to rotate a vector defined by its start at (0,0,0) and ending on a given point in order to be parallel to a given direction vector. For this reason, I have based my code on the following answer:
I guess by "parallel" you intend "pointing in the same direction."
We may as well rotate (d,e,f)
in the plane spanned by v=(d,e,f) and w=(a,b,c). The axis of rotation >would be around a vector perpendicular to this plane such as a=v×w, which >you'd normalize to a unit length vector u. Finally, we'd need the angle of >rotation θ, which can be retrieved from v⋅w=∥v∥∥w∥cos(θ) by solving for θ.
Then following the scheme for using quaternions to perform rotations, the >quaternion you're looking for is q=cos(θ/2)+usin(θ/2). The transformation >x→qxq−1 moves v to point in the same direction as w.
https://math.stackexchange.com/questions/734707/how-to-rotate-a-3d-vector-to-be-parallel-to-another-3d-vector-using-quaternions/735136#735136
I have managed to implement this, though by visualizing the results, it is clear that they are not parallel.
## Example code
def get_unit_vector(vector):
return vector / np.linalg.norm(vector)
def get_angle_between_vectors(vector1, vector2):
unit_vector_1 = get_unit_vector(vector1)
unit_vector_2 = get_unit_vector(vector2)
angle = np.arccos(np.dot(unit_vector_2, unit_vector_1))
return min(angle, np.pi-angle)
def rotate_point(point, direction):
# get the axis and normalize it
axis = np.cross(point, direction)
norm_axis = get_unit_vector(axis)
angle = get_angle_between_vectors(point, direction)
q = np.cos((angle/2))+norm_axis*np.sin(angle/2)
q = get_unit_vector(q)
new_point = (q*point)*np.conj(q)
new_angle = get_angle_between_vectors(new_point, direction)
if new_angle != 0:
q = np.cos((np.pi-angle / 2)) + norm_axis * np.sin(np.pi-angle / 2)
new_point = (q * point) * (np.conj(q))
return new_point
Here the results:
As stated, I would expect both orange and green vectors to be parallels but they are not. Are there steps that I am missing?
So after some modification I managed to get the desired output. I got the multiplication function from here.
def get_unit_vector(vector):
return vector / np.linalg.norm(vector)
def get_angle_between_vectors(vector1, vector2):
unit_vector_1 = get_unit_vector(vector1)
unit_vector_2 = get_unit_vector(vector2)
angle = np.arccos(np.dot(unit_vector_2, unit_vector_1))
return min(angle, np.pi-angle)
def quaternion_conjugate(q):
first = q[0]
q_prime = -1*q
q_prime[0] = first
return q_prime
def quaternion_multiply(quaternion1, quaternion0):
w0, x0, y0, z0 = quaternion0
w1, x1, y1, z1 = quaternion1
return np.array([-x1 * x0 - y1 * y0 - z1 * z0 + w1 * w0,
x1 * w0 + y1 * z0 - z1 * y0 + w1 * x0,
-x1 * z0 + y1 * w0 + z1 * x0 + w1 * y0,
x1 * y0 - y1 * x0 + z1 * w0 + w1 * z0], dtype=np.float64)
def rotate_point(point, direction):
axis = np.cross(point, direction)
norm_axis = get_unit_vector(axis)
angle = -get_angle_between_vectors(point, direction)
q = np.array([np.cos((angle/2)),*(norm_axis*np.sin(angle/2))])
point_as_q = np.array([0,*point])
q3 = quaternion_multiply(q, point_as_q)
q3 = quaternion_multiply(q3, quaternion_conjugate(q))
new_point = q3[1:]
return new_point

How to get the x,y coordinates of a offset spline from a x,y list of points and offset distance

I need to make an offset parallel enclosure of an airfoil profile curve, but I cant figure out how to make all the points be equidistant to the points on the primary profile curve at desired distance.
this is my example airfoil profile
this is my best and not good approach
EDIT #Patrick Solution for distance 0.2
You'll have to special-case slopes of infinity/zero, but the basic approach is to use interpolation to calculate the slope at a point, and then find the perpendicular slope, and then calculate the point at that distance.
I have modified the example from here to add a second graph. It works with the data file you provided, but you might need to change the sign calculation for a different envelope.
EDIT As per your comments about wanting the envelope to be continuous, I have added a cheesy semicircle at the end that gets really close to doing this for you. Essentially, when creating the envelope, the rounder and more convex you can make it, the better it will work. Also, you need to overlap the beginning and the end, or you'll have a gap.
Also, it could almost certainly be made more efficient -- I am not a numpy expert by any means, so this is just pure Python.
def offset(coordinates, distance):
coordinates = iter(coordinates)
x1, y1 = coordinates.next()
z = distance
points = []
for x2, y2 in coordinates:
# tangential slope approximation
try:
slope = (y2 - y1) / (x2 - x1)
# perpendicular slope
pslope = -1/slope # (might be 1/slope depending on direction of travel)
except ZeroDivisionError:
continue
mid_x = (x1 + x2) / 2
mid_y = (y1 + y2) / 2
sign = ((pslope > 0) == (x1 > x2)) * 2 - 1
# if z is the distance to your parallel curve,
# then your delta-x and delta-y calculations are:
# z**2 = x**2 + y**2
# y = pslope * x
# z**2 = x**2 + (pslope * x)**2
# z**2 = x**2 + pslope**2 * x**2
# z**2 = (1 + pslope**2) * x**2
# z**2 / (1 + pslope**2) = x**2
# z / (1 + pslope**2)**0.5 = x
delta_x = sign * z / ((1 + pslope**2)**0.5)
delta_y = pslope * delta_x
points.append((mid_x + delta_x, mid_y + delta_y))
x1, y1 = x2, y2
return points
def add_semicircle(x_origin, y_origin, radius, num_x = 50):
points = []
for index in range(num_x):
x = radius * index / num_x
y = (radius ** 2 - x ** 2) ** 0.5
points.append((x, -y))
points += [(x, -y) for x, y in reversed(points)]
return [(x + x_origin, y + y_origin) for x, y in points]
def round_data(data):
# Add infinitesimal rounding of the envelope
assert data[-1] == data[0]
x0, y0 = data[0]
x1, y1 = data[1]
xe, ye = data[-2]
x = x0 - (x0 - x1) * .01
y = y0 - (y0 - y1) * .01
yn = (x - xe) / (x0 - xe) * (y0 - ye) + ye
data[0] = x, y
data[-1] = x, yn
data.extend(add_semicircle(x, (y + yn) / 2, abs((y - yn) / 2)))
del data[-18:]
from pylab import *
with open('ah79100c.dat', 'rb') as f:
f.next()
data = [[float(x) for x in line.split()] for line in f if line.strip()]
t = [x[0] for x in data]
s = [x[1] for x in data]
round_data(data)
parallel = offset(data, 0.1)
t2 = [x[0] for x in parallel]
s2 = [x[1] for x in parallel]
plot(t, s, 'g', t2, s2, 'b', lw=1)
title('Wing with envelope')
grid(True)
axes().set_aspect('equal', 'datalim')
savefig("test.png")
show()
If you are willing (and able) to install a third-party tool, I'd highly recommend the Shapely module. Here's a small sample that offsets both inward and outward:
from StringIO import StringIO
import matplotlib.pyplot as plt
import numpy as np
import requests
import shapely.geometry as shp
# Read the points
AFURL = 'http://m-selig.ae.illinois.edu/ads/coord_seligFmt/ah79100c.dat'
afpts = np.loadtxt(StringIO(requests.get(AFURL).content), skiprows=1)
# Create a Polygon from the nx2 array in `afpts`
afpoly = shp.Polygon(afpts)
# Create offset airfoils, both inward and outward
poffafpoly = afpoly.buffer(0.03) # Outward offset
noffafpoly = afpoly.buffer(-0.03) # Inward offset
# Turn polygon points into numpy arrays for plotting
afpolypts = np.array(afpoly.exterior)
poffafpolypts = np.array(poffafpoly.exterior)
noffafpolypts = np.array(noffafpoly.exterior)
# Plot points
plt.plot(*afpolypts.T, color='black')
plt.plot(*poffafpolypts.T, color='red')
plt.plot(*noffafpolypts.T, color='green')
plt.axis('equal')
plt.show()
And here's the output; notice how the 'bowties' (self-intersections) on the inward offset are automatically removed:

Adding tuples of lists to a list in python

I don't understand the syntax of adding tuples whose elements are also elements of a list to another list that encompasses all of the information.
I'm trying to create a trajectory list that contains tuples of flight data of a projectile during flight. I want to use tuples so that I can see all of the information for each moment in time.
import random
import math
gg = -9.81 # gravity m/s**2
tt = 10 **-9 # seconds
wind = random.randint(-10,10) # m/s
#position
x=random.randint(0,100) # m/s
#projectile
v0 = float(raw_input('Enter the initial velocity (m/s) -> '));
theta = float(raw_input('Enter the initial launch angle of projectile (degrees) -> '));
theta *= (180/3.14159265359)
xx = [x]
yy = [.000000000000000000001]
dz =[v0]
time = [0];
data = ( time, xx, yy, dz)
traj = [data]
while yy[-1] >0:
traj.append ( math.sqrt( (traj[-1][3][-1] * math.sin(theta) + gg * tt)** 2+ (traj[-1][4] * math.cos(theta) -wind) ** 2 )) # velocity
traj.append ( traj[-1][2][-1] + dz[-1] * math.sin(theta) * tt + .5* gg * tt) # y position
traj.append ( traj[-1][1][-1] * dz[-1] * math.cos(theta) - wind * tt) # x position
traj.append ( traj[-1][0][-1] + tt) # time
print traj
Edit:
I would input integers for the initial angle and velocity (i.e.-45,45). Expected outputs would be a list of tuples containing four elements corresponding to the time, x coordinate, y coordinate, and velocity, respectively. Currently, I'm receiving a tuple index out of range error.
Where you have
traj[-1][4]
in your first traj.append line, traj[-1] is data, and data is only four elements long, so the last item is at index 3.
The problem with your code is that you append data the values you compute in the while loop to the traj list. This will not update the lists xx, yy, time or dz.
You could modify the code in the following way
while yy[-1] > 0:
dz_next = math.sqrt( (yy[-1] * math.sin(theta) + gg * tt)** 2+(dz[-1] * math.cos(theta) -wind)** 2)
yy_next = yy[-1] + dz[-1] * math.sin(theta) * tt + .5* gg * tt
xx_next = xx[-1] * dz[-1] * math.cos(theta) - wind * tt
dz.append(dz_next) # velocity
yy.append(yy_next) # y position
xx.append(xx_next) # x position
time.append(time[-1] + tt) # time
I think a better way would be the following
data = ( 0, x, 1e-20, v0) # initial data
traj = [data]
while True:
t, x, y, v = traj[-1]
if y < 0:
break
traj.append((t + tt, # time
x * v * math.cos(theta) - wind * tt, # x position
y + v * math.sin(theta) * tt + .5* gg * tt, # y position
(y* math.sin(theta) + gg * tt)** 2 + (v*math.cos(theta) -wind) ** 2) # velocity
)
print traj
t_traj, x_traj, y_traj, z_traj = zip(*traj)

Categories