NVIDIA Modulus Sym (Latest Release)
Sym (Latest Release)

Geometry Modules

Modulus Sym provides several 1D, 2D and 3D primitives that can be used for sampling point clouds required for the physics-informed training. These primitives support the standard boolean operations* like Union (+ ), Intersection (& ) and Subtraction (- ). The boolean operations work on the signed distance fields of the differnt primitives.

Below example shows a simple CSG primitive being built using Modulus Sym.

Listing 1 Constructive solid geometry

Copy
Copied!
            

import numpy as np from modulus.sym.geometry.primitives_3d import Box, Sphere, Cylinder from modulus.sym.utils.io.vtk import var_to_polyvtk # number of points to sample nr_points = 100000 # make standard constructive solid geometry example # make primitives box = Box(point_1=(-1, -1, -1), point_2=(1, 1, 1)) sphere = Sphere(center=(0, 0, 0), radius=1.2) cylinder_1 = Cylinder(center=(0, 0, 0), radius=0.5, height=2) cylinder_2 = cylinder_1.rotate(angle=float(np.pi / 2.0), axis="x") cylinder_3 = cylinder_1.rotate(angle=float(np.pi / 2.0), axis="y") # combine with boolean operations all_cylinders = cylinder_1 + cylinder_2 + cylinder_3 box_minus_sphere = box & sphere geo = box_minus_sphere - all_cylinders # sample geometry for plotting in Paraview s = geo.sample_boundary(nr_points=nr_points) var_to_polyvtk(s, "boundary") print("Surface Area:{:.3f}".format(np.sum(s["area"]))) s = geo.sample_interior(nr_points=nr_points, compute_sdf_derivatives=True) var_to_polyvtk(s, "interior") print("Volume:{:.3f}".format(np.sum(s["area"])))


csg_demo.png

Fig. 33 Constructive Solid Geometry using Modulus Sym primitives

A complete list of primitives can be referred in modulus.geometry.primitives_*

Note

While generating a complex primitive, it should be noted that the boolean operation are performed at the final stage, meaning it is invariant to the order of the boolean operations. In other words, if you are subtracting a primitive from another primitive, and if you decide to add a different primitive in the area that is subtracted, you will not see the newly added primitive because the subtracted primitive.

The geometry objects created also support operations like translate, scale, rotate and repeat to further generate more complicated primitives

Listing 2 Transforms and Repeat

Copy
Copied!
            

# apply transformations geo = geo.scale(0.5) geo = geo.rotate(angle=np.pi / 4, axis="z") geo = geo.rotate(angle=np.pi / 4, axis="y") geo = geo.repeat(spacing=4.0, repeat_lower=(-1, -1, -1), repeat_higher=(1, 1, 1)) # sample geometry for plotting in Paraview s = geo.sample_boundary(nr_points=nr_points) var_to_polyvtk(s, "repeated_boundary") print("Repeated Surface Area:{:.3f}".format(np.sum(s["area"]))) s = geo.sample_interior(nr_points=nr_points, compute_sdf_derivatives=True) var_to_polyvtk(s, "repeated_interior") print("Repeated Volume:{:.3f}".format(np.sum(s["area"])))


csg_repeat_demo.png

Fig. 34 Geometry transforms

The CSG objects can be easily parameterized using sympy. An example of this is used in Parameterized 3D Heat Sink

Listing 3 Parameterized geometry

Copy
Copied!
            

from modulus.sym.geometry.primitives_2d import Rectangle, Circle from modulus.sym.utils.io.vtk import var_to_polyvtk from modulus.sym.geometry.parameterization import Parameterization, Parameter # make plate with parameterized hole # make parameterized primitives plate = Rectangle(point_1=(-1, -1), point_2=(1, 1)) y_pos = Parameter("y_pos") parameterization = Parameterization({y_pos: (-1, 1)}) circle = Circle(center=(0, y_pos), radius=0.3, parameterization=parameterization) geo = plate - circle # sample geometry over entire parameter range s = geo.sample_boundary(nr_points=100000) var_to_polyvtk(s, "parameterized_boundary") s = geo.sample_interior(nr_points=100000) var_to_polyvtk(s, "parameterized_interior") # sample specific parameter s = geo.sample_boundary( nr_points=100000, parameterization=Parameterization({y_pos: 0}) ) var_to_polyvtk(s, "y_pos_zero_boundary") s = geo.sample_interior( nr_points=100000, parameterization=Parameterization({y_pos: 0}) ) var_to_polyvtk(s, "y_pos_zero_interior")


csg_parameterized_demo.png

Fig. 35 Parameterized Constructive Solid Geometry using Modulus Sym primitives

Some interesting shapes generated using Modulus Sym’ geometry module are presented below

naca_demo.png

Fig. 36 NACA airfoil using Polygon primitive. (script at /examples/geometry/naca_airfoil.py)

ahmed_demo.png

Fig. 37 Ahmed body

industrial_geo_demo.png

Fig. 38 Industrial heatsink geometry

If you don’t find a primitive defined for your application, it is easy to setup using the base classes from Modulus Sym. All you need to do is come up with and appropriate expression for the signed distance field and the surfaces of the geometry. An example is shown below.

Listing 4 Custom Primitive

Copy
Copied!
            

from sympy import Symbol, pi, sin, cos, sqrt, Min, Max, Abs from modulus.sym.geometry.geometry import Geometry, csg_curve_naming from modulus.sym.geometry.helper import _sympy_sdf_to_sdf from modulus.sym.geometry.curve import SympyCurve, Curve from modulus.sym.geometry.parameterization import Parameterization, Parameter, Bounds from modulus.sym.geometry.primitives_3d import Cylinder from modulus.sym.utils.io.vtk import var_to_polyvtk class InfiniteCylinder(Geometry): """ 3D Infinite Cylinder Axis parallel to z-axis, no caps on ends Parameters ---------- center : tuple with 3 ints or floats center of cylinder radius : int or float radius of cylinder height : int or float height of cylinder parameterization : Parameterization Parameterization of geometry. """ def __init__(self, center, radius, height, parameterization=Parameterization()): # make sympy symbols to use x, y, z = Symbol("x"), Symbol("y"), Symbol("z") h, r = Symbol(csg_curve_naming(0)), Symbol(csg_curve_naming(1)) theta = Symbol(csg_curve_naming(2)) # surface of the cylinder curve_parameterization = Parameterization( {h: (-1, 1), r: (0, 1), theta: (0, 2 * pi)} ) curve_parameterization = Parameterization.combine( curve_parameterization, parameterization ) curve_1 = SympyCurve( functions={ "x": center[0] + radius * cos(theta), "y": center[1] + radius * sin(theta), "z": center[2] + 0.5 * h * height, "normal_x": 1 * cos(theta), "normal_y": 1 * sin(theta), "normal_z": 0, }, parameterization=curve_parameterization, area=height * 2 * pi * radius, ) curves = [curve_1] # calculate SDF r_dist = sqrt((x - center[0]) ** 2 + (y - center[1]) ** 2) sdf = radius - r_dist # calculate bounds bounds = Bounds( { Parameter("x"): (center[0] - radius, center[0] + radius), Parameter("y"): (center[1] - radius, center[1] + radius), Parameter("z"): (center[2] - height / 2, center[2] + height / 2), }, parameterization=parameterization, ) # initialize Infinite Cylinder super().__init__( curves, _sympy_sdf_to_sdf(sdf), dims=3, bounds=bounds, parameterization=parameterization, ) nr_points = 100000 cylinder_1 = Cylinder(center=(0, 0, 0), radius=0.5, height=2) cylinder_2 = InfiniteCylinder(center=(0, 0, 0), radius=0.5, height=2) s = cylinder_1.sample_interior(nr_points=nr_points, compute_sdf_derivatives=True) var_to_polyvtk(s, "interior_cylinder") s = cylinder_2.sample_interior(nr_points=nr_points, compute_sdf_derivatives=True) var_to_polyvtk(s, "interior_infinite_cylinder")


custom_primitive_demo.png

Fig. 39 Custom primitive in Modulus Sym. The cylinders are sliced to visualize the interior SDF

For more complicated shapes, Modulus Sym allows geometries to be imported in the STL format. The module uses ray tracing to compute SDF and its derivatives. The module also gives surface normals of the geometry for surface sampling. Once the geometry is imported, the point cloud can be used for training. An example of this can be found in STL Geometry: Blood Flow in Intracranial Aneurysm.

Tesselated geometries can also be combined with the primitives

Listing 5 Tesselated Geometry

Copy
Copied!
            

import numpy as np from modulus.sym.geometry.tessellation import Tessellation from modulus.sym.geometry.primitives_3d import Plane from modulus.sym.utils.io.vtk import var_to_polyvtk # number of points to sample nr_points = 100000 # make tesselated geometry from stl file geo = Tessellation.from_stl("./stl_files/tessellated_example.stl") # tesselated geometries can be combined with primitives cut_plane = Plane((0, -1, -1), (0, 1, 1)) geo = geo & cut_plane # sample geometry for plotting in Paraview s = geo.sample_boundary(nr_points=nr_points) var_to_polyvtk(s, "tessellated_boundary") print("Repeated Surface Area:{:.3f}".format(np.sum(s["area"]))) s = geo.sample_interior(nr_points=nr_points, compute_sdf_derivatives=True) var_to_polyvtk(s, "tessellated_interior") print("Repeated Volume:{:.3f}".format(np.sum(s["area"])))


stl_demo_1.png

Fig. 40 Tesselated Geometry sampling using Modulus Sym

stl_demo_2.png

Fig. 41 Tesselated Geometry sampling using Modulus Sym: Stanford bunny

Mathematically, signed distance field or signed distance function (SDF) is defined as the orthonogal distance of a given point to the boundary / surface of a geometric shape. It is widely used to describe the geometry in mathematics, rendering, and similar applications. In physics-informed learning, it is also used to represent geometric inputs to the neural networks.

Modulus’ geometry module (CSG and Tesselation) computes the SDF (and its derivatives) on points sampled in the interior for use in the training pipelines. Additionally, the SDF can be computed on custom points using the .sdf attribute.

Modulus also provides a utility to recover the STL geometry from the SDF using marching cubes algorithm. For more details refer here.

Below example shows the use of these utilities for a CSG geometry.

Listing 6 Computing SDF for CSG geometry and reconstructing the STL

Copy
Copied!
            

import numpy as np from modulus.sym.geometry.primitives_3d import Box, Sphere, Cylinder from modulus.utils.mesh import sdf_to_stl # make standard constructive solid geometry example box = Box(point_1=(-1, -1, -1), point_2=(1, 1, 1)) sphere = Sphere(center=(0, 0, 0), radius=1.2) cylinder_1 = Cylinder(center=(0, 0, 0), radius=0.5, height=2) cylinder_2 = cylinder_1.rotate(angle=float(np.pi / 2.0), axis="x") cylinder_3 = cylinder_1.rotate(angle=float(np.pi / 2.0), axis="y") all_cylinders = cylinder_1 + cylinder_2 + cylinder_3 box_minus_sphere = box & sphere geo = box_minus_sphere - all_cylinders # generate cartesian grid to compute SDF x = np.linspace(-2, 2, 100) y = np.linspace(-2, 2, 100) z = np.linspace(-2, 2, 100) xx, yy, zz = np.meshgrid(x, y, z, indexing='ij') # compute the SDF on the grid points sdf = geo.sdf( { "x": xx.reshape(-1, 1), "y": yy.reshape(-1, 1), "z": zz.reshape(-1, 1), }, params={} )["sdf"] # reconstruct the STL from SDF sdf_to_stl( sdf.reshape(100, 100, 100), # sdf field in [nx, ny, nz] shape threshold=0.0, backend="skimage", # skimage backend works better for CSG geometry filename="csg_reconstruction.stl" )


sdf_to_stl_csg.png

Fig. 42 Computing SDF for CSG geometry and reconstructing the STL

Below example shows the use of these utilities for a Tesselation geometry.

Listing 7 Computing SDF for Tesselation geometry and reconstructing the STL

Copy
Copied!
            

import numpy as np from modulus.sym.geometry.tessellation import Tessellation from modulus.utils.mesh import sdf_to_stl # read the Stanford Bunny stl geo = Tessellation.from_stl("./Stanford_Bunny.stl") # generate cartesian grid to compute SDF bounds = {str(k): v for k, v in geo.bounds.bound_ranges.items()} x = np.linspace(bounds["x"][0] - 1, bounds["x"][1] + 1, 100) y = np.linspace(bounds["y"][0] - 1, bounds["y"][1] + 1, 100) z = np.linspace(bounds["z"][0] - 1, bounds["z"][1] + 1, 100) xx, yy, zz = np.meshgrid(x, y, z, indexing='ij') # compute the SDF on the grid points sdf = geo.sdf( { "x": xx.reshape(-1, 1), "y": yy.reshape(-1, 1), "z": zz.reshape(-1, 1), }, params={} )["sdf"] # reconstruct the STL from SDF sdf_to_stl( sdf.reshape(100, 100, 100), # sdf field in [nx, ny, nz] shape threshold=0.0, backend="warp", # warp backend works better and is faster for STL geometry filename="stl_reconstruction.stl" )


sdf_to_stl_tesselation.png

Fig. 43 Computing SDF for Tesselation geometry and reconstructing the STL

Previous Miscellaneous Concepts
Next Computational Graph, Nodes and Architectures
© Copyright 2023, NVIDIA Modulus Team. Last updated on Nov 27, 2024.