Volume of 3d shape using numerical integration with scipy

限于喜欢 提交于 2021-02-10 08:45:36

问题


I have written a function for computing volume of intersection of a cube and a half-space and now I'm writing tests for it.

I've tried computing the volume numerically like this:

integral = scipy.integrate.tplquad(lambda z, y, x: int(Vector(x, y, z).dot(normal) < distance),
                                   -0.5, 0.5,
                                   lambda x: -0.5, lambda x: 0.5,
                                   lambda x, y: -0.5, lambda x, y: 0.5,
                                   epsabs=1e-5,
                                   epsrel=1e-5)

... basically I integrate over the whole cube and each point gets value 1 or 0 based on if it is inside the half space. This gets very slow (more than several seconds per invocation) and keeps giving me warnings like

scipy.integrate.quadpack.IntegrationWarning: The integral is probably divergent, or slowly convergent

Is there a better way to calculate this volume?


回答1:


Integration

Integration of a discontinuous function is problematic, especially in multiple dimension. Some preliminary work, reducing the problem to an integral of a continuous function, is needed. Here I work out the height (top-bottom) as a function of x and y, and use dblquad for that: it returns in 36.2 ms.

I express the plane equations as a*x + b*y + c*z = distance. Some care is needed with the sign of c, as the plane could be a part of the top or of the bottom.

from scipy.integrate import dblquad
distance = 0.1
a, b, c = 3, -4, 2  # normal
zmin, zmax = -0.5, 0.5  # cube bounds

# preprocessing: make sure that c > 0
# by rearranging coordinates, and flipping the signs of all if needed

height = lambda y, x: min(zmax, max(zmin, (distance-a*x-b*y)/c)) - zmin
integral = dblquad(height, -0.5, 0.5, 
                   lambda x: -0.5, lambda x: 0.5,
                   epsabs=1e-5, epsrel=1e-5)

Monte Carlo methods

Picking sample points at random (Monte Carlo method) avoids the issues with discontinuity: the accuracy is about the same for discontinuous as for continuous functions, the error decreases at the rate 1/sqrt(N) where N is the number of sample points.

The polytope package uses it internally. With it, a computation could go as

import numpy as np
import polytope as pc
a, b, c = 3, 4, -5  # normal vector
distance = 0.1 
A = np.concatenate((np.eye(3), -np.eye(3), [[a, b, c]]), axis=0)
b = np.array(6*[0.5] + [distance])
p = pc.Polytope(A, b)
print(p.volume)

Here A and b encode the halfspaces as Ax<=b: the first fix rows are for faces of the cube, the last is for the plane.

To have more control over precision, either implement Monte-Carlo method yourself (easy) or use mcint package (about as easy).

Polytope volume: a task for linear algebra, not for integrators

You want to compute the volume of a polytope, a convex body formed by intersecting halfspaces. This ought to have an algebraic solution. SciPy has HalfspaceIntersection class for these but so far (1.0.0) does not implement finding the volume of such an object. If you could find the vertices of the polytope, then the ConvexHull class could be used to compute the volume. But as is, it seems that SciPy spatial module is no help. Maybe in a future version of SciPy...




回答2:


Integration of characteristic function is mathematically correct, but not practical. That is because most integration schemes are designed to integrate polynomials to some degree exactly, and in consequence all "relatively smooth" functions reasonably well. Characteristic functions, however, are everything but smooth. Polynomial-style integration will get you nowhere.

A much better-suited approach is to build a discretized version of the domain first, and then simply sum up the volumes of the little tetrahedra.

Discretization in 3D can be done, for example, with pygalmesh (a project of mine interfacing CGAL). The below code discretizes the cut-off cube to

You can increase the precision by decreasing max_cell_circumradius and/or max_edge_size_at_feature_edges, but meshing will take longer then. Moreover, you could specify "feature edges" to resolve the intersection edges exactly. This would give you the exactly correct result even with the coarsest cell size.

import pygalmesh
import numpy


c = pygalmesh.Cuboid([0, 0, 0], [1, 1, 1])
h = pygalmesh.HalfSpace([1.0, 2.0, 3.0], 4.0, 10.0)
u = pygalmesh.Intersection([c, h])
mesh = pygalmesh.generate_mesh(
    u, max_cell_circumradius=3.0e-2, max_edge_size_at_feature_edges=1.0e-2
)


def compute_tet_volumes(vertices, tets):
    cell_coords = vertices[tets]
    a = cell_coords[:, 1, :] - cell_coords[:, 0, :]
    b = cell_coords[:, 2, :] - cell_coords[:, 0, :]
    c = cell_coords[:, 3, :] - cell_coords[:, 0, :]
    # omega = <a, b x c>
    omega = numpy.einsum("ij,ij->i", a, numpy.cross(b, c))
    # https://en.wikipedia.org/wiki/Tetrahedron#Volume
    return abs(omega) / 6.0

vol = numpy.sum(compute_tet_volumes(mesh.points, mesh.get_cells_type("tetra")))
print(f"{vol:.8e}")
8.04956436e-01



回答3:


If we assume that boundary of the half-space is given by $\{(x, y, z) \mid ax + by + cz + d = 0 \}$ with $c \not= 0$, and that the half-space of interest is that below the plane (in the $z$-direction), then your integral is given by

scipy.integrate.tplquad(lambda z, y, x: 1,
                        -0.5, 0.5,
                        lambda x: -0.5, lambda x: 0.5,
                        lambda x, y: -0.5, lambda x, y: max(-0.5, min(0.5, -(b*y+a*x+d)/c)))

Since at least one of $a$, $b$, and $c$ must be non-zero, the case $c = 0$ can be handled by changing coordinates.



来源:https://stackoverflow.com/questions/49338406/volume-of-3d-shape-using-numerical-integration-with-scipy

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!