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.
- DeepSDFStruct/SDF.py +1734 -0
- DeepSDFStruct/__init__.py +66 -0
- DeepSDFStruct/deep_sdf/__init__.py +69 -0
- DeepSDFStruct/deep_sdf/create_screenshots_from_plyfiles.py +65 -0
- DeepSDFStruct/deep_sdf/data.py +258 -0
- DeepSDFStruct/deep_sdf/metrics/__init__.py +14 -0
- DeepSDFStruct/deep_sdf/metrics/mesh_to_analytical.py +24 -0
- DeepSDFStruct/deep_sdf/models.py +197 -0
- DeepSDFStruct/deep_sdf/networks/__init__.py +8 -0
- DeepSDFStruct/deep_sdf/networks/analytic_round_cross.py +105 -0
- DeepSDFStruct/deep_sdf/networks/deep_sdf_decoder.py +155 -0
- DeepSDFStruct/deep_sdf/networks/hierarchical_deep_sdf_decoder.py +139 -0
- DeepSDFStruct/deep_sdf/networks/hierarchical_positional_sdf_decoder.py +190 -0
- DeepSDFStruct/deep_sdf/networks/resnet_positional_sdf_decoder.py +108 -0
- DeepSDFStruct/deep_sdf/nn_utils.py +70 -0
- DeepSDFStruct/deep_sdf/plotting.py +143 -0
- DeepSDFStruct/deep_sdf/reconstruction.py +165 -0
- DeepSDFStruct/deep_sdf/training.py +840 -0
- DeepSDFStruct/deep_sdf/workspace.py +363 -0
- DeepSDFStruct/design_of_experiments.py +235 -0
- DeepSDFStruct/flexicubes/__init__.py +15 -0
- DeepSDFStruct/flexicubes/flexicubes.py +1005 -0
- DeepSDFStruct/flexicubes/tables.py +2338 -0
- DeepSDFStruct/flexisquares/__init__.py +15 -0
- DeepSDFStruct/flexisquares/flexisquares.py +967 -0
- DeepSDFStruct/flexisquares/tables.py +63 -0
- DeepSDFStruct/lattice_structure.py +254 -0
- DeepSDFStruct/local_shapes.py +226 -0
- DeepSDFStruct/mesh.py +1184 -0
- DeepSDFStruct/optimization.py +357 -0
- DeepSDFStruct/parametrization.py +215 -0
- DeepSDFStruct/pretrained_models.py +128 -0
- DeepSDFStruct/sampling.py +670 -0
- DeepSDFStruct/sdf_operations.py +990 -0
- DeepSDFStruct/sdf_primitives.py +1416 -0
- DeepSDFStruct/splinepy_unitcells/__init__.py +35 -0
- DeepSDFStruct/splinepy_unitcells/chi_3D.py +1617 -0
- DeepSDFStruct/splinepy_unitcells/cross_lattice.py +165 -0
- DeepSDFStruct/splinepy_unitcells/double_lattice_extruded.py +298 -0
- DeepSDFStruct/splinepy_unitcells/snappy_3d.py +544 -0
- DeepSDFStruct/torch_spline.py +446 -0
- DeepSDFStruct/trained_models/analytic_round_cross/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/analytic_round_cross/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/analytic_round_cross/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/analytic_round_cross/specs.json +45 -0
- DeepSDFStruct/trained_models/chi_and_cross/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/chi_and_cross/Logs.pth +0 -0
- DeepSDFStruct/trained_models/chi_and_cross/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/chi_and_cross/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/chi_and_cross/specs.json +76 -0
- DeepSDFStruct/trained_models/primitives/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives/specs.json +46 -0
- DeepSDFStruct/trained_models/primitives_2d/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives_2d/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives_2d/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives_2d/specs.json +46 -0
- DeepSDFStruct/trained_models/round_cross/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/round_cross/Logs.pth +0 -0
- DeepSDFStruct/trained_models/round_cross/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/round_cross/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/round_cross/specs.json +45 -0
- DeepSDFStruct/trained_models/test_experiment/LatentCodes/latent_code_data_map.json +106 -0
- DeepSDFStruct/trained_models/test_experiment/specs.json +45 -0
- DeepSDFStruct/trained_models/test_experiment/training_summary.json +10 -0
- DeepSDFStruct/trained_models/test_experiment_hierarchical/LatentCodes/latent_code_data_map.json +106 -0
- DeepSDFStruct/trained_models/test_experiment_hierarchical/specs.json +45 -0
- DeepSDFStruct/trained_models/test_experiment_hierarchical/training_summary.json +10 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/LatentCodes/latent_code_data_map.json +5006 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/Logs.png +0 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/specs.json +83 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/training_summary.json +10 -0
- DeepSDFStruct/utils.py +96 -0
- DeepSDFStruct/visualization/latent_viewer.py +211 -0
- DeepSDFStruct/visualization/latent_viewer_cli.py +8 -0
- benchmarks/generate_sdf_showcase.py +622 -0
- benchmarks/sdf_from_mesh_benchmark.py +52 -0
- benchmarks/sdf_from_mesh_comparison.md +33 -0
- benchmarks/sdf_showcase/operations/boolean/difference_sphere_box.png +0 -0
- benchmarks/sdf_showcase/operations/boolean/union_sphere_box.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/circular_array_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/dilate_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/mirror_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/repeat_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/revolve_circle.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/shell_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/twist_torus.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/circle.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/equilateral_triangle.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/hexagon.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/polygon_pentagon.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/rectangle.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/rounded_rectangle.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/box.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/capped_cone.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/capped_cylinder.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/capsule.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/cone.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/corner_spheres.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/cross_ms.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/cylinder.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/dodecahedron.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/ellipsoid.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/icosahedron.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/octahedron.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/pyramid.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/rounded_box.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/rounded_cone.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/rounded_cylinder.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/sphere.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/tetrahedron.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/torus.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/wireframe_box.png +0 -0
- deepsdfstruct-1.7.2.dist-info/METADATA +44 -0
- deepsdfstruct-1.7.2.dist-info/RECORD +165 -0
- deepsdfstruct-1.7.2.dist-info/WHEEL +5 -0
- deepsdfstruct-1.7.2.dist-info/licenses/LICENSE +208 -0
- deepsdfstruct-1.7.2.dist-info/licenses/NOTICE +67 -0
- deepsdfstruct-1.7.2.dist-info/scm_file_list.json +178 -0
- deepsdfstruct-1.7.2.dist-info/scm_version.json +8 -0
- deepsdfstruct-1.7.2.dist-info/top_level.txt +4 -0
- docs/Makefile +20 -0
- docs/api_reference.rst +6 -0
- docs/conf.py +60 -0
- docs/index.rst +136 -0
- docs/make.bat +35 -0
- docs/qr_code_github.png +0 -0
- docs/qr_code_paper.png +0 -0
- docs/readme_images/example_output_01.png +0 -0
- docs/readme_images/example_output_02.png +0 -0
- docs/readme_images/example_output_03.png +0 -0
- docs/readme_images/example_output_04.png +0 -0
- tests/data/chairs/1005.obj +282 -0
- tests/data/chairs/1024.obj +222 -0
- tests/data/chairs/README.md +1 -0
- tests/data/circular_balls_test_case.stl +0 -0
- tests/data/cone.stl +1794 -0
- tests/data/example_disconnectd_mesh.inp +1784 -0
- tests/data/example_line_mesh.vtk +0 -0
- tests/data/stanford_bunny.stl +0 -0
- tests/data/sweep_test_case.stl +0 -0
- tests/test_DOE.py +49 -0
- tests/test_elongate_sdf.py +195 -0
- tests/test_flexisquares.py +187 -0
- tests/test_generate_dataset.py +76 -0
- tests/test_lattice_evaluation.py +137 -0
- tests/test_mesh_export.py +137 -0
- tests/test_mesh_functions.py +49 -0
- tests/test_networks.py +71 -0
- tests/test_pretrained_models.py +40 -0
- tests/test_reconstruction.py +101 -0
- tests/test_sdf_from_mesh.py +52 -0
- tests/test_sdf_functions.py +117 -0
- tests/test_sdf_primitives.py +668 -0
- tests/test_sdf_trimesh_comparison.py +482 -0
- tests/test_splinepy_unitcells.py +64 -0
- tests/test_structural_optimization.py +178 -0
- tests/test_torch_spline.py +216 -0
- tests/test_train_model.py +110 -0
- tests/test_training_data_ids.py +41 -0
- 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
|