morphopt 3.1.6__tar.gz → 3.2.1__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.
Potentially problematic release.
This version of morphopt might be problematic. Click here for more details.
- morphopt-3.2.1/PKG-INFO +151 -0
- morphopt-3.2.1/README.md +127 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/pyproject.toml +2 -2
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/__init__.py +1 -2
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/codesign/feaparams.py +2 -2
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/opt_runner.py +2 -2
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/__init__.py +2 -2
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/controller.py +16 -2
- morphopt-3.2.1/src/morphopt/optcore/modelparams/__init__.py +5 -0
- morphopt-3.2.1/src/morphopt/optcore/modelparams/geometry.py +251 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/geometryparams.py +3 -2
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/simp/__init__.py +1 -1
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/simp/objectivefuncs/volfrac.py +1 -1
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/simp/simpmaterial.py +40 -8
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/__init__.py +2 -1
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/app.py +9 -2
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/codegen/generator.py +189 -58
- morphopt-3.2.1/src/morphopt/ui/i18n.py +74 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/launcher.py +71 -8
- morphopt-3.2.1/src/morphopt/ui/mainwindow.py +209 -0
- morphopt-3.2.1/src/morphopt/ui/model/problem.py +1119 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/model/schemas.py +390 -78
- morphopt-3.2.1/src/morphopt/ui/observe_panel.py +968 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/schemes/__init__.py +6 -2
- morphopt-3.2.1/src/morphopt/ui/schemes/base.py +186 -0
- morphopt-3.2.1/src/morphopt/ui/schemes/codesign.py +143 -0
- morphopt-3.2.1/src/morphopt/ui/schemes/shapeopt.py +100 -0
- morphopt-3.2.1/src/morphopt/ui/schemes/simp.py +107 -0
- morphopt-3.2.1/src/morphopt/ui/schemes/snippets.py +236 -0
- morphopt-3.2.1/src/morphopt/ui/widgets/__init__.py +1 -0
- morphopt-3.2.1/src/morphopt/ui/widgets/code_completion.py +180 -0
- morphopt-3.2.1/src/morphopt/ui/widgets/codeeditor.py +372 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/widgets/editor.py +80 -41
- morphopt-3.2.1/src/morphopt/ui/widgets/model_tree.py +485 -0
- morphopt-3.2.1/src/morphopt/ui/widgets/objective_editor.py +233 -0
- morphopt-3.2.1/src/morphopt/ui/widgets/observation_pages.py +246 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/widgets/param_form.py +13 -6
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/widgets/solver_editor.py +22 -16
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/widgets/stepmatrix.py +71 -52
- morphopt-3.2.1/src/morphopt/ui/widgets/template_insert_dialog.py +198 -0
- morphopt-3.2.1/src/morphopt/ui/widgets/torchfea_model_editor.py +428 -0
- morphopt-3.2.1/src/morphopt/ui/widgets/updater_editor.py +699 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/widgets/values.py +23 -0
- morphopt-3.2.1/src/morphopt/ui/widgets/viewer.py +353 -0
- morphopt-3.2.1/src/morphopt/ui/workbench.py +549 -0
- morphopt-3.2.1/src/morphopt.egg-info/PKG-INFO +151 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt.egg-info/SOURCES.txt +6 -3
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt.egg-info/requires.txt +1 -1
- morphopt-3.1.6/PKG-INFO +0 -95
- morphopt-3.1.6/README.md +0 -71
- morphopt-3.1.6/src/morphopt/optcore/modelparams/__init__.py +0 -5
- morphopt-3.1.6/src/morphopt/optcore/modelparams/geometry.py +0 -90
- morphopt-3.1.6/src/morphopt/taskui.py +0 -18
- morphopt-3.1.6/src/morphopt/ui/codegen/objective_templates.py +0 -52
- morphopt-3.1.6/src/morphopt/ui/mainwindow.py +0 -146
- morphopt-3.1.6/src/morphopt/ui/model/problem.py +0 -170
- morphopt-3.1.6/src/morphopt/ui/monitor.py +0 -558
- morphopt-3.1.6/src/morphopt/ui/observe_panel.py +0 -533
- morphopt-3.1.6/src/morphopt/ui/schemes/base.py +0 -101
- morphopt-3.1.6/src/morphopt/ui/schemes/codesign.py +0 -173
- morphopt-3.1.6/src/morphopt/ui/schemes/shapeopt.py +0 -127
- morphopt-3.1.6/src/morphopt/ui/schemes/simp.py +0 -115
- morphopt-3.1.6/src/morphopt/ui/widgets/__init__.py +0 -1
- morphopt-3.1.6/src/morphopt/ui/widgets/codeeditor.py +0 -207
- morphopt-3.1.6/src/morphopt/ui/widgets/model_tree.py +0 -282
- morphopt-3.1.6/src/morphopt/ui/widgets/objective_editor.py +0 -141
- morphopt-3.1.6/src/morphopt/ui/widgets/updater_editor.py +0 -214
- morphopt-3.1.6/src/morphopt/ui/widgets/viewer.py +0 -252
- morphopt-3.1.6/src/morphopt/ui/workbench.py +0 -313
- morphopt-3.1.6/src/morphopt.egg-info/PKG-INFO +0 -95
- {morphopt-3.1.6 → morphopt-3.2.1}/setup.cfg +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/codesign/__init__.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/codesign/constraints.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/codesign/geometry.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/codesign/material.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/codesign/params.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/baseobject.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/history.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/modelparams/baseparam.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/modelparams/feainterface/__init__.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/modelparams/feainterface/basefeainterface.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/modelparams/feainterface/bodyforceinterface.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/modelparams/feainterface/boundaryconditioninterface.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/modelparams/feainterface/contactinterface.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/modelparams/feainterface/coupleinterface.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/modelparams/feainterface/pointinterface.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/modelparams/feainterface/pressureinterface.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/modelparams/feainterface/referencepointinterface.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/modelparams/feainterface/springinterface.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/modelparams/feaparams.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/modelparams/materials.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/modelparams/params.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/objfunc.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/solver.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/optcore/updaters.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/__init__.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/geometryinterfaces/__init__.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/geometryinterfaces/basesurfaceinterface.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/geometryinterfaces/bspsurfaceinterface.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/geometryinterfaces/cpgeosurfaceinterface.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/objectivefuncs/__init__.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/objectivefuncs/basefuncs.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/objectivefuncs/boundarys.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/objectivefuncs/distancesurface.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/objectivefuncs/shapederivative.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/objectivefuncs/surfacefairness.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/objectivefuncs/volumemaximization.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/objfunc.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/shapeopt/update_geometry.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/simp/objectivefuncs/__init__.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/simp/objectivefuncs/basefuncs.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/simp/objectivefuncs/boundarys.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/simp/objectivefuncs/densityfield.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/simp/objectivefuncs/sensitivity.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/simp/solver.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/simp/update_material.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/taskoptmization.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/__main__.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/codegen/__init__.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/model/__init__.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/ui/model/loaders.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/utils/__init__.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/utils/gradient_check.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt/utils/history_read.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt.egg-info/dependency_links.txt +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt.egg-info/entry_points.txt +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/src/morphopt.egg-info/top_level.txt +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/tests/test_fixedGeometry.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/tests/test_path.py +0 -0
- {morphopt-3.1.6 → morphopt-3.2.1}/tests/test_ui.py +0 -0
morphopt-3.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: morphopt
|
|
3
|
+
Version: 3.2.1
|
|
4
|
+
Summary: A morphing optimization framework using PyTorch and Abaqus.
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: torch>=2.9.1
|
|
8
|
+
Requires-Dist: torchvision>=0.15.1
|
|
9
|
+
Requires-Dist: numpy>=2.0.0
|
|
10
|
+
Requires-Dist: pypardiso>=0.4.7
|
|
11
|
+
Requires-Dist: pyside6>=6.8.0
|
|
12
|
+
Requires-Dist: pyvista>=0.47.1
|
|
13
|
+
Requires-Dist: pyvistaqt>=0.11.3
|
|
14
|
+
Requires-Dist: vtk>=9.6.1
|
|
15
|
+
Requires-Dist: gmsh>=4.15.0
|
|
16
|
+
Requires-Dist: tabulate>=0.9.0
|
|
17
|
+
Requires-Dist: imageio>=2.37.3
|
|
18
|
+
Requires-Dist: bspmap>=1.0.3
|
|
19
|
+
Requires-Dist: torchfea>=1.1.1
|
|
20
|
+
Requires-Dist: trimesh>=4.11.5
|
|
21
|
+
Requires-Dist: networkx>=3.6.1
|
|
22
|
+
Requires-Dist: scipy>=1.17.1
|
|
23
|
+
Requires-Dist: cpgeo>=1.0.12
|
|
24
|
+
|
|
25
|
+
# MorphOpt — A Differentiable Optimization Framework for Soft Structure & Morphology Design
|
|
26
|
+
|
|
27
|
+
**Developed by Zenan Song**
|
|
28
|
+
|
|
29
|
+
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.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Custom model definition covering geometry, material, and load parameters
|
|
34
|
+
- Differentiable FEA via torchfea with automatic solving and sensitivity propagation
|
|
35
|
+
- L-BFGS and trust-region methods for parameter updates within a subproblem framework
|
|
36
|
+
- Fully extensible via inheritance — users customize every component in their definition scripts
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
Install the package:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install morphopt
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Requirements: Python ≥ 3.12; a PyTorch **float64** build is recommended.
|
|
47
|
+
|
|
48
|
+
> **Optimizations are run as scripts.** Each job is an ordinary Python file that calls `morphopt.start_optimization(...)`. It runs headlessly in a terminal (local or remote), and a finished run can be stopped and resumed later from its result folder. Script execution is the recommended way to run jobs.
|
|
49
|
+
|
|
50
|
+
1. **Run a minimal example** — execute it directly in a terminal:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Shape optimization (pneumatic bending actuator)
|
|
54
|
+
python examples/bendingactuator.py
|
|
55
|
+
|
|
56
|
+
# SIMP topology optimization (compliant gripper)
|
|
57
|
+
python examples/gripper.py
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
2. **Run your own job** — copy an example as a starting point, adapt the definition (geometry, material, load, updater, objective), and launch it as a script:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python my_task.py
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Each run writes its results (logs, FEA files, restart script) into a timestamped result folder under the run root.
|
|
67
|
+
|
|
68
|
+
3. **Optional — the GUI** is a visual aid for authoring a problem definition and inspecting finished runs in one window. It can also export a runnable script (`.py`), which you can either run directly in the UI or further customize before executing it via Step 2 in a terminal.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
morphopt-ui # console command, available after `pip install morphopt`
|
|
72
|
+
# python -m morphopt.ui works too (handy when running from a source code directory)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The complete UI workflow (definition part + observer part) is described in [`docs/UI_usage.md`](docs/UI_usage.md).
|
|
76
|
+
|
|
77
|
+
Further references:
|
|
78
|
+
|
|
79
|
+
- Task definition guide: `docs/module_definition_guide.md`
|
|
80
|
+
- Module API reference: `docs/module_reference.md`
|
|
81
|
+
- Theory & papers: `docs/theory/`
|
|
82
|
+
|
|
83
|
+
## Directory Overview
|
|
84
|
+
|
|
85
|
+
- `src/morphopt/`
|
|
86
|
+
- `optcore` — Core package (controller, params, solver, objective, updaters)
|
|
87
|
+
- `shapeopt/` — B-spline shape optimization
|
|
88
|
+
- `simp/` — SIMP topology optimization
|
|
89
|
+
- `codesign/` — Shell co-design (shape + material)
|
|
90
|
+
- `ui/` — PySide6 graphical UI (definition + observer in one window)
|
|
91
|
+
- `examples/` — Example tasks (bending actuator, gripper, etc.)
|
|
92
|
+
- `docs/` — Usage guides, API reference, theory papers
|
|
93
|
+
- `tests/` — Gradient checks, geometry tests, UI tests
|
|
94
|
+
- `ui_runs/` — runs launched from the UI and their results (`<label>_T<timestamp>/`)
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
Research use preferred. For production or commercial use, please evaluate and ensure numerical and engineering robustness first.
|
|
99
|
+
|
|
100
|
+
## Citation
|
|
101
|
+
|
|
102
|
+
If you use MorphOpt in your research, please cite:
|
|
103
|
+
|
|
104
|
+
- TRO 2023
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
@ARTICLE{10296178,
|
|
108
|
+
author={Chen, Feifei and Song, Zenan and Chen, Shitong and Gu, Guoying and Zhu, Xiangyang},
|
|
109
|
+
journal={IEEE Transactions on Robotics},
|
|
110
|
+
title={Morphological Design for Pneumatic Soft Actuators and Robots With Desired Deformation Behavior},
|
|
111
|
+
year={2023},
|
|
112
|
+
volume={39},
|
|
113
|
+
number={6},
|
|
114
|
+
pages={4408-4428},
|
|
115
|
+
keywords={Shape;Surface morphology;Robots;Splines (mathematics);Pneumatic systems;Actuators;Soft robotics;Morphological design;shape optimization;pneumatic soft robots},
|
|
116
|
+
doi={10.1109/TRO.2023.3323825}}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- TRO 2026
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
@ARTICLE{11617347,
|
|
123
|
+
author={Song, Zenan and Chen, Feifei and Gu, Guoying and Zhu, Xiangyang},
|
|
124
|
+
journal={IEEE Transactions on Robotics},
|
|
125
|
+
title={Continuum Jacobian-Based Computational Morphogenesis for Soft Robotic Workspace Optimization},
|
|
126
|
+
year={2026},
|
|
127
|
+
volume={42},
|
|
128
|
+
number={},
|
|
129
|
+
pages={3048-3068},
|
|
130
|
+
keywords={Optimization;Design methodology;Surfaces;Modeling;End effectors;Shape;Soft robotics;Deformation;Vectors;Robots;Computational morphogenesis;Jacobian shape derivative;soft robotics;workspace optimization},
|
|
131
|
+
doi={10.1109/TRO.2026.3716051}}
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- TMECH 2026
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
@ARTICLE{11536090,
|
|
139
|
+
author={Song, Zenan and Zhou, Guozhen and Chen, Feifei},
|
|
140
|
+
journal={IEEE/ASME Transactions on Mechatronics},
|
|
141
|
+
title={Contact-Aware Soft Robot Design via Differentiable Simulation and Morphological Optimization},
|
|
142
|
+
year={2026},
|
|
143
|
+
volume={31},
|
|
144
|
+
number={4},
|
|
145
|
+
pages={4576-4584},
|
|
146
|
+
keywords={Contacts;Optimization;Modeling;Design methodology;Soft robotics;Surfaces;Actuators;Materials;Force;Simulation;Differentiable contact modeling;morphological design;soft robotics},
|
|
147
|
+
doi={10.1109/TMECH.2026.3692920}}
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
|
morphopt-3.2.1/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# MorphOpt — A Differentiable Optimization Framework for Soft Structure & Morphology Design
|
|
2
|
+
|
|
3
|
+
**Developed by Zenan Song**
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Custom model definition covering geometry, material, and load parameters
|
|
10
|
+
- Differentiable FEA via torchfea with automatic solving and sensitivity propagation
|
|
11
|
+
- L-BFGS and trust-region methods for parameter updates within a subproblem framework
|
|
12
|
+
- Fully extensible via inheritance — users customize every component in their definition scripts
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
Install the package:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install morphopt
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Requirements: Python ≥ 3.12; a PyTorch **float64** build is recommended.
|
|
23
|
+
|
|
24
|
+
> **Optimizations are run as scripts.** Each job is an ordinary Python file that calls `morphopt.start_optimization(...)`. It runs headlessly in a terminal (local or remote), and a finished run can be stopped and resumed later from its result folder. Script execution is the recommended way to run jobs.
|
|
25
|
+
|
|
26
|
+
1. **Run a minimal example** — execute it directly in a terminal:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Shape optimization (pneumatic bending actuator)
|
|
30
|
+
python examples/bendingactuator.py
|
|
31
|
+
|
|
32
|
+
# SIMP topology optimization (compliant gripper)
|
|
33
|
+
python examples/gripper.py
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2. **Run your own job** — copy an example as a starting point, adapt the definition (geometry, material, load, updater, objective), and launch it as a script:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
python my_task.py
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Each run writes its results (logs, FEA files, restart script) into a timestamped result folder under the run root.
|
|
43
|
+
|
|
44
|
+
3. **Optional — the GUI** is a visual aid for authoring a problem definition and inspecting finished runs in one window. It can also export a runnable script (`.py`), which you can either run directly in the UI or further customize before executing it via Step 2 in a terminal.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
morphopt-ui # console command, available after `pip install morphopt`
|
|
48
|
+
# python -m morphopt.ui works too (handy when running from a source code directory)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The complete UI workflow (definition part + observer part) is described in [`docs/UI_usage.md`](docs/UI_usage.md).
|
|
52
|
+
|
|
53
|
+
Further references:
|
|
54
|
+
|
|
55
|
+
- Task definition guide: `docs/module_definition_guide.md`
|
|
56
|
+
- Module API reference: `docs/module_reference.md`
|
|
57
|
+
- Theory & papers: `docs/theory/`
|
|
58
|
+
|
|
59
|
+
## Directory Overview
|
|
60
|
+
|
|
61
|
+
- `src/morphopt/`
|
|
62
|
+
- `optcore` — Core package (controller, params, solver, objective, updaters)
|
|
63
|
+
- `shapeopt/` — B-spline shape optimization
|
|
64
|
+
- `simp/` — SIMP topology optimization
|
|
65
|
+
- `codesign/` — Shell co-design (shape + material)
|
|
66
|
+
- `ui/` — PySide6 graphical UI (definition + observer in one window)
|
|
67
|
+
- `examples/` — Example tasks (bending actuator, gripper, etc.)
|
|
68
|
+
- `docs/` — Usage guides, API reference, theory papers
|
|
69
|
+
- `tests/` — Gradient checks, geometry tests, UI tests
|
|
70
|
+
- `ui_runs/` — runs launched from the UI and their results (`<label>_T<timestamp>/`)
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
Research use preferred. For production or commercial use, please evaluate and ensure numerical and engineering robustness first.
|
|
75
|
+
|
|
76
|
+
## Citation
|
|
77
|
+
|
|
78
|
+
If you use MorphOpt in your research, please cite:
|
|
79
|
+
|
|
80
|
+
- TRO 2023
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
@ARTICLE{10296178,
|
|
84
|
+
author={Chen, Feifei and Song, Zenan and Chen, Shitong and Gu, Guoying and Zhu, Xiangyang},
|
|
85
|
+
journal={IEEE Transactions on Robotics},
|
|
86
|
+
title={Morphological Design for Pneumatic Soft Actuators and Robots With Desired Deformation Behavior},
|
|
87
|
+
year={2023},
|
|
88
|
+
volume={39},
|
|
89
|
+
number={6},
|
|
90
|
+
pages={4408-4428},
|
|
91
|
+
keywords={Shape;Surface morphology;Robots;Splines (mathematics);Pneumatic systems;Actuators;Soft robotics;Morphological design;shape optimization;pneumatic soft robots},
|
|
92
|
+
doi={10.1109/TRO.2023.3323825}}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- TRO 2026
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
@ARTICLE{11617347,
|
|
99
|
+
author={Song, Zenan and Chen, Feifei and Gu, Guoying and Zhu, Xiangyang},
|
|
100
|
+
journal={IEEE Transactions on Robotics},
|
|
101
|
+
title={Continuum Jacobian-Based Computational Morphogenesis for Soft Robotic Workspace Optimization},
|
|
102
|
+
year={2026},
|
|
103
|
+
volume={42},
|
|
104
|
+
number={},
|
|
105
|
+
pages={3048-3068},
|
|
106
|
+
keywords={Optimization;Design methodology;Surfaces;Modeling;End effectors;Shape;Soft robotics;Deformation;Vectors;Robots;Computational morphogenesis;Jacobian shape derivative;soft robotics;workspace optimization},
|
|
107
|
+
doi={10.1109/TRO.2026.3716051}}
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- TMECH 2026
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
@ARTICLE{11536090,
|
|
115
|
+
author={Song, Zenan and Zhou, Guozhen and Chen, Feifei},
|
|
116
|
+
journal={IEEE/ASME Transactions on Mechatronics},
|
|
117
|
+
title={Contact-Aware Soft Robot Design via Differentiable Simulation and Morphological Optimization},
|
|
118
|
+
year={2026},
|
|
119
|
+
volume={31},
|
|
120
|
+
number={4},
|
|
121
|
+
pages={4576-4584},
|
|
122
|
+
keywords={Contacts;Optimization;Modeling;Design methodology;Soft robotics;Surfaces;Actuators;Materials;Force;Simulation;Differentiable contact modeling;morphological design;soft robotics},
|
|
123
|
+
doi={10.1109/TMECH.2026.3692920}}
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "morphopt"
|
|
3
|
-
version = "3.1
|
|
3
|
+
version = "3.2.1"
|
|
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.
|
|
20
|
+
"torchfea>=1.1.1",
|
|
21
21
|
"trimesh>=4.11.5",
|
|
22
22
|
"networkx>=3.6.1",
|
|
23
23
|
"scipy>=1.17.1",
|
|
@@ -57,7 +57,7 @@ def enable_logging(level=__logging.INFO, log_file=None, file_log_level=__logging
|
|
|
57
57
|
|
|
58
58
|
# region optcore imports
|
|
59
59
|
from .optcore.controller import Controller
|
|
60
|
-
from .optcore.modelparams import FEAParams, FixedGeometryINP, FixedGeometry, HomogeneousMaterial, Params
|
|
60
|
+
from .optcore.modelparams import FEAParams, FixedGeometryINP, FixedGeometryTorchFEA, FixedGeometry, HomogeneousMaterial, Params
|
|
61
61
|
from .optcore.solver import Solver
|
|
62
62
|
from .optcore.modelparams import Params
|
|
63
63
|
from .optcore.updaters import Updaters
|
|
@@ -79,4 +79,3 @@ from .utils import check_gradients, get_controller
|
|
|
79
79
|
# endregion
|
|
80
80
|
|
|
81
81
|
controller: Controller = None
|
|
82
|
-
|
|
@@ -40,7 +40,7 @@ class CodesignFEAParams(FEAParams):
|
|
|
40
40
|
)
|
|
41
41
|
surfcenter = surfnodes.mean(dim=1)
|
|
42
42
|
|
|
43
|
-
fe.assembly.get_instance('final_model').
|
|
43
|
+
fe.assembly.get_instance('final_model').exterior_surface = 'surface_1_offset'
|
|
44
44
|
|
|
45
45
|
mesh_1all = fe.assembly.get_instance('final_model').get_mesh(surf_name='surface_1_All')
|
|
46
46
|
mesh_1offset = fe.assembly.get_instance('final_model').get_mesh(surf_name='surface_1_offset')
|
|
@@ -72,4 +72,4 @@ class CodesignFEAParams(FEAParams):
|
|
|
72
72
|
|
|
73
73
|
raise ValueError("The Gaussian weights of the C3D6 elements are negative. Please check the mesh quality.")
|
|
74
74
|
|
|
75
|
-
return fe
|
|
75
|
+
return fe
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Runs started from *code* never open a UI: the PySide6 observer lives inside the
|
|
4
4
|
MorphOpt UI and simply polls the result folder on disk (per finished
|
|
5
|
-
iteration). The old ``no_gui`` flag and
|
|
6
|
-
therefore removed;
|
|
5
|
+
iteration). The old ``no_gui`` flag and separate observer process were
|
|
6
|
+
therefore removed; browse results from the observer page in the main UI.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .modelparams import Params, BaseGeometry, FEAParams, HomogeneousMaterial, BaseParams, FixedGeometryINP, FixedGeometry
|
|
1
|
+
from .modelparams import Params, BaseGeometry, FEAParams, HomogeneousMaterial, BaseParams, FixedGeometryINP, FixedGeometryTorchFEA, FixedGeometry
|
|
2
2
|
from .updaters import BaseUpdater, Updaters
|
|
3
3
|
from .solver import Solver
|
|
4
|
-
from .objfunc import ObjectiveFunction
|
|
4
|
+
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
|
-
|
|
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
|
"""
|
|
@@ -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)
|
|
@@ -640,7 +640,9 @@ class GeometryParams(BaseGeometry):
|
|
|
640
640
|
assembly = torchfea.Assembly()
|
|
641
641
|
|
|
642
642
|
assembly.add_part(part=part, name='final_model')
|
|
643
|
-
|
|
643
|
+
instance = torchfea.Instance(part_name='final_model')
|
|
644
|
+
assembly.add_instance(instance=instance, name='final_model')
|
|
645
|
+
instance.exterior_surface = 'surface_0_All'
|
|
644
646
|
return assembly
|
|
645
647
|
|
|
646
648
|
def _regenerate(self, path_result: str, pools = None):
|
|
@@ -776,4 +778,3 @@ class GeometryParams(BaseGeometry):
|
|
|
776
778
|
nodes_new[part.mid_pt_idxmap_torch[:, 2]] = (nodes_new[part.mid_pt_idxmap_torch[:, 0]] + nodes_new[part.mid_pt_idxmap_torch[:, 1]]) / 2
|
|
777
779
|
|
|
778
780
|
part.nodes = nodes_new
|
|
779
|
-
|
|
@@ -6,4 +6,4 @@ This module implements the SIMP (Solid Isotropic Material with Penalization) met
|
|
|
6
6
|
from .solver import SIMPSolver
|
|
7
7
|
from .update_material import UpdaterMaterials
|
|
8
8
|
from .simpmaterial import SIMP_BSPFieldMaterials
|
|
9
|
-
from ..optcore import FEAParams, HomogeneousMaterial, Solver, Updaters, ObjectiveFunction, Params, FixedGeometry, FixedGeometryINP
|
|
9
|
+
from ..optcore import FEAParams, HomogeneousMaterial, Solver, Updaters, ObjectiveFunction, Params, FixedGeometry, FixedGeometryINP, FixedGeometryTorchFEA
|
|
@@ -22,7 +22,7 @@ class VolFrac(BaseConstraints):
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def initialize(self, material_params: SIMP_BSPFieldMaterials, *args, **kwargs):
|
|
25
|
-
part = morphopt.controller.objfun.fe.assembly.get_part(
|
|
25
|
+
part = morphopt.controller.objfun.fe.assembly.get_part(material_params.part_name)
|
|
26
26
|
elems = part.elems[self.element_name]
|
|
27
27
|
self.gaussian_points = elems.get_gaussian_points(part.nodes).reshape(-1, 3)
|
|
28
28
|
self.gaussian_weights = elems.gaussian_weight.flatten()
|