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.
- {morphopt-3.2.0 → morphopt-3.2.2}/PKG-INFO +2 -2
- {morphopt-3.2.0 → morphopt-3.2.2}/README.md +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/pyproject.toml +2 -2
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/__init__.py +11 -2
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/codesign/__init__.py +6 -4
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/codesign/constraints.py +27 -2
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/codesign/feaparams.py +9 -4
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/codesign/geometry.py +9 -6
- morphopt-3.2.2/src/morphopt/optcore/__init__.py +8 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/baseobject.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/controller.py +16 -2
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/history.py +0 -0
- morphopt-3.2.2/src/morphopt/optcore/modelparams/__init__.py +5 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/baseparam.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/__init__.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/basefeainterface.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/bodyforceinterface.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/boundaryconditioninterface.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/contactinterface.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/coupleinterface.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/pointinterface.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/pressureinterface.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/referencepointinterface.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/springinterface.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feaparams.py +0 -0
- morphopt-3.2.2/src/morphopt/optcore/modelparams/geometry.py +251 -0
- morphopt-3.2.2/src/morphopt/optcore/modelparams/materialinterface/__init__.py +11 -0
- morphopt-3.2.2/src/morphopt/optcore/modelparams/materialinterface/basematerialinterface.py +69 -0
- morphopt-3.2.2/src/morphopt/optcore/modelparams/materialinterface/homogeneousmaterial.py +21 -0
- morphopt-3.2.2/src/morphopt/optcore/modelparams/materialinterface/materialmodels.py +111 -0
- morphopt-3.2.2/src/morphopt/optcore/modelparams/materials.py +175 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/params.py +5 -7
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/objfunc.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/solver.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/updaters.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/__init__.py +3 -1
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/geometryinterfaces/__init__.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/geometryparams.py +32 -18
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objectivefuncs/__init__.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objectivefuncs/basefuncs.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objectivefuncs/boundarys.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objectivefuncs/surfacefairness.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objectivefuncs/volumemaximization.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objfunc.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/update_geometry.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/__init__.py +4 -1
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/objectivefuncs/__init__.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/objectivefuncs/basefuncs.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/objectivefuncs/boundarys.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/objectivefuncs/densityfield.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/objectivefuncs/sensitivity.py +0 -0
- morphopt-3.2.2/src/morphopt/simp/objectivefuncs/volfrac.py +87 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/simpmaterial.py +216 -132
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/solver.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/simp/update_material.py +36 -25
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/taskoptmization.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/codegen/generator.py +202 -47
- morphopt-3.2.2/src/morphopt/ui/model/__init__.py +20 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/model/problem.py +180 -29
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/model/schemas.py +102 -52
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/schemes/base.py +23 -3
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/schemes/codesign.py +23 -11
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/schemes/shapeopt.py +6 -2
- morphopt-3.2.2/src/morphopt/ui/schemes/simp.py +112 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/editor.py +61 -13
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/model_tree.py +86 -6
- morphopt-3.2.2/src/morphopt/ui/widgets/torchfea_model_editor.py +428 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/values.py +1 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/viewer.py +95 -20
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/workbench.py +51 -4
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/utils/__init__.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/utils/gradient_check.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/utils/history_read.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt.egg-info/PKG-INFO +2 -2
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt.egg-info/SOURCES.txt +5 -1
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt.egg-info/dependency_links.txt +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt.egg-info/requires.txt +1 -1
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt.egg-info/top_level.txt +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/tests/test_fixedGeometry.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/tests/test_path.py +0 -0
- morphopt-3.2.0/src/morphopt/codesign/material.py +0 -53
- morphopt-3.2.0/src/morphopt/optcore/__init__.py +0 -4
- morphopt-3.2.0/src/morphopt/optcore/modelparams/__init__.py +0 -5
- morphopt-3.2.0/src/morphopt/optcore/modelparams/geometry.py +0 -92
- morphopt-3.2.0/src/morphopt/optcore/modelparams/materials.py +0 -119
- morphopt-3.2.0/src/morphopt/simp/objectivefuncs/volfrac.py +0 -47
- morphopt-3.2.0/src/morphopt/ui/model/__init__.py +0 -16
- morphopt-3.2.0/src/morphopt/ui/schemes/simp.py +0 -96
- {morphopt-3.2.0 → morphopt-3.2.2}/setup.cfg +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/codesign/params.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/opt_runner.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/geometryinterfaces/basesurfaceinterface.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/geometryinterfaces/bspsurfaceinterface.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/geometryinterfaces/cpgeosurfaceinterface.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objectivefuncs/distancesurface.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/shapeopt/objectivefuncs/shapederivative.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/__init__.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/__main__.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/app.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/codegen/__init__.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/i18n.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/launcher.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/mainwindow.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/model/loaders.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/observe_panel.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/schemes/__init__.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/schemes/snippets.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/__init__.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/code_completion.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/codeeditor.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/objective_editor.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/observation_pages.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/param_form.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/solver_editor.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/stepmatrix.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/template_insert_dialog.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/ui/widgets/updater_editor.py +0 -0
- {morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt.egg-info/entry_points.txt +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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(
|
|
48
|
+
fe.assembly.get_instance(instance_name).exterior_surface = 'surface_1_offset'
|
|
44
49
|
|
|
45
|
-
mesh_1all = fe.assembly.get_instance(
|
|
46
|
-
mesh_1offset = fe.assembly.get_instance(
|
|
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(
|
|
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
|
|
File without changes
|
|
@@ -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
|
-
|
|
327
|
-
|
|
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
|
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/basefeainterface.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/contactinterface.py
RENAMED
|
File without changes
|
{morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/coupleinterface.py
RENAMED
|
File without changes
|
{morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/pointinterface.py
RENAMED
|
File without changes
|
{morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/pressureinterface.py
RENAMED
|
File without changes
|
|
File without changes
|
{morphopt-3.2.0 → morphopt-3.2.2}/src/morphopt/optcore/modelparams/feainterface/springinterface.py
RENAMED
|
File without changes
|
|
File without changes
|
|
@@ -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)
|