morphopt 3.2.2__tar.gz → 4.0.0__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 (151) hide show
  1. {morphopt-3.2.2 → morphopt-4.0.0}/PKG-INFO +5 -3
  2. {morphopt-3.2.2 → morphopt-4.0.0}/pyproject.toml +17 -3
  3. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/__init__.py +41 -29
  4. morphopt-4.0.0/src/morphopt/codesign/__init__.py +23 -0
  5. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/codesign/constraints.py +133 -52
  6. morphopt-4.0.0/src/morphopt/codesign/feaparams.py +66 -0
  7. morphopt-4.0.0/src/morphopt/codesign/geometry.py +265 -0
  8. morphopt-4.0.0/src/morphopt/codesign/geometryparams.py +47 -0
  9. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/opt_runner.py +36 -19
  10. morphopt-4.0.0/src/morphopt/optcore/__init__.py +21 -0
  11. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/controller.py +181 -120
  12. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/history.py +174 -141
  13. morphopt-4.0.0/src/morphopt/optcore/modelparams/__init__.py +14 -0
  14. morphopt-4.0.0/src/morphopt/optcore/modelparams/baseparam.py +170 -0
  15. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/feainterface/basefeainterface.py +23 -2
  16. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/feainterface/bodyforceinterface.py +24 -14
  17. morphopt-4.0.0/src/morphopt/optcore/modelparams/feaparams.py +134 -0
  18. morphopt-4.0.0/src/morphopt/optcore/modelparams/geometry.py +64 -0
  19. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/materialinterface/basematerialinterface.py +31 -9
  20. morphopt-4.0.0/src/morphopt/optcore/modelparams/materials.py +25 -0
  21. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/params.py +60 -47
  22. morphopt-4.0.0/src/morphopt/optcore/modelparams/partinterface/__init__.py +17 -0
  23. morphopt-4.0.0/src/morphopt/optcore/modelparams/partinterface/basepartinterface.py +205 -0
  24. morphopt-4.0.0/src/morphopt/optcore/modelparams/partinterface/inppartinterface.py +72 -0
  25. morphopt-4.0.0/src/morphopt/optcore/modelparams/partinterface/torchfeapartinterface.py +177 -0
  26. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/objfunc.py +14 -10
  27. morphopt-4.0.0/src/morphopt/optcore/protocal.py +122 -0
  28. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/solver.py +107 -49
  29. morphopt-4.0.0/src/morphopt/optcore/updaters.py +779 -0
  30. morphopt-4.0.0/src/morphopt/shapeopt/__init__.py +12 -0
  31. morphopt-4.0.0/src/morphopt/shapeopt/boundarypartinterface.py +502 -0
  32. morphopt-4.0.0/src/morphopt/shapeopt/meshgenerator.py +309 -0
  33. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/shapeopt/objectivefuncs/shapederivative.py +88 -63
  34. morphopt-4.0.0/src/morphopt/shapeopt/objectivefuncs/surfacefairness.py +66 -0
  35. morphopt-4.0.0/src/morphopt/shapeopt/objectivefuncs/volumemaximization.py +58 -0
  36. morphopt-4.0.0/src/morphopt/shapeopt/surfaceinterfaces/__init__.py +21 -0
  37. {morphopt-3.2.2/src/morphopt/shapeopt/geometryinterfaces → morphopt-4.0.0/src/morphopt/shapeopt/surfaceinterfaces}/basesurfaceinterface.py +80 -76
  38. {morphopt-3.2.2/src/morphopt/shapeopt/geometryinterfaces → morphopt-4.0.0/src/morphopt/shapeopt/surfaceinterfaces}/bspsurfaceinterface.py +14 -67
  39. {morphopt-3.2.2/src/morphopt/shapeopt/geometryinterfaces → morphopt-4.0.0/src/morphopt/shapeopt/surfaceinterfaces}/cpgeosurfaceinterface.py +9 -32
  40. morphopt-4.0.0/src/morphopt/shapeopt/update_boundarypart.py +452 -0
  41. morphopt-4.0.0/src/morphopt/simp/__init__.py +10 -0
  42. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/simp/objectivefuncs/volfrac.py +13 -17
  43. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/simp/simpmaterial.py +471 -257
  44. morphopt-4.0.0/src/morphopt/simp/update_simpmaterial.py +403 -0
  45. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/taskoptmization.py +44 -17
  46. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/__init__.py +3 -1
  47. morphopt-4.0.0/src/morphopt/ui/application/__init__.py +31 -0
  48. morphopt-4.0.0/src/morphopt/ui/application/definitions.py +171 -0
  49. morphopt-4.0.0/src/morphopt/ui/application/editor_routing.py +52 -0
  50. morphopt-4.0.0/src/morphopt/ui/application/results.py +128 -0
  51. morphopt-4.0.0/src/morphopt/ui/application/runs.py +138 -0
  52. morphopt-4.0.0/src/morphopt/ui/codegen/generator.py +816 -0
  53. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/launcher.py +3 -2
  54. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/mainwindow.py +30 -34
  55. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/model/__init__.py +6 -6
  56. morphopt-4.0.0/src/morphopt/ui/model/modelinfo.py +118 -0
  57. morphopt-4.0.0/src/morphopt/ui/model/problem.py +2487 -0
  58. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/model/schemas.py +700 -204
  59. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/observe_panel.py +127 -307
  60. morphopt-4.0.0/src/morphopt/ui/schemes/__init__.py +16 -0
  61. morphopt-4.0.0/src/morphopt/ui/schemes/base.py +353 -0
  62. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/schemes/snippets.py +3 -3
  63. morphopt-4.0.0/src/morphopt/ui/templates/cantilever_beam.npz +0 -0
  64. morphopt-4.0.0/src/morphopt/ui/templates/multidesign.morph +443 -0
  65. morphopt-4.0.0/src/morphopt/ui/templates/shapeopt.morph +202 -0
  66. morphopt-4.0.0/src/morphopt/ui/templates/simp.morph +262 -0
  67. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/code_completion.py +14 -20
  68. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/editor.py +265 -70
  69. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/model_tree.py +362 -59
  70. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/objective_editor.py +6 -3
  71. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/observation_pages.py +148 -14
  72. morphopt-4.0.0/src/morphopt/ui/widgets/run_dialogs.py +149 -0
  73. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/stepmatrix.py +22 -23
  74. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/template_insert_dialog.py +23 -17
  75. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/torchfea_model_editor.py +182 -102
  76. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/updater_editor.py +277 -150
  77. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/viewer.py +234 -81
  78. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/workbench.py +274 -188
  79. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt.egg-info/PKG-INFO +5 -3
  80. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt.egg-info/SOURCES.txt +26 -17
  81. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt.egg-info/requires.txt +4 -1
  82. morphopt-3.2.2/src/morphopt/codesign/__init__.py +0 -20
  83. morphopt-3.2.2/src/morphopt/codesign/feaparams.py +0 -80
  84. morphopt-3.2.2/src/morphopt/codesign/geometry.py +0 -362
  85. morphopt-3.2.2/src/morphopt/optcore/__init__.py +0 -8
  86. morphopt-3.2.2/src/morphopt/optcore/baseobject.py +0 -48
  87. morphopt-3.2.2/src/morphopt/optcore/modelparams/__init__.py +0 -5
  88. morphopt-3.2.2/src/morphopt/optcore/modelparams/baseparam.py +0 -133
  89. morphopt-3.2.2/src/morphopt/optcore/modelparams/feaparams.py +0 -239
  90. morphopt-3.2.2/src/morphopt/optcore/modelparams/geometry.py +0 -251
  91. morphopt-3.2.2/src/morphopt/optcore/modelparams/materials.py +0 -175
  92. morphopt-3.2.2/src/morphopt/optcore/updaters.py +0 -384
  93. morphopt-3.2.2/src/morphopt/shapeopt/__init__.py +0 -10
  94. morphopt-3.2.2/src/morphopt/shapeopt/geometryinterfaces/__init__.py +0 -3
  95. morphopt-3.2.2/src/morphopt/shapeopt/geometryparams.py +0 -794
  96. morphopt-3.2.2/src/morphopt/shapeopt/objectivefuncs/surfacefairness.py +0 -38
  97. morphopt-3.2.2/src/morphopt/shapeopt/objectivefuncs/volumemaximization.py +0 -25
  98. morphopt-3.2.2/src/morphopt/shapeopt/objfunc.py +0 -13
  99. morphopt-3.2.2/src/morphopt/shapeopt/update_geometry.py +0 -335
  100. morphopt-3.2.2/src/morphopt/simp/__init__.py +0 -12
  101. morphopt-3.2.2/src/morphopt/simp/solver.py +0 -41
  102. morphopt-3.2.2/src/morphopt/simp/update_material.py +0 -324
  103. morphopt-3.2.2/src/morphopt/ui/codegen/generator.py +0 -586
  104. morphopt-3.2.2/src/morphopt/ui/model/problem.py +0 -1220
  105. morphopt-3.2.2/src/morphopt/ui/schemes/__init__.py +0 -18
  106. morphopt-3.2.2/src/morphopt/ui/schemes/base.py +0 -206
  107. morphopt-3.2.2/src/morphopt/ui/schemes/codesign.py +0 -155
  108. morphopt-3.2.2/src/morphopt/ui/schemes/shapeopt.py +0 -104
  109. morphopt-3.2.2/src/morphopt/ui/schemes/simp.py +0 -112
  110. morphopt-3.2.2/tests/test_fixedGeometry.py +0 -24
  111. morphopt-3.2.2/tests/test_path.py +0 -12
  112. morphopt-3.2.2/tests/test_ui.py +0 -62
  113. {morphopt-3.2.2 → morphopt-4.0.0}/README.md +0 -0
  114. {morphopt-3.2.2 → morphopt-4.0.0}/setup.cfg +0 -0
  115. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/codesign/params.py +0 -0
  116. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/feainterface/__init__.py +0 -0
  117. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/feainterface/boundaryconditioninterface.py +0 -0
  118. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/feainterface/contactinterface.py +0 -0
  119. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/feainterface/coupleinterface.py +0 -0
  120. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/feainterface/pointinterface.py +0 -0
  121. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/feainterface/pressureinterface.py +0 -0
  122. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/feainterface/referencepointinterface.py +0 -0
  123. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/feainterface/springinterface.py +0 -0
  124. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/materialinterface/__init__.py +0 -0
  125. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/materialinterface/homogeneousmaterial.py +0 -0
  126. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/optcore/modelparams/materialinterface/materialmodels.py +0 -0
  127. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/shapeopt/objectivefuncs/__init__.py +0 -0
  128. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/shapeopt/objectivefuncs/basefuncs.py +0 -0
  129. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/shapeopt/objectivefuncs/boundarys.py +0 -0
  130. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/shapeopt/objectivefuncs/distancesurface.py +0 -0
  131. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/simp/objectivefuncs/__init__.py +0 -0
  132. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/simp/objectivefuncs/basefuncs.py +0 -0
  133. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/simp/objectivefuncs/boundarys.py +0 -0
  134. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/simp/objectivefuncs/densityfield.py +0 -0
  135. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/simp/objectivefuncs/sensitivity.py +0 -0
  136. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/__main__.py +0 -0
  137. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/app.py +0 -0
  138. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/codegen/__init__.py +0 -0
  139. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/i18n.py +0 -0
  140. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/model/loaders.py +0 -0
  141. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/__init__.py +0 -0
  142. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/codeeditor.py +0 -0
  143. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/param_form.py +0 -0
  144. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/solver_editor.py +0 -0
  145. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/ui/widgets/values.py +0 -0
  146. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/utils/__init__.py +0 -0
  147. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/utils/gradient_check.py +0 -0
  148. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt/utils/history_read.py +0 -0
  149. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt.egg-info/dependency_links.txt +0 -0
  150. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt.egg-info/entry_points.txt +0 -0
  151. {morphopt-3.2.2 → morphopt-4.0.0}/src/morphopt.egg-info/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: morphopt
3
- Version: 3.2.2
4
- Summary: A morphing optimization framework using PyTorch and Abaqus.
3
+ Version: 4.0.0
4
+ Summary: A morphing optimization framework using PyTorch and torchfea.
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
7
7
  Requires-Dist: torch>=2.9.1
@@ -16,11 +16,13 @@ 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.1
19
+ Requires-Dist: torchfea>=1.1.2
20
20
  Requires-Dist: trimesh>=4.11.5
21
21
  Requires-Dist: networkx>=3.6.1
22
22
  Requires-Dist: scipy>=1.17.1
23
23
  Requires-Dist: cpgeo>=1.0.12
24
+ Provides-Extra: test
25
+ Requires-Dist: pytest>=8.0; extra == "test"
24
26
 
25
27
  # MorphOpt — A Differentiable Optimization Framework for Soft Structure & Morphology Design
26
28
 
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "morphopt"
3
- version = "3.2.2"
4
- description = "A morphing optimization framework using PyTorch and Abaqus."
3
+ version = "4.0.0"
4
+ description = "A morphing optimization framework using PyTorch and torchfea."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
7
7
  dependencies = [
@@ -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.1",
20
+ "torchfea>=1.1.2",
21
21
  "trimesh>=4.11.5",
22
22
  "networkx>=3.6.1",
23
23
  "scipy>=1.17.1",
@@ -27,3 +27,17 @@ dependencies = [
27
27
  [project.scripts]
28
28
  # console entry for the graphical UI (after `pip install morphopt`)
29
29
  morphopt-ui = "morphopt.ui:run_app"
30
+
31
+ [project.optional-dependencies]
32
+ # Install with: python -m pip install -e ".[test]"
33
+ test = [
34
+ "pytest>=8.0",
35
+ ]
36
+
37
+ [tool.pytest.ini_options]
38
+ testpaths = ["tests"]
39
+ pythonpath = ["src"]
40
+ addopts = "-ra"
41
+
42
+ [tool.setuptools.package-data]
43
+ "morphopt.ui" = ["templates/*.morph", "templates/*.npz"]
@@ -13,6 +13,7 @@ if __sys.platform.startswith("linux"):
13
13
  # region: Logging Configuration
14
14
  import logging as __logging
15
15
 
16
+
16
17
  def enable_logging(level=__logging.INFO, log_file=None, file_log_level=__logging.DEBUG):
17
18
  """
18
19
  Enable logging for the FEA package.
@@ -25,7 +26,7 @@ def enable_logging(level=__logging.INFO, log_file=None, file_log_level=__logging
25
26
  the path to a log file where logs will be written. If None, logs will only be printed to the console.
26
27
  file_log_level : int
27
28
  the logging level for the log file. Default is logging.INFO.
28
-
29
+
29
30
  Examples
30
31
  --------
31
32
  >>> import torchfea
@@ -33,59 +34,70 @@ def enable_logging(level=__logging.INFO, log_file=None, file_log_level=__logging
33
34
  """
34
35
  logger = __logging.getLogger(__name__)
35
36
  logger.setLevel(min(level, file_log_level))
36
-
37
+
37
38
  # clear existing handlers to avoid duplicate logs
38
39
  logger.handlers.clear()
39
-
40
+
40
41
  # logging to console
41
42
  console = __logging.StreamHandler()
42
43
  console.setLevel(level)
43
- console.setFormatter(__logging.Formatter(
44
- '%(asctime)s | %(levelname)-7s | %(message)s',
45
- datefmt='%Y-%m-%d %H:%M:%S'
46
- ))
44
+ console.setFormatter(
45
+ __logging.Formatter(
46
+ "%(asctime)s | %(levelname)-7s | %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
47
+ )
48
+ )
47
49
  logger.addHandler(console)
48
-
50
+
49
51
  # logging to file if log_file is provided
50
52
  if log_file:
51
- file_handler = __logging.FileHandler(log_file, encoding='utf-8')
53
+ file_handler = __logging.FileHandler(log_file, encoding="utf-8")
52
54
  file_handler.setLevel(file_log_level)
53
- file_handler.setFormatter(__logging.Formatter(
54
- '%(asctime)s | %(levelname)-7s | %(name)s | %(message)s'
55
- ))
55
+ file_handler.setFormatter(
56
+ __logging.Formatter(
57
+ "%(asctime)s | %(levelname)-7s | %(name)s | %(message)s"
58
+ )
59
+ )
56
60
  logger.addHandler(file_handler)
57
61
 
58
62
  return logger
59
- # endregion
60
-
61
63
 
62
64
 
65
+ # endregion
63
66
 
64
67
 
65
- # region optcore imports
68
+ # region modules (loaded after the core classes they subclass)
69
+ from . import codesign, shapeopt, simp
70
+ from .opt_runner import debug_optimization, start_optimization
66
71
  from .optcore.controller import Controller
72
+ from .optcore.history import History
67
73
  from .optcore.modelparams import (
68
- FEAParams, FixedGeometryINP, FixedGeometryTorchFEA, FixedGeometry,
69
- MaterialsParams, Params,
74
+ BasePartInterface,
75
+ FEAParams,
76
+ GeometryParams,
77
+ INPPartInterface,
78
+ MaterialsParams,
79
+ Params,
80
+ TorchFEAPartInterface,
81
+ load_model_assembly,
82
+ resolve_model_path,
83
+ )
84
+ from .optcore.modelparams.materialinterface import HomogeneousMaterial
85
+ from .optcore.objfunc import ObjectiveFunction
86
+ from .optcore.protocal import (
87
+ ProtocalInitializable,
88
+ ProtocalSavable,
89
+ ProtocalUpdatable,
90
+ ProtocalVisualizable,
70
91
  )
71
92
  from .optcore.solver import Solver
72
- from .optcore.modelparams import Params
73
93
  from .optcore.updaters import Updaters
74
- from .optcore.objfunc import ObjectiveFunction
75
- from .optcore.history import History
76
- from .optcore.baseobject import BaseObject
77
- # endregion
78
-
79
- from .opt_runner import start_optimization, debug_optimization
94
+ from .shapeopt import BoundaryPartInterface, UpdaterBoundaryPart
95
+ from .simp import SIMP_BSPFieldMaterials, UpdaterSIMPMaterial
80
96
 
81
- # region modules
82
- from . import shapeopt
83
- from . import codesign
84
- from . import simp
85
97
  # endregion
86
-
87
98
  # region scripts
88
99
  from .utils import check_gradients, get_controller
100
+
89
101
  # endregion
90
102
 
91
103
  controller: Controller = None
@@ -0,0 +1,23 @@
1
+ """Co-design (codesign) scheme: shape optimization coupled with SIMP materials."""
2
+
3
+ from ..optcore import MaterialsParams, ObjectiveFunction, Solver, Updaters
4
+ from ..shapeopt import UpdaterBoundaryPart
5
+ from ..simp import SIMP_BSPFieldMaterials, UpdaterSIMPMaterial
6
+ from .constraints import InwardCurvatureRadius, OffsetSurfaceMinThickness
7
+ from .feaparams import CodesignFEAParams
8
+ from .geometry import CodesignBoundaryPartInterface
9
+ from .geometryparams import GeometryParams
10
+ from .params import Params
11
+
12
+
13
+ def plot_geometry_and_materials(geometry: GeometryParams, materials: MaterialsParams):
14
+ """Plot every geometry interface together with the material fields."""
15
+ import pyvista as pv
16
+
17
+ plotter = pv.Plotter(window_size=(1400, 900))
18
+ plotter.set_background("white")
19
+
20
+ plotter = materials.plot(plotter=plotter)
21
+ geometry.plot(plotter=plotter)
22
+
23
+ plotter.show()
@@ -1,7 +1,7 @@
1
1
  import torch
2
2
 
3
3
  from ..shapeopt.objectivefuncs.basefuncs import BaseConstraints
4
- from .geometry import CodesignGeometry
4
+ from .geometry import CodesignBoundaryPartInterface
5
5
 
6
6
 
7
7
  class InwardCurvatureRadius(BaseConstraints):
@@ -15,7 +15,6 @@ class InwardCurvatureRadius(BaseConstraints):
15
15
 
16
16
  def __init__(
17
17
  self,
18
- geometry: CodesignGeometry,
19
18
  surface_start: int = 1,
20
19
  surface_ids: list[int] | None = None,
21
20
  margin: float = 0.4,
@@ -31,7 +30,6 @@ class InwardCurvatureRadius(BaseConstraints):
31
30
  Initialize the inward curvature radius constraint.
32
31
 
33
32
  Args:
34
- geometry (CodesignGeometry): The geometry parameters containing the surfaces.
35
33
  surface_start (int, optional): The starting index of surfaces to apply the constraint. Defaults to 1.
36
34
  surface_ids (list[int] | None, optional): The specific surface indices to apply the constraint. If None, applies to all surfaces starting from surface_start. Defaults to None.
37
35
  margin (float, optional): The absolute margin added to thickness for curvature limit. Defaults to 0.4.
@@ -42,17 +40,16 @@ class InwardCurvatureRadius(BaseConstraints):
42
40
  penalty_scale (float, optional): Global multiplier for this constraint penalty. Defaults to 1e3.
43
41
  hard_violation_beta (float, optional): Exponential amplification factor for large violations. Defaults to 8.0.
44
42
  normal_sign (float, optional): The sign to determine inward direction. Use 1.0 if normals point outward, -1.0 if normals point inward. Defaults to 1.0.
45
-
46
- """
47
43
 
44
+ """
48
45
 
49
46
  super().__init__()
50
47
 
51
- self.geometry = geometry
52
- """Stores the geometry used by InwardCurvatureRadius."""
53
48
  self.surface_start = int(surface_start)
54
49
  """Stores the surface start used by InwardCurvatureRadius."""
55
- self.surface_ids = None if surface_ids is None else [int(i) for i in surface_ids]
50
+ self.surface_ids = (
51
+ None if surface_ids is None else [int(i) for i in surface_ids]
52
+ )
56
53
  """Stores the surface ids used by InwardCurvatureRadius."""
57
54
 
58
55
  self.margin = margin
@@ -73,7 +70,9 @@ class InwardCurvatureRadius(BaseConstraints):
73
70
  """Stores the normal sign used by InwardCurvatureRadius."""
74
71
 
75
72
  @staticmethod
76
- def _principal_curvatures(rdu: torch.Tensor, rdu2: torch.Tensor, eps: float = 1e-12) -> tuple[torch.Tensor, torch.Tensor]:
73
+ def _principal_curvatures(
74
+ rdu: torch.Tensor, rdu2: torch.Tensor, eps: float = 1e-12
75
+ ) -> tuple[torch.Tensor, torch.Tensor]:
77
76
  normal0 = torch.cross(rdu[:, :, 1], rdu[:, :, 0], dim=1)
78
77
  normal = normal0 / (normal0.norm(dim=1, keepdim=True) + eps)
79
78
 
@@ -93,26 +92,45 @@ class InwardCurvatureRadius(BaseConstraints):
93
92
  k2 = 0.5 * (tr - root)
94
93
  return k1, k2
95
94
 
95
+ def initialize(self, r0, rdu0, *args, part_interface=None, **kwargs) -> None:
96
+ """Take the Part this constraint is evaluated for.
97
+
98
+ The geometry updater hands over the Part it evaluates; only a
99
+ :class:`~morphopt.codesign.CodesignBoundaryPartInterface` carries a
100
+ shell, so anything else is rejected here.
101
+ """
102
+ super().initialize(r0=r0, rdu0=rdu0, *args, **kwargs)
103
+ if not isinstance(part_interface, CodesignBoundaryPartInterface):
104
+ raise TypeError(
105
+ f"{type(self).__name__} is evaluated for one co-design Part "
106
+ f"(got {type(part_interface).__name__})."
107
+ )
108
+ self.shell_thickness = float(part_interface.shell_thickness)
109
+
96
110
  def _selected_surface_ids(self, n_surface: int) -> list[int]:
97
111
  if self.surface_ids is None:
98
112
  return [i for i in range(max(self.surface_start, 0), n_surface)]
99
113
  return [i for i in self.surface_ids if 0 <= i < n_surface]
100
114
 
101
- def __call__(self, r: list[torch.Tensor], rdu: list[torch.Tensor], rdu2: list[torch.Tensor], *args, **kwargs) -> torch.Tensor:
115
+ def __call__(
116
+ self,
117
+ r: list[torch.Tensor],
118
+ rdu: list[torch.Tensor],
119
+ rdu2: list[torch.Tensor],
120
+ *args,
121
+ **kwargs,
122
+ ) -> torch.Tensor:
102
123
  loss = torch.zeros(1, device=r[0].device, dtype=r[0].dtype)
103
124
 
104
- t = float(self.geometry.shell_thickness)
105
- if t <= 0:
106
- return loss
107
-
108
- margin_abs = (self.margin_ratio * t) + self.margin
109
- margin_abs = max(float(margin_abs), 1e-12)
110
- t_eff = t + margin_abs
125
+ for i in self._selected_surface_ids(len(r)):
126
+ t = self.shell_thickness
127
+ if t <= 0:
128
+ continue
111
129
 
112
- k_limit = 1.0 / t_eff
113
- barrier_thre = max(k_limit * self.barrier_thre_ratio, 1e-12)
130
+ margin_abs = max(float((self.margin_ratio * t) + self.margin), 1e-12)
131
+ k_limit = 1.0 / (t + margin_abs)
132
+ barrier_thre = max(k_limit * self.barrier_thre_ratio, 1e-12)
114
133
 
115
- for i in self._selected_surface_ids(len(r)):
116
134
  k1, k2 = self._principal_curvatures(rdu[i], rdu2[i])
117
135
 
118
136
  k1_in = torch.relu(self.normal_sign * k1)
@@ -120,16 +138,23 @@ class InwardCurvatureRadius(BaseConstraints):
120
138
  k_inward = torch.maximum(k1_in, k2_in)
121
139
 
122
140
  violation = k_inward - k_limit
123
- index, penalty = self.barrier_function(violation, barrier_thre, self.barrier_ratio, self.p)
141
+ index, penalty = self.barrier_function(
142
+ violation, barrier_thre, self.barrier_ratio, self.p
143
+ )
124
144
  if index.numel() > 0:
125
145
  # Strong amplification: base polynomial barrier + exponential hard-violation boost.
126
146
  rel_violation = (violation[index] / barrier_thre).clamp(min=0.0)
127
- hard_boost = torch.exp((self.hard_violation_beta * (rel_violation - 1.0)).clamp(min=0.0, max=20.0))
147
+ hard_boost = torch.exp(
148
+ (self.hard_violation_beta * (rel_violation - 1.0)).clamp(
149
+ min=0.0, max=20.0
150
+ )
151
+ )
128
152
  penalty_strong = self.penalty_scale * penalty * hard_boost
129
153
  loss = loss + (self.scaler[i][index] * penalty_strong).sum()
130
154
 
131
155
  return loss
132
156
 
157
+
133
158
  class OffsetSurfaceMinThickness(BaseConstraints):
134
159
  """
135
160
  Enforce a minimum distance on the offset surface to reduce self-intersection risk.
@@ -140,7 +165,6 @@ class OffsetSurfaceMinThickness(BaseConstraints):
140
165
 
141
166
  def __init__(
142
167
  self,
143
- geometry: CodesignGeometry,
144
168
  surface_start: int = 1,
145
169
  surface_ids: list[int] | None = None,
146
170
  min_distance: float | None = None,
@@ -155,13 +179,22 @@ class OffsetSurfaceMinThickness(BaseConstraints):
155
179
  normal_min: float = -0.7,
156
180
  normal_max: float = -0.5,
157
181
  ) -> None:
182
+ """
183
+ Initialize the minimum thickness constraint for the offset surface.
184
+
185
+ Args:
186
+ surface_start (int, optional): The starting index of surfaces to apply the constraint. Defaults to 1.
187
+ surface_ids (list[int] | None, optional): The specific surface indices to apply the constraint. If None, applies to all surfaces starting from surface_start. Defaults to None.
188
+ min_distance (float | None, optional): The required minimum distance.
189
+ Defaults to twice the shell thickness.
190
+ """
158
191
  super().__init__()
159
192
 
160
- self.geometry = geometry
161
- """Stores the geometry used by OffsetSurfaceMinThickness."""
162
193
  self.surface_start = int(surface_start)
163
194
  """Stores the surface start used by OffsetSurfaceMinThickness."""
164
- self.surface_ids = None if surface_ids is None else [int(i) for i in surface_ids]
195
+ self.surface_ids = (
196
+ None if surface_ids is None else [int(i) for i in surface_ids]
197
+ )
165
198
  """Stores the surface ids used by OffsetSurfaceMinThickness."""
166
199
 
167
200
  self.min_distance = min_distance
@@ -203,33 +236,60 @@ class OffsetSurfaceMinThickness(BaseConstraints):
203
236
  normals = normal0 / (normal0.norm(dim=1, keepdim=True) + 1e-12)
204
237
  return normals
205
238
 
206
- def _offset_points(self, r_sf: torch.Tensor, rdu_sf: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
239
+ def _offset_points(
240
+ self, r_sf: torch.Tensor, rdu_sf: torch.Tensor, thickness: float
241
+ ) -> tuple[torch.Tensor, torch.Tensor]:
242
+ """Offset surface points and the normals they were offset along."""
207
243
  normals = self._surface_normals(rdu_sf=rdu_sf)
208
- thickness = float(self.geometry.shell_thickness)
209
244
  return r_sf + self.normal_sign * thickness * normals, normals
210
245
 
211
- def initialize(self, r0, rdu0, *args, **kwargs) -> None:
246
+ def _min_distance(self, thickness: float) -> float:
247
+ dmin = (
248
+ float(self.min_distance)
249
+ if self.min_distance is not None
250
+ else (2.0 * thickness)
251
+ )
252
+ return max(dmin, 1e-12)
253
+
254
+ def initialize(self, r0, rdu0, *args, part_interface=None, **kwargs) -> None:
255
+ """Take the Part this constraint is evaluated for and pair its points.
256
+
257
+ The geometry updater hands over the Part it evaluates; only a
258
+ :class:`~morphopt.codesign.CodesignBoundaryPartInterface` carries a
259
+ shell, so anything else is rejected here.
260
+ """
212
261
  super().initialize(r0=r0, rdu0=rdu0, *args, **kwargs)
262
+ if not isinstance(part_interface, CodesignBoundaryPartInterface):
263
+ raise TypeError(
264
+ f"{type(self).__name__} is evaluated for one co-design Part "
265
+ f"(got {type(part_interface).__name__})."
266
+ )
267
+ self.shell_thickness = float(part_interface.shell_thickness)
213
268
 
214
269
  self._neighbor_pairs = {}
215
270
  self._neighbor_mindist = {}
216
271
 
217
- import scipy.spatial
272
+ if self.shell_thickness <= 0:
273
+ return
218
274
 
219
- thickness = float(self.geometry.shell_thickness)
220
- dmin = float(self.min_distance) if self.min_distance is not None else (2.0 * thickness)
221
- dmin = max(dmin, 1e-12)
222
- search_radius = max(dmin * self.search_ratio, dmin)
275
+ import scipy.spatial
223
276
 
224
277
  for sf_idx in self._selected_surface_ids(len(r0)):
225
- roff, normals = self._offset_points(r_sf=r0[sf_idx], rdu_sf=rdu0[sf_idx])
278
+ dmin = self._min_distance(self.shell_thickness)
279
+ search_radius = max(dmin * self.search_ratio, dmin)
280
+
281
+ roff, normals = self._offset_points(
282
+ r_sf=r0[sf_idx], rdu_sf=rdu0[sf_idx], thickness=self.shell_thickness
283
+ )
226
284
 
227
285
  tree = scipy.spatial.KDTree(roff.detach().cpu().numpy())
228
- pairs_np = tree.query_pairs(r=search_radius, output_type='ndarray')
286
+ pairs_np = tree.query_pairs(r=search_radius, output_type="ndarray")
229
287
  if pairs_np.size == 0:
230
288
  continue
231
289
 
232
- pairs = torch.from_numpy(pairs_np.T).to(dtype=torch.int64, device=r0[sf_idx].device)
290
+ pairs = torch.from_numpy(pairs_np.T).to(
291
+ dtype=torch.int64, device=r0[sf_idx].device
292
+ )
233
293
 
234
294
  dotn = (normals[pairs[0]] * normals[pairs[1]]).sum(dim=1)
235
295
  opposition = (-dotn).clamp(min=0.0)
@@ -239,22 +299,28 @@ class OffsetSurfaceMinThickness(BaseConstraints):
239
299
  self._neighbor_pairs[sf_idx] = pairs
240
300
  self._neighbor_mindist[sf_idx] = dmin * opposition[keep]
241
301
 
242
- def __call__(self, r: list[torch.Tensor], rdu: list[torch.Tensor], rdu2: list[torch.Tensor], *args, **kwargs) -> torch.Tensor:
302
+ def __call__(
303
+ self,
304
+ r: list[torch.Tensor],
305
+ rdu: list[torch.Tensor],
306
+ rdu2: list[torch.Tensor],
307
+ *args,
308
+ **kwargs,
309
+ ) -> torch.Tensor:
243
310
  loss = torch.zeros(1, device=r[0].device, dtype=r[0].dtype)
244
311
 
245
- if float(self.geometry.shell_thickness) <= 0:
246
- return loss
247
-
248
- thickness = float(self.geometry.shell_thickness)
249
- dmin = float(self.min_distance) if self.min_distance is not None else (2.0 * thickness)
250
- dmin = max(dmin, 1e-12)
251
- barrier_thre = max(dmin * self.barrier_thre_ratio, 1e-12)
252
-
253
312
  for sf_idx in self._selected_surface_ids(len(r)):
254
313
  pairs = self._neighbor_pairs.get(sf_idx, None)
255
314
  if pairs is None or pairs.numel() == 0:
256
315
  continue
257
316
 
317
+ thickness = self.shell_thickness
318
+ if thickness <= 0:
319
+ continue
320
+ barrier_thre = max(
321
+ self._min_distance(thickness) * self.barrier_thre_ratio, 1e-12
322
+ )
323
+
258
324
  # Base surface normals (offset surface shares same normal direction)
259
325
  normals = self._surface_normals(rdu_sf=rdu[sf_idx])
260
326
  dotn = (normals[pairs[0]] * normals[pairs[1]]).sum(dim=1)
@@ -267,27 +333,42 @@ class OffsetSurfaceMinThickness(BaseConstraints):
267
333
  pairs_n = pairs[:, mask_n]
268
334
  dotn_n = dotn[mask_n]
269
335
  mindist_n = self._neighbor_mindist[sf_idx][mask_n].to(
270
- device=r[sf_idx].device, dtype=r[sf_idx].dtype)
336
+ device=r[sf_idx].device, dtype=r[sf_idx].dtype
337
+ )
271
338
  scaler_n0 = self.scaler[sf_idx][pairs_n[0]]
272
339
  scaler_n1 = self.scaler[sf_idx][pairs_n[1]]
273
340
 
274
341
  # Offset points and distance
275
- roff, _ = self._offset_points(r_sf=r[sf_idx], rdu_sf=rdu[sf_idx])
342
+ roff, _ = self._offset_points(
343
+ r_sf=r[sf_idx], rdu_sf=rdu[sf_idx], thickness=thickness
344
+ )
276
345
  delta = roff[pairs_n[0]] - roff[pairs_n[1]]
277
346
  dist = torch.sqrt((delta * delta).sum(dim=1) + 1e-18)
278
347
 
279
348
  violation = mindist_n - dist + barrier_thre
280
- indexl, l = self.barrier_function(violation, barrier_thre, self.barrier_ratio, self.p)
349
+ indexl, l = self.barrier_function(
350
+ violation, barrier_thre, self.barrier_ratio, self.p
351
+ )
281
352
 
282
353
  if indexl.numel() > 0:
283
354
  # Smooth normal penalty (same as Distance class)
284
- np_val = (self.normal_max - torch.clamp(dotn_n[indexl], self.normal_min, self.normal_max)) / (self.normal_max - self.normal_min)
355
+ np_val = (
356
+ self.normal_max
357
+ - torch.clamp(dotn_n[indexl], self.normal_min, self.normal_max)
358
+ ) / (self.normal_max - self.normal_min)
285
359
  np_smooth = (6 * np_val**2 - 15 * np_val + 10) * np_val**3
286
360
 
287
361
  rel_violation = (violation[indexl] / barrier_thre).clamp(min=0.0)
288
- hard_boost = torch.exp((self.hard_violation_beta * (rel_violation - 1.0)).clamp(min=0.0, max=20.0))
362
+ hard_boost = torch.exp(
363
+ (self.hard_violation_beta * (rel_violation - 1.0)).clamp(
364
+ min=0.0, max=20.0
365
+ )
366
+ )
289
367
  penalty_strong = self.penalty_scale * l * hard_boost * np_smooth
290
368
 
291
- loss = loss + (penalty_strong * scaler_n0[indexl] * scaler_n1[indexl]).sum()
369
+ loss = (
370
+ loss
371
+ + (penalty_strong * scaler_n0[indexl] * scaler_n1[indexl]).sum()
372
+ )
292
373
 
293
374
  return loss
@@ -0,0 +1,66 @@
1
+ """FEA parameter handling for the co-design (``codesign``) scheme.
2
+
3
+ Co-design builds extra ``C3D6``/``C3D15`` shell elements on the cavity surfaces
4
+ of every shell-carrying geometry interface. Those elements need to be
5
+ initialized (integration points and weights) against their Part before the
6
+ analysis runs, which is what this specialization of ``FEAParams`` does.
7
+ """
8
+
9
+ import torchfea
10
+
11
+ import morphopt
12
+
13
+ from ..optcore.modelparams.feaparams import FEAParams
14
+ from ..optcore.modelparams.geometry import GeometryParams
15
+ from .geometry import CodesignBoundaryPartInterface
16
+
17
+ __all__ = ["CodesignFEAParams"]
18
+
19
+
20
+ class CodesignFEAParams(FEAParams):
21
+ """FEA parameters that initialize the co-design offset shells."""
22
+
23
+ def shell_interfaces(
24
+ self, geometry: GeometryParams | None = None
25
+ ) -> list[CodesignBoundaryPartInterface]:
26
+ """Geometry interfaces carrying an offset shell."""
27
+ if geometry is None:
28
+ controller = morphopt.controller
29
+ geometry = controller.params.geometry if controller is not None else None
30
+ if geometry is None:
31
+ return []
32
+ return [
33
+ interface
34
+ for interface in geometry.interfaces.values()
35
+ if isinstance(interface, CodesignBoundaryPartInterface)
36
+ ]
37
+
38
+ def create_fea(self, assembly: torchfea.Assembly) -> torchfea.FEAController:
39
+ """Create the FEA controller and initialize every offset shell."""
40
+ fe = super().create_fea(assembly=assembly)
41
+
42
+ for interface in self.shell_interfaces():
43
+ if float(interface.shell_thickness) <= 0:
44
+ continue
45
+
46
+ part = fe.assembly.get_part(interface.part_name)
47
+ element_shell = None
48
+ for name in ("C3D6", "C3D15"):
49
+ if name in part.elems:
50
+ element_shell = part.elems[name]
51
+ break
52
+ if element_shell is None:
53
+ raise KeyError(
54
+ f"Shell wedge elements not found on Part "
55
+ f"{interface.part_name!r} (expected 'C3D6' or 'C3D15')."
56
+ )
57
+
58
+ element_shell.initialize(nodes=part.nodes)
59
+ if element_shell.gaussian_weight.min() < 0:
60
+ raise ValueError(
61
+ f"The Gaussian weights of the shell elements on Part "
62
+ f"{interface.part_name!r} are negative. Check the mesh "
63
+ "quality of the offset shell."
64
+ )
65
+
66
+ return fe