morphopt 3.2.0__tar.gz → 3.2.2__tar.gz

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 (119) hide show
  1. {morphopt-3.2.0 → morphopt-3.2.2}/PKG-INFO +2 -2
  2. {morphopt-3.2.0 → morphopt-3.2.2}/README.md +0 -0
  3. {morphopt-3.2.0 → morphopt-3.2.2}/pyproject.toml +2 -2
  4. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/__init__.py +11 -2
  5. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/codesign/__init__.py +6 -4
  6. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/codesign/constraints.py +27 -2
  7. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/codesign/feaparams.py +9 -4
  8. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/codesign/geometry.py +9 -6
  9. morphopt-3.2.2/src/morphopt/optcore/__init__.py +8 -0
  10. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/baseobject.py +0 -0
  11. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/controller.py +16 -2
  12. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/history.py +0 -0
  13. morphopt-3.2.2/src/morphopt/optcore/modelparams/__init__.py +5 -0
  14. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/baseparam.py +0 -0
  15. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/__init__.py +0 -0
  16. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/basefeainterface.py +0 -0
  17. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/bodyforceinterface.py +0 -0
  18. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/boundaryconditioninterface.py +0 -0
  19. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/contactinterface.py +0 -0
  20. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/coupleinterface.py +0 -0
  21. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/pointinterface.py +0 -0
  22. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/pressureinterface.py +0 -0
  23. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/referencepointinterface.py +0 -0
  24. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/springinterface.py +0 -0
  25. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feaparams.py +0 -0
  26. morphopt-3.2.2/src/morphopt/optcore/modelparams/geometry.py +251 -0
  27. morphopt-3.2.2/src/morphopt/optcore/modelparams/materialinterface/__init__.py +11 -0
  28. morphopt-3.2.2/src/morphopt/optcore/modelparams/materialinterface/basematerialinterface.py +69 -0
  29. morphopt-3.2.2/src/morphopt/optcore/modelparams/materialinterface/homogeneousmaterial.py +21 -0
  30. morphopt-3.2.2/src/morphopt/optcore/modelparams/materialinterface/materialmodels.py +111 -0
  31. morphopt-3.2.2/src/morphopt/optcore/modelparams/materials.py +175 -0
  32. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/params.py +5 -7
  33. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/objfunc.py +0 -0
  34. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/solver.py +0 -0
  35. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/updaters.py +0 -0
  36. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/__init__.py +3 -1
  37. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/geometryinterfaces/__init__.py +0 -0
  38. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/geometryparams.py +32 -18
  39. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objectivefuncs/__init__.py +0 -0
  40. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objectivefuncs/basefuncs.py +0 -0
  41. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objectivefuncs/boundarys.py +0 -0
  42. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objectivefuncs/surfacefairness.py +0 -0
  43. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objectivefuncs/volumemaximization.py +0 -0
  44. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objfunc.py +0 -0
  45. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/update_geometry.py +0 -0
  46. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/__init__.py +4 -1
  47. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/objectivefuncs/__init__.py +0 -0
  48. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/objectivefuncs/basefuncs.py +0 -0
  49. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/objectivefuncs/boundarys.py +0 -0
  50. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/objectivefuncs/densityfield.py +0 -0
  51. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/objectivefuncs/sensitivity.py +0 -0
  52. morphopt-3.2.2/src/morphopt/simp/objectivefuncs/volfrac.py +87 -0
  53. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/simpmaterial.py +216 -132
  54. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/solver.py +0 -0
  55. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/update_material.py +36 -25
  56. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/taskoptmization.py +0 -0
  57. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/codegen/generator.py +202 -47
  58. morphopt-3.2.2/src/morphopt/ui/model/__init__.py +20 -0
  59. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/model/problem.py +180 -29
  60. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/model/schemas.py +102 -52
  61. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/schemes/base.py +23 -3
  62. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/schemes/codesign.py +23 -11
  63. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/schemes/shapeopt.py +6 -2
  64. morphopt-3.2.2/src/morphopt/ui/schemes/simp.py +112 -0
  65. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/editor.py +61 -13
  66. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/model_tree.py +86 -6
  67. morphopt-3.2.2/src/morphopt/ui/widgets/torchfea_model_editor.py +428 -0
  68. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/values.py +1 -0
  69. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/viewer.py +95 -20
  70. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/workbench.py +51 -4
  71. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/utils/__init__.py +0 -0
  72. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/utils/gradient_check.py +0 -0
  73. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/utils/history_read.py +0 -0
  74. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt.egg-info/PKG-INFO +2 -2
  75. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt.egg-info/SOURCES.txt +5 -1
  76. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt.egg-info/dependency_links.txt +0 -0
  77. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt.egg-info/requires.txt +1 -1
  78. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt.egg-info/top_level.txt +0 -0
  79. {morphopt-3.2.0 → morphopt-3.2.2}/tests/test_fixedGeometry.py +0 -0
  80. {morphopt-3.2.0 → morphopt-3.2.2}/tests/test_path.py +0 -0
  81. morphopt-3.2.0/src/morphopt/codesign/material.py +0 -53
  82. morphopt-3.2.0/src/morphopt/optcore/__init__.py +0 -4
  83. morphopt-3.2.0/src/morphopt/optcore/modelparams/__init__.py +0 -5
  84. morphopt-3.2.0/src/morphopt/optcore/modelparams/geometry.py +0 -92
  85. morphopt-3.2.0/src/morphopt/optcore/modelparams/materials.py +0 -119
  86. morphopt-3.2.0/src/morphopt/simp/objectivefuncs/volfrac.py +0 -47
  87. morphopt-3.2.0/src/morphopt/ui/model/__init__.py +0 -16
  88. morphopt-3.2.0/src/morphopt/ui/schemes/simp.py +0 -96
  89. {morphopt-3.2.0 → morphopt-3.2.2}/setup.cfg +0 -0
  90. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/codesign/params.py +0 -0
  91. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/opt_runner.py +0 -0
  92. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/geometryinterfaces/basesurfaceinterface.py +0 -0
  93. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/geometryinterfaces/bspsurfaceinterface.py +0 -0
  94. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/geometryinterfaces/cpgeosurfaceinterface.py +0 -0
  95. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objectivefuncs/distancesurface.py +0 -0
  96. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objectivefuncs/shapederivative.py +0 -0
  97. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/__init__.py +0 -0
  98. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/__main__.py +0 -0
  99. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/app.py +0 -0
  100. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/codegen/__init__.py +0 -0
  101. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/i18n.py +0 -0
  102. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/launcher.py +0 -0
  103. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/mainwindow.py +0 -0
  104. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/model/loaders.py +0 -0
  105. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/observe_panel.py +0 -0
  106. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/schemes/__init__.py +0 -0
  107. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/schemes/snippets.py +0 -0
  108. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/__init__.py +0 -0
  109. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/code_completion.py +0 -0
  110. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/codeeditor.py +0 -0
  111. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/objective_editor.py +0 -0
  112. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/observation_pages.py +0 -0
  113. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/param_form.py +0 -0
  114. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/solver_editor.py +0 -0
  115. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/stepmatrix.py +0 -0
  116. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/template_insert_dialog.py +0 -0
  117. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/updater_editor.py +0 -0
  118. {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt.egg-info/entry_points.txt +0 -0
  119. {morphopt-3.2.0 → morphopt-3.2.2}/tests/test_ui.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: morphopt
3
- Version: 3.2.0
3
+ Version: 3.2.2
4
4
  Summary: A morphing optimization framework using PyTorch and Abaqus.
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
@@ -16,7 +16,7 @@ Requires-Dist: gmsh>=4.15.0
16
16
  Requires-Dist: tabulate>=0.9.0
17
17
  Requires-Dist: imageio>=2.37.3
18
18
  Requires-Dist: bspmap>=1.0.3
19
- Requires-Dist: torchfea>=1.1.0
19
+ Requires-Dist: torchfea>=1.1.1
20
20
  Requires-Dist: trimesh>=4.11.5
21
21
  Requires-Dist: networkx>=3.6.1
22
22
  Requires-Dist: scipy>=1.17.1
File without changes
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "morphopt"
3
- version = "3.2.0"
3
+ version = "3.2.2"
4
4
  description = "A morphing optimization framework using PyTorch and Abaqus."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -17,7 +17,7 @@ dependencies = [
17
17
  "tabulate>=0.9.0",
18
18
  "imageio>=2.37.3",
19
19
  "bspmap>=1.0.3",
20
- "torchfea>=1.1.0",
20
+ "torchfea>=1.1.1",
21
21
  "trimesh>=4.11.5",
22
22
  "networkx>=3.6.1",
23
23
  "scipy>=1.17.1",
@@ -2,6 +2,13 @@
2
2
  A differentiable structural optimization framework that eliminates manual sensitivity derivation by fusing the adjoint method with automatic differentiation (AD). The sensitivity corresponds to the virtual work of residual force derivatives on the adjoint displacement field, computed via a single backpropagation through the residual graph. MorphOpt provides a unified interface for geometry, load, and material definition, calls torchfea for GPU-accelerated differentiable nonlinear FEA and adjoint-AD sensitivity, and employs a trust-region optimizer with L-BFGS for design updates. Validated on SIMP topology optimization with B-spline density fields and pneumatic soft robot shape optimization with deformation-dependent follower loads.
3
3
  """
4
4
 
5
+ # Torch and TorchFEA use GNU OpenMP in this environment. Set MKL's matching
6
+ # threading layer before importing any module that can load NumPy or Torch.
7
+ import os as __os
8
+ import sys as __sys
9
+
10
+ if __sys.platform.startswith("linux"):
11
+ __os.environ["MKL_THREADING_LAYER"] = "GNU"
5
12
 
6
13
  # region: Logging Configuration
7
14
  import logging as __logging
@@ -57,7 +64,10 @@ def enable_logging(level=__logging.INFO, log_file=None, file_log_level=__logging
57
64
 
58
65
  # region optcore imports
59
66
  from .optcore.controller import Controller
60
- from .optcore.modelparams import FEAParams, FixedGeometryINP, FixedGeometry, HomogeneousMaterial, Params
67
+ from .optcore.modelparams import (
68
+ FEAParams, FixedGeometryINP, FixedGeometryTorchFEA, FixedGeometry,
69
+ MaterialsParams, Params,
70
+ )
61
71
  from .optcore.solver import Solver
62
72
  from .optcore.modelparams import Params
63
73
  from .optcore.updaters import Updaters
@@ -79,4 +89,3 @@ from .utils import check_gradients, get_controller
79
89
  # endregion
80
90
 
81
91
  controller: Controller = None
82
-
@@ -1,13 +1,15 @@
1
- from .material import CodesignMaterials
2
1
  from .geometry import CodesignGeometry
3
2
  from .constraints import InwardCurvatureRadius, OffsetSurfaceMinThickness
4
3
  from .feaparams import CodesignFEAParams
5
4
  from .params import Params
6
- from ..optcore import Solver, Updaters
5
+ from ..optcore import (
6
+ MaterialsParams, Solver, Updaters,
7
+ )
7
8
  from ..shapeopt import GeometryParams, UpdaterGeometries, ObjectiveFunction
8
9
  from ..simp import UpdaterMaterials
10
+ from ..simp import SIMP_BSPFieldMaterials
9
11
 
10
- def plot_geometry_and_materials(geometry: CodesignGeometry, materials: CodesignMaterials):
12
+ def plot_geometry_and_materials(geometry: CodesignGeometry, materials: MaterialsParams):
11
13
  import pyvista as pv
12
14
  plotter = pv.Plotter(window_size=(1400, 900))
13
15
  plotter.set_background('white')
@@ -15,4 +17,4 @@ def plot_geometry_and_materials(geometry: CodesignGeometry, materials: CodesignM
15
17
  plotter = materials.plot(plotter=plotter)
16
18
  geometry.plot(plotter=plotter, opacity=[0.0, 1.0])
17
19
 
18
- plotter.show()
20
+ plotter.show()
@@ -49,17 +49,28 @@ class InwardCurvatureRadius(BaseConstraints):
49
49
  super().__init__()
50
50
 
51
51
  self.geometry = geometry
52
+ """Stores the geometry used by InwardCurvatureRadius."""
52
53
  self.surface_start = int(surface_start)
54
+ """Stores the surface start used by InwardCurvatureRadius."""
53
55
  self.surface_ids = None if surface_ids is None else [int(i) for i in surface_ids]
56
+ """Stores the surface ids used by InwardCurvatureRadius."""
54
57
 
55
58
  self.margin = margin
59
+ """Stores the margin used by InwardCurvatureRadius."""
56
60
  self.margin_ratio = float(margin_ratio)
61
+ """Stores the margin ratio used by InwardCurvatureRadius."""
57
62
  self.barrier_thre_ratio = float(barrier_thre_ratio)
63
+ """Stores the barrier thre ratio used by InwardCurvatureRadius."""
58
64
  self.barrier_ratio = float(barrier_ratio)
65
+ """Stores the barrier ratio used by InwardCurvatureRadius."""
59
66
  self.p = int(p)
67
+ """Stores the p used by InwardCurvatureRadius."""
60
68
  self.penalty_scale = float(penalty_scale)
69
+ """Stores the penalty scale used by InwardCurvatureRadius."""
61
70
  self.hard_violation_beta = float(hard_violation_beta)
71
+ """Stores the hard violation beta used by InwardCurvatureRadius."""
62
72
  self.normal_sign = 1.0 if normal_sign >= 0 else -1.0
73
+ """Stores the normal sign used by InwardCurvatureRadius."""
63
74
 
64
75
  @staticmethod
65
76
  def _principal_curvatures(rdu: torch.Tensor, rdu2: torch.Tensor, eps: float = 1e-12) -> tuple[torch.Tensor, torch.Tensor]:
@@ -119,7 +130,6 @@ class InwardCurvatureRadius(BaseConstraints):
119
130
 
120
131
  return loss
121
132
 
122
-
123
133
  class OffsetSurfaceMinThickness(BaseConstraints):
124
134
  """
125
135
  Enforce a minimum distance on the offset surface to reduce self-intersection risk.
@@ -148,23 +158,39 @@ class OffsetSurfaceMinThickness(BaseConstraints):
148
158
  super().__init__()
149
159
 
150
160
  self.geometry = geometry
161
+ """Stores the geometry used by OffsetSurfaceMinThickness."""
151
162
  self.surface_start = int(surface_start)
163
+ """Stores the surface start used by OffsetSurfaceMinThickness."""
152
164
  self.surface_ids = None if surface_ids is None else [int(i) for i in surface_ids]
165
+ """Stores the surface ids used by OffsetSurfaceMinThickness."""
153
166
 
154
167
  self.min_distance = min_distance
168
+ """Stores the min distance used by OffsetSurfaceMinThickness."""
155
169
  self.search_ratio = float(search_ratio)
170
+ """Stores the search ratio used by OffsetSurfaceMinThickness."""
156
171
  self.barrier_thre_ratio = float(barrier_thre_ratio)
172
+ """Stores the barrier thre ratio used by OffsetSurfaceMinThickness."""
157
173
  self.barrier_ratio = float(barrier_ratio)
174
+ """Stores the barrier ratio used by OffsetSurfaceMinThickness."""
158
175
  self.p = int(p)
176
+ """Stores the p used by OffsetSurfaceMinThickness."""
159
177
  self.penalty_scale = float(penalty_scale)
178
+ """Stores the penalty scale used by OffsetSurfaceMinThickness."""
160
179
  self.hard_violation_beta = float(hard_violation_beta)
180
+ """Stores the hard violation beta used by OffsetSurfaceMinThickness."""
161
181
  self.normal_sign = 1.0 if normal_sign >= 0 else -1.0
182
+ """Stores the normal sign used by OffsetSurfaceMinThickness."""
162
183
  self.normal_opposition_thre = float(normal_opposition_thre)
184
+ """Stores the normal opposition thre used by OffsetSurfaceMinThickness."""
163
185
  self.normal_min = float(normal_min)
186
+ """Stores the normal min used by OffsetSurfaceMinThickness."""
164
187
  self.normal_max = float(normal_max)
188
+ """Stores the normal max used by OffsetSurfaceMinThickness."""
165
189
 
166
190
  self._neighbor_pairs: dict[int, torch.Tensor] = {}
191
+ """Stores the neighbor pairs used by OffsetSurfaceMinThickness."""
167
192
  self._neighbor_mindist: dict[int, torch.Tensor] = {}
193
+ """Stores the neighbor mindist used by OffsetSurfaceMinThickness."""
168
194
 
169
195
  def _selected_surface_ids(self, n_surface: int) -> list[int]:
170
196
  if self.surface_ids is None:
@@ -265,4 +291,3 @@ class OffsetSurfaceMinThickness(BaseConstraints):
265
291
  loss = loss + (penalty_strong * scaler_n0[indexl] * scaler_n1[indexl]).sum()
266
292
 
267
293
  return loss
268
-
@@ -4,6 +4,7 @@ import torch
4
4
  import numpy as np
5
5
 
6
6
  import torchfea
7
+ import morphopt
7
8
  from .. import FEAParams
8
9
  import bspmap
9
10
 
@@ -15,7 +16,11 @@ class CodesignFEAParams(FEAParams):
15
16
 
16
17
  def create_fea(self, assembly: torchfea.Assembly) -> torchfea.FEAController:
17
18
  fe = super().create_fea(assembly=assembly)
18
- fe_part = fe.assembly.get_part('final_model')
19
+ geometry = getattr(getattr(morphopt, "controller", None), "params", None)
20
+ geometry = getattr(geometry, "geometry", None)
21
+ part_name = getattr(geometry, "part_name", "final_model")
22
+ instance_name = getattr(geometry, "instance_name", "final_model")
23
+ fe_part = fe.assembly.get_part(part_name)
19
24
 
20
25
  if 'C3D6' in fe_part.elems:
21
26
  element_shell = fe_part.elems['C3D6']
@@ -40,10 +45,10 @@ class CodesignFEAParams(FEAParams):
40
45
  )
41
46
  surfcenter = surfnodes.mean(dim=1)
42
47
 
43
- fe.assembly.get_instance('final_model').exterior_surface = 'surface_1_offset'
48
+ fe.assembly.get_instance(instance_name).exterior_surface = 'surface_1_offset'
44
49
 
45
- mesh_1all = fe.assembly.get_instance('final_model').get_mesh(surf_name='surface_1_All')
46
- mesh_1offset = fe.assembly.get_instance('final_model').get_mesh(surf_name='surface_1_offset')
50
+ mesh_1all = fe.assembly.get_instance(instance_name).get_mesh(surf_name='surface_1_All')
51
+ mesh_1offset = fe.assembly.get_instance(instance_name).get_mesh(surf_name='surface_1_offset')
47
52
 
48
53
  import morphopt
49
54
  normal_cpgeo = morphopt.controller.params.geometry.surface_list[1].get_normals(torch.from_numpy(morphopt.controller.params.geometry.surface_list[1].surf_node_uv).to(torch.get_default_device()))
@@ -6,10 +6,6 @@ import torchfea
6
6
 
7
7
  class CodesignGeometry(GeometryParams):
8
8
 
9
-
10
-
11
-
12
-
13
9
  def __init__(self,
14
10
  fea_seed_size: float,
15
11
  mesh_order: int = 1,
@@ -50,12 +46,19 @@ class CodesignGeometry(GeometryParams):
50
46
  """Element-id offset for the shell block (0-based)."""
51
47
 
52
48
  self._reinit_opt_steps: int = 100
49
+ """Stores the reinit opt steps used by CodesignGeometry."""
53
50
  self._reinit_opt_lr: float = 5e-3
51
+ """Stores the reinit opt lr used by CodesignGeometry."""
54
52
  self._reinit_opt_curvature_weight: float = 1e2
53
+ """Stores the reinit opt curvature weight used by CodesignGeometry."""
55
54
  self._reinit_opt_shape_weight: float = 1e2
55
+ """Stores the reinit opt shape weight used by CodesignGeometry."""
56
56
  self._reinit_opt_curvature_margin_ratio: float = 0.25
57
+ """Stores the reinit opt curvature margin ratio used by CodesignGeometry."""
57
58
  self._reinit_opt_inward_sign: float = 1.0
59
+ """Stores the reinit opt inward sign used by CodesignGeometry."""
58
60
  self._reinit_opt_accept_only_improve: bool = True
61
+ """Stores the reinit opt accept only improve used by CodesignGeometry."""
59
62
 
60
63
  # def reinitialize(self, iteration):
61
64
  # super().reinitialize(iteration)
@@ -334,7 +337,7 @@ class CodesignGeometry(GeometryParams):
334
337
  if len(self._node_idx) == 0 or self._tri_local is None:
335
338
  return
336
339
 
337
- part = assembly.get_part('final_model')
340
+ part = assembly.get_part(self.part_name)
338
341
 
339
342
  nodes = part.nodes.clone()
340
343
  base_ids_t = torch.as_tensor(self._node_idx[0], dtype=torch.long, device=nodes.device)
@@ -356,4 +359,4 @@ class CodesignGeometry(GeometryParams):
356
359
 
357
360
  def plot(self, plotter=None, opacity = None, meshes=None):
358
361
  opacity = [0.0 if i == 0 else 1.0 for i in range(self.num_surface)]
359
- return super().plot(plotter, opacity, meshes)
362
+ return super().plot(plotter, opacity, meshes)
@@ -0,0 +1,8 @@
1
+ from .modelparams import (
2
+ Params, BaseGeometry, FEAParams,
3
+ MaterialsParams, BaseParams,
4
+ FixedGeometryINP, FixedGeometryTorchFEA, FixedGeometry,
5
+ )
6
+ from .updaters import BaseUpdater, Updaters
7
+ from .solver import Solver
8
+ from .objfunc import ObjectiveFunction
@@ -323,8 +323,22 @@ class Controller:
323
323
  self.history.append('objective', loss.item())
324
324
  self.history.append('metrics', self.objfun.get_metrics())
325
325
  self.history.append('time', [t1-t0, t2-t1, t3-t2, t4-t3])
326
- self.history.append('num_elements', sum([elem._elems.shape[0] for elem in self.objfun.fe.assembly.get_instance('final_model').elems.values()]))
327
- self.history.append('num_nodes', self.objfun.fe.assembly.get_instance('final_model').nodes.shape[0])
326
+ # Shape/codesign jobs historically used one ``final_model`` instance,
327
+ # while a SIMP job may import an Assembly from torchfea-ui with
328
+ # arbitrary Part/Instance names (for example ``Part-1-1``). Record
329
+ # statistics from the actual Assembly instead of assuming a name.
330
+ instances = self.objfun.fe.assembly._instances.values()
331
+ num_elements = sum(
332
+ element._elems.shape[0]
333
+ for instance in instances
334
+ for element in instance.elems.values()
335
+ )
336
+ num_nodes = sum(
337
+ instance.nodes.shape[0]
338
+ for instance in self.objfun.fe.assembly._instances.values()
339
+ )
340
+ self.history.append('num_elements', num_elements)
341
+ self.history.append('num_nodes', num_nodes)
328
342
 
329
343
  def print_info(self, t0, t1, t2, t3, t4) -> None:
330
344
  """
@@ -0,0 +1,5 @@
1
+ from .geometry import FixedGeometryINP, FixedGeometryTorchFEA, FixedGeometry, BaseGeometry
2
+ from .feaparams import FEAParams
3
+ from .materials import MaterialsParams
4
+ from .params import Params
5
+ from .baseparam import BaseParams
@@ -0,0 +1,251 @@
1
+ """Geometry parameter classes and the TorchFEA Assembly import helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from functools import lru_cache
7
+ from pathlib import Path
8
+
9
+ import numpy as np
10
+ import torchfea
11
+
12
+ from .baseparam import BaseParams
13
+
14
+
15
+ # ---------------------------------------------------------------------------
16
+ # TorchFEA model import helpers
17
+ # ---------------------------------------------------------------------------
18
+
19
+ @dataclass(frozen=True)
20
+ class PartSummary:
21
+ name: str
22
+ node_sets: tuple[str, ...]
23
+ element_sets: tuple[str, ...]
24
+ surface_sets: tuple[str, ...]
25
+ element_types: tuple[str, ...]
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class InstanceSummary:
30
+ name: str
31
+ part_name: str
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class TorchFEAModelSummary:
36
+ path: str
37
+ parts: tuple[PartSummary, ...]
38
+ instances: tuple[InstanceSummary, ...]
39
+
40
+ def part_for_instance(self, instance_name: str) -> PartSummary | None:
41
+ instance = next(
42
+ (item for item in self.instances if item.name == instance_name), None)
43
+ if instance is None:
44
+ return None
45
+ return next(
46
+ (item for item in self.parts if item.name == instance.part_name), None)
47
+
48
+
49
+ def resolve_model_path(model_directory: str, model_filename: str = "") -> Path:
50
+ """Resolve one exported TorchFEA ``.npz`` inside ``model_directory``."""
51
+ directory = Path(model_directory).expanduser().resolve()
52
+ if not directory.is_dir():
53
+ raise FileNotFoundError(
54
+ f"TorchFEA model directory does not exist: {directory}")
55
+
56
+ if model_filename:
57
+ candidate = Path(model_filename)
58
+ path = candidate if candidate.is_absolute() else directory / candidate
59
+ path = path.expanduser().resolve()
60
+ if not path.is_file():
61
+ raise FileNotFoundError(f"TorchFEA model file does not exist: {path}")
62
+ return path
63
+
64
+ candidates = sorted(
65
+ directory.glob("*.npz"), key=lambda item: item.stat().st_mtime_ns,
66
+ reverse=True)
67
+ if not candidates:
68
+ raise FileNotFoundError(
69
+ f"No TorchFEA .npz model was found in: {directory}")
70
+ return candidates[0].resolve()
71
+
72
+
73
+ def inspect_model(model_directory: str,
74
+ model_filename: str = "") -> TorchFEAModelSummary:
75
+ """Return selectable Part/Instance/set names from an exported model."""
76
+ path = resolve_model_path(model_directory, model_filename)
77
+ return _inspect_model_cached(str(path), path.stat().st_mtime_ns)
78
+
79
+
80
+ @lru_cache(maxsize=16)
81
+ def _inspect_model_cached(path: str, _mtime_ns: int) -> TorchFEAModelSummary:
82
+ controller = _read_controller(path)
83
+ source = controller.assembly
84
+ if source is None:
85
+ raise ValueError("The TorchFEA model does not contain an Assembly.")
86
+
87
+ parts = tuple(
88
+ PartSummary(
89
+ name=name,
90
+ node_sets=tuple(part.set_nodes.keys()),
91
+ element_sets=tuple(part.set_elements.keys()),
92
+ surface_sets=tuple(part.surfaces.keys()),
93
+ element_types=tuple(part.elems.keys()),
94
+ )
95
+ for name, part in source._parts.items()
96
+ )
97
+ instances = tuple(
98
+ InstanceSummary(name=name, part_name=instance.part_name)
99
+ for name, instance in source._instances.items()
100
+ )
101
+ if not parts:
102
+ raise ValueError("The TorchFEA Assembly contains no Parts.")
103
+ if not instances:
104
+ raise ValueError("The TorchFEA Assembly contains no Instances.")
105
+ return TorchFEAModelSummary(str(Path(path).resolve()), parts, instances)
106
+
107
+
108
+ def load_geometry_assembly(model_directory: str,
109
+ model_filename: str = "") -> torchfea.Assembly:
110
+ """Load a clean Assembly containing only cloned Parts and Instances."""
111
+ path = resolve_model_path(model_directory, model_filename)
112
+ controller = _read_controller(str(path))
113
+ source = controller.assembly
114
+ if source is None:
115
+ raise ValueError("The TorchFEA model does not contain an Assembly.")
116
+
117
+ assembly = torchfea.Assembly()
118
+ for name, part in source._parts.items():
119
+ cloned_part = torchfea.Serializable._deserialize(part._serialize())
120
+ if (cloned_part.exterior_surface == "extern"
121
+ and "extern" not in cloned_part.surfaces):
122
+ boundary = []
123
+ for element in cloned_part.elems.values():
124
+ boundary.extend(element.extract_boundary_surface_set())
125
+ if boundary:
126
+ cloned_part.surfaces["extern"] = boundary
127
+ assembly.add_part(cloned_part, name=name)
128
+ for name, instance in source._instances.items():
129
+ cloned_instance = torchfea.Serializable._deserialize(instance._serialize())
130
+ assembly.add_instance(cloned_instance, name=name)
131
+
132
+ if not assembly._parts:
133
+ raise ValueError("The TorchFEA Assembly contains no Parts.")
134
+ if not assembly._instances:
135
+ raise ValueError("The TorchFEA Assembly contains no Instances.")
136
+ return assembly
137
+
138
+
139
+ def _read_controller(path: str) -> torchfea.FEAController:
140
+ """Deserialize a controller without initializing its analysis objects."""
141
+ with np.load(path, allow_pickle=True) as loaded:
142
+ if "data" not in loaded:
143
+ raise ValueError("The .npz file is not a TorchFEA model archive.")
144
+ return torchfea.FEAController._deserialize(loaded["data"])
145
+
146
+ class BaseGeometry(BaseParams):
147
+ """
148
+ Base class for geometry parameter classes.
149
+
150
+ Subclasses must provide the mesh/geometry generation and assembly modification
151
+ behavior used by the optimization pipeline.
152
+ """
153
+
154
+ def __init__(self, *args, **kwargs):
155
+ super().__init__(**kwargs)
156
+
157
+ def generate(self, path_result: str, pools=None) -> torchfea.Assembly:
158
+ """Generate the FEA part for the current geometry."""
159
+ raise NotImplementedError
160
+
161
+
162
+
163
+ class FixedGeometry(BaseGeometry):
164
+ """
165
+ Base class for geometry whose FE assembly stays fixed during optimization.
166
+
167
+ Subclasses may read an INP mesh or a saved TorchFEA model. In both cases
168
+ the assembly is built once and reused across optimization iterations.
169
+ """
170
+ def __init__(self):
171
+ super().__init__()
172
+ self._assembly: torchfea.Assembly = None
173
+ """
174
+ The FEA assembly containing the geometry for the optimization problem. This assembly is generated from a fixed mesh and does not change during optimization iterations.
175
+ """
176
+
177
+ @property
178
+ def assembly(self) -> torchfea.Assembly:
179
+ """
180
+ Get the FEA assembly containing the geometry for the optimization problem.
181
+
182
+ Returns:
183
+ torchfea.Assembly: The FEA assembly containing the geometry.
184
+ """
185
+ if self._assembly is None:
186
+ self._assembly = self.define_assembly()
187
+ return self._assembly
188
+
189
+ def define_assembly(self)-> torchfea.Assembly:
190
+ """
191
+ Define the assembly for the geometry using node and element data.
192
+
193
+ This method should be implemented in subclasses to create the assembly based on specific node and element data.
194
+
195
+ Returns:
196
+ torchfea.Assembly: The defined assembly for the geometry.
197
+ """
198
+ raise NotImplementedError
199
+
200
+ def generate(self, *args, **kwargs):
201
+ """Return the lazily built fixed torchfea assembly."""
202
+ return self.assembly
203
+
204
+ class FixedGeometryINP(FixedGeometry):
205
+ """
206
+ Geometry parameters class for fixed mesh geometry using an Abaqus .inp file.
207
+ """
208
+ def __init__(self, mesh_file: str, part_name: str = 'final_model'):
209
+ super().__init__()
210
+ self._mesh_file = mesh_file
211
+ """
212
+ The file path of the mesh to be loaded for the geometry.
213
+ """
214
+
215
+ self._part_name = part_name
216
+ """The name of the part in the Abaqus INP file to be used for the geometry."""
217
+
218
+ def define_assembly(self):
219
+ inp = torchfea.FEA_INP()
220
+ inp.read_inp(self._mesh_file)
221
+
222
+ fe_ext = torchfea.from_inp(inp)
223
+
224
+ assembly = torchfea.Assembly()
225
+
226
+ assembly.add_part(part=fe_ext.assembly.get_part(self._part_name), name='final_model')
227
+ instance = torchfea.Instance(part_name='final_model')
228
+ assembly.add_instance(instance=instance, name='final_model')
229
+ instance.exterior_surface = 'surface_0_All'
230
+ return assembly
231
+
232
+
233
+ class FixedGeometryTorchFEA(FixedGeometry):
234
+ """Fixed geometry imported from a model exported by ``torchfea-ui``.
235
+
236
+ Only the saved Parts and Instances are used. MorphOpt creates its own
237
+ loads, constraints, boundary conditions, reference points, and solver.
238
+ """
239
+
240
+ def __init__(self, model_directory: str, model_filename: str):
241
+ super().__init__()
242
+ self.model_directory = str(model_directory)
243
+ self.model_filename = str(model_filename)
244
+ # Fail early with a path-specific message while keeping assembly
245
+ # construction lazy.
246
+ self.model_path = str(resolve_model_path(
247
+ self.model_directory, self.model_filename))
248
+
249
+ def define_assembly(self) -> torchfea.Assembly:
250
+ return load_geometry_assembly(
251
+ self.model_directory, self.model_filename)
@@ -0,0 +1,11 @@
1
+ """Material assignment interfaces."""
2
+
3
+ from .basematerialinterface import BaseMaterialInterface
4
+ from .homogeneousmaterial import HomogeneousMaterial
5
+ from .materialmodels import MaterialModels
6
+
7
+ __all__ = [
8
+ "BaseMaterialInterface",
9
+ "HomogeneousMaterial",
10
+ "MaterialModels",
11
+ ]
@@ -0,0 +1,69 @@
1
+ """Base class for material assignments to TorchFEA parts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import torch
6
+ import torchfea
7
+
8
+ from ..baseparam import BaseParams
9
+ from .materialmodels import MaterialModels
10
+
11
+
12
+ class BaseMaterialInterface(BaseParams):
13
+ """Assign one material definition to elements in one Part.
14
+
15
+ ``elementname`` may be empty. In that case the interface is applied to
16
+ every element family in ``part_name``.
17
+ """
18
+
19
+ from .materialmodels import MaterialModels as materialmodels
20
+
21
+ def __init__(
22
+ self,
23
+ part_name: str,
24
+ material_parameters: MaterialModels.MaterialParameters,
25
+ elementname: str = "",
26
+ density: float = 0.0,
27
+ ) -> None:
28
+ super().__init__()
29
+ self.part_name = str(part_name or "").strip()
30
+ if not self.part_name:
31
+ raise ValueError("part_name cannot be empty.")
32
+ self.elementname = str(elementname or "").strip()
33
+ self.material_parameters: MaterialModels.MaterialParameters = material_parameters
34
+ self._density: float = float(density)
35
+ self._name: str = ""
36
+
37
+ @property
38
+ def density(self) -> float:
39
+ """Return the mass density assigned to the target elements."""
40
+ return self._density
41
+
42
+ @density.setter
43
+ def density(self, value: float) -> None:
44
+ """Set the mass density assigned to the target elements."""
45
+ self._density = float(value)
46
+
47
+ def target_elements(
48
+ self,
49
+ assembly: torchfea.Assembly,
50
+ ) -> list[tuple[str, torchfea.elements.Element_3D]]:
51
+ """Return the selected element families from the target Part."""
52
+ part = assembly.get_part(self.part_name)
53
+ if self.elementname:
54
+ if self.elementname not in part.elems:
55
+ raise KeyError(
56
+ f"Element {self.elementname!r} does not exist on Part "
57
+ f"{self.part_name!r}.")
58
+ return [(self.elementname, part.elems[self.elementname])]
59
+ if not part.elems:
60
+ raise ValueError(f"Part {self.part_name!r} contains no elements.")
61
+ return list(part.elems.items())
62
+
63
+ def set_materials(self, fe: torchfea.FEAController) -> None:
64
+ """Assign this interface to its target elements."""
65
+ raise NotImplementedError
66
+
67
+ def get_design_values(self) -> torch.Tensor:
68
+ """Return live design values for a designable material interface."""
69
+ return torch.zeros(0)