Polar plot Magnus effect not showing correct data - python

I wanted to plot the velocity equations of the flow around a rotating cylinder on a polar plot. (The equations are from "Fundamentals of Aerodynamics" by Andersen.) You can see the two equations inside the for loop statements.
I cannot for crying out loud manage to represent the calculated data onto the polar plot. I have tried every idea of mine, but arrived nowhere. I did check the data, and this one seems all correct, as it behaves how it should.
Here the code of my last attempt:
import numpy as np
import matplotlib.pyplot as plt
RadiusColumn = 1.0
VelocityInfinity = 10.0
RPM_Columns = 0.0#
ColumnOmega = (2*np.pi*RPM_Columns)/(60)#rad/s
VortexStrength = 2*np.pi*RadiusColumn**2 * ColumnOmega#rad m^2/s
NumberRadii = 6
NumberThetas = 19
theta = np.linspace(0,2*np.pi,NumberThetas)
radius = np.linspace(RadiusColumn, 10 * RadiusColumn, NumberRadii)
f = plt.figure()
ax = f.add_subplot(111, polar=True)
for r in xrange(len(radius)):
for t in xrange(len(theta)):
VelocityRadius = (1.0 - (RadiusColumn**2/radius[r]**2)) * VelocityInfinity * np.cos(theta[t])
VelocityTheta = - (1.0 + (RadiusColumn**2/radius[r]**2))* VelocityInfinity * np.sin(theta[t]) - (VortexStrength/(2*np.pi*radius[r]))
TotalVelocity = np.linalg.norm((VelocityRadius, VelocityTheta))
ax.quiver(theta[t], radius[r], theta[t] + VelocityTheta/TotalVelocity, radius[r] + VelocityRadius/TotalVelocity)
plt.show()
As you can see, I have set for now the RPMs to 0. That means that the flow should go from left to right, and be symmetric across the horizontal axis. (The flow should go around the cylinder the same way on both sides.) The result however looks more like this:
This is complete nonsense. There seems to be a vorticity, even when there is none set! Even weirder, when I only display data from 0 to pi/2, the flow changes!
As you can see from the code, I have tried to make use of unit vectors, but clearly this is not the way to go. I would appreciate any useful input.
Thanks!

The basic problem is that the .quiver method of a polar Axes object still expects its vector components in Cartesian coordinates, so you need to convert your theta and radial components to x and y yourself:
for r in range(len(radius)):
for t in range(len(theta)):
VelocityRadius = (1.0 - (RadiusColumn**2/radius[r]**2)) * VelocityInfinity * np.cos(theta[t])
VelocityTheta = - (1.0 + (RadiusColumn**2/radius[r]**2))* VelocityInfinity * np.sin(theta[t]) - (VortexStrength/(2*np.pi*radius[r]))
TotalVelocity = np.linalg.norm((VelocityRadius, VelocityTheta))
ax.quiver(theta[t], radius[r],
VelocityRadius/TotalVelocity*np.cos(theta[t])
- VelocityTheta/TotalVelocity*np.sin(theta[t]),
VelocityRadius/TotalVelocity*np.sin(theta[t])
+ VelocityTheta/TotalVelocity*np.cos(theta[t]))
plt.show()
However, you can improve your code a lot by making use of vectorization: you don't need to loop over each point to obtain what you need. So the equivalent of your code, but even clearer:
def pol2cart(th,v_th,v_r):
"""convert polar velocity components to Cartesian, return v_x,v_y"""
return v_r*np.cos(th) - v_th*np.sin(th), v_r*np.sin(th) + v_th*np.cos(th)
theta = np.linspace(0, 2*np.pi, NumberThetas, endpoint=False)
radius = np.linspace(RadiusColumn, 10 * RadiusColumn, NumberRadii)[:,None]
f = plt.figure()
ax = f.add_subplot(111, polar=True)
VelocityRadius = (1.0 - (RadiusColumn**2/radius**2)) * VelocityInfinity * np.cos(theta)
VelocityTheta = - (1.0 + (RadiusColumn**2/radius**2))* VelocityInfinity * np.sin(theta) - (VortexStrength/(2*np.pi*radius))
TotalVelocity = np.linalg.norm([VelocityRadius, VelocityTheta],axis=0)
VelocityX,VelocityY = pol2cart(theta, VelocityTheta, VelocityRadius)
ax.quiver(theta, radius, VelocityX/TotalVelocity, VelocityY/TotalVelocity)
plt.show()
Few notable changes:
I added endpoint=False to theta: since your function is periodic in 2*pi, you don't need to plot the endpoints twice. Note that this means that currently you have more visible arrows; if you want the original behaviour I suggest that you decrease NumberThetas by one.
I added [:,None] to radius: this will make it a 2d array, so later operations in the definition of the velocities will give you 2d arrays: different columns correspond to different angles, different rows correspond to different radii. quiver is compatible with array-valued input, so a single call to quiver will do your work.
Since the velocities are now 2d arrays, we need to call np.linalg.norm on essentially a 3d array, but this works as expected if we specify an axis to work over.
I defined the pol2cart auxiliary function to do the conversion from polar to Cartesian components; this is not necessary but it seems clearer to me this way.
Final remark: I suggest choosing shorter variable names, and ones that don't have CamelCase. That would probably make your coding faster too.

Related

Converting matplotlib's streamplot coordiantes to numpy coordiantes

I'm currently working with matplotlib in order to create a module of a specific vector field using matplotlib.pyplot.streamplot
after the creation and coloring of the lines in the streamplot, i'm trying to color the whitespace around the divergence points around the plot, im trying to achieve a gradient of color that is dependent on the distance of the white pixels around it.
The streamplot in question is built according to:
xs=np.linspace(-10,10,2000)
ys=np.linspace(-10,10,2000)
Therefore, if the divergence is located (for demonstration purposes) at (0,0) it will be located exactly in the middle of the plot.
Now, the only method i can think of for coloring according to distance from it, is kind of clunky since it requires me to:
add a matplotlib.patches.Rectangle on top of the divergence point in a specific color that is not in the image yet.
convert the plot, with the streamlines and rectangles (one rectangle for each divergence point in streamplot) to a np.array
find the new coordinates of the colors of the rectangles (they represent the location of the divergence point in the new np.array created from streamplot).
calculate the pixels like i want from the colored pixels.
This whole method feels way to clunky and over-complicating, and obviously slower than i could do. im sure theres a way to convert the coordinates from the matplotlib plot to the ones in np.array somehow or perhaps handle the coloring in matplotlib which will be even easier.
sadly i couldn't find a solution that answers this specific need yet.
thanks in advance for any help given!
EDIT
I'm adding an example (not my code, but a representation of what I wish to achieve).
I want to clarify that the solution of adding a patches.circle on top of a circle patch is not my go to, since i'm looking to keep my painting options more dynamic.
If you can define the color intensity you want as a 2-dimensional function, you can plot that function with plt.imshow() and then put the streamplot on top of it. You just need to transform the coordinates linearly to match the image coordinates.
Here is an example:
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = [10, 10]
# plot 2d function
grid = np.arange(-1, 1, 0.001)
x, y = np.meshgrid(grid, grid)
z = 1 - (x ** 2 + y ** 2) ** 0.5
plt.imshow(z, cmap='Blues')
# streamplot example from matplotlib docs (modified)
w = 3
Y, X = np.mgrid[-w:w:100j, -w:w:100j]
U = Y ** 2
V = X ** 2
# transform according to previous plot
n = len(grid) / 2
scale = n / w
X = (X + w) * scale
Y = (Y + w) * scale
U = (U + w) * scale
V = (V + w) * scale
plt.xticks(ticks=[0, n, 2*n],
labels=[-w, 0, w])
plt.yticks(ticks=[0, n, 2*n],
labels=[-w, 0, w])
plt.streamplot(X, Y, U, V);

Mapping the color scale of 3D isosurface on a scalar field

Let's say we have some 3D complex valued function f(x,y,z). Using Plotly, I'm trying to plot isosurfaces of the magnitude |f(x,y,z)| of such function. So far, everything is OK and my code seems to do well, please find below a working example on atomic orbitals functions :
import chart_studio.plotly as py
import plotly.graph_objs as go
import scipy.special as scispe
import numpy as np
import math
a=5.29e-11 # Bohr radius (m)
def orbital(n,l,m,r,theta,phi): # Complex function I want to plot
L=scispe.genlaguerre(n-l-1,2*l+1) # Laguerre polynomial
radial= (2/(n*a))**(3/2) * np.sqrt(math.factorial(n-l-1)/(2*n*math.factorial(n+l))) * np.exp(-2*r/n) * (2*r/n)**l * L(2*r/n)
wavefunction = radial * scispe.sph_harm(m,l, phi, theta)
return wavefunction
#Quantum numbers
n=2
l=1
m=0
goodspan = (3 * n**2 - l * (l+1))/2 #Plot span adpated to the mean electron position
x, y, z = np.mgrid[-goodspan:goodspan:40j, -goodspan:goodspan:40j, -goodspan:goodspan:40j] #in units of a
r = np.sqrt(x**2 + y**2 + z**2) #Function has to be evaluated in spherical coordinates
theta = np.arccos(z/r)
phi = np.arctan(y/x)
AO=orbital(n,l,m,r,theta,phi)
magnitude = abs(AO) # Compute the magnitude of the function
phase = np.angle(AO) # Compute the phase of the function
isoprob = np.amax(magnitude)/2 # Set value the isosurface
fig = go.Figure(data=go.Isosurface(
x=x.flatten(),
y=y.flatten(),
z=z.flatten(),
value=magnitude.flatten(),
opacity=0.5,
isomin=isoprob,
isomax=isoprob,
surface_count=1,
caps=dict(x_show=True, y_show=True)
))
fig.show()
which gives me this :
At this point, the color scale of the graph is attributed depending on the value of the magnitude |f(x,y,z)|, so that a single isosurface is always uniform in color.
Now, instead to have a color scale mapped on the magnitude |f(x,y,z)|, I would like it to be mapped on the value of the phase Ф(x,y,z) = arg(f(x,y,z)), so that the color of each point of a ploted isosurface tells us about the value of the field Ф(x,y,z) (which would be distributed on [-π,π] ideally) instead of |f(x,y,z)| in thsi point.
Basically, I would like to do this with Plotly instead of Mayavi if it's possible.
It seems to me that all of that has something to do with a special way to set the cmin and cmax parameters of the function Isosurface, but I can't figure out how to do this.
As #gnodab mentioned in his comment, plotly isosurfaces do not really support colouring the surfaces by a fifth dimension (at least there is no obvious way to do it). I am also not sure if it might be possible to extract the data describing the isosurface somehow to be re-plotted as a regular surface.
In this post, however, they describe how to generate an isosurface with skimage.measure.marching_cubes_lewiner which is then plotted and coloured by a custom colorscale with plotly as 'mesh3d' trace. This might be what you want. If I find the time, I'll give that a try and edit my answer later.
Given #Jan Joswig's answer and the link they provided, the quick/compact way of doing it will be:
import plotly.graph_objects as go
from skimage import measure
import numpy as np
xyz_shape = vol.shape
verts, faces = measure.marching_cubes(vol, .5)[:2] # iso-surface at .5 level
x, y, z = verts.T
I, J, K = faces.T
fig = go.Figure(
data=[go.Mesh3d(
x=x,
y=y,
z=z,
color='lightpink',
opacity=0.50,
i=I,
j=J,
k=K, )])
fig.show()

Rearrange data in two-dimensional array according to transformation from polar to Cartesian coordinates

I have a two-dimensional array that represents function values at positions in a polar coordinate system. For example:
import numpy as np
radius = np.linspace(0, 1, 50)
angle = np.linspace(0, 2*np.pi, radius.size)
r_grid, a_grid = np.meshgrid(radius, angle)
data = np.sqrt((r_grid/radius.max())**2
+ (a_grid/angle.max())**2)
Here the data is arranged in a rectangular grid corresponding to the polar coordinates. I want to rearrange the data in the array such that the axes represent the corresponding Cartesian coordinate system. The old versus new layout can be visualized as follows:
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=plt.figaspect(0.5))
ax1.set(title='Polar coordinates', xlabel='Radius', ylabel='Angle')
ax1.pcolormesh(r_grid, a_grid, data)
ax2.set(title='Cartesian coordinates', xlabel='X', ylabel='Y')
x_grid = r_grid * np.cos(a_grid)
y_grid = r_grid * np.sin(a_grid)
ax2.pcolormesh(x_grid, y_grid, data)
Here the coordinates are explicitly given and the plot is adjusted accordingly. I want the data to be rearranged in the data array itself instead. It should contain all values, optionally filling with zeros to fit the shape (similar to scipy.ndimage.rotate(..., reshape=True)).
If I manually loop over the polar arrays to compute the Cartesian coordinates, the result contains empty regions which ideally should be filled as well:
new = np.zeros_like(data)
visits = np.zeros_like(new)
for r, a, d in np.nditer((r_grid, a_grid, data)):
i = 0.5 * (1 + r * np.sin(a)) * new.shape[0]
j = 0.5 * (1 + r * np.cos(a)) * new.shape[1]
i = min(int(i), new.shape[0] - 1)
j = min(int(j), new.shape[1] - 1)
new[i, j] += d
visits[i, j] += 1
new /= np.maximum(visits, 1)
ax2.imshow(new, origin='lower')
Is there a way to achieve the transformation while avoiding empty regions in the resulting data array?
tl;dr: No, not without changing some conditions of your problem.
The artefact you are seeing is a property of the transformation.
It is not due to the fixed resolution in angle for all radii.
Hence, it is not due to a wrong or bad implementation of the transformation.
The Cartesian grid simply implies a higher special resolution at these areas as there are resolved points from the polar map.
The only "clean" way (that I can think of right now) to handle this is to have an adjustable resolution in the polar coordinates to account for the 1/r scaling. (If you input data allows it)
A somewhat cheating way of visualizing this without the gaps would to randomly distribute them over the gaps. The argument here is, that you do not have the resolution to decide in which bin they were to begin with. So you could just randomly throw them in one which might have been a possible origin and not throw them all in same one (as you are doing right now).
However, I would like discourage this stronlgy. It just gives you a prettier plot.
Note, that this is somewhat equivalent to the behaviour of the upper right plot in your question.
This doesn't really give the expected result, but maybe will help you in achieving a solution after some needed correction...
import numpy as np
radius = np.linspace(0, 1, 50)
angle = np.linspace(0, 2*np.pi, radius.size)
r_grid, a_grid = np.meshgrid(radius, angle)
data = np.sqrt((r_grid/radius.max())**2
+ (a_grid/angle.max())**2)
def polar_to_cartesian(data):
new = np.zeros_like(data) * np.nan
x = np.linspace(-1, 1, new.shape[1])
y = np.linspace(-1, 1, new.shape[0])
for i in range(new.shape[0]):
for j in range(new.shape[1]):
x0, y0 = x[j], y[i]
r, a = np.sqrt(x0**2 + y0**2), np.arctan2(y0, x0)
data_i = np.argmin(np.abs(a_grid[:, 0] - a))
data_j = np.argmin(np.abs(r_grid[0, :] - r))
val = data[data_i, data_j]
if r <= 1:
new[i, j] = val
return new
new = polar_to_cartesian(data)
fig, ax = plt.subplots()
ax.imshow(new, origin='lower')
EDIT:
Modified using np.arctan2 according to the suggestions of OP.
You could loop over the Cartesian array, transforming each grid point to polar coordinates and approximating the function value by interpolation from your polar grid data. You may still want to leave the corner regions blank though, for lack of close enough data.
I don't think there is a better way, unless of course you have access to the original function.

How to generate a random sample of points from a 3-D ellipsoid using Python?

I am trying to sample around 1000 points from a 3-D ellipsoid, uniformly. Is there some way to code it such that we can get points starting from the equation of the ellipsoid?
I want points on the surface of the ellipsoid.
Theory
Using this excellent answer to the MSE question How to generate points uniformly distributed on the surface of an ellipsoid? we can
generate a point uniformly on the sphere, apply the mapping f :
(x,y,z) -> (x'=ax,y'=by,z'=cz) and then correct the distortion
created by the map by discarding the point randomly with some
probability p(x,y,z).
Assuming that the 3 axes of the ellipsoid are named such that
0 < a < b < c
We discard a generated point with
p(x,y,z) = 1 - mu(x,y,y)/mu_max
probability, ie we keep it with mu(x,y,y)/mu_max probability where
mu(x,y,z) = ((acy)^2 + (abz)^2 + (bcx)^2)^0.5
and
mu_max = bc
Implementation
import numpy as np
np.random.seed(42)
# Function to generate a random point on a uniform sphere
# (relying on https://stackoverflow.com/a/33977530/8565438)
def randompoint(ndim=3):
vec = np.random.randn(ndim,1)
vec /= np.linalg.norm(vec, axis=0)
return vec
# Give the length of each axis (example values):
a, b, c = 1, 2, 4
# Function to scale up generated points using the function `f` mentioned above:
f = lambda x,y,z : np.multiply(np.array([a,b,c]),np.array([x,y,z]))
# Keep the point with probability `mu(x,y,z)/mu_max`, ie
def keep(x, y, z, a=a, b=b, c=c):
mu_xyz = ((a * c * y) ** 2 + (a * b * z) ** 2 + (b * c * x) ** 2) ** 0.5
return mu_xyz / (b * c) > np.random.uniform(low=0.0, high=1.0)
# Generate points until we have, let's say, 1000 points:
n = 1000
points = []
while len(points) < n:
[x], [y], [z] = randompoint()
if keep(x, y, z):
points.append(f(x, y, z))
Checks
Check if all points generated satisfy the ellipsoid condition (ie that x^2/a^2 + y^2/b^2 + z^2/c^2 = 1):
for p in points:
pscaled = np.multiply(p,np.array([1/a,1/b,1/c]))
assert np.allclose(np.sum(np.dot(pscaled,pscaled)),1)
Runs without raising any errors. Visualize the points:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(projection="3d")
points = np.array(points)
ax.scatter(points[:, 0], points[:, 1], points[:, 2])
# set aspect ratio for the axes using https://stackoverflow.com/a/64453375/8565438
ax.set_box_aspect((np.ptp(points[:, 0]), np.ptp(points[:, 1]), np.ptp(points[:, 2])))
plt.show()
These points seem evenly distributed.
Problem with currently accepted answer
Generating a point on a sphere and then just reprojecting it without any further corrections to an ellipse will result in a distorted distribution. This is essentially the same as setting this posts's p(x,y,z) to 0. Imagine an ellipsoid where one axis is orders of magnitude bigger than another. This way, it is easy to see, that naive reprojection is not going to work.
Consider using Monte-Carlo simulation: generate a random 3D point; check if the point is inside the ellipsoid; if it is, keep it. Repeat until you get 1,000 points.
P.S. Since the OP changed their question, this answer is no longer valid.
J.F. Williamson, "Random selection of points distributed on curved surfaces", Physics in Medicine & Biology 32(10), 1987, describes a general method of choosing a uniformly random point on a parametric surface. It is an acceptance/rejection method that accepts or rejects each candidate point depending on its stretch factor (norm-of-gradient). To use this method for a parametric surface, several things have to be known about the surface, namely—
x(u, v), y(u, v) and z(u, v), which are functions that generate 3-dimensional coordinates from two dimensional coordinates u and v,
The ranges of u and v,
g(point), the norm of the gradient ("stretch factor") at each point on the surface, and
gmax, the maximum value of g for the entire surface.
The algorithm is then:
Generate a point on the surface, xyz.
If g(xyz) >= RNDU01()*gmax, where RNDU01() is a uniform random variate in [0, 1), accept the point. Otherwise, repeat this process.
Chen and Glotzer (2007) apply the method to the surface of a prolate spheroid (one form of ellipsoid) in "Simulation studies of a phenomenological model for elongated virus capsid formation", Physical Review E 75(5), 051504 (preprint).
Here is a generic function to pick a random point on a surface of a sphere, spheroid or any triaxial ellipsoid with a, b and c parameters. Note that generating angles directly will not provide uniform distribution and will cause excessive population of points along z direction. Instead, phi is obtained as an inverse of randomly generated cos(phi).
import numpy as np
def random_point_ellipsoid(a,b,c):
u = np.random.rand()
v = np.random.rand()
theta = u * 2.0 * np.pi
phi = np.arccos(2.0 * v - 1.0)
sinTheta = np.sin(theta);
cosTheta = np.cos(theta);
sinPhi = np.sin(phi);
cosPhi = np.cos(phi);
rx = a * sinPhi * cosTheta;
ry = b * sinPhi * sinTheta;
rz = c * cosPhi;
return rx, ry, rz
This function is adopted from this post: https://karthikkaranth.me/blog/generating-random-points-in-a-sphere/
One way of doing this whch generalises for any shape or surface is to convert the surface to a voxel representation at arbitrarily high resolution (the higher the resolution the better but also the slower). Then you can easily select the voxels randomly however you want, and then you can select a point on the surface within the voxel using the parametric equation. The voxel selection should be completely unbiased, and the selection of the point within the voxel will suffer the same biases that come from using the parametric equation but if there are enough voxels then the size of these biases will be very small.
You need a high quality cube intersection code but with something like an elipsoid that can optimised quite easily. I'd suggest stepping through the bounding box subdivided into voxels. A quick distance check will eliminate most cubes and you can do a proper intersection check for the ones where an intersection is possible. For the point within the cube I'd be tempted to do something simple like a random XYZ distance from the centre and then cast a ray from the centre of the elipsoid and the selected point is where the ray intersects the surface. As I said above, it will be biased but with small voxels, the bias will probably be small enough.
There are libraries that do convex shape intersection very efficiently and cube/elipsoid will be one of the options. They will be highly optimised but I think the distance culling would probably be worth doing by hand whatever. And you will need a library that differentiates between a surface intersection and one object being totally inside the other.
And if you know your elipsoid is aligned to an axis then you can do the voxel/edge intersection very easily as a stack of 2D square intersection elipse problems with the set of squares to be tested defined as those that are adjacent to those in the layer above. That might be quicker.
One of the things that makes this approach more managable is that you do not need to write all the code for edge cases (it is a lot of work to get around issues with floating point inaccuracies that can lead to missing or doubled voxels at the intersection). That's because these will be very rare so they won't affect your sampling.
It might even be quicker to simply find all the voxels inside the elipse and then throw away all the voxels with 6 neighbours... Lots of options. It all depends how important performance is. This will be much slower than the opther suggestions but if you want ~1000 points then ~100,000 voxels feels about the minimum for the surface, so you probably need ~1,000,000 voxels in your bounding box. However even testing 1,000,000 intersections is pretty fast on modern computers.
Depending on what "uniformly" refers to, different methods are applicable. In any case, we can use the parametric equations using spherical coordinates (from Wikipedia):
where s = 1 refers to the ellipsoid given by the semi-axes a > b > c. From these equations we can derive the relevant volume/area element and generate points such that their probability of being generated is proportional to that volume/area element. This will provide constant volume/area density across the surface of the ellipsoid.
1. Constant volume density
This method generates points on the surface of an ellipsoid such that their volume density across the surface of the ellipsoid is constant. A consequence of this is that the one-dimensional projections (i.e. the x, y, z coordinates) are uniformly distributed; for details see the plot below.
The volume element for a triaxial ellipsoid is given by (see here):
and is thus proportional to sin(theta) (for 0 <= theta <= pi). We can use this as the basis for a probability distribution that indicates "how many" points should be generated for a given value of theta: where the area density is low/high, the probability for generating a corresponding value of theta should be low/high, too.
Hence, we can use the function f(theta) = sin(theta)/2 as our probability distribution on the interval [0, pi]. The corresponding cumulative distribution function is F(theta) = (1 - cos(theta))/2. Now we can use Inverse transform sampling to generate values of theta according to f(theta) from a uniform random distribution. The values of phi can be obtained directly from a uniform distribution on [0, 2*pi].
Example code:
import matplotlib.pyplot as plt
import numpy as np
from numpy import sin, cos, pi
rng = np.random.default_rng(seed=0)
a, b, c = 10, 3, 1
N = 5000
phi = rng.uniform(0, 2*pi, size=N)
theta = np.arccos(1 - 2*rng.random(size=N))
x = a*sin(theta)*cos(phi)
y = b*sin(theta)*sin(phi)
z = c*cos(theta)
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.scatter(x, y, z, s=2)
plt.show()
which produces the following plot:
The following plot shows the one-dimensional projections (i.e. density plots of x, y, z):
import seaborn as sns
sns.kdeplot(data=dict(x=x, y=y, z=z))
plt.show()
2. Constant area density
This method generates points on the surface of an ellipsoid such that their area density is constant across the surface of the ellipsoid.
Again, we start by calculating the corresponding area element. For simplicity we can use SymPy:
from sympy import cos, sin, symbols, Matrix
a, b, c, t, p = symbols('a b c t p')
x = a*sin(t)*cos(p)
y = b*sin(t)*sin(p)
z = c*cos(t)
J = Matrix([
[x.diff(t), x.diff(p)],
[y.diff(t), y.diff(p)],
[z.diff(t), z.diff(p)],
])
print((J.T # J).det().simplify())
This yields
-a**2*b**2*sin(t)**4 + a**2*b**2*sin(t)**2 + a**2*c**2*sin(p)**2*sin(t)**4 - b**2*c**2*sin(p)**2*sin(t)**4 + b**2*c**2*sin(t)**4
and further simplifies to (dividing by (a*b)**2 and taking the sqrt):
sin(t)*np.sqrt(1 + ((c/b)**2*sin(p)**2 + (c/a)**2*cos(p)**2 - 1)*sin(t)**2)
Since for this case the area element is more complex, we can use rejection sampling:
import matplotlib.pyplot as plt
import numpy as np
from numpy import cos, sin
def f_redo(t, p):
return (
sin(t)*np.sqrt(1 + ((c/b)**2*sin(p)**2 + (c/a)**2*cos(p)**2 - 1)*sin(t)**2)
< rng.random(size=t.size)
)
rng = np.random.default_rng(seed=0)
N = 5000
a, b, c = 10, 3, 1
t = rng.uniform(0, np.pi, size=N)
p = rng.uniform(0, 2*np.pi, size=N)
redo = f_redo(t, p)
while redo.any():
t[redo] = rng.uniform(0, np.pi, size=redo.sum())
p[redo] = rng.uniform(0, 2*np.pi, size=redo.sum())
redo[redo] = f_redo(t[redo], p[redo])
x = a*np.sin(t)*np.cos(p)
y = b*np.sin(t)*np.sin(p)
z = c*np.cos(t)
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.scatter(x, y, z, s=2)
plt.show()
which yields the following distribution:
The following plot shows the corresponding one-dimensional projections (x, y, z):

How to scale Graphs and Vector field on the same plot nicely? [weird scales ]

I'm new to matplotlib and trying to plot 2 graphs and one vector field on them on the same plot.
When I run the code below, I ran into some scaling problems. I asssume it's due to the undefined y axis on the graphs but I couldn't figure out how to plot them with y. When I define y's interval, I got very weird pics. I need some high resolution pics [0,7],[0,7] for the graphs and vector fields. When I ran the code below, I got results from the negative y axis which I don't want to plot.
I also wonder how I can make this plot bigger and higher resolution. The current image's size is pretty small.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
#graphs
a=1
b=1
x= np.mgrid[0:6:15j] #problematic part I assume
k = b/(a*x)
l = (-1 + (b+1)*x) / (a*(x**2))
ca =plt.plot(x, k, x,l)
#Vector Field,Quiver
Y, X = np.mgrid[0:6:12j, 0:6:12j]
U = 1- (b + 1)* X + ( a*(X**2) * Y)
V = (b * X) - (a * X**2 * Y)
speed = np.sqrt(U**2 + V**2)
UN = U/speed
VN = V/speed
quiv = plt.quiver(X, Y, UN, VN,
color='Black',
headlength=5)
plt.show()
When i run your code, I get runtime warnings for division by zero, which turns the first value of k infinite, and l -infinite. not sure that's your problem though, what exactly do you mean by scaling problems? do you want the graph to be focused on a specific range of x and y? you would normally use plt.xlim and plt.ylim for that sort of thing. try adding something like this:
plt.ylim((-0.2,6.2))
if you're running this from the command line, you should also be able to resize the graph manually (just by dragging the corner of the window), and save that graph, for a larger image. if you need an even higher resolution or more precise control, check out the savefig function. you can call it before running show().

Categories

Resources