DeepSDFStruct 1.7.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. DeepSDFStruct/SDF.py +1734 -0
  2. DeepSDFStruct/__init__.py +66 -0
  3. DeepSDFStruct/deep_sdf/__init__.py +69 -0
  4. DeepSDFStruct/deep_sdf/create_screenshots_from_plyfiles.py +65 -0
  5. DeepSDFStruct/deep_sdf/data.py +258 -0
  6. DeepSDFStruct/deep_sdf/metrics/__init__.py +14 -0
  7. DeepSDFStruct/deep_sdf/metrics/mesh_to_analytical.py +24 -0
  8. DeepSDFStruct/deep_sdf/models.py +197 -0
  9. DeepSDFStruct/deep_sdf/networks/__init__.py +8 -0
  10. DeepSDFStruct/deep_sdf/networks/analytic_round_cross.py +105 -0
  11. DeepSDFStruct/deep_sdf/networks/deep_sdf_decoder.py +155 -0
  12. DeepSDFStruct/deep_sdf/networks/hierarchical_deep_sdf_decoder.py +139 -0
  13. DeepSDFStruct/deep_sdf/networks/hierarchical_positional_sdf_decoder.py +190 -0
  14. DeepSDFStruct/deep_sdf/networks/resnet_positional_sdf_decoder.py +108 -0
  15. DeepSDFStruct/deep_sdf/nn_utils.py +70 -0
  16. DeepSDFStruct/deep_sdf/plotting.py +143 -0
  17. DeepSDFStruct/deep_sdf/reconstruction.py +165 -0
  18. DeepSDFStruct/deep_sdf/training.py +840 -0
  19. DeepSDFStruct/deep_sdf/workspace.py +363 -0
  20. DeepSDFStruct/design_of_experiments.py +235 -0
  21. DeepSDFStruct/flexicubes/__init__.py +15 -0
  22. DeepSDFStruct/flexicubes/flexicubes.py +1005 -0
  23. DeepSDFStruct/flexicubes/tables.py +2338 -0
  24. DeepSDFStruct/flexisquares/__init__.py +15 -0
  25. DeepSDFStruct/flexisquares/flexisquares.py +967 -0
  26. DeepSDFStruct/flexisquares/tables.py +63 -0
  27. DeepSDFStruct/lattice_structure.py +254 -0
  28. DeepSDFStruct/local_shapes.py +226 -0
  29. DeepSDFStruct/mesh.py +1184 -0
  30. DeepSDFStruct/optimization.py +357 -0
  31. DeepSDFStruct/parametrization.py +215 -0
  32. DeepSDFStruct/pretrained_models.py +128 -0
  33. DeepSDFStruct/sampling.py +670 -0
  34. DeepSDFStruct/sdf_operations.py +990 -0
  35. DeepSDFStruct/sdf_primitives.py +1416 -0
  36. DeepSDFStruct/splinepy_unitcells/__init__.py +35 -0
  37. DeepSDFStruct/splinepy_unitcells/chi_3D.py +1617 -0
  38. DeepSDFStruct/splinepy_unitcells/cross_lattice.py +165 -0
  39. DeepSDFStruct/splinepy_unitcells/double_lattice_extruded.py +298 -0
  40. DeepSDFStruct/splinepy_unitcells/snappy_3d.py +544 -0
  41. DeepSDFStruct/torch_spline.py +446 -0
  42. DeepSDFStruct/trained_models/analytic_round_cross/LatentCodes/latest.pth +0 -0
  43. DeepSDFStruct/trained_models/analytic_round_cross/ModelParameters/latest.pth +0 -0
  44. DeepSDFStruct/trained_models/analytic_round_cross/OptimizerParameters/latest.pth +0 -0
  45. DeepSDFStruct/trained_models/analytic_round_cross/specs.json +45 -0
  46. DeepSDFStruct/trained_models/chi_and_cross/LatentCodes/latest.pth +0 -0
  47. DeepSDFStruct/trained_models/chi_and_cross/Logs.pth +0 -0
  48. DeepSDFStruct/trained_models/chi_and_cross/ModelParameters/latest.pth +0 -0
  49. DeepSDFStruct/trained_models/chi_and_cross/OptimizerParameters/latest.pth +0 -0
  50. DeepSDFStruct/trained_models/chi_and_cross/specs.json +76 -0
  51. DeepSDFStruct/trained_models/primitives/LatentCodes/latest.pth +0 -0
  52. DeepSDFStruct/trained_models/primitives/ModelParameters/latest.pth +0 -0
  53. DeepSDFStruct/trained_models/primitives/OptimizerParameters/latest.pth +0 -0
  54. DeepSDFStruct/trained_models/primitives/specs.json +46 -0
  55. DeepSDFStruct/trained_models/primitives_2d/LatentCodes/latest.pth +0 -0
  56. DeepSDFStruct/trained_models/primitives_2d/ModelParameters/latest.pth +0 -0
  57. DeepSDFStruct/trained_models/primitives_2d/OptimizerParameters/latest.pth +0 -0
  58. DeepSDFStruct/trained_models/primitives_2d/specs.json +46 -0
  59. DeepSDFStruct/trained_models/round_cross/LatentCodes/latest.pth +0 -0
  60. DeepSDFStruct/trained_models/round_cross/Logs.pth +0 -0
  61. DeepSDFStruct/trained_models/round_cross/ModelParameters/latest.pth +0 -0
  62. DeepSDFStruct/trained_models/round_cross/OptimizerParameters/latest.pth +0 -0
  63. DeepSDFStruct/trained_models/round_cross/specs.json +45 -0
  64. DeepSDFStruct/trained_models/test_experiment/LatentCodes/latent_code_data_map.json +106 -0
  65. DeepSDFStruct/trained_models/test_experiment/specs.json +45 -0
  66. DeepSDFStruct/trained_models/test_experiment/training_summary.json +10 -0
  67. DeepSDFStruct/trained_models/test_experiment_hierarchical/LatentCodes/latent_code_data_map.json +106 -0
  68. DeepSDFStruct/trained_models/test_experiment_hierarchical/specs.json +45 -0
  69. DeepSDFStruct/trained_models/test_experiment_hierarchical/training_summary.json +10 -0
  70. DeepSDFStruct/trained_models/test_experiment_homogenization/LatentCodes/latent_code_data_map.json +5006 -0
  71. DeepSDFStruct/trained_models/test_experiment_homogenization/LatentCodes/latest.pth +0 -0
  72. DeepSDFStruct/trained_models/test_experiment_homogenization/Logs.png +0 -0
  73. DeepSDFStruct/trained_models/test_experiment_homogenization/ModelParameters/latest.pth +0 -0
  74. DeepSDFStruct/trained_models/test_experiment_homogenization/OptimizerParameters/latest.pth +0 -0
  75. DeepSDFStruct/trained_models/test_experiment_homogenization/specs.json +83 -0
  76. DeepSDFStruct/trained_models/test_experiment_homogenization/training_summary.json +10 -0
  77. DeepSDFStruct/utils.py +96 -0
  78. DeepSDFStruct/visualization/latent_viewer.py +211 -0
  79. DeepSDFStruct/visualization/latent_viewer_cli.py +8 -0
  80. benchmarks/generate_sdf_showcase.py +622 -0
  81. benchmarks/sdf_from_mesh_benchmark.py +52 -0
  82. benchmarks/sdf_from_mesh_comparison.md +33 -0
  83. benchmarks/sdf_showcase/operations/boolean/difference_sphere_box.png +0 -0
  84. benchmarks/sdf_showcase/operations/boolean/union_sphere_box.png +0 -0
  85. benchmarks/sdf_showcase/operations/transformations/circular_array_sphere.png +0 -0
  86. benchmarks/sdf_showcase/operations/transformations/dilate_sphere.png +0 -0
  87. benchmarks/sdf_showcase/operations/transformations/mirror_sphere.png +0 -0
  88. benchmarks/sdf_showcase/operations/transformations/repeat_sphere.png +0 -0
  89. benchmarks/sdf_showcase/operations/transformations/revolve_circle.png +0 -0
  90. benchmarks/sdf_showcase/operations/transformations/shell_sphere.png +0 -0
  91. benchmarks/sdf_showcase/operations/transformations/twist_torus.png +0 -0
  92. benchmarks/sdf_showcase/primitives/2D/circle.png +0 -0
  93. benchmarks/sdf_showcase/primitives/2D/equilateral_triangle.png +0 -0
  94. benchmarks/sdf_showcase/primitives/2D/hexagon.png +0 -0
  95. benchmarks/sdf_showcase/primitives/2D/polygon_pentagon.png +0 -0
  96. benchmarks/sdf_showcase/primitives/2D/rectangle.png +0 -0
  97. benchmarks/sdf_showcase/primitives/2D/rounded_rectangle.png +0 -0
  98. benchmarks/sdf_showcase/primitives/3D/box.png +0 -0
  99. benchmarks/sdf_showcase/primitives/3D/capped_cone.png +0 -0
  100. benchmarks/sdf_showcase/primitives/3D/capped_cylinder.png +0 -0
  101. benchmarks/sdf_showcase/primitives/3D/capsule.png +0 -0
  102. benchmarks/sdf_showcase/primitives/3D/cone.png +0 -0
  103. benchmarks/sdf_showcase/primitives/3D/corner_spheres.png +0 -0
  104. benchmarks/sdf_showcase/primitives/3D/cross_ms.png +0 -0
  105. benchmarks/sdf_showcase/primitives/3D/cylinder.png +0 -0
  106. benchmarks/sdf_showcase/primitives/3D/dodecahedron.png +0 -0
  107. benchmarks/sdf_showcase/primitives/3D/ellipsoid.png +0 -0
  108. benchmarks/sdf_showcase/primitives/3D/icosahedron.png +0 -0
  109. benchmarks/sdf_showcase/primitives/3D/octahedron.png +0 -0
  110. benchmarks/sdf_showcase/primitives/3D/pyramid.png +0 -0
  111. benchmarks/sdf_showcase/primitives/3D/rounded_box.png +0 -0
  112. benchmarks/sdf_showcase/primitives/3D/rounded_cone.png +0 -0
  113. benchmarks/sdf_showcase/primitives/3D/rounded_cylinder.png +0 -0
  114. benchmarks/sdf_showcase/primitives/3D/sphere.png +0 -0
  115. benchmarks/sdf_showcase/primitives/3D/tetrahedron.png +0 -0
  116. benchmarks/sdf_showcase/primitives/3D/torus.png +0 -0
  117. benchmarks/sdf_showcase/primitives/3D/wireframe_box.png +0 -0
  118. deepsdfstruct-1.7.2.dist-info/METADATA +44 -0
  119. deepsdfstruct-1.7.2.dist-info/RECORD +165 -0
  120. deepsdfstruct-1.7.2.dist-info/WHEEL +5 -0
  121. deepsdfstruct-1.7.2.dist-info/licenses/LICENSE +208 -0
  122. deepsdfstruct-1.7.2.dist-info/licenses/NOTICE +67 -0
  123. deepsdfstruct-1.7.2.dist-info/scm_file_list.json +178 -0
  124. deepsdfstruct-1.7.2.dist-info/scm_version.json +8 -0
  125. deepsdfstruct-1.7.2.dist-info/top_level.txt +4 -0
  126. docs/Makefile +20 -0
  127. docs/api_reference.rst +6 -0
  128. docs/conf.py +60 -0
  129. docs/index.rst +136 -0
  130. docs/make.bat +35 -0
  131. docs/qr_code_github.png +0 -0
  132. docs/qr_code_paper.png +0 -0
  133. docs/readme_images/example_output_01.png +0 -0
  134. docs/readme_images/example_output_02.png +0 -0
  135. docs/readme_images/example_output_03.png +0 -0
  136. docs/readme_images/example_output_04.png +0 -0
  137. tests/data/chairs/1005.obj +282 -0
  138. tests/data/chairs/1024.obj +222 -0
  139. tests/data/chairs/README.md +1 -0
  140. tests/data/circular_balls_test_case.stl +0 -0
  141. tests/data/cone.stl +1794 -0
  142. tests/data/example_disconnectd_mesh.inp +1784 -0
  143. tests/data/example_line_mesh.vtk +0 -0
  144. tests/data/stanford_bunny.stl +0 -0
  145. tests/data/sweep_test_case.stl +0 -0
  146. tests/test_DOE.py +49 -0
  147. tests/test_elongate_sdf.py +195 -0
  148. tests/test_flexisquares.py +187 -0
  149. tests/test_generate_dataset.py +76 -0
  150. tests/test_lattice_evaluation.py +137 -0
  151. tests/test_mesh_export.py +137 -0
  152. tests/test_mesh_functions.py +49 -0
  153. tests/test_networks.py +71 -0
  154. tests/test_pretrained_models.py +40 -0
  155. tests/test_reconstruction.py +101 -0
  156. tests/test_sdf_from_mesh.py +52 -0
  157. tests/test_sdf_functions.py +117 -0
  158. tests/test_sdf_primitives.py +668 -0
  159. tests/test_sdf_trimesh_comparison.py +482 -0
  160. tests/test_splinepy_unitcells.py +64 -0
  161. tests/test_structural_optimization.py +178 -0
  162. tests/test_torch_spline.py +216 -0
  163. tests/test_train_model.py +110 -0
  164. tests/test_training_data_ids.py +41 -0
  165. tests/tmp_outputs/temp_file.txt +0 -0
@@ -0,0 +1,178 @@
1
+ from DeepSDFStruct.pretrained_models import get_model, PretrainedModels
2
+ from DeepSDFStruct.SDF import SDFfromDeepSDF, CappedBorderSDF
3
+ from DeepSDFStruct.lattice_structure import LatticeSDFStruct
4
+ from DeepSDFStruct.torch_spline import TorchSpline
5
+ from DeepSDFStruct.mesh import create_3D_mesh, torchVolumeMesh
6
+ from DeepSDFStruct.parametrization import SplineParametrization
7
+ from DeepSDFStruct.optimization import MMA, tet_signed_vol, get_mesh_from_torchfem
8
+ from DeepSDFStruct.utils import configure_logging
9
+ import DeepSDFStruct
10
+ import torchfem.materials
11
+ import torchfem.solid
12
+ import splinepy
13
+ import torch
14
+ import logging
15
+ import numpy as np
16
+
17
+ logger = logging.getLogger(DeepSDFStruct.__name__)
18
+ configure_logging()
19
+
20
+
21
+ def test_structural_optimization(num_iter=1):
22
+ # torch.set_default_device("cuda")
23
+
24
+ logger.info("Loading Model")
25
+ # Load a pretrained DeepSDF model
26
+ model = get_model(PretrainedModels.AnalyticRoundCross)
27
+ sdf = SDFfromDeepSDF(model)
28
+
29
+ cap_border_dict = {
30
+ "x0": {"cap": 1, "measure": 0.05},
31
+ "z1": {"cap": 1, "measure": 0.1},
32
+ }
33
+
34
+ param_spline_sp = splinepy.BSpline(
35
+ [1, 0, 1],
36
+ [[0, 0, 0.25, 0.5, 0.75, 1, 1], [0, 1], [0, 0, 0.5, 1, 1]],
37
+ [[0.3] * model._trained_latent_vectors[0].shape[0]] * 3 * 5,
38
+ )
39
+ logger.info("using spline with knot vectors:")
40
+ logger.info(param_spline_sp.knot_vectors)
41
+ logger.info(f"and control points: {param_spline_sp.control_points}")
42
+ tiling = [2, 1, 1]
43
+
44
+ param_spline = SplineParametrization(param_spline_sp, device=model.device)
45
+ # Define a spline-based deformation field
46
+ deformation_spline = TorchSpline(
47
+ splinepy.helpme.create.box(2, 1, 1).bspline, device=model.device
48
+ )
49
+
50
+ # Create the lattice structure with deformation and microtile
51
+ lattice_struct_uncapped = LatticeSDFStruct(
52
+ tiling=tiling, microtile=sdf, parametrization=param_spline
53
+ )
54
+ lattice_struct = CappedBorderSDF(
55
+ CappedBorderSDF(lattice_struct_uncapped, cap_border_dict)
56
+ )
57
+
58
+ lr = 1e-2
59
+ param = next(lattice_struct_uncapped.parametrization.parameters())
60
+ # optimizer = torch.optim.Adam([param], lr=lr)
61
+ init_vol = None
62
+ init_compl = None
63
+ target_vol = 0.5
64
+
65
+ bounds = np.zeros(param.shape) + np.array([0.15, 0.75])
66
+ optimizer = MMA(param, bounds)
67
+
68
+ for e in range(num_iter):
69
+ # log(
70
+ # f"Starting iteration with parameters: {lattice_struct.parametrization.parameters.T}"
71
+ # )
72
+ # torch.set_default_device("cuda")
73
+ torch.set_default_dtype(torch.float32)
74
+ mesh, _ = create_3D_mesh(
75
+ lattice_struct,
76
+ 10,
77
+ mesh_type="volume",
78
+ differentiate=False,
79
+ device=model.device,
80
+ deformation_function=deformation_spline,
81
+ )
82
+ surf_mesh, _ = create_3D_mesh(
83
+ lattice_struct,
84
+ 10,
85
+ mesh_type="surface",
86
+ differentiate=False,
87
+ device=model.device,
88
+ deformation_function=deformation_spline,
89
+ )
90
+
91
+ surf_trimesh = surf_mesh.to_trimesh()
92
+ assert surf_trimesh.is_watertight
93
+ if isinstance(mesh, torchVolumeMesh):
94
+ tets = mesh.volumes
95
+ verts = mesh.vertices
96
+ else:
97
+ raise RuntimeError("Resulting mesh should be volume mesh.")
98
+
99
+ # Ensure consistent positive orientation for each tetrahedron.
100
+ tets_oriented = tets.clone()
101
+ vols = tet_signed_vol(verts, tets_oriented)
102
+ neg_mask = vols < 0
103
+ if neg_mask.any():
104
+ tets_oriented_neg = tets_oriented[neg_mask]
105
+ tets_oriented_neg = tets_oriented_neg[:, [0, 2, 1, 3]]
106
+ tets_oriented[neg_mask] = tets_oriented_neg
107
+
108
+ vols = tet_signed_vol(verts, tets_oriented)
109
+ if init_vol is None:
110
+ init_vol = vols.sum().item()
111
+ logger.info(f"Initial volume: {init_vol} on {len(vols)} elements.")
112
+ eps = 1e-12
113
+ # Use float64 for the mask to match torchfem's internal precision;
114
+ # tets with tiny float32-positive volumes can become negative in float64.
115
+ vols_f64 = tet_signed_vol(verts.to(torch.float64), tets_oriented)
116
+ mask = vols_f64 > eps
117
+ vol = vols[mask].sum()
118
+
119
+ # keep only the good tets
120
+ tets_clean = tets_oriented[mask]
121
+
122
+ # check how many were removed
123
+ removed = (~mask).sum()
124
+ # print(f"Removed {removed} negative volume tets")
125
+
126
+ # Create model
127
+ # torch.set_default_device("cpu")
128
+ torch.set_default_dtype(torch.float64)
129
+ verts_tf = verts.to("cpu").to(torch.float64)
130
+ tets_tf = tets_clean.to("cpu")
131
+
132
+ # Material
133
+ material = torchfem.materials.IsotropicElasticity3D(E=1000.0, nu=0.3)
134
+
135
+ cantilever = torchfem.solid.Solid(verts_tf, tets_tf, material)
136
+
137
+ # Constrained displacement at left end [Node_IDs, DOFs]
138
+ left_const_mask = cantilever.nodes[:, 0] < 1e-5
139
+ cantilever.constraints[left_const_mask, :] = True
140
+
141
+ # Load at tip [Node_ID, DOF]
142
+ top_mask = cantilever.nodes[:, 2] > (1 - 1e-1)
143
+ top_mask_no_side = top_mask & (~left_const_mask)
144
+ num_nodes = top_mask_no_side.sum().item()
145
+ cantilever.forces[top_mask_no_side, 2] = -100.0 / num_nodes
146
+
147
+ # log("Starting Simulation")
148
+ u, f, _, _, _ = cantilever.solve(
149
+ rtol=1e-2, atol=1e-2, device="cpu", method="spsolve"
150
+ )
151
+
152
+ # Compute sensitivity of compliance w.r.t. element thicknesses
153
+ compliance = torch.inner(f.ravel(), u.ravel())
154
+
155
+ F = compliance
156
+ G = vol - target_vol
157
+ dF = torch.autograd.grad(F, param, retain_graph=True)[0]
158
+ dG = torch.autograd.grad(G, param, retain_graph=True)[0]
159
+ optimizer.step(F, dF, G, dG)
160
+
161
+ # Reset default dtype to float32 for other tests
162
+ torch.set_default_dtype(torch.float32)
163
+
164
+ # torch.autograd.grad(compliance, cantilever.thickness)[0]
165
+ mesh = get_mesh_from_torchfem(cantilever)
166
+ mesh.point_data["u"] = u.detach().cpu().numpy()
167
+ out_file_name = "sim_out.vtk"
168
+ logger.info(f"Writing Output to {out_file_name}")
169
+ mesh.save(out_file_name)
170
+ assert abs(dF.sum().item()) > 1e-8, "Derivative of objective is zero"
171
+ assert abs(dG.sum().item()) > 1e-8, "Derivative of constraint is zero"
172
+
173
+
174
+ if __name__ == "__main__":
175
+ import warnings
176
+
177
+ warnings.filterwarnings("error")
178
+ test_structural_optimization()
@@ -0,0 +1,216 @@
1
+ import torch
2
+ import splinepy
3
+ from DeepSDFStruct.torch_spline import (
4
+ # TorchSpline,
5
+ generate_bbox_spline,
6
+ torch_spline_1D,
7
+ torch_spline_3D,
8
+ )
9
+ from DeepSDFStruct.torch_spline import TorchSpline
10
+ import numpy as np
11
+ import pytest
12
+
13
+
14
+ @pytest.fixture
15
+ def np_rng():
16
+ return np.random.default_rng(0)
17
+
18
+
19
+ @pytest.fixture
20
+ def bspline():
21
+ control_points = np.random.rand(64, 3) # 4x4x4 Bezier -> 64 control points
22
+
23
+ knot_vecs = [
24
+ [0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0],
25
+ [0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0],
26
+ [0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0],
27
+ ]
28
+
29
+ return splinepy.BSpline(
30
+ degrees=[2, 2, 2], knot_vectors=knot_vecs, control_points=control_points
31
+ )
32
+
33
+
34
+ def test_torchspline_evaluation(np_rng, bspline, device="cpu"):
35
+ """Check that TorchSpline evaluation matches splinepy evaluation"""
36
+
37
+ spline_module = TorchSpline(bspline, device=device)
38
+
39
+ queries = torch.tensor(np_rng.random((5, 3)), dtype=torch.float32, device=device)
40
+
41
+ # Torch output
42
+ output_torch = spline_module(queries).detach().cpu().numpy()
43
+
44
+ # Numpy output
45
+ output_np = bspline.evaluate(queries.detach().cpu().numpy())
46
+
47
+ assert np.allclose(output_torch, output_np, atol=1e-6)
48
+
49
+
50
+ def test_torchspline_derivative(np_rng, bspline, device="cpu", eps=1e-3):
51
+ """Check that TorchSpline forward derivatives w.r.t input are correct via finite differences"""
52
+
53
+ spline_module = TorchSpline(bspline, device=device)
54
+
55
+ queries = torch.tensor(
56
+ np_rng.random((5, 3)), dtype=torch.float32, device=device, requires_grad=True
57
+ )
58
+
59
+ # Torch output
60
+ output = spline_module(queries)
61
+
62
+ # Finite differences approximation
63
+ fd_jacobian = np.zeros((queries.shape[0], output.shape[1], queries.shape[1]))
64
+ for i in range(queries.shape[0]):
65
+ for j in range(queries.shape[1]):
66
+ q_plus = queries.clone()
67
+ q_plus[i, j] += eps
68
+ q_minus = queries.clone()
69
+ q_minus[i, j] -= eps
70
+ f_plus = spline_module(q_plus).detach().cpu().numpy()[i]
71
+ f_minus = spline_module(q_minus).detach().cpu().numpy()[i]
72
+ fd_jacobian[i, :, j] = (f_plus - f_minus) / (2 * eps)
73
+
74
+ # Torch autograd jacobian
75
+ jacobian_torch = []
76
+ for i in range(output.shape[1]):
77
+ grad = (
78
+ torch.autograd.grad(
79
+ output[:, i].sum(), queries, create_graph=False, retain_graph=True
80
+ )[0]
81
+ .detach()
82
+ .cpu()
83
+ .numpy()
84
+ )
85
+ jacobian_torch.append(grad)
86
+ jacobian_torch = np.stack(jacobian_torch, axis=1)
87
+
88
+ fd_flat = fd_jacobian.flatten()
89
+ jac_flat = jacobian_torch.flatten()
90
+
91
+ np.testing.assert_allclose(
92
+ fd_jacobian,
93
+ jacobian_torch,
94
+ rtol=1e-5,
95
+ atol=1e-4,
96
+ err_msg="Finite difference Jacobian does not match autograd Jacobian.\n"
97
+ f"First 5 entries of FD Jacobian:\n{fd_flat[:5]}\n"
98
+ f"First 5 entries of Autograd Jacobian:\n{jac_flat[:5]}",
99
+ )
100
+
101
+
102
+ def test_generate_bbox_spline():
103
+ # Define bounding box
104
+ bbox = np.array([[-1.0, -2.0, -3.0], [4.0, 5.0, 6.0]])
105
+
106
+ spline = generate_bbox_spline(bbox)
107
+
108
+ # Check that min/max corners match bbox
109
+ cp = spline.control_points
110
+ mins, maxs = cp.min(axis=0), cp.max(axis=0)
111
+ np.testing.assert_allclose(mins, bbox[0])
112
+ np.testing.assert_allclose(maxs, bbox[1])
113
+
114
+ params = np.array(np.meshgrid([0, 1], [0, 1], [0, 1])).T.reshape(-1, 3)
115
+
116
+ evals = spline.evaluate(params)
117
+
118
+ expected = np.array(
119
+ np.meshgrid(
120
+ [bbox[0, 0], bbox[1, 0]], [bbox[0, 1], bbox[1, 1]], [bbox[0, 2], bbox[1, 2]]
121
+ )
122
+ ).T.reshape(-1, 3)
123
+
124
+ # Order might differ, so compare sets
125
+ assert set(map(tuple, np.round(evals, 8))) == set(map(tuple, np.round(expected, 8)))
126
+
127
+
128
+ def test_custom_torchspline_3D(bspline):
129
+
130
+ device = "cuda" if torch.cuda.is_available() else "cpu"
131
+
132
+ n_queries = 1000
133
+
134
+ queries = torch.rand((n_queries, 3), dtype=torch.float32, device=device)
135
+
136
+ splinepy_eval = bspline.evaluate(queries.detach().cpu().numpy())
137
+
138
+ splinepy_eval_as_tensor = torch.tensor(
139
+ splinepy_eval, dtype=torch.float32, device=device
140
+ )
141
+
142
+ output_ip = torch_spline_3D(
143
+ torch.tensor(bspline.knot_vectors, device=device, dtype=torch.float32),
144
+ control_points=torch.tensor(
145
+ bspline.control_points, device=device, dtype=torch.float32
146
+ ),
147
+ degrees=bspline.degrees,
148
+ queries=queries,
149
+ )
150
+
151
+ diff = torch.linalg.norm(splinepy_eval_as_tensor - output_ip, axis=1)
152
+
153
+ print("Max L2 difference:", diff.max())
154
+ torch.testing.assert_close(
155
+ diff.max(),
156
+ torch.tensor(0, device=diff.device, dtype=diff.dtype),
157
+ atol=1e-5,
158
+ rtol=1e-5,
159
+ )
160
+
161
+
162
+ def test_custom_torchspline_1D():
163
+
164
+ device = "cuda" if torch.cuda.is_available() else "cpu"
165
+
166
+ n_queries = 1000
167
+
168
+ queries = torch.rand((n_queries, 1), dtype=torch.float32, device=device)
169
+ control_points = np.random.rand(4, 1) # 4x4x4 Bezier -> 64 control points
170
+
171
+ knot_vecs = [[0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0]]
172
+ bspline = splinepy.BSpline(
173
+ degrees=[2], knot_vectors=knot_vecs, control_points=control_points
174
+ )
175
+ splinepy_eval = bspline.evaluate(queries.detach().cpu().numpy())
176
+
177
+ splinepy_eval_as_tensor = torch.tensor(
178
+ splinepy_eval, dtype=torch.float32, device=device
179
+ )
180
+
181
+ output_ip = torch_spline_1D(
182
+ torch.tensor(knot_vecs, device=device, dtype=torch.float32),
183
+ control_points=torch.tensor(control_points, device=device, dtype=torch.float32),
184
+ degrees=[2],
185
+ queries=queries,
186
+ )
187
+
188
+ diff = torch.linalg.norm(splinepy_eval_as_tensor - output_ip, axis=1)
189
+
190
+ print("Max L2 difference:", diff.max())
191
+ torch.testing.assert_close(
192
+ diff.max(),
193
+ torch.tensor(0, device=diff.device, dtype=diff.dtype),
194
+ atol=1e-5,
195
+ rtol=1e-5,
196
+ )
197
+
198
+
199
+ if __name__ == "__main__":
200
+ control_points = np.random.rand(64, 3) # 4x4x4 Bezier -> 64 control points
201
+
202
+ knot_vecs = [
203
+ [0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0],
204
+ [0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0],
205
+ [0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0],
206
+ ]
207
+
208
+ bspline_test = splinepy.BSpline(
209
+ degrees=[2, 2, 2], knot_vectors=knot_vecs, control_points=control_points
210
+ )
211
+ test_custom_torchspline_3D(bspline_test)
212
+ test_custom_torchspline_1D()
213
+ test_generate_bbox_spline()
214
+ np_rng = np.random.default_rng(0)
215
+ test_torchspline_evaluation(np_rng, bspline_test)
216
+ test_torchspline_derivative(np_rng, bspline_test)
@@ -0,0 +1,110 @@
1
+ from DeepSDFStruct.deep_sdf.training import (
2
+ train_deep_sdf,
3
+ reconstruct_meshs_from_latent,
4
+ create_interpolated_meshes_from_latent,
5
+ )
6
+ from DeepSDFStruct.pretrained_models import get_model
7
+ from huggingface_hub import snapshot_download
8
+ from huggingface_hub.utils import HfHubHTTPError
9
+ import pytest
10
+ import torch
11
+ import time
12
+
13
+ REVISION = "dbe58ebaa00057d5f15096c2b253c7efa91e19d3"
14
+
15
+
16
+ def snapshot_download_with_retry(*args, max_retries=3, **kwargs):
17
+ """Download with retry on 429 rate limit errors."""
18
+ for attempt in range(max_retries):
19
+ try:
20
+ return snapshot_download(*args, **kwargs)
21
+ except HfHubHTTPError as e:
22
+ if e.response and e.response.status_code == 429:
23
+ if attempt < max_retries - 1:
24
+ wait_time = 60 * (attempt + 1)
25
+ print(
26
+ f"Rate limited (429). Waiting {wait_time}s before retry {attempt + 2}/{max_retries}"
27
+ )
28
+ time.sleep(wait_time)
29
+ else:
30
+ raise
31
+ raise
32
+
33
+
34
+ @pytest.fixture(scope="module", autouse=True)
35
+ def set_float32_dtype():
36
+ torch.set_default_dtype(torch.float32)
37
+ torch.set_default_device("cpu")
38
+ yield
39
+
40
+
41
+ @pytest.fixture(scope="module")
42
+ def data_dir():
43
+ return snapshot_download_with_retry(
44
+ "mkofler/lattice_structure_unit_cells",
45
+ repo_type="dataset",
46
+ revision=REVISION,
47
+ ignore_patterns=["*.stl", "**/*.stl"],
48
+ )
49
+
50
+
51
+ def test_train_homogenization_model(data_dir):
52
+ exp_dir = "DeepSDFStruct/trained_models/test_experiment_homogenization"
53
+
54
+ device = "cpu"
55
+ train_deep_sdf(exp_dir, data_dir, device=device)
56
+
57
+
58
+ def test_train_hierarchical_model(data_dir):
59
+ exp_dir = "DeepSDFStruct/trained_models/test_experiment_hierarchical"
60
+
61
+ device = "cpu"
62
+ train_deep_sdf(exp_dir, data_dir, device=device)
63
+
64
+
65
+ def test_train_model(data_dir):
66
+ exp_dir = "DeepSDFStruct/trained_models/test_experiment"
67
+
68
+ device = "cpu"
69
+ train_deep_sdf(exp_dir, data_dir, device=device)
70
+
71
+
72
+ def test_continue_from(data_dir):
73
+ exp_dir = "DeepSDFStruct/trained_models/test_experiment"
74
+
75
+ device = "cpu"
76
+ train_deep_sdf(exp_dir, data_dir, device=device, continue_from="1")
77
+
78
+
79
+ def test_latent_recon():
80
+ exp_dir = "DeepSDFStruct/trained_models/analytic_round_cross"
81
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
82
+ device = "cpu"
83
+ reconstruct_meshs_from_latent(exp_dir, filetype="obj", device=device)
84
+ create_interpolated_meshes_from_latent(exp_dir, [1, 2, 3], 4, device=device)
85
+
86
+
87
+ def test_cpp_file_export():
88
+ exp_dir = "DeepSDFStruct/trained_models/analytic_round_cross"
89
+ model = get_model(exp_dir)
90
+ model.export_libtorch_executable("tests/tmp_outputs/test_cpp_model.pt")
91
+
92
+
93
+ if __name__ == "__main__":
94
+ import warnings
95
+
96
+ warnings.filterwarnings("error")
97
+ torch.set_default_dtype(torch.float32)
98
+ torch.set_default_device("cpu")
99
+ data_dir = snapshot_download_with_retry(
100
+ "mkofler/lattice_structure_unit_cells",
101
+ repo_type="dataset",
102
+ revision=REVISION,
103
+ ignore_patterns=["*.stl", "**/*.stl"],
104
+ )
105
+ test_train_homogenization_model(data_dir)
106
+ test_train_hierarchical_model(data_dir)
107
+ test_train_model(data_dir)
108
+ test_continue_from(data_dir)
109
+ test_latent_recon()
110
+ test_cpp_file_export()
@@ -0,0 +1,41 @@
1
+ import json
2
+ import os
3
+
4
+ from DeepSDFStruct.deep_sdf.training import save_latent_code_data_map
5
+ import DeepSDFStruct.deep_sdf.workspace as ws
6
+
7
+
8
+ def test_save_latent_code_data_map(tmp_path):
9
+ experiment_directory = str(tmp_path / "experiment")
10
+ data_source = str(tmp_path / "data_source")
11
+ npz_filenames = [
12
+ "dataset_a/class_1/instance_1.npz",
13
+ "dataset_b/class_2/instance_2.npz",
14
+ ]
15
+
16
+ save_latent_code_data_map(experiment_directory, data_source, npz_filenames)
17
+
18
+ mapping_filename = ws.get_latent_code_data_map_filename(experiment_directory)
19
+ assert os.path.isfile(mapping_filename)
20
+
21
+ with open(mapping_filename, "r", encoding="utf-8") as f:
22
+ latent_code_data_map = json.load(f)
23
+
24
+ assert latent_code_data_map["data_source"] == data_source
25
+ assert latent_code_data_map["sdf_samples_subdir"] == ws.sdf_samples_subdir
26
+ assert latent_code_data_map["latent_codes"] == [
27
+ {
28
+ "latent_index": 0,
29
+ "relative_npz_filename": npz_filenames[0],
30
+ "npz_filename": os.path.join(
31
+ data_source, ws.sdf_samples_subdir, npz_filenames[0]
32
+ ),
33
+ },
34
+ {
35
+ "latent_index": 1,
36
+ "relative_npz_filename": npz_filenames[1],
37
+ "npz_filename": os.path.join(
38
+ data_source, ws.sdf_samples_subdir, npz_filenames[1]
39
+ ),
40
+ },
41
+ ]
File without changes