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,357 @@
1
+ """
2
+ Structural Optimization Utilities
3
+ ==================================
4
+
5
+ This module provides tools for gradient-based optimization of SDF-based geometries,
6
+ with a focus on structural design problems. It integrates with TorchFEM for
7
+ finite element analysis and provides optimization algorithms suitable for
8
+ constrained design problems.
9
+
10
+ Key Features
11
+ ------------
12
+
13
+ MMA Optimizer
14
+ Implementation of the Method of Moving Asymptotes (MMA), a gradient-based
15
+ algorithm well-suited for structural optimization with nonlinear constraints.
16
+ MMA is particularly effective for:
17
+ - Topology optimization
18
+ - Shape optimization with constraints
19
+ - Problems with expensive objective evaluations
20
+ - Highly nonlinear design spaces
21
+
22
+ Finite Element Integration
23
+ - Conversion between TorchFEM and PyVista mesh formats
24
+ - Support for tetrahedral and hexahedral elements
25
+ - Linear and quadratic element types
26
+ - Integration with gradient computation
27
+
28
+ Mesh Quality Utilities
29
+ - Signed volume computation for tetrahedra
30
+ - Mesh quality metrics
31
+ - Degeneracy detection
32
+
33
+ The module is designed to work seamlessly with differentiable SDF representations,
34
+ enabling gradient-based optimization of complex 3D structures.
35
+ """
36
+
37
+ import torchfem.materials
38
+ import torchfem.solid
39
+ from torchfem.elements import Hexa1, Hexa2, Tetra1, Tetra2
40
+ import torch
41
+ import numpy as np
42
+ from mmapy import mmasub
43
+ import pyvista
44
+ import logging
45
+ import DeepSDFStruct
46
+
47
+ logger = logging.getLogger(DeepSDFStruct.__name__)
48
+
49
+
50
+ def get_mesh_from_torchfem(Solid: torchfem.Solid) -> pyvista.UnstructuredGrid:
51
+ """Convert a TorchFEM Solid mesh to PyVista UnstructuredGrid.
52
+
53
+ This function enables visualization and export of TorchFEM finite element
54
+ meshes using PyVista. It supports both tetrahedral and hexahedral elements
55
+ with linear and quadratic shape functions.
56
+
57
+ Parameters
58
+ ----------
59
+ Solid : torchfem.Solid
60
+ TorchFEM solid mesh object containing nodes, elements, and element type.
61
+
62
+ Returns
63
+ -------
64
+ pyvista.UnstructuredGrid
65
+ PyVista mesh representation suitable for visualization and I/O.
66
+
67
+ Raises
68
+ ------
69
+ NotImplementedError
70
+ If input is not a torchfem.Solid object.
71
+
72
+ Notes
73
+ -----
74
+ Supported element types:
75
+ - Tetra1: 4-node linear tetrahedron
76
+ - Tetra2: 10-node quadratic tetrahedron
77
+ - Hexa1: 8-node linear hexahedron
78
+ - Hexa2: 20-node quadratic hexahedron
79
+
80
+ Examples
81
+ --------
82
+ >>> from DeepSDFStruct.optimization import get_mesh_from_torchfem
83
+ >>> import torchfem
84
+ >>>
85
+ >>> # Assume we have a TorchFEM solid mesh
86
+ >>> # solid = torchfem.Solid(...)
87
+ >>>
88
+ >>> # Convert to PyVista for visualization
89
+ >>> pv_mesh = get_mesh_from_torchfem(solid)
90
+ >>> pv_mesh.plot()
91
+ """
92
+ if not isinstance(Solid, torchfem.Solid):
93
+ raise NotImplementedError("Currently only solid mesh is supported.")
94
+ # VTK cell types
95
+ etype = Solid.etype
96
+
97
+ if etype is Tetra1 or isinstance(etype, Tetra1):
98
+ cell_types = [pyvista.CellType.TETRA] * Solid.n_elem
99
+ elif etype is Tetra2 or isinstance(etype, Tetra2):
100
+ cell_types = [pyvista.CellType.QUADRATIC_TETRA] * Solid.n_elem
101
+ elif etype is Hexa1 or isinstance(etype, Hexa1):
102
+ cell_types = [pyvista.CellType.HEXAHEDRON] * Solid.n_elem
103
+ elif etype is Hexa2 or isinstance(etype, Hexa2):
104
+ cell_types = [pyvista.CellType.QUADRATIC_HEXAHEDRON] * Solid.n_elem
105
+ else:
106
+ raise TypeError(f"Unsupported element type: {etype} ({type(etype)})")
107
+
108
+ # VTK element list
109
+ el = len(Solid.elements[0]) * torch.ones(Solid.n_elem, dtype=Solid.elements.dtype)
110
+ elements = torch.cat([el[:, None], Solid.elements], dim=1).view(-1).tolist()
111
+
112
+ # Deformed node positions
113
+ pos = Solid.nodes
114
+
115
+ # Create unstructured mesh
116
+ mesh = pyvista.UnstructuredGrid(elements, cell_types, pos.tolist())
117
+ return mesh
118
+
119
+
120
+ def tet_signed_vol(vertices, tets):
121
+ """Compute signed volumes of tetrahedral elements.
122
+
123
+ Calculates the signed volume of each tetrahedron, which is positive for
124
+ correctly oriented elements and negative for inverted elements. This is
125
+ useful for detecting mesh degeneracies and enforcing mesh quality constraints.
126
+
127
+ Parameters
128
+ ----------
129
+ vertices : torch.Tensor
130
+ Vertex coordinates of shape (N, 3).
131
+ tets : torch.Tensor
132
+ Tetrahedral connectivity of shape (M, 4), where each row contains
133
+ vertex indices [v0, v1, v2, v3].
134
+
135
+ Returns
136
+ -------
137
+ torch.Tensor
138
+ Signed volumes of shape (M,), one per tetrahedron. Positive volumes
139
+ indicate correctly oriented elements.
140
+
141
+ Notes
142
+ -----
143
+ The signed volume is computed as:
144
+ V = (1/6) * ((v1-v0) × (v2-v0)) · (v3-v0)
145
+
146
+ Examples
147
+ --------
148
+ >>> import torch
149
+ >>> from DeepSDFStruct.optimization import tet_signed_vol
150
+ >>>
151
+ >>> # Define a simple tetrahedron
152
+ >>> vertices = torch.tensor([
153
+ ... [0.0, 0.0, 0.0],
154
+ ... [1.0, 0.0, 0.0],
155
+ ... [0.0, 1.0, 0.0],
156
+ ... [0.0, 0.0, 1.0]
157
+ ... ])
158
+ >>> tets = torch.tensor([[0, 1, 2, 3]])
159
+ >>> volumes = tet_signed_vol(vertices, tets)
160
+ >>> print(f"Volume: {volumes[0]:.3f}") # Should be 1/6 ≈ 0.167
161
+ """
162
+ v0 = vertices[tets[:, 0]]
163
+ v1 = vertices[tets[:, 1]]
164
+ v2 = vertices[tets[:, 2]]
165
+ v3 = vertices[tets[:, 3]]
166
+ vols = torch.einsum("ij,ij->i", torch.cross(v1 - v0, v2 - v0, dim=1), v3 - v0) / 6.0
167
+ return vols
168
+
169
+
170
+ class MMA:
171
+ """Method of Moving Asymptotes (MMA) optimizer for constrained problems.
172
+
173
+ MMA is a gradient-based optimization algorithm designed for nonlinear
174
+ constrained problems. It constructs convex subproblems using moving
175
+ asymptotes and is particularly effective for structural optimization.
176
+
177
+ The optimizer handles a single objective function and a single constraint,
178
+ with box bounds on design variables. It automatically normalizes the
179
+ objective by its initial value for better numerical behavior.
180
+
181
+ Parameters
182
+ ----------
183
+ parameters : torch.Tensor
184
+ Initial design variables (will be optimized in-place).
185
+ bounds : array-like of shape (n, 2)
186
+ Box constraints [[lower_1, upper_1], ..., [lower_n, upper_n]]
187
+ for each design variable.
188
+ max_step : float, default 0.1
189
+ Maximum allowed change in design variables per iteration,
190
+ as a fraction of the bound range.
191
+
192
+ Attributes
193
+ ----------
194
+ parameters : torch.Tensor
195
+ Current design variables (updated in-place each iteration).
196
+ loop : int
197
+ Current iteration number.
198
+ x : ndarray
199
+ Current design variables in numpy format.
200
+ xold1, xold2 : ndarray
201
+ Design variables from previous two iterations (for MMA history).
202
+
203
+ Methods
204
+ -------
205
+ step(F, dF, G, dG)
206
+ Perform one MMA optimization step given objective, constraint,
207
+ and their gradients.
208
+
209
+ Notes
210
+ -----
211
+ MMA was developed by Krister Svanberg and is widely used in topology
212
+ optimization. It is particularly effective for problems where:
213
+ - The objective and constraints are expensive to evaluate
214
+ - Gradients are available (via automatic differentiation)
215
+ - The design space is high-dimensional
216
+ - Strong nonlinearity is present
217
+
218
+ The implementation uses the mmapy package for the core MMA algorithm.
219
+
220
+ Examples
221
+ --------
222
+ >>> import torch
223
+ >>> from DeepSDFStruct.optimization import MMA
224
+ >>>
225
+ >>> # Define design variables
226
+ >>> params = torch.ones(10, requires_grad=True)
227
+ >>> bounds = [[0.0, 2.0]] * 10
228
+ >>>
229
+ >>> # Create optimizer
230
+ >>> optimizer = MMA(params, bounds, max_step=0.1)
231
+ >>>
232
+ >>> # Optimization loop
233
+ >>> for i in range(100):
234
+ ... # Compute objective and constraint
235
+ ... objective = (params ** 2).sum()
236
+ ... constraint = params.sum() - 5.0
237
+ ...
238
+ ... # Compute gradients
239
+ ... dF = torch.autograd.grad(objective, params, create_graph=True)[0]
240
+ ... dG = torch.autograd.grad(constraint, params, create_graph=True)[0]
241
+ ...
242
+ ... # MMA step
243
+ ... optimizer.step(objective, dF, constraint, dG)
244
+ ...
245
+ ... if optimizer.ch < 1e-3:
246
+ ... break
247
+
248
+ References
249
+ ----------
250
+ .. [1] Svanberg, K. (1987). "The method of moving asymptotes—a new method
251
+ for structural optimization." International Journal for Numerical
252
+ Methods in Engineering, 24(2), 359-373.
253
+ .. [2] mmapy: Python implementation of MMA
254
+ https://github.com/arjendeetman/mmapy
255
+ """
256
+
257
+ def __init__(self, parameters, bounds, max_step=0.1):
258
+ self.max_step = max_step
259
+ self.bounds = np.array(bounds)
260
+ self.parameters = parameters
261
+ self.m = 1
262
+ self.n = len(parameters)
263
+ self.x = parameters.detach().cpu().numpy()
264
+ self.xold1 = parameters.detach().cpu().numpy()
265
+ self.xold2 = parameters.detach().cpu().numpy()
266
+ self.low = []
267
+ self.upp = []
268
+ self.a0_MMA = 1
269
+ self.a_MMA = np.zeros((self.m, 1))
270
+ self.c_MMA = 10000 * np.ones((self.m, 1))
271
+ self.d_MMA = np.zeros((self.m, 1))
272
+
273
+ self.loop = 0
274
+ self.ch = 1.0
275
+ self.F0 = None
276
+
277
+ def step(self, F, dF, G, dG):
278
+ """Perform one MMA optimization step.
279
+
280
+ Updates design variables by solving a convex subproblem constructed
281
+ from the objective, constraint, and their gradients.
282
+
283
+ Parameters
284
+ ----------
285
+ F : torch.Tensor or float
286
+ Objective function value at current design.
287
+ dF : torch.Tensor
288
+ Gradient of objective w.r.t. design variables, shape (n,).
289
+ G : torch.Tensor or float
290
+ Constraint function value at current design (≤ 0 is feasible).
291
+ dG : torch.Tensor
292
+ Gradient of constraint w.r.t. design variables, shape (n,).
293
+
294
+ Notes
295
+ -----
296
+ The method automatically:
297
+ - Normalizes the objective by its initial value
298
+ - Enforces move limits based on max_step
299
+ - Updates MMA history (xold1, xold2)
300
+ - Computes and logs convergence metric (ch)
301
+ - Updates self.parameters in-place
302
+
303
+ The convergence metric ch is the relative change in design variables.
304
+ """
305
+ orig_shape = dF.shape
306
+ F_np = F.detach().cpu().numpy().reshape(-1, 1)
307
+ dFdx_np = dF.detach().cpu().numpy().reshape(-1, 1)
308
+ G_np = G.detach().cpu().numpy().reshape(-1, 1)
309
+ dGdx_np = dG.detach().cpu().numpy().reshape(1, -1)
310
+ if self.loop == 0:
311
+ self.F0 = F_np
312
+ F_np = F_np / self.F0
313
+ dFdx_np = dFdx_np / self.F0
314
+
315
+ xmin = np.maximum(self.x - self.max_step, self.bounds[:, 0:1])
316
+ xmax = np.minimum(self.x + self.max_step, self.bounds[:, 1:2])
317
+ move = 0.1
318
+ self.loop = self.loop + 1
319
+ xmma, ymma, zmma, lam, xsi, eta, muMMA, zet, s, low, upp = mmasub(
320
+ self.m,
321
+ self.n,
322
+ self.loop,
323
+ self.x,
324
+ xmin,
325
+ xmax,
326
+ self.xold1,
327
+ self.xold2,
328
+ F_np,
329
+ dFdx_np,
330
+ G_np,
331
+ dGdx_np,
332
+ self.low,
333
+ self.upp,
334
+ self.a0_MMA,
335
+ self.a_MMA,
336
+ self.c_MMA,
337
+ self.d_MMA,
338
+ )
339
+
340
+ self.xold2 = self.xold1.copy()
341
+ self.xold1 = self.x.copy()
342
+ self.x = xmma
343
+ self.upp = upp
344
+ self.low = low
345
+
346
+ ch = np.abs(np.mean(self.x.T - self.xold1.T) / np.mean(self.x.T))
347
+ with torch.no_grad():
348
+ self.parameters.copy_(
349
+ torch.tensor(
350
+ xmma, dtype=self.parameters.dtype, device=self.parameters.device
351
+ )
352
+ )
353
+ logger.info(
354
+ "It.: {0:4} | J.: {1:1.3e} | Constr.: {2:1.3e} | ch.: {3:1.3e}".format(
355
+ self.loop, F_np[0][0], G_np[0][0], ch
356
+ )
357
+ )
@@ -0,0 +1,215 @@
1
+ """
2
+ Parametrization Functions for Spatially-Varying Properties
3
+ ===========================================================
4
+
5
+ This module provides parametrization functions that map from spatial coordinates
6
+ to parameter values, enabling SDFs with spatially-varying properties. This is
7
+ particularly useful for lattice structures where properties like strut thickness
8
+ or material density vary across the structure.
9
+
10
+ Classes
11
+ -------
12
+ Constant
13
+ Spatially-constant parameter that is independent of position.
14
+ Useful as a baseline or for uniform structures.
15
+
16
+ SplineParametrization
17
+ B-spline-based parametrization providing smooth spatial variation.
18
+ Parameters vary smoothly according to a spline function.
19
+
20
+ Both classes inherit from torch.nn.Module, making their parameters
21
+ automatically discoverable for gradient-based optimization.
22
+
23
+ Examples
24
+ --------
25
+ Create a lattice with varying thickness::
26
+
27
+ from DeepSDFStruct.parametrization import SplineParametrization
28
+ from DeepSDFStruct.torch_spline import TorchSpline
29
+ import splinepy
30
+
31
+ # Create a B-spline for thickness variation
32
+ spline = splinepy.BSpline(
33
+ degrees=[2, 2, 2],
34
+ control_points=...,
35
+ knot_vectors=...
36
+ )
37
+
38
+ # Wrap in parametrization module
39
+ param_func = SplineParametrization(spline)
40
+
41
+ # Use in lattice structure
42
+ lattice = LatticeSDFStruct(
43
+ tiling=[3, 3, 3],
44
+ microtile=unit_cell,
45
+ parametrization=param_func
46
+ )
47
+ """
48
+
49
+ import torch
50
+ import torch.nn as nn
51
+ import splinepy as sp
52
+ from DeepSDFStruct.torch_spline import TorchSpline
53
+
54
+
55
+ class Constant(nn.Module):
56
+ """Spatially-constant parameter value.
57
+
58
+ This parametrization returns the same parameter value(s) for all query
59
+ points, independent of position. Useful for uniform structures or as
60
+ a simple baseline.
61
+
62
+ The parameter is stored as a torch.nn.Parameter, making it automatically
63
+ discoverable for optimization.
64
+
65
+ Parameters
66
+ ----------
67
+ value : float, list, or torch.Tensor
68
+ The constant parameter value(s). Can be scalar or vector.
69
+ device : str or torch.device, optional
70
+ Device for computation ('cpu' or 'cuda').
71
+ dtype : torch.dtype, optional
72
+ Data type for the parameter.
73
+
74
+ Attributes
75
+ ----------
76
+ param : torch.nn.Parameter
77
+ The learnable constant parameter.
78
+
79
+ Examples
80
+ --------
81
+ >>> from DeepSDFStruct.parametrization import Constant
82
+ >>> import torch
83
+ >>>
84
+ >>> # Scalar constant
85
+ >>> const = Constant(0.5)
86
+ >>> queries = torch.rand(100, 3)
87
+ >>> params = const(queries)
88
+ >>> print(params.shape) # (100, 1)
89
+ >>> print(params[0]) # tensor([0.5])
90
+ >>>
91
+ >>> # Vector constant
92
+ >>> const_vec = Constant([0.5, 1.0, 1.5])
93
+ >>> params = const_vec(queries)
94
+ >>> print(params.shape) # (100, 3)
95
+ """
96
+
97
+ def __init__(self, value, device=None, dtype=None):
98
+ super().__init__()
99
+ if not isinstance(value, torch.Tensor):
100
+ value = torch.tensor(value, device=device, dtype=dtype)
101
+ self.param = nn.Parameter(value)
102
+
103
+ def forward(self, queries: torch.Tensor) -> torch.Tensor:
104
+ """Evaluate constant parameter at query points.
105
+
106
+ Parameters
107
+ ----------
108
+ queries : torch.Tensor
109
+ Query point coordinates of shape (N, d). The spatial
110
+ coordinates are ignored.
111
+
112
+ Returns
113
+ -------
114
+ torch.Tensor
115
+ Parameter values of shape (N, m) where m is the
116
+ parameter dimension. All rows are identical.
117
+ """
118
+ N = queries.shape[0]
119
+ return self.param.expand(N, -1)
120
+
121
+ def set_param(self, new_value: torch.Tensor):
122
+ """Update the constant parameter value.
123
+
124
+ Parameters
125
+ ----------
126
+ new_value : torch.Tensor
127
+ New parameter value(s).
128
+ """
129
+ with torch.no_grad():
130
+ self.param.copy_(new_value.to(self.param.device))
131
+
132
+
133
+ class SplineParametrization(nn.Module):
134
+ """B-spline-based spatially-varying parametrization.
135
+
136
+ Uses a B-spline, Bezier, or NURBS function to provide smoothly-varying
137
+ parameters across space. The spline control points are learnable parameters
138
+ that can be optimized.
139
+
140
+ This is useful for creating gradual transitions in material properties,
141
+ thickness variations, or other spatially-dependent design variables.
142
+
143
+ Parameters
144
+ ----------
145
+ spline : splinepy.BSpline, splinepy.Bezier, or splinepy.NURBS
146
+ The spline function defining parameter variation. The spline's
147
+ output dimension determines the parameter dimension.
148
+ device : str or torch.device, optional
149
+ Device for computation ('cpu' or 'cuda').
150
+
151
+ Attributes
152
+ ----------
153
+ torch_spline : TorchSpline
154
+ PyTorch-compatible wrapper for the spline.
155
+
156
+ Examples
157
+ --------
158
+ >>> from DeepSDFStruct.parametrization import SplineParametrization
159
+ >>> import splinepy
160
+ >>> import torch
161
+ >>>
162
+ >>> # Create a 3D B-spline for thickness variation
163
+ >>> spline = splinepy.BSpline(
164
+ ... degrees=[2, 2, 2],
165
+ ... control_points=torch.rand(27, 1), # 3x3x3 grid, 1D output
166
+ ... knot_vectors=[
167
+ ... [0, 0, 0, 1, 1, 1],
168
+ ... [0, 0, 0, 1, 1, 1],
169
+ ... [0, 0, 0, 1, 1, 1]
170
+ ... ]
171
+ ... )
172
+ >>>
173
+ >>> # Create parametrization
174
+ >>> param = SplineParametrization(spline)
175
+ >>>
176
+ >>> # Evaluate at query points
177
+ >>> queries = torch.rand(100, 3)
178
+ >>> thickness = param(queries)
179
+ >>> print(thickness.shape) # (100, 1)
180
+ """
181
+
182
+ def __init__(self, spline: sp.BSpline | sp.Bezier | sp.NURBS, device=None):
183
+ super().__init__()
184
+ self.torch_spline = TorchSpline(spline, device=device)
185
+
186
+ def forward(self, queries: torch.Tensor) -> torch.Tensor:
187
+ """Evaluate spline parametrization at query points.
188
+
189
+ Parameters
190
+ ----------
191
+ queries : torch.Tensor
192
+ Query point coordinates of shape (N, d) where d matches
193
+ the spline's parametric dimension.
194
+
195
+ Returns
196
+ -------
197
+ torch.Tensor
198
+ Parameter values of shape (N, m) where m is the spline's
199
+ output dimension (number of parameter values per point).
200
+ """
201
+ return self.torch_spline(queries)
202
+
203
+ def set_param(self, new_value: torch.Tensor):
204
+ """Update the spline control points.
205
+
206
+ Parameters
207
+ ----------
208
+ new_value : torch.Tensor
209
+ New control point values. Shape must match the spline's
210
+ control point array.
211
+ """
212
+ with torch.no_grad():
213
+ self.torch_spline.control_points.copy_(
214
+ new_value.to(self.torch_spline.control_points.device)
215
+ )
@@ -0,0 +1,128 @@
1
+ """
2
+ Pretrained DeepSDF Models
3
+ ========================
4
+
5
+ This module provides access to pretrained DeepSDF neural network models for
6
+ common microstructure geometries. These models can be used directly for
7
+ geometry generation or as starting points for transfer learning.
8
+
9
+ Available Models
10
+ ----------------
11
+
12
+ ChiAndCross
13
+ Chi-shaped and cross-shaped lattice structures, commonly used in
14
+ mechanical metamaterials.
15
+
16
+ AnalyticRoundCross
17
+ Round cross-section variations with analytical parameterization,
18
+ useful for smooth stress distribution.
19
+
20
+ RoundCross
21
+ Standard round cross structures with various connectivity patterns.
22
+
23
+ Primitives
24
+ Basic 3D geometric primitives (spheres, cylinders, cubes, etc.)
25
+ for building more complex structures.
26
+
27
+ Primitives2D
28
+ 2D geometric primitives for planar structures and cross-sections.
29
+
30
+ Functions
31
+ ---------
32
+
33
+ get_model(model, checkpoint='latest', device=None)
34
+ Load a pretrained model by name or enum value. Returns a DeepSDFModel
35
+ ready for inference.
36
+
37
+ list_available_models()
38
+ Get a list of all available pretrained models.
39
+
40
+ The pretrained models are stored within the package and loaded on demand,
41
+ enabling quick prototyping and exploration without requiring training.
42
+
43
+ Examples
44
+ --------
45
+ Load and use a pretrained model::
46
+
47
+ from DeepSDFStruct.pretrained_models import get_model, PretrainedModels
48
+ import torch
49
+
50
+ # Load a model
51
+ model = get_model(PretrainedModels.RoundCross)
52
+
53
+ # Generate geometry with latent code
54
+ latent_code = torch.zeros(256) # Use learned latent vector
55
+ points = torch.rand(1000, 3)
56
+ sdf_values = model(points, latent_code)
57
+
58
+ List available models::
59
+
60
+ from DeepSDFStruct.pretrained_models import list_available_models
61
+
62
+ models = list_available_models()
63
+ for model in models:
64
+ print(f"Available model: {model.value}")
65
+ """
66
+
67
+ from enum import Enum
68
+ import importlib.resources
69
+ from DeepSDFStruct.deep_sdf.workspace import load_trained_model, load_latent_vectors
70
+ from DeepSDFStruct.deep_sdf.models import DeepSDFModel
71
+ import torch
72
+
73
+
74
+ class PretrainedModels(Enum):
75
+ ChiAndCross = "chi_and_cross"
76
+ AnalyticRoundCross = "analytic_round_cross"
77
+ RoundCross = "round_cross"
78
+ Primitives = "primitives"
79
+ Primitives2D = "primitives_2d"
80
+
81
+
82
+ main_dir = importlib.resources.files("DeepSDFStruct")
83
+ with importlib.resources.as_file(main_dir) as path:
84
+ PRETRAINED_MODELS_DIR = path / "trained_models"
85
+
86
+ # Maps enum entries to file paths
87
+ _MODEL_REGISTRY = {
88
+ PretrainedModels.ChiAndCross: PRETRAINED_MODELS_DIR / "chi_and_cross",
89
+ PretrainedModels.AnalyticRoundCross: PRETRAINED_MODELS_DIR / "analytic_round_cross",
90
+ PretrainedModels.RoundCross: PRETRAINED_MODELS_DIR / "round_cross",
91
+ PretrainedModels.Primitives: PRETRAINED_MODELS_DIR / "primitives",
92
+ PretrainedModels.Primitives2D: PRETRAINED_MODELS_DIR / "primitives_2d",
93
+ }
94
+
95
+
96
+ def get_model(
97
+ model: str | PretrainedModels, checkpoint: str = "latest", device=None
98
+ ) -> DeepSDFModel:
99
+ """
100
+ Load a pretrained model by name or enum.
101
+
102
+ Args:
103
+ model (str | PretrainedModels): model identifier
104
+ checkpoint (str): checkpoint file name (default: 'latest')
105
+
106
+ Returns:
107
+ Trained PyTorch model
108
+ """
109
+ if isinstance(model, str):
110
+ path = model
111
+ else:
112
+ model_enum = model
113
+ path = _MODEL_REGISTRY.get(model_enum)
114
+
115
+ if device is None:
116
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
117
+
118
+ if not path:
119
+ raise ValueError(f"Model path {path} not found.")
120
+ decoder = load_trained_model(path, checkpoint, device=device)
121
+ latent_vectors = load_latent_vectors(path, checkpoint, device=device)
122
+ decoder.eval()
123
+ deep_sdf_model = DeepSDFModel(decoder, latent_vectors, device=device)
124
+ return deep_sdf_model
125
+
126
+
127
+ def list_available_models():
128
+ return list(PretrainedModels)