Calculate Divergence of Velocity Field (3D) in Python - python

I am trying to calculate the divergence of a 3D velocity field in a multi-phase flow setting (with solids immersed in a fluid). If we assume u,v,w to be the three velocity components (each a n x n x n) 3D numpy array, here is the function I have for calculating divergence:
def calc_divergence_velocity(df,h=0.025):
"""
#param df: A dataframe with the entire vector field with columns [x,y,z,u,v,w] with
x,y,z indicating the 3D coordinates of each point in the field and u,v,w
the velocities in the x,y,z directions respectively.
#param h: This is the dimension of a single side of the 3D (uniform) grid. Used
as input to numpy.gradient() function.
"""
"""
Reshape dataframe columns to get 3D numpy arrays (dim = 80) so each u,v,w is a
80x80x80 ndarray.
"""
u = df['u'].values.reshape((dim,dim,dim))
v = df['v'].values.reshape((dim,dim,dim))
w = df['w'].values.reshape((dim,dim,dim))
#Supply x,y,z coordinates appropriately.
#Note: Only a scalar `h` has been supplied to np.gradient because
#the type of grid we are dealing with is a uniform grid with each
#grid cell having the same dimensions in x,y,z directions.
u_grad = np.gradient(u,h,axis=0) #central diff. du_dx
v_grad = np.gradient(v,h,axis=1) #central diff. dv_dy
w_grad = np.gradient(w,h,axis=2) #central diff. dw_dz
"""
The `mask` column in the dataframe is a binary column indicating the locations
in the field where we are interested in measuring divergence.
The problem I am looking at is multi-phase flow with solid particles and a fluid
hence we are only interested in the fluid locations.
"""
sdf = df['mask'].values.reshape((dim,dim,dim))
div = (u_grad*sdf) + (v_grad*sdf) + (w_grad*sdf)
return div
The problem I'm having is that the divergence values that I am seeing are far too high.
For example the image below showcases, a distribution with values between [-350,350] whereas most values should technically be close to zero and somewhere between [20,-20] in my case. This tells me I'm calculating the divergence incorrectly and I would like some pointers as to how to correct the above function to calculate the divergence appropriately. As far as I can tell (please correct me if I'm wrong), I think have done something similar to this upvoted SO response. Thanks in advance!

Related

Sampling random points from linear subspaces of a given radius in arbitary dimensions

For a project, I need to be able to sample random points uniformly from linear subspaces (ie. lines and hyperplanes) within a certain radius. Since these are linear subspaces, they must go through the origin. This should work for any dimension n from which we draw our subspaces for in Rn.
I want my range of values to be from -0.5 to 0.5 (ie, all the points should fall within a hypercube whose center is at the origin and length is 1). I have tried to do the following to generate random subspaces and then points from those subspaces but I don't think it's exactly correct (I think I'm missing some form of normalization for the points):
def make_pd_line_in_rn(p, n, amount=1000):
# n is the dimension we draw our subspaces from
# p is the dimension of the subspace we want to draw (eg p=2 => line, p=3 => plane, etc)
# assume that n >= p
coeffs = np.random.rand(n, p) - 0.5
t = np.random.rand(amount, p)-0.5
return np.matmul(t, coeffs.T)
I'm not really good at visualizing the 3D stuff and have been banging my head against the wall for a couple of days.
Here is a perfect example of what I'm trying to achieve:
I think I'm missing some form of normalization for the points
Yes, you identified the issue correctly. Let me sum up your algorithm as it stands:
Generate a random subspace basis coeffs made of p random vectors in dimension n;
Generate coordinates t for amount points in the basis coeffs
Return the coordinates of the amount points in R^n, which is the matrix product of t and coeffs.
This works, except for one detail: the basis coeffs is not an orthonormal basis. The vectors of coeffs do not define a hypercube of side length 1; instead, they define a random parallelepiped.
To fix your code, you need to generate a random orthonormal basis instead of coeffs. You can do that using scipy.stats.ortho_group.rvs, or if you don't want to import scipy.stats, refer to the accepted answer to that question: How to create a random orthonormal matrix in python numpy?
from scipy.stats import ortho_group # ortho_group.rvs random orthogonal matrix
import numpy as np # np.random.rand random matrix
def make_pd_line_in_rn(p, n, amount=1000):
# n is the dimension we draw our subspaces from
# p is the dimension of the subspace we want to draw (eg p=2 => line, p=3 => plane, etc)
# assume that n >= p
coeffs = ortho_group.rvs(n)[:p]
t = np.random.rand(amount, p) - 0.5
return np.matmul(t, coeffs)
Please note that this method returns a rotated hypercube, aligned with the subspace. This makes sense; for instance, if you want to draw a square on a plane embed in R^3, then the square has to be aligned with the plane (otherwise it's not in the plane).
If what you wanted instead, is the intersection of a dimension-n hypercube with the dimension-p subspace, as suggested in the comments, then please do clarify your question.

Normalise max value of probability function for all frames

I have working code that plots a bivariate gaussian distribution. The distribution is produced by adjusting the COV matrix to account for specific variables. Specifically, every XY coordinate is applied with a radius. The COV matrix is then adjusted by a scaling factor to expand the radius in x-direction and contract in y-direction. The direction of this is measured by theta. The output is expressed as a probability density function (PDF).
I have normalised the PDF values. However, I'm calling a separate PDF for each frame. As such, the maximum value changes and hence the probability will be transformed differently for each frame.
Question: Using #Prasanth's suggestion. Is it possible to create normalized arrays for each frame before plotting, and then plot these arrays?
Below is the function I'm currently using to normalise the PDF for a single frame.
normPDF = (PDFs[0]-PDFs[1])/max(PDFs[0].max(),PDFs[1].max())
Is it possible to create normalized arrays for each frame before plotting, and then plot these arrays?
Indeed is possible. In your case you probably need to rescale your arrays between two values, say -1 and 1, before plotting. So that the minimum becomes -1, the maximum 1 and the intermediate values are scaled accordingly.
You could also choose 0 and 1 or whatever as minimum and maximum, but let's go with -1 and 1 so that a the middle value is 0.
To do this, in your code replace:
normPDF = (PDFs[0]-PDFs[1])/max(PDFs[0].max(),PDFs[1].max())
with:
renormPDF = PDFs[0]-PDFs[1]
renormPDF -= renormPDF.min()
normPDF = (renormPDF * 2 / renormPDF.max()) -1
This three lines ensure that normPDF.min() == -1 and normPDF.max() == 1.
Now when plotting the animation the axis on the right of your image does not change.
Your problem is to find the maximum values of PDFs[0].max() and PDFs[1].max() for all frames.
Why don't you run plotmvs on all your planned frames in order to find the absolute maximum for PDFs[0] and PDFs[1] and then run your animation with these absolute maxima to normalize your plots? This way, the colorbar will be the same for all frames.

How to obtain a correlogram using Moran's I values at different lag distances

I am new to calculating these values and am having a hard time figuring out how to calculate a (global?) Moran's I value for an increasing neighbour distance between points. Specifically, I'm not really sure how to set this lag/neighbour distance so that I can plot a correlogram.
The data I have is for the variation of single parameter in a 2D list (matrix). This can be plotted simply as a colorplot where the axes represent the points/pixels in each direction of the image, and the colormap shows the value of this parameter for each box across the 2D surface. As they seem to be clumping, I would like to see how long this 'parameter clump length' is using a correlogram.
So far I have managed to create another colorplot which I don't know exactly how to interpret.
y = 2D_Array
w = pysal.lat2W(rows,cols,rook=False,id_type="float")
lm = pysal.Moran_Local(y,w)
moran_significance = np.reshape(lm.p_sim,np.shape(ListOrArray))
plt.imshow(moran_significance)
I have also managed to obtain the global Moran I value by converting the 2D_Array into a 1D list.
y = 1D_List
w = pysal.lat2W(rows,cols)
mi = pysal.Moran(y,w,two_tailed=False)
But what I am really looking for is, how does I change when looking at how the parameter changes for neighbour n = 1,2,3,4,... where n = 1 is the nearest neighbour and n = 2 is the next nearest, and so on. Here is an example of what I'd like: https://creativesciences.files.wordpress.com/2015/05/morins-i-e1430616786173.png

Generate random points on any 3 dimensional surface

I was wondering, how would you, mathematically speaking, generate x points at random positions on a 3D surface, knowing the number of triangle polygons composing the surface (their dimensions, positions, normals, etc.)? In how many steps would you proceed?
I'm trying to create a "scatterer" in Maya (with Python and API), but I don't even know where to start in terms of concept. Should I generate the points first, and then check if they belong to the surface? Should I create the points directly on the surface (and how, in this case)?
Edit: I want to achieve this without using 2D projection or UVs, as far as possible.
You should compute the area of each triangle, and use those as weights to determine the destination of each random point. It is probably easiest to do this as a batch operation:
def sample_areas(triangles, samples):
# compute and sum triangle areas
totalA = 0.0
areas = []
for t in triangles:
a = t.area()
areas.append(a)
totalA += a
# compute and sort random numbers from [0,1)
rands = sorted([random.random() for x in range(samples)])
# sample based on area
area_limit = 0.0
rand_index = 0
rand_value = rands[rand_index]
for i in range(len(areas)):
area_limit += areas[i]
while rand_value * totalA < area_limit:
# sample randomly over current triangle
triangles[i].add_random_sample()
# advance to next sorted random number
rand_index += 1;
if rand_index >= samples:
return
rand_value = rands[rand_index]
Note that ridged or wrinkled regions may appear to have higher point density, simply because they have more surface area in a smaller space.
If the constraint is that all of the output points be on the surface, you want a consistent method of addressing the surface itself rather than worrying about the 3d > surface conversion for your points.
The hacktastic way to do that would be to create a UV map for your 3d object, and then scatter points randomly in 2 dimensions (throwing away points which happened not to land inside a valid UV shell). Once your UV shells are filled up as much as you'd like, you can convert your UV points to barycentric coordinates to convert those 2-d points back to 3-d points: effectively you say "i am 30% vertex A, 30 % vertex B, and 40% vertex C, so my position is (.3A + .3B + .4C)
Besides simplicity, another advantage of using is UV map is that it would allow you to customize the density and relative importance of different parts of the mesh: a larger UV face will get a lot of scattered points, and a smaller one fewer -- even if that doesn't match the physical size or the faces.
Going to 2D will introduce some artifacts because you probably will not be able to come up with a UV map that is both stretch-free and seam-free, so you'll get variations in the density of your scatter because of that. However for many applications this will be fine, since the algorithm is really simple and the results easy to hand tune.
I have not used this one but this looks like it's based on this general approach: http://www.shanemarks.co.za/uncategorized/uv-scatter-script/
If you need a more mathematically rigorous method, you'd need a fancier method of mesh parameterization : a way to turn your 3-d collection of triangles into a consistent space. There is a lot of interesting work in that field but it would be hard to pick a particular path without knowing the application.
Pick 2 random edges from random triangle.
Create 2 random points on edges.
Create new random point between them.
My ugly mel script:
//Select poly and target object
{
$sel = `ls -sl -fl`; select $sel[0];
polyTriangulate -ch 0;
$poly_s = `polyListComponentConversion -toFace`;$poly_s = `ls -fl $poly_s`;//poly flat list
int $numPoly[] = `polyEvaluate -fc`;//max random from number of poly
int $Rand = rand($numPoly[0]);//random number
$vtx_s =`polyListComponentConversion -tv $poly_s[$Rand]`;$vtx_s=`ls- fl $vtx_s`;//3 vertex from random poly flat list
undo; //for polyTriangulate
vector $A = `pointPosition $vtx_s[0]`;
vector $B = `pointPosition $vtx_s[1]`;
vector $C = `pointPosition $vtx_s[2]`;
vector $AB = $B-$A; $AB = $AB/mag($AB); //direction vector and normalize
vector $AC = $A-$C; $AC = $AC/mag($AC); //direction vector and normalize
$R_AB = mag($B-$A) - rand(mag($B-$A)); vector $AB = $A + ($R_AB * $AB);//new position
$R_AC = mag($A-$C) - rand(mag($A-$C)); vector $AC = $C + ($R_AC * $AC);//new position
vector $ABC = $AB-$AC; $ABC = $ABC/mag($ABC); //direction vector and normalize
$R_ABC = mag($AB-$AC) - rand(mag($AB-$AC)); //random
vector $ABC = $AC + ($R_ABC * $ABC);
float $newP2[] = {$ABC.x,$ABC.y,$ABC.z};//back to float
move $newP2[0] $newP2[1] $newP2[2] $sel[1];
select -add $sel[1];
}
PS UV method is better
Here is pseudo code that might be a good starting point:
Let N = no of vertices of 3D face that you are working with.
Just generate N random numbers, compute their sum, divide each one by the sum. Now you have N random number whose sum is = 1.0.
Using above random numbers, take a linear combination of 3D vertices of the 3D face that you are interested in. This should give you a random 3D point on the face.
Repeat till you get sufficient no. of random points on the 3D face.

Python - Kriging (Gaussian Process) in scikit_learn

I am considering using this method to interpolate some 3D points I have. As an input I have atmospheric concentrations of a gas at various elevations over an area. The data I have appears as values every few feet of vertical elevation for several tens of feet, but horizontally separated by many hundreds of feet (so 'columns' of tightly packed values).
The assumption is that values vary in the vertical direction significantly more than in the horizontal direction at any given point in time.
I want to perform 3D kriging with that assumption accounted for (as a parameter I can adjust or that is statistically defined - either/or).
I believe the scikit learn module can do this. If it can, my question is how do I create a discrete cell output? That is, output into a 3D grid of data with dimensions of, say, 50 x 50 x 1 feet. Ideally, I would like an output of [x_location, y_location, value] with separation of those (or similar) distances.
Unfortunately I don't have a lot of time to play around with it, so I'm just hoping to figure out if this is possible in Python before delving into it. Thanks!
Yes, you can definitely do that in scikit_learn.
In fact, it is a basic feature of kriging/Gaussian process regression that you can use anisotropic covariance kernels.
As it is precised in the manual (cited below) ou can either set the parameters of the covariance yourself or estimate them. And you can choose either having all parameters equal or all different.
theta0 : double array_like, optional
An array with shape (n_features, ) or (1, ). The parameters in the
autocorrelation model. If thetaL and thetaU are also specified, theta0
is considered as the starting point for the maximum likelihood
estimation of the best set of parameters. Default assumes isotropic
autocorrelation model with theta0 = 1e-1.
In the 2d case, something like this should work:
import numpy as np
from sklearn.gaussian_process import GaussianProcess
x = np.arange(1,51)
y = np.arange(1,51)
X, Y = np.meshgrid(lons, lats)
points = zip(obs_x, obs_y)
values = obs_data # Replace with your observed data
gp = GaussianProcess(theta0=0.1, thetaL=.001, thetaU=1., nugget=0.001)
gp.fit(points, values)
XY_pairs = np.column_stack([X.flatten(), Y.flatten()])
predicted = gp.predict(XY_pairs).reshape(X.shape)

Categories