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,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)
|