-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
3D Voronoi Diagram : convex hull instead of cubic box ? #839
Comments
Hi @Optimox! This is an interesting question. The freud library uses a library called voro++ for its Voronoi computations. I recommend looking at the voro++ examples and/or source code to see if the kind of computation you want is supported. I see there are ways to constrain a particle’s Voronoi cell to fit within a predefined polyhedral volume using “initial walls.” See the example of dealing with irregular particle packings. http://math.lbl.gov/voro++/examples/ Pretty much any feature of voro++ could be used in the freud interface / implementation with some work, or perhaps you can call voro++ from C++ directly if that better suits your needs. I’m happy to provide guidance if you want help implementing an existing interface to voro++ in freud. Other ideas: you might want a geometry library that can perform cuts on the Voronoi cells’ polyhedra after the fact. That partly depends on whether you need the polyhedra or neighbor information as well. You can use freud’s NeighborList.filter method if you can define which bonds should be cut. Let us know what you think! Hope these ideas are helpful for your analysis. |
To the second part of your question:
Every side of a polytope (polygon/polyhedron) in the Voronoi diagram defines a neighbor bond in freud. Because the box is treated as periodic, there are neighbor bonds that will cross the periodic boundary. You can see an example of how to draw the neighbor bonds here at the bottom: https://freud.readthedocs.io/en/latest/gettingstarted/examples/module_intros/locality.Voronoi.html |
hello @bdice, Thanks for your quick and detailed answer. Maybe I can explain a bit more in details what I'm trying to do and see if there's an elegant solution. I have points detected automatically within a 3D image, those points represent the center of nuclei (medical images). Currently I tried to use freud.box to avoid having very large voronoi cells on top and at the bottom layers but the fact that top cells can be at different height makes the cube not very appropriate and if I compute the volumes of each cell some will be huge because they are far from the box frontier. So I thought I could replace the cube with the convex hull of all my detected points, which I would dilate a bit (the size of typical cell) so that the cells on the border will have a decent space for their voronoi cell (but not too big). However I don't know anything in C++ so I think I'll have a hard time using it. If you already know where/how it could be implemented inside freud maybe I can try working on it with some help on where to start. About my second question, I would like to use the Voronoi neighbours to create a graph connecting adjacent cells in 3D. However I'd like to make a difference between "real" neighbor bonds and "periodic" bonds as they do not reflect a physical connection. But I think I can detect the "periodic" bonds by computing distances between the two nodes so it should be ok right? |
@Optimox Interesting! I think I understand your goals better now. If you’re dealing with real data, freud’s handling of periodic boxes is probably not what you want. Periodic boxes are common in simulations, our usual focus. SciPy has a non-periodic Voronoi implementation from QHull that could be helpful as well: https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.Voronoi.html Is the key thing you want out of this the Voronoi bonds? Do the polyhedra matter at all, e.g. for determining cell volume? If only the bonds matter, this idea might work without needing to alter freud or voro++.
The result should contain the neighbor pairs that share a side in the Voronoi diagram AND are closer than the cutoff distance (to eliminate bonds between separate clusters or across boundaries). If you need local cell volumes, perhaps you can approximate it by computing the Voronoi cell volumes and then take min(volume, max_cell_volume) where you plug in a reasonable maximum value that will be used for the particles on the edge of a cluster (which have large Voronoi cells extending into the empty space). Does that help? |
Thank you very much, yes I think I can go with this solution. Maybe I'll ask you one last question before thanking you very much for your precious help. I'd like to define bottom and top cells from my voronoi graph. Is there a way to detect that a cell has neighbor across a specific plane of the cube (top plane and bottom plane)? So that I would consider a cell to be on top of the voronoi graph if it does not have any real physical neighbor on top of it? |
@Optimox I’m not sure if I understand. You can try something like this to see if the bond crosses a periodic boundary: # Given a NeighborList called nlist, a Box object called box, and a NumPy array of particle positions, of shape (N, 3), called points
unwrapped_bonds = points[nlist.query_point_indices] + box.wrap(points[nlist.point_indices] - points[nlist.query_point_indices])
box.contains(unwrapped_bonds) The result should be a boolean array telling you if each bond crosses a periodic boundary. This boolean array can be passed to nlist.filter to drop bonds that cross the boundary. |
Thanks very much @bdice. Just to be sure I understand:
|
I think I don't fully understand what Here is a minimal code that shows something I don't get:
Since all the points I created are already inside the main box, how can it be that wrapping them changes their positions? EDIT Ok I think it's just a matter of precision if I actually try this I get the correct answer:
|
@Optimox Correct, this is an issue of precision. The freud library uses single precision (float32) for all calculations. NumPy defaults to float64. |
Yes, the array of particle positions (which we call points) is supposed to be contained in the box. See the tutorial on Periodic Boundary Conditions for some more information on this topic.
Yes, exactly. It's checking cases like the following. The two red points are neighbors under periodic boundaries, and their "ghost" images are shown in blue. The periodic image points cross periodic boundaries, and you could use that criterion to reject specific bonds. You might also try setting
This ensures that you get the short neighbor vectors shown in gray in the above image, and not the "long vector" (not shown) between the two red points. If you don't wrap, then you're effectively computing
See the NeighborList docs and Query API docs. |
Thanks @bdice, it's very clear and helpful. I have around 20k points inside my box and 200K bounds, which leads Using something like this would be much faster but I don't get the same results:
I don't understand why it's not the case, since any "crossing" bond should end up oustide the original box (as shown in your image above). What's the difference? Any idea how to speed up the final computation? |
Your simplified check in NumPy will only work for orthorhombic boxes (tilt factors xy, xz, yz equal to zero). freud supports triclinic boxes, which requires significant additional math. You can see the box documentation and C++ code in
Perhaps you can produce a synthetic benchmark using the |
my boxes are orthorhombic so I don't understand how I end up with different results. I'll come back to you with a simple reproducible example so that you can see exactly what I mean. Sorry I read too quickly your response and did not see my typo, thanks for pointing it! I do have the same results now! |
Hello again @bdice, Thanks again for your help, very much appreciated. I just wanted to show you the problem I saw with the computational time using You can see bellow a code snippet to reproduce the experiment. Here are the results in seconds (red for Moreover I wanted to point out something that I would consider a bug in the library (or at least something that could be improved): if you ask for a voronoi computation with one duplicated point in your set of points the code crashes without giving any error. I think it would be better to perform a duplicated check inside the function and throw a clear error. The same behavior happens if one point is outside the box, a simple check and clear error would be very useful for the users. I can create a specific issue about this if you prefer.
|
@Optimox Good observation. freud's Voronoi crashing silently is also a bug! It should return an error because it is not possible to compute a Voronoi diagram of points that overlap one another. The computation is undefined. However, this should raise an error instead of crashing the program! This may be an issue in the library we use, voro++. Unfortunately I won't have time to look at this very soon. Would you be able to file a separate issue for that problem? Just copy-paste your script into the new issue. |
Yes sure I'll close this issue and create a few new issues if you don't mind :) |
Hi,
I'm wondering if it's feasible to bound the 3D space by a dilatation of the convex hull of a set of points instead of a cube?
Indeed, for my problem I have points at the top and bottom surface which are not aligned on two planes. This creates quite big voronoi cells at the top and/or bottom (see an example in 2D bellow)
Also I noticed on the example above that cells at the top have a high number of sides, does that mean that they are considered to be neighbors to a lof of other top cells, even if not shown in the plot?
Thanks a lot for your help and congratulations on the repository!
The text was updated successfully, but these errors were encountered: