I'm using streamplot in order to plot stress trajectories around an open circle. I do not want the stress trajectories to be analyzed inside the radius of the circle for two reasons: (1) The stresses will not propagate through the air as they would through the medium surrounding the hole, and (2) The math doesn't allow for it. I have been messing around with the idea of a mask but I haven't been able to get it to work. There might be a better way. Does anyone know how I can plot these trajectories without them plotting inside the radius of the hole? I effectively need some sort of command to tell the streamplot to stop whenever it gets to the outer radius of the hole, but then also know where to pick back up again. The first bit of code below is just the math used to derive the directions of the stress trajectories. I included this for reference. Following this I plot the trajectories.
import numpy as np
import matplotlib.pyplot as plt
from pylab import *
def stress_trajectory_cartesian(X,Y,chi,F,a):
# r is the radius out from the center of the hole at which we want to know the stress
# Theta is the angle from reference at which we want to know the stress
# a is the radius of the hole
r = np.sqrt(np.power(X,2)+np.power(Y,2))*1.0
c = (1.0*a)/(1.0*r)
theta = np.arctan2(Y,X)
A = 0.5*(1 - c**2. + (1 - 4*c**2. + 3*c**4.)*np.cos(2*theta))
B = 0.5*(1 - c**2. - (1 - 4*c**2. + 3*c**4.)*np.cos(2*theta))
C = 0.5*(1 + c**2. - (1 + 3*c**4.)*np.cos(2*theta))
D = 0.5*(1 + c**2. + (1+ 3*c**4.)*np.cos(2*theta))
E = 0.5*((1 + 2*c**2. - 3*c**4.)*np.sin(2*theta))
tau_r = 1.0*F*c**2. + (A-1.0*chi*B) # Radial stress
tau_theta = -1.*F*c**2. + (C - 1.0*chi*D) # Tangential stress
tau_r_theta = (-1 - 1.0*chi)*E # Shear stress
tau_xx = .5*tau_r*(np.cos(2*theta)+1) -1.0*tau_r_theta*np.sin(2*theta) + .5*(1-np.cos(2*theta))*tau_theta
tau_xy = .5*np.sin(2*theta)*(tau_r - tau_theta) + 1.0*tau_r_theta*np.cos(2*theta)
tau_yy = .5*(1-np.cos(2*theta))*tau_r + 1.0*tau_r_theta*np.sin(2*theta) + .5*(np.cos(2*theta)+1)*tau_theta
tan_2B = (2.*tau_xy)/(1.0*tau_xx - 1.0*tau_yy)
beta1 = .5*np.arctan(tan_2B)
beta2 = .5*np.arctan(tan_2B) + np.pi/2.
return beta1, beta2
# Functions to plot beta as a vector field in the Cartesian plane
def stress_beta1_cartesian(X,Y,chi,F,a):
return stress_trajectory_cartesian(X,Y,chi,F,a)[0]
def stress_beta2_cartesian(X,Y,chi,F,a):
return stress_trajectory_cartesian(X,Y,chi,F,a)[1]
#Used to return the directions of the betas
def to_unit_vector_x(angle):
return np.cos(angle)
def to_unit_vector_y(angle):
return np.sin(angle)
The code below plots the stress trajectories:
# Note that R_min is taken as the radius of the hole here
# Using R_min for a in these functions under the assumption that we don't want to analyze stresses across the hole
def plot_stresses_cartesian(F,chi,R_min):
Y_grid, X_grid = np.mgrid[-5:5:100j, -5:5:100j]
R_grid = np.sqrt(X_grid**2. + Y_grid**2.)
cart_betas1 = stress_beta1_cartesian(X_grid,Y_grid,chi,F,R_min)
beta_X1s = to_unit_vector_x(cart_betas1)
beta_Y1s = to_unit_vector_y(cart_betas1)
beta_X1s[R_grid<1] = np.nan
beta_Y1s[R_grid<1] = np.nan
cart_betas2 = stress_beta2_cartesian(X_grid,Y_grid,chi,F,R_min)
beta_X2s = to_unit_vector_x(cart_betas2)
beta_Y2s = to_unit_vector_y(cart_betas2)
beta_X2s[R_grid<1] = np.nan
beta_Y2s[R_grid<1] = np.nan
fig = plt.figure(figsize=(5,5))
#streamplot
ax=fig.add_subplot(111)
ax.set_title('Stress Trajectories')
plt.streamplot(X_grid, Y_grid, beta_X1s, beta_Y1s, minlength=0.9, arrowstyle='-', density=2.5, color='b')
plt.streamplot(X_grid, Y_grid, beta_X2s, beta_Y2s, minlength=0.9, arrowstyle='-', density=2.5, color='r')
plt.axis("image")
plt.xlabel(r'$\chi = $'+str(round(chi,1)) + ', ' + r'$F = $'+ str(round(F,1)))
plt.ylim(-5,5)
plt.xlim(-5,5)
plt.show()
plot_stresses_cartesian(0,1,1)
I think that you just need to have NaN values for the region that you do not want to consider. I generated a simple example below.
import numpy as np
import matplotlib.pyplot as plt
Y, X = np.mgrid[-5:5:100j, -5:5:100j]
R = np.sqrt(X**2 + Y**2)
U = -1 - X**2 + Y
V = 1 + X - Y**2
U[R<1] = np.nan
V[R<1] = np.nan
plt.streamplot(X, Y, U, V, density=2.5, arrowstyle='-')
plt.axis("image")
plt.savefig("stream.png", dpi=300)
plt.show()
With plot
Related
I am plotting a vector field using the numpy function quiver() and it works. But I would like to emphasize the cowlick in the following plot:
I am not sure how to go about it, but increasing the density of arrows in the center could possibly do the trick. To do so, I would like to resort to some option within np.meshgrid() that would allow me to get more tightly packed x,y coordinate points in the center. A linear, quadratic or other specification does not seem to be built in. I am not sure if sparse can be modified to this end.
The code:
lim = 10
int = 0.22 *lim
x,y = np.meshgrid(np.arange(-lim, lim, int), np.arange(-lim, lim, int))
u = 3 * np.cos(np.arctan2(y,x)) - np.sqrt(x**2+y**2) * np.sin(np.arctan2(y,x))
v = 3 * np.sin(np.arctan2(y,x)) + np.sqrt(x**2+y**2) * np.cos(np.arctan2(y,x))
color = x**2 + y**2
plt.rcParams["image.cmap"] = "Greys_r"
mult = 1
plt.figure(figsize=(mult*lim, mult*lim))
plt.quiver(x,y,u,v,color, linewidths=.006, lw=.1)
plt.show()
Closing the loop on this, thanks to the accepted answer I was able to finally strike a balance between the density of the mesh as I learned from to do from #flwr and keeping the "cowlick" structure of the vector field conspicuous (avoiding the radial structure around the origin as much as possible):
You can construct the points whereever you want to calculate your field on and quivers will be happy about it. The code below uses polar coordinates and stretches the radial coordinate non-linearly.
import numpy as np
import matplotlib.pyplot as plt
lim = 10
N = 10
theta = np.linspace(0.1, 2*np.pi, N*2)
stretcher_factor = 2
r = np.linspace(0.3, lim**(1/stretcher_factor), N)**stretcher_factor
R, THETA = np.meshgrid(r, theta)
x = R * np.cos(THETA)
y = R * np.sin(THETA)
# x,y = np.meshgrid(x, y)
r = x**2 + y**2
u = 3 * np.cos(THETA) - np.sqrt(r) * np.sin(THETA)
v = 3 * np.sin(THETA) + np.sqrt(r) * np.cos(THETA)
plt.rcParams["image.cmap"] = "Greys_r"
mult = 1
plt.figure(figsize=(mult*lim, mult*lim))
plt.quiver(x,y,u,v,r, linewidths=.006, lw=.1)
Edit: Bug taking meshgrid twice
np.meshgrid just makes a grid of the vectors you provide.
What you could do is contract this regular grid in the center to have more points in the center (best visible with more points), e.g. like so:
# contract in the center
a = 0.5 # how far to contract
b = 0.8 # how strongly to contract
c = 1 - b*np.exp(-((x/lim)**2 + (y/lim)**2)/a**2)
x, y = c*x, c*y
plt.plot(x,y,'.k')
plt.show()
Alternatively you can x,y cooridnates that are not dependent on a grid at all:
x = np.random.randn(500)
y = np.random.randn(500)
plt.plot(x,y,'.k')
plt.show()
But I think you'd prefer a slightly more regular patterns you could look into poisson disk sampling with adaptive distances or something like that, but the key point here is that for using quiver, you can use ANY set of coordinates, they do not have to be in a regular grid.
I am trying to create a plot that looks like the picture.
Wave Particle Motions under Wave
This is not homework, i'm trying to do this for experience.
I have the following parameters:
Plot the water particle motions under Trough (Lowest point on wave elevation profile) at water depths
from 0 to 100 meters in increments of 10 m below mean water line.
The wave profile varying over space is ๐(๐ฅ) = ๐ดcos(๐x) at time = 0. Plot this wave profile first for one wave.
๐(๐ฅ) = ๐ด*cos(๐x) #at time = 0
Next compute vertical and horizontal particle displacements for different water depths of 0 to 100m
XDisp = -A * e**(k*z) * np.sin(-w*t)
YDisp = -A * e**(k*z) * np.cos(-w*t) # when x=0
You could use any x.
Motion magnitudes donโt change. Where z is depth below mean water level. All other parameters are as defined in earlier problems above.
Do not forget to shift the horizontally particle displacement to under trough and โzโ below water line for vertical particle displacement.
Here is my code, but im doing something wrong. I have the plot looking like the example but my circles are not right. I think it has to do with the x&y disp.
import numpy as np
import matplotlib.pyplot as plt
A = 1 # Wave amplitude in meters
T = 10 # Time Period in secs
n_w = 1 # Number of waves
wavelength = 156 # Wavelength in meters
# Wave Number
k = (2 * np.pi) / wavelength
# Wave angular frequency
w = (2 * np.pi) / T
def XDisp(z,t):
return -A * np.e**(k * z) * np.sin(-w * t)
def YDisp(z,t):
return -A * np.e**(k * z) * np.cos(-w * t)
def wave_elevation(x):
return A * np.cos(k * x)
t_list = np.array([0,0.25,0.5,0.75,1.0])*T
z = [0,-10,-20,-30,-40,-50,-60,-70,-80,-90,-100]
A_d = []
x_plot2 = []
for i in z:
A_d.append(A * np.e**(k * i))
x_plot2.append(wavelength/2)
x_plot = np.linspace(0,wavelength)
Y_plot = []
for i in x_plot:
Y_plot.append(wave_elevation(i))
plt.plot(x_plot,Y_plot,'.-r')
plt.scatter(x_plot2,z,s= A_d, facecolors = 'none',edgecolors = 'b',marker='o',linewidth=2)
plt.xlabel('X (m)')
plt.ylabel("\u03B7 & Water Depth")
plt.title('Wave Particle Motions Under Wave')
plt.legend()
plt.grid()
plt.show()
I am afraid with provided information, I don't follow science part of the question, but if you have problem in marker size you can put an array of sizes as third argument of plt.scatter. I think this code may help you, although I change your code a little bit to make it simpler
import numpy as np
import matplotlib.pyplot as plt
A = 1 # Wave amplitude in meters
T = 10 # Time Period in secs
n_w = 1 # Number of waves
wavelength = 156 # Wavelength in meters
k = (2 * np.pi) / wavelength # Wave Number
w = (2 * np.pi) / T # Wave angular frequency
def wave_elevation(x):
return A * np.cos(k * x)
A_d = [] # marker size
x2 = [] # for particle place on x axis which is wavelength/2
y2 = [] # for particle place on y axis
for i in range(0,100,10):
x2.append(wavelength/2)
y2.append(-i)
A_d.append(15 * np.exp(-k * i)) # here I change A to 15
x = []
y = []
for i in range(0,wavelength):
x.append(i)
y.append(wave_elevation(i))
plt.plot(x,y,'red')
plt.scatter(x2,y2,A_d)
plt.ylim(-100, 10)
plt.xlabel('X (m)')
plt.ylabel("\u03B7 & Water Depth")
plt.title('Wave Particle Motions Under Wave')
plt.grid()
plt.show()
I have coordinates of some points. My task is to get the direction of those points and find where future possible points will be located in the calculated direction. To do so, I have planned the following-
Fit a line to the points
Draw a quarter circle at the end of the fitted line. In common sense, the quarter circle might not be the right option to go for. However, it is a part of another problem and has to be solved this way.
I am using the following codes to fit a line
from matplotlib import pyplot as plt
from scipy import stats
x = [1,2,3,2,5,6,7,8,9,10]
y = [2,4,11,8,8,18,14,11,18,20]
slope, intercept, r_value, p_value, std_err = stats.linregress(x,y)
line = [slope*i+intercept for i in x]
plt.plot(x, line)
Suppose, the two points on the fitted line is (9,17) and (10,19). How can I draw a quarter circle at (10,19) with a radius of 5 in the direction of the line?
Ultimately, I will have a point location and I have to check whether the point falls inside the quarter circle or not, I assume which could be done with shapely.
Instead of calculating all the math by yourself, you can delegate it to Shapely.
First, create a circle at the end of the line with the help of buffer:
from shapely.affinity import rotate
from shapely.geometry import LineString, Point
from shapely.ops import split
a = (10, 20)
b = (15, 30)
ab = LineString([a, b]) # the line you got from linear regression
circle = Point(b).buffer(5)
Now, let's get two new lines that will delimit the area of the sector we want. We will do it by rotating the line using rotate to 135ยบ at each direction, so that the sector's central angle will be 360ยบ - 135ยบ * 2 = 90ยบ, which is a quarter of circle:
left_border = rotate(ab, -135, origin=b)
right_border = rotate(ab, 135, origin=b)
Finally, use split to get the sector:
splitter = LineString([*left_border.coords, *right_border.coords[::-1]])
sector = split(circle, splitter)[1]
From here you can easily find out if a point lies inside of the sector using contains method. For example:
points_of_interest = [Point(16, 32), Point(12, 30)]
for point in points_of_interest:
print(sector.contains(point))
# True
# False
To check whether point P falls inside the quarter circle, you can find distance from line end B (length of BP) and cosine of angle between unit line direction vector d and vector BP
distance = sqrt(BP.x * BP.x + BP.y * BP.y)
cosine = (d.x * BP.x + d.y * BP.y) / (distance)
if (distance < radius) and (cosine >= sqrt(2)/2)
P in sector
Unit vector d might be calculated from data you already have:
d.x = sign(slope) * sqrt(1/(1+slope**2))
d.y = sqrt(slope**2/1+slope**2)
Note that sign of components is not defined clearly (because two opposite vectors have the same slope)
To address the main question - end points of arc might be calculated using rotated (by Pi/4) direction vector
cf = sqrt(2)/2
arcbegin.x = b.x + radius * d.x * cf - radius * d.y * cf
arcbegin.y = b.y + radius * d.x * cf + radius * d.y * cf
arcend.x = b.x + radius * d.x * cf + radius * d.y * cf
arcend.y = b.y - radius * d.x * cf + radius * d.y * cf
I think You should implement the arch as follows. (I just shown the your missing logic, You haft to add your plot ). Good luck
from matplotlib import pyplot as plt
from scipy import stats
x = [1,2,3,2,5,6,7,8,9,10]
y = [2,4,11,8,8,18,14,11,18,20]
slope, intercept, r_value, p_value, std_err = stats.linregress(x,y)
line = [slope*i + intercept for i in x]
# Logic Part *****************************************************
from matplotlib.patches import Arc
import math
# circuile parameters
R = 5
xEnd,yEnd = 10 , 20 #Your end point cords, in your case Point B
LowerThita = math.degrees(math.atan(slope)) - 45
UpperThita = math.degrees(math.atan(slope)) + 45
# Figure setup
fig, ax = plt.subplots()
ax.set_xlim(-R , (R+xEnd) * 1.05)
ax.set_ylim(-R , (R+yEnd) * 1.05)
# Arcs
ax.add_patch(Arc((xEnd, yEnd), R, R,
theta1=LowerThita, theta2=UpperThita, edgecolor='k'))
plt.show()
#NOTE : You Haft to add your line to the plot
data file
import matplotlib.pylab as plt
import numpy as np
#initial data
data=np.loadtxt('profile_nonoisebigd02.txt')
x=data[:,0]
y=data[:,1]
#first derivatives
dx= np.gradient(data[:,0])
dy = np.gradient(data[:,1])
#second derivatives
d2x = np.gradient(dx)
d2y = np.gradient(dy)
#calculation of curvature from the typical formula
curvature = np.abs(dx * d2y - d2x * dy) / (dx * dx + dy * dy)**1.5
Can anyone help me out as to where am I going wrong with the curvature ?
The set of points give me a parabola, but curvature is not what I expect.
It seems that your data just isn't smooth enough; I used pandas to replace x, y, dx, dy, d2x, d2y and curvature by rolling means for different values window sizes. As the window size increases, the curvature starts to look more and more like what you'd expect to see for a smooth parabola (legend gives window size):
For the reference, here is the plot of your original data:
The code used to create the smoothed frames:
def get_smooth(smoothing=10, return_df=False):
data=np.loadtxt('profile_nonoisebigd02.txt')
if return_df:
return pd.DataFrame(data)
df = pd.DataFrame(data).sort_values(by=0).reset_index(drop=True).rolling(smoothing).mean().dropna()
# first derivatives
df['dx'] = np.gradient(df[0])
df['dy'] = np.gradient(df[1])
df['dx'] = df.dx.rolling(smoothing, center=True).mean()
df['dy'] = df.dy.rolling(smoothing, center=True).mean()
# second derivatives
df['d2x'] = np.gradient(df.dx)
df['d2y'] = np.gradient(df.dy)
df['d2x'] = df.d2x.rolling(smoothing, center=True).mean()
df['d2y'] = df.d2y.rolling(smoothing, center=True).mean()
# calculation of curvature from the typical formula
df['curvature'] = df.eval('abs(dx * d2y - d2x * dy) / (dx * dx + dy * dy) ** 1.5')
# mask = curvature < 100
df['curvature'] = df.curvature.rolling(smoothing, center=True).mean()
df.dropna(inplace=True)
return df[0], df.curvature
The purpose of my experiment is to fit a ring gaussian model to image data and find out the parameters of elliptical or ring Gaussian object in an image.
I've tried Astropy to make a Ring Gaussian model for simplicity and trial. Unfortunately, it's completely different from my artificial data.
from astropy.modeling import Fittable2DModel, Parameter, fitting
import matplotlib.pyplot as plt
import numpy as np
class ringGaussian(Fittable2DModel):
background = Parameter(default=5.)
amplitude = Parameter(default=1500.)
x0 = Parameter(default=15.)
y0 = Parameter(default=15.)
radius = Parameter(default=2.)
width = Parameter(default=1.)
#staticmethod
def evaluate(x, y, background, amplitude, x0, y0, radius, width):
z = background + amplitude * np.exp( -((np.sqrt((x-x0)**2. + (y-y0)**2.) - radius) / width)**2 )
return z
Then I made some artificial data (initial parameters) to test the fitting function of ring gaussian class.
back = 10 # background
amp = 2000 # highest value of the Gaussian
x0 = 10 # x coordinate of center
y0 = 10 # y coordinate of center
radius = 3
width = 1
xx, yy = np.mgrid[:30, :30]
z = back + amp * np.exp( -((np.sqrt((xx-x0)**2. + (yy-y0)**2.) - radius) / width)**2 )
I plotted xx, yy and z using contourf:
fig = plt.subplots(figsize=(7,7))
plt.contourf(xx,yy,z)
plt.show()
It is what I got:
enter image description here
Then I tried to fit the z using my fittable class:
p_init = ringGaussian() #bounds={"x0":[0., 20.], "y0":[0., 20.]}
fit_p = fitting.LevMarLSQFitter()
p = fit_p(p_init, xx, yy, z)
# It is the parameter I got:
<ringGaussian(background=133.0085329497139, amplitude=-155.53652181827655, x0=25.573499373946227, y0=25.25813520725603, radius=8.184302497405568, width=-7.273935403490675)>
I plotted the model:
fig = plt.subplots(figsize=(7,7))
plt.contourf(xx,yy,p(xx,yy))
plt.show()
It is what I got:
enter image description here
Originally, I also tried to include the derivative in my class:
#staticmethod
def fit_deriv(x, y, background, amplitude, x0, y0, radius, width):
g = (np.sqrt((x-x0)**2. + (y-y0)**2.) - radius) / width
z = amplitude * np.exp( -g**2 )
dg_dx0 = - (x-x0)/np.sqrt((x-x0)**2. + (y-y0)**2.)
dg_dy0 = - (y-y0)/np.sqrt((x-x0)**2. + (y-y0)**2.)
dg_dr0 = - 1/width
dg_dw0 = g * -1/width
dz_dB = 1.
dz_dA = z / amplitude
dz_dx0 = -2 * z * g**3 * dg_dx0
dz_dy0 = -2 * z * g**3 * dg_dy0
dz_dr0 = -2 * z * g**3 * dg_dr0
dz_dw0 = -2 * z * g**3 * dg_dw0
return [dz_dB, dz_dA, dz_dx0, dz_dy0, dz_dr0, dz_dw0]
But it returned "ValueError: setting an array element with a sequence."
I'm quite desperate now. Can anyone suggest some possible solutions? or alternative ways to implement the ring gaussian fit in python?
Many many thanks~~~
Your implementation is OK (I did not try or check your fit_deriv implementation).
The issue is just that your fit doesn't converge because the initial model parameters are too far from the true values, so the optimiser fails. When I run your code, I get this warning:
WARNING: The fit may be unsuccessful; check fit_info['message'] for more information. [astropy.modeling.fitting]
If you change to model parameters so that your model roughly matches the data, the fit succeeds:
p_init = ringGaussian(x0=11, y0=11)
To check how your model compares with the data, you can use imshow to display data and model images (or also e.g. residual images):
plt.imshow(z)
plt.imshow(p_init(xx,yy))
plt.imshow(p(xx,yy))