Related
I am trying to pack hard-spheres in a unit cubical box, such that these spheres cannot overlap on each other. This is being done in Python.
I am given some packing fraction f, and the number of spheres in the system is N.
So, I say that the diameter of each sphere will be
d = (p*6/(math.pi*N)**)1/3).
My box has periodic boundary conditions - which means that there is a recurring image of my box in all direction. If there is a particle who is at the edge of the box and has a portion of it going beyond the wall, it will stick out at the other side.
My attempt:
Create a numpy N-by-3 array box which holds the position vector of each particle [x,y,z]
The first particle is fine as it is.
The next particle in the array is checked with all the previous particles. If the distance between them is more than d, move on to the next particle. If they overlap, randomly change the position vector of the particle in question. If the new position does not overlap with the previous atoms, accept it.
Repeat steps 2-3 for the next particle.
I am trying to populate my box with these hard spheres, in the following manner:
for i in range(1,N):
mybool=True
print("particles in box: " + str(i))
while (mybool): #the deal with this while loop is that if we place a bad particle, we need to change its position, and restart the process of checking
for j in range(0,i):
displacement=box[j,:]-box[i,:]
for k in range(3):
if abs(displacement[k])>L/2:
displacement[k] -= L*np.sign(displacement[k])
distance = np.linalg.norm(displacement,2) #check distance between ith particle and the trailing j particles
if distance<diameter:
box[i,:] = np.random.uniform(0,1,(1,3)) #change the position of the ith particle randomly, restart the process
break
if j==i-1 and distance>diameter:
mybool = False
break
The problem with this code is that if p=0.45, it is taking a really, really long time to converge. Is there a better method to solve this problem, more efficiently?
I think what you are looking for is either the hexagonal closed-packed (HCP or sometime called face-centered cubic, FCC) lattice or the cubic closed-packed one (CCP). See e.g. Wikipedia on Close-packing of equal spheres.
Since your space has periodic conditions, I believe it doesn't matter which one you chose (hcp or ccp), and they both achieve the same density of ~74.04%, which was proved by Gauss to be the highest density by lattice packing.
Update:
For the follow-up question on how to generate efficiently one such lattice, let's take as an example the HCP lattice. First, let's create a bunch of (i, j, k) indices [(0,0,0), (1,0,0), (2,0,0), ..., (0,1,0), ...]. Then, get xyz coordinates from those indices and return a DataFrame with them:
def hcp(n):
dim = 3
k, j, i = [v.flatten()
for v in np.meshgrid(*([range(n)] * dim), indexing='ij')]
df = pd.DataFrame({
'x': 2 * i + (j + k) % 2,
'y': np.sqrt(3) * (j + 1/3 * (k % 2)),
'z': 2 * np.sqrt(6) / 3 * k,
})
return df
We can plot the result as scatter3d using plotly for interactive exploration:
import plotly.graph_objects as go
df = hcp(12)
fig = go.Figure(data=go.Scatter3d(
x=df.x, y=df.y, z=df.z, mode='markers',
marker=dict(size=df.x*0 + 30, symbol="circle", color=-df.z, opacity=1),
))
fig.show()
Note: plotly's scatter3d is not a very good rendering of spheres: the marker sizes are constant (so when you zoom in and out, the "spheres" will appear to change relative size), and there is no shading, limited z-ordering faithfulness, etc., but it's convenient to interact with the plot.
Resize and clip to the unit box:
Here, a strict clipping (each sphere needs to be completely inside the unit box). Your "periodic boundary condition" is something you will need to address separately (see further below for ideas).
def hcp_unitbox(r):
n = int(np.ceil(1 / (np.sqrt(3) * r)))
df = hcp(n) * r
df += r
df = df[(df <= 1 - r).all(axis=1)]
return df
With this, you find that a radius of 0.06 gives you 608 fully enclosed spheres:
hcp_unitbox(.06).shape # (608, 3)
Where you would go next:
You may dig deeper into the effect of your so-called "periodic boundary conditions", and perhaps play with some rotations (and small translations).
To do so, you may try to generate an HCP-lattice that is large enough that any rotation will still fully enclose your unit cube. For example:
r = 0.2 # example
n = int(np.ceil(2 / r))
df = hcp(n) * r - 1
Then rotate it (by any amount) and translate it (by up to 1 radius in any direction) as you wish for your research, and clip. The "periodic boundary conditions", as you call them, present a bit of extra challenge, as the clipping becomes trickier. First, clip any sphere whose center is outside your box. Then select spheres close enough to the boundaries, or even partition the regions of interest into overlapping regions along the walls of your cube, then check for collisions among the spheres (as per your periodic boundary conditions) that fall in each such region.
I have this distribution of points (allPoints, which is a list of lists: [[x1,y1][x2,y2][x3,y3][x4,y4]...[xn,yn]]):
From which I'd like to select points, randomly.
in Python I would do something like:
from random import *
point = choice(allPoints)
Except, I need the random pick to not be biased by the existing density. For instance, here, "choice" would tend to pick a point in the upmost-leftmost part of the plot.
How can I, in Python, get rid of this bias?
I've tried to divide the space in portions of size "div", and then, sample within this portion, but in many cases, no points exist at all and the while loop doesn't find any solution:
def column(matrix, i):
return [row[i] for row in matrix]
div = 10
min_x,max_x = min(column(allPoints,0)),max(column(allPoints,0))
min_y, max_y = min(column(allPoints,1)),max(column(allPoints,1))
zone_x_min = randint(1,div-1) * (max_x - min_x) / div + min_x
zone_x_max = zone_x_min + (max_x - min_x) / div
zone_y_min = randint(1,div-1) * (max_y - min_y) / div + min_y
zone_y_max = zone_yl_min + (max_y - min_y) / div
p = choice(allPoints)
cont = True
while cont == True:
if (p[0] > zone_x_min and p[0] < zone_x_max) and (e[1] > zone_y_min and e[1] < zone_y_max):
cont = False
else:
p = choice(allPoints)
what would be a correct, inexpensive (if possible) solution to this problem?
If it wasn't ridiculous, I think something like would work for me, in theory:
p = [uniform(min_x,max_x),uniform(min_y,max_y)]
while p not in allPoints:
p = [uniform(min_x,max_x),uniform(min_y,max_y)]
The question is a little ill-formed, but here's a stab.
The idea is to use a gaussian kernel density estimate, then sample from your data with weights equal to the inverse of the pdf at each point.
This is not statistically justifiable in any real sense.
import numpy as np
from scipy import stats
#random data
x = np.random.normal(size = 200)
y = np.random.normal(size = 200)
#estimate the density
kernel = stats.gaussian_kde(np.vstack([x,y]))
#calculate the inverse of pdf for each point, and normalise to sum to 1
pvector = 1/kernel.pdf(np.vstack([x,y]))/sum(1/kernel.pdf(np.vstack([x,y])))
#get a vector of indices based on your weights
np.random.choice(range(len(x)), size = 10, replace = True, p = pvector)
I believe you want to randomly select a datum point from your graph.That is, one of the little black dots.
Compute a centroid, or pick a point like (1.0, 70).
Compute the distance from each point to the centroid and let that be the probability of your choice of that point.
That is if distance(P,C) is 100 and distance(Q,C) is 1 then let P be 100x more likely to be chosen. All points are eligible to win, but the crowded ones are individually less likely (but make it up with.volume).
If I understand your initial attempt correctly, I believe there is a simple adjustment you can make to make this work.
Randomly generate an x value (0,4.5), and a y value (0,70).
Then loop through allPoints to find the closest dot.
This has the downside of large empty areas all converging to a single point. A way to help (not remove) this problem would be to make your random point have a range. If no dot exists in that range, randomly generate a new dot.
Assuming you want your selected points to be visually spread I can think of at least one "efficient/easy" method.
Choose a random point (with random.choice for example) ;
remove from your initial set any point that is "close"*;
repeat until there is no point left in your set.
*This requires that you know from the beginning how dense you want your sample to be.
This might seem a bit strange, but I really feel like there should be a relatively straightforward solution to it. Basically I've got an image in the form of a 3D numpy array (x, y, color). I was following along with this tutorial for a slightly different product area, and found that these methods did not extend well.
As a result, I'm making a modified edge detection algorithm for my use case. As of now this is just some basic signal processing on top of a 1d array. This works great if I only want to sample in the x and y directions, as I can just use the existing rows and columns of the array.
However, to determine orientation of these edges, I would like to be able to sample any arbitrary vector across the image below is an image to help illustrate:
I tried hacking together something that would just append pixels as it crossed them, but it was inefficient, inelegant, and non-ideal in a number of ways. I feel like there must be some relatively elegant way of doing this.
Any ideas? The size of the sample across the vector doesn't really matter to me if that makes things any easier.
I would make an equation for the line you want to cut along, then make a mask around it and keep all pixels that come within some width of it. For example, say you want a cut along i = 2*j + 34, where i and j are measured in pixels:
h, w = im.shape[:2]
width = 2 # width of slice in pixels, too narrow and it will have gaps
i, j = np.ogrid[:h, :w]
mask = np.abs(2*j + 34 - i) < width
im[mask]
Note that im[mask] will be a 2d array, since it should still have the colors. It will be ordered so that the uppermost pixels are first, and the bottom pixels are last, opposite of that shown in your arrow, unless of course you have origin=lower in your plotting :) And if several pixels are selected in each row (if width > 1), then they'll go left to right, so the shape for a slice like your drawing would be a tiny sequence of z's, and for the other direction, backwards z's (s's?).
Keep in mind that for an array there doesn't exist a diagonal slice without some weird zigzag (or alternatively, interpolation) no matter how elegant your implementation is. You could rotate the image (by some algorithm) and take a horizontal slice.
Using the equation
x2 = x1 + length * cos(θ)
y2 = y1 + length * sin(θ)
where
θ = angle * 3.14 / 180.0
You can iterate through the pixel using angle and length like
int angle =45; //angle of iteration
int length = 0; //Alternately you can skip the pixel by giving value other than 0
Point P1(starX,startY); //Your starting point.
Point P2;//??
while(1){
length++;
P2.x = (int)round(P1.x + length * cos(angle * CV_PI / 180.0));
P2.y = (int)round(P1.y + length * sin(angle * CV_PI / 180.0));
if(P2_exceed_boundary()) break;
do_Whatever_with_P2();
}
I'm trying to build a basic heatmap based on points. Each point has a heat radius, and therefore is represented by a circle.
Problem is that the circle needs to be converted in a list of pixels colored based on the distance from the circle's center.
Finding it hard to find an optimal solution for many points, what I have for now is something similar to this:
for pixels in pixels:
if (pixel.x - circle.x)**2 + (pixel.y - circle.y)**2 <= circle.radius:
pixel.set_color(circle.color)
Edit:
data I have:
pixel at the center of the circle
circle radius (integer)
Any tips?
Instead of doing it pixel-by-pixel, use a higher level interface with anti-aliasing, like the aggdraw module and its ellipse(xy, pen, brush) function.
Loop over the number of color steps you want (lets say, radius/2) and use 255/number_of_steps*current_step as the alpha value for the fill color.
For plotting it is usually recommended to use the matplotlib library (e.g. using imshow for heatmaps). Of course matplotlib also supports color gradients.
However, I don't really understand what you are trying to accomplish. If you just want to draw a bunch of colored circles then pretty much any graphics library will do (e.g. using the ellipse function in PIL).
It sounds like you want to color the pixel according to their distance from the center, but your own example code suggests that the color is constant?
If you are handling your pixels by yourself and your point is to increase performances, you can just focus on the square [x - radius; x + radius] * [y - radius; y + radius] since the points of your circle live here. That will save you a lot of useless iterations, if of course you CAN focus on this region (i.e. your pixels are not just an array without index per line and column).
You can even be sure that the pixels in the square [x - radius*sqrt(2)/2; x + radius*sqrt(2)/2] * [y - radius*sqrt(2)/2; y + radius*sqrt(2)/2] must be colored, with basic trigonometry (maximum square inside the circle).
So you could do:
import math
half_sqrt = math.sqrt(2) / 2
x_max = x + half_sqrt
y_max = y + half_sqrt
for (i in range(x, x + radius + 1):
for (j in range(y, y + radius + 1):
if (x <= x_max and y <= y_max):
colorize_4_parts(i, j)
else:
pixel = get_pixel(i, j)
if (pixel.x - circle.x)**2 + (pixel.y - circle.y)**2 <= circle.radius:
# Apply same colors as above, could be a function
colorize_4_parts(i, j)
def colorize_4_parts(i, j):
# Hoping you have access to such a function get_pixel !
pixel_top_right = get_pixel(i, j)
pixel_top_right.set_color(circle.color)
pixel_top_left = get_pixel(2 * x - i, j)
pixel_top_leftt.set_color(circle.color)
pixel_bot_right = get_pixel(i, 2 * y - j)
pixel_bot_right.set_color(circle.color)
pixel_bot_left = get_pixel(2 * x - i, 2 * y - j)
pixel_bot_leftt.set_color(circle.color)
This is optimized to reduce costly computations to the minimum.
EDIT: function updated to be more efficient again: I had forgotten that we had a double symetry horizontal and vertical, so we can compute only for the top right corner !
This is a very common operation, and here's how people do it...
summary: Represent the point density on a grid, smooth this using a 2D convolution if needed (this gives your points to circles), and plot this as a heatmap using matplotlib.
In more detail: First, make a 2D grid for your heatmap, and add your data points to the grid, incrementing by the cells by 1 when a data point lands in the cell. Second, make another grid to represents the shape you want to give each point (usually people use a cylinder or gaussian or something like this). Third, convolve these two together, using, say scipy.signal.convolve2d. Finally, use matplotlib's imshow function to plot the convolution, and this will be your heatmap.
If you can't use the tools suggested in the standard approach then you might find work-arounds, but it has advantages. For example, the convolution will deal well with cases when the circles overlap.
I have created a Python file to generate a Mandelbrot set image. The original maths code was not mine, so I do not understand it - I only heavily modified it to make it about 250x faster (Threads rule!).
Anyway, I was wondering how I could modify the maths part of the code to make it render one specific bit. Here is the maths part:
for y in xrange(size[1]):
coords = (uleft[0] + (x/size[0]) * (xwidth),uleft[1] - (y/size[1]) * (ywidth))
z = complex(coords[0],coords[1])
o = complex(0,0)
dotcolor = 0 # default, convergent
for trials in xrange(n):
if abs(o) <= 2.0:
o = o**2 + z
else:
dotcolor = trials
break # diverged
im.putpixel((x,y),dotcolor)
And the size definitions:
size1 = 500
size2 = 500
n=64
box=((-2,1.25),(0.5,-1.25))
plus = size[1]+size[0]
uleft = box[0]
lright = box[1]
xwidth = lright[0] - uleft[0]
ywidth = uleft[1] - lright[1]
what do I need to modify to make it render a certain section of the set?
The line:
box=((-2,1.25),(0.5,-1.25))
is the bit that defines the area of coordinate space that is being rendered, so you just need to change this line. First coordinate pair is the top-left of the area, the second is the bottom right.
To get a new coordinate from the image should be quite straightforward. You've got two coordinate systems, your "image" system 100x100 pixels in size, origin at (0,0). And your "complex" plane coordinate system defined by "box". For X:
X_complex=X_complex_origin+(X_image/X_image_width)*X_complex_width
The key in understanding how to do this is to understand what the coords = line is doing:
coords = (uleft[0] + (x/size[0]) * (xwidth),uleft[1] - (y/size[1]) * (ywidth))
Effectively, the x and y values you are looping through which correspond to the coordinates of the on-screen pixel are being translated to the corresponding point on the complex plane being looked at. This means that (0,0) screen coordinate will translate to the upper left region being looked at (-2,1.25), and (1,0) will be the same, but moved 1/500 of the distance (assuming a 500 pixel width window) between the -2 and 0.5 x-coordinate.
That's exactly what that line is doing - I'll expand just the X-coordinate bit with more illustrative variable names to indicate this:
mandel_x = mandel_start_x + (screen_x / screen_width) * mandel_width
(The mandel_ variables refer to the coordinates on the complex plane, the screen_ variables refer to the on-screen coordinates of the pixel being plotted.)
If you want then to take a region of the screen to zoom into, you want to do exactly the same: take the screen coordinates of the upper-left and lower-right region, translate them to the complex-plane coordinates, and make those the new uleft and lright variables. ie to zoom in on the box delimited by on-screen coordinates (x1,y1)..(x2,y2), use:
new_uleft = (uleft[0] + (x1/size[0]) * (xwidth), uleft[1] - (y1/size[1]) * (ywidth))
new_lright = (uleft[0] + (x2/size[0]) * (xwidth), uleft[1] - (y2/size[1]) * (ywidth))
(Obviously you'll need to recalculate the size, xwidth, ywidth and other dependent variables based on the new coordinates)
In case you're curious, the maths behind the mandelbrot set isn't that complicated (just complex).
All it is doing is taking a particular coordinate, treating it as a complex number, and then repeatedly squaring it and adding the original number to it.
For some numbers, doing this will cause the result diverge, constantly growing towards infinity as you repeat the process. For others, it will always stay below a certain level (eg. obviously (0.0, 0.0) never gets any bigger under this process. The mandelbrot set (the black region) is those coordinates which don't diverge. Its been shown that if any number gets above the square root of 5, it will diverge - your code is just using 2.0 as its approximation to sqrt(5) (~2.236), but this won't make much noticeable difference.
Usually the regions that diverge get plotted with the number of iterations of the process that it takes for them to exceed this value (the trials variable in your code) which is what produces the coloured regions.