33from typing import Any
44
55import numpy as np
6+ import pyvista as pv
67import trimesh
78from pydantic import BaseModel , Field
89from pysdf import SDF
@@ -94,6 +95,10 @@ class InputSchema(BaseModel):
9495 default = 2000 ,
9596 description = ("Maximum number of faces in the output mesh." ),
9697 )
98+ sdf_backend : str = Field (
99+ default = "pysdf" ,
100+ description = ("Backend used to compute the SDF. Can be either pysdf or pyvista" ),
101+ )
97102
98103
99104class TriangularMesh (BaseModel ):
@@ -199,7 +204,33 @@ def grid_points(
199204 return np .vstack ((x .ravel (), y .ravel (), z .ravel ())).T
200205
201206
202- def compute_sdf (
207+ def compute_sdf_pyvista (
208+ geometry : trimesh .Trimesh ,
209+ grid_center : list [float ],
210+ Lx : float ,
211+ Ly : float ,
212+ Lz : float ,
213+ Nx : int ,
214+ Ny : int ,
215+ Nz : int ,
216+ ) -> np .ndarray :
217+ pv_mesh = pv .wrap (geometry )
218+ grid = pv .ImageData (
219+ dimensions = (Nx , Ny , Nz ),
220+ spacing = (Lx / Nx , Ly / Ny , Lz / Nz ),
221+ origin = (
222+ grid_center [0 ] - Lx / 2 ,
223+ grid_center [1 ] - Ly / 2 ,
224+ grid_center [2 ] - Lz / 2 ,
225+ ),
226+ )
227+
228+ sdf = grid .compute_implicit_distance (pv_mesh )
229+
230+ return sdf ["implicit_distance" ].reshape (Nx , Ny , Nz , order = "F" )
231+
232+
233+ def compute_sdf_pysdf (
203234 geometry : trimesh .Trimesh ,
204235 grid_center : list [float ],
205236 Lx : float ,
@@ -243,6 +274,7 @@ def geometries_and_sdf(
243274 grid_size : np .ndarray ,
244275 grid_elements : np .ndarray ,
245276 grid_center : np .ndarray ,
277+ sdf_backend : str ,
246278) -> tuple [list [np .ndarray ], list [trimesh .Trimesh ]]:
247279 """Get the sdf values of a the geometry defined by the parameters as a 2D array."""
248280 geos = get_geometries (
@@ -258,7 +290,18 @@ def geometries_and_sdf(
258290 geos = [geo .apply_scale (scale_mesh ) for geo in geos ]
259291
260292 sd_fields = [
261- compute_sdf (
293+ compute_sdf_pysdf (
294+ geo ,
295+ grid_center = grid_center ,
296+ Lx = grid_size [0 ],
297+ Ly = grid_size [1 ],
298+ Lz = grid_size [2 ],
299+ Nx = grid_elements [0 ],
300+ Ny = grid_elements [1 ],
301+ Nz = grid_elements [2 ],
302+ )
303+ if sdf_backend == "pysdf"
304+ else compute_sdf_pyvista (
262305 geo ,
263306 grid_center = grid_center ,
264307 Lx = grid_size [0 ],
@@ -302,6 +345,7 @@ def apply(inputs: InputSchema) -> OutputSchema:
302345 scale_mesh = inputs .scale_mesh ,
303346 grid_elements = inputs .grid_elements ,
304347 grid_center = inputs .grid_center ,
348+ sdf_backend = inputs .sdf_backend ,
305349 )
306350
307351 sdf = sdfs [0 ] # only one geometry
@@ -331,6 +375,7 @@ def apply(inputs: InputSchema) -> OutputSchema:
331375 grid_elements = inputs .grid_elements ,
332376 grid_center = inputs .grid_center ,
333377 epsilon = inputs .epsilon ,
378+ sdf_backend = inputs .sdf_backend ,
334379 )
335380
336381 global jacobian_future
@@ -360,6 +405,7 @@ def jac_sdf_wrt_params(
360405 grid_elements : np .ndarray ,
361406 grid_center : np .ndarray ,
362407 epsilon : float ,
408+ sdf_backend : str ,
363409) -> np .ndarray :
364410 """Compute the Jacobian of the SDF values with respect to the parameters.
365411
@@ -398,6 +444,7 @@ def jac_sdf_wrt_params(
398444 grid_elements = grid_elements ,
399445 grid_size = grid_size ,
400446 grid_center = grid_center ,
447+ sdf_backend = sdf_backend ,
401448 )
402449
403450 for i in range (n_params ):
@@ -406,25 +453,13 @@ def jac_sdf_wrt_params(
406453 return jac .astype (np .float32 )
407454
408455
409- def vector_jacobian_product (
456+ def jacobian (
410457 inputs : InputSchema ,
411- vjp_inputs : set [str ],
412- vjp_outputs : set [str ],
413- cotangent_vector : dict [str , Any ],
414- ) -> dict [str , Any ]:
415- """Compute vector-Jacobian product for backpropagation.
416-
417- Args:
418- inputs: Input schema containing bar geometry parameters.
419- vjp_inputs: Set of input variable names for gradient computation.
420- vjp_outputs: Set of output variable names for gradient computation.
421- cotangent_vector: Cotangent vectors for the specified outputs.
422-
423- Returns:
424- Dictionary containing VJP for the specified inputs.
425- """
426- assert vjp_inputs == {"differentiable_parameters" }
427- assert vjp_outputs == {"sdf" }
458+ jac_inputs : set [str ],
459+ jac_outputs : set [str ],
460+ ):
461+ assert jac_inputs == {"differentiable_parameters" }
462+ assert jac_outputs == {"sdf" }
428463
429464 # lets also check if the thread is still running
430465 if jacobian_future is not None :
@@ -450,12 +485,40 @@ def vector_jacobian_product(
450485 grid_elements = inputs .grid_elements ,
451486 epsilon = inputs .epsilon ,
452487 grid_center = inputs .grid_center ,
488+ sdf_backend = inputs .sdf_backend ,
453489 )
454490 if inputs .normalize_jacobian :
455491 n_elements = (
456492 inputs .grid_elements [0 ] * inputs .grid_elements [1 ] * inputs .grid_elements [2 ]
457493 )
458494 jac = jac / n_elements
495+
496+ jacobian = {"sdf" : {"differentiable_parameters" : jac }}
497+
498+ return jacobian
499+
500+
501+ def vector_jacobian_product (
502+ inputs : InputSchema ,
503+ vjp_inputs : set [str ],
504+ vjp_outputs : set [str ],
505+ cotangent_vector : dict [str , Any ],
506+ ) -> dict [str , Any ]:
507+ """Compute vector-Jacobian product for backpropagation.
508+
509+ Args:
510+ inputs: Input schema containing bar geometry parameters.
511+ vjp_inputs: Set of input variable names for gradient computation.
512+ vjp_outputs: Set of output variable names for gradient computation.
513+ cotangent_vector: Cotangent vectors for the specified outputs.
514+
515+ Returns:
516+ Dictionary containing VJP for the specified inputs.
517+ """
518+ assert vjp_inputs == {"differentiable_parameters" }
519+ assert vjp_outputs == {"sdf" }
520+
521+ jac = jacobian (inputs , vjp_inputs , vjp_outputs )["sdf" ]["differentiable_parameters" ]
459522 # Reduce the cotangent vector to the shape of the Jacobian, to compute VJP by hand
460523 vjp = np .einsum ("klmn,lmn->k" , jac , cotangent_vector ["sdf" ])
461524
0 commit comments