morphopt 3.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of morphopt might be problematic. Click here for more details.
- morphopt/__init__.py +32 -0
- morphopt/opt_runner.py +130 -0
- morphopt/optcore/baseobject.py +48 -0
- morphopt/optcore/controller.py +404 -0
- morphopt/optcore/history.py +440 -0
- morphopt/optcore/modelparams/__init__.py +4 -0
- morphopt/optcore/modelparams/base_params.py +97 -0
- morphopt/optcore/modelparams/feamodel/__init__.py +0 -0
- morphopt/optcore/modelparams/feamodel/feainterface/__init__.py +10 -0
- morphopt/optcore/modelparams/feamodel/feainterface/basefeainterface.py +50 -0
- morphopt/optcore/modelparams/feamodel/feainterface/bodyforceinterface.py +71 -0
- morphopt/optcore/modelparams/feamodel/feainterface/boundaryconditioninterface.py +59 -0
- morphopt/optcore/modelparams/feamodel/feainterface/contactinterface.py +113 -0
- morphopt/optcore/modelparams/feamodel/feainterface/coupleinterface.py +31 -0
- morphopt/optcore/modelparams/feamodel/feainterface/pointinterface.py +79 -0
- morphopt/optcore/modelparams/feamodel/feainterface/pressureinterface.py +68 -0
- morphopt/optcore/modelparams/feamodel/feainterface/referencepointinterface.py +29 -0
- morphopt/optcore/modelparams/feamodel/feainterface/springinterface.py +104 -0
- morphopt/optcore/modelparams/feamodel/feaparams.py +264 -0
- morphopt/optcore/modelparams/geometry/__init__.py +3 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/__init__.py +1 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/basesurfaceinterface.py +763 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/bspsurfaceinterface.py +723 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/cpgeosurfaceinterface.py +374 -0
- morphopt/optcore/modelparams/geometry/geometryparams.py +715 -0
- morphopt/optcore/modelparams/materials/__init__.py +1 -0
- morphopt/optcore/modelparams/materials/materialparams.py +110 -0
- morphopt/optcore/modelparams/params.py +88 -0
- morphopt/optcore/objfunc.py +248 -0
- morphopt/optcore/solver.py +159 -0
- morphopt/optcore/updaters/__init__.py +2 -0
- morphopt/optcore/updaters/base_updater.py +110 -0
- morphopt/optcore/updaters/geometry/__init__.py +2 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/__init__.py +5 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/basefuncs.py +117 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/boundarys.py +91 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/distancesurface.py +123 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/shapederivative.py +179 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/surfacefairness.py +40 -0
- morphopt/optcore/updaters/geometry/update_geometry.py +347 -0
- morphopt/optcore/updaters/optimizer.py +177 -0
- morphopt/optcore/updaters/updaters.py +82 -0
- morphopt/optcore/utils/plot_history_surface.py +239 -0
- morphopt/taskoptmization.py +75 -0
- morphopt/taskui.py +458 -0
- morphopt-3.1.1.dist-info/METADATA +242 -0
- morphopt-3.1.1.dist-info/RECORD +49 -0
- morphopt-3.1.1.dist-info/WHEEL +5 -0
- morphopt-3.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .materialparams import Materials
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from torch._refs import to
|
|
3
|
+
|
|
4
|
+
import torchfea
|
|
5
|
+
from ..base_params import BaseParams
|
|
6
|
+
|
|
7
|
+
class Materials(BaseParams):
|
|
8
|
+
"""
|
|
9
|
+
Class to handle the materials of the morphable model.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, mu: float, kappa: float, density: float) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Initialize the Materials class.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
mu (float): The shear modulus of the material.
|
|
18
|
+
kappa (float): The bulk modulus of the material.
|
|
19
|
+
"""
|
|
20
|
+
super().__init__()
|
|
21
|
+
self._mu: float
|
|
22
|
+
self._kappa: float
|
|
23
|
+
self._density: float
|
|
24
|
+
|
|
25
|
+
self.mu = mu
|
|
26
|
+
self.kappa = kappa
|
|
27
|
+
self.density = density
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def mu(self) -> float:
|
|
31
|
+
"""
|
|
32
|
+
Get the shear modulus.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
float: The shear modulus.
|
|
36
|
+
"""
|
|
37
|
+
return self._mu
|
|
38
|
+
|
|
39
|
+
@mu.setter
|
|
40
|
+
def mu(self, value: float) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Set the shear modulus.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
value (float): The new shear modulus.
|
|
46
|
+
"""
|
|
47
|
+
self._mu = float(value)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def kappa(self) -> float:
|
|
51
|
+
"""
|
|
52
|
+
Get the bulk modulus.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
float: The bulk modulus.
|
|
56
|
+
"""
|
|
57
|
+
return self._kappa
|
|
58
|
+
|
|
59
|
+
@kappa.setter
|
|
60
|
+
def kappa(self, value: float) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Set the bulk modulus.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
value (float): The new bulk modulus.
|
|
66
|
+
"""
|
|
67
|
+
self._kappa = float(value)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def density(self) -> float:
|
|
71
|
+
"""
|
|
72
|
+
Get the density.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
float: The density.
|
|
76
|
+
"""
|
|
77
|
+
return self._density
|
|
78
|
+
|
|
79
|
+
@density.setter
|
|
80
|
+
def density(self, value: float) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Set the density.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
value (float): The new density.
|
|
86
|
+
"""
|
|
87
|
+
self._density = float(value)
|
|
88
|
+
|
|
89
|
+
def get_ratio(self, nodes: torch.Tensor) -> float:
|
|
90
|
+
"""
|
|
91
|
+
Get the ratio of maximum to minimum modulus.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
float: The ratio of maximum to minimum modulus.
|
|
95
|
+
"""
|
|
96
|
+
return 1.0
|
|
97
|
+
|
|
98
|
+
def set_materials(self, fe: torchfea.FEAController) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Set the materials of the FEA model.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
fe (torchfea.FEAController): The FEA controller.
|
|
104
|
+
"""
|
|
105
|
+
elements = fe.assembly.get_part('final_model').elems['element-0']
|
|
106
|
+
mu = self.mu
|
|
107
|
+
kappa = self.kappa
|
|
108
|
+
materials = torchfea.materials.NeoHookean(mu=mu, kappa=kappa)
|
|
109
|
+
elements.set_materials(materials)
|
|
110
|
+
elements.density = self.density
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
|
|
2
|
+
from ..baseobject import BaseObject
|
|
3
|
+
from .feamodel.feaparams import FEAParams
|
|
4
|
+
from .geometry.geometryparams import GeometryParams
|
|
5
|
+
from .materials.materialparams import Materials
|
|
6
|
+
class Params(BaseObject):
|
|
7
|
+
"""
|
|
8
|
+
Class to handle the parameters of the model.
|
|
9
|
+
"""
|
|
10
|
+
def __init__(self, surfaces: GeometryParams, feamodel: FEAParams, materials: Materials) -> None:
|
|
11
|
+
"""
|
|
12
|
+
Initialize the Params class.
|
|
13
|
+
"""
|
|
14
|
+
self.geometry = surfaces
|
|
15
|
+
"""
|
|
16
|
+
Surfaces: An instance of the Surfaces class from the ModelParams module.
|
|
17
|
+
"""
|
|
18
|
+
self.feamodel = feamodel
|
|
19
|
+
"""
|
|
20
|
+
Loads: An instance of the Loads class from the ModelParams module.
|
|
21
|
+
"""
|
|
22
|
+
self.materials = materials
|
|
23
|
+
"""
|
|
24
|
+
Materials: An instance of the Materials class from the ModelParams module.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def reinitialize(self, iteration: int) -> None:
|
|
28
|
+
"""
|
|
29
|
+
reInitialize the parameters.
|
|
30
|
+
"""
|
|
31
|
+
self.geometry.reinitialize(iteration=iteration)
|
|
32
|
+
self.feamodel.reinitialize(iteration=iteration)
|
|
33
|
+
self.materials.reinitialize(iteration=iteration)
|
|
34
|
+
|
|
35
|
+
def initialize(self):
|
|
36
|
+
"""
|
|
37
|
+
Initialize the parameters.
|
|
38
|
+
"""
|
|
39
|
+
self.geometry.initialize()
|
|
40
|
+
self.feamodel.initialize()
|
|
41
|
+
self.materials.initialize()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def save(self, foldpath: str, iteration: int) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Save the parameters to a file.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
foldpath (str): The path to save the parameters.
|
|
50
|
+
"""
|
|
51
|
+
self.geometry.save(foldpath=foldpath, iteration=iteration)
|
|
52
|
+
self.feamodel.save(foldpath=foldpath, iteration=iteration)
|
|
53
|
+
self.materials.save(foldpath=foldpath, iteration=iteration)
|
|
54
|
+
|
|
55
|
+
def load(self, foldpath: str, iteration: int) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Load the parameters from a file.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
foldpath (str): The path to load the parameters from.
|
|
61
|
+
"""
|
|
62
|
+
self.geometry.load(foldpath=foldpath, iteration=iteration)
|
|
63
|
+
# self.feamodel.load(foldpath=foldpath, iteration=iteration)
|
|
64
|
+
# self.materials.load(foldpath=foldpath, iteration=iteration)
|
|
65
|
+
|
|
66
|
+
def export_data(self, filepath: str):
|
|
67
|
+
"""
|
|
68
|
+
Export the data of parameters to file(s).
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
filepath (str): The path to export the data.
|
|
72
|
+
"""
|
|
73
|
+
self.geometry._export_data(filepath=filepath)
|
|
74
|
+
self.feamodel._export_data(foldpath=filepath)
|
|
75
|
+
self.materials._export_data(foldpath=filepath)
|
|
76
|
+
|
|
77
|
+
def pathlog_required(self) -> list[str]:
|
|
78
|
+
"""
|
|
79
|
+
Allocate the path for saving data.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
foldpath (str): The path to allocate.
|
|
83
|
+
"""
|
|
84
|
+
paths = []
|
|
85
|
+
paths += self.geometry.pathlog_required()
|
|
86
|
+
paths += self.feamodel.pathlog_required()
|
|
87
|
+
paths += self.materials.pathlog_required()
|
|
88
|
+
return paths
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import torchfea
|
|
3
|
+
import numpy as np
|
|
4
|
+
import torch
|
|
5
|
+
import scipy.sparse as sp
|
|
6
|
+
import pypardiso
|
|
7
|
+
import morphopt
|
|
8
|
+
|
|
9
|
+
from .baseobject import BaseObject
|
|
10
|
+
|
|
11
|
+
class ObjectiveFunction(BaseObject):
|
|
12
|
+
"""
|
|
13
|
+
The objective functions in morphopt.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.fe: torchfea.FEAController = None
|
|
18
|
+
"""
|
|
19
|
+
The FEA solver instance.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
self.inp: torchfea.FEA_INP = None
|
|
23
|
+
"""
|
|
24
|
+
The FEA .inp file.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
self.U: torch.Tensor
|
|
28
|
+
"""
|
|
29
|
+
The displacement field.
|
|
30
|
+
[shape: (num_tasks, num_dofs)]
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
self.ADJu: torch.Tensor
|
|
34
|
+
"""
|
|
35
|
+
The first adjoint displacement field.
|
|
36
|
+
[shape: (num_tasks, num_dofs)]
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
self.K_sp: list[sp.csr_matrix]
|
|
40
|
+
"""
|
|
41
|
+
The sparse stiffness matrices.
|
|
42
|
+
[shape: (num_tasks,)]
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
self.K_solver: list[pypardiso.PyPardisoSolver]
|
|
46
|
+
"""
|
|
47
|
+
The solvers for the stiffness matrix.
|
|
48
|
+
[shape: (num_tasks,)]
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def get_objective(self, *args, **kwargs) -> torch.Tensor:
|
|
52
|
+
"""
|
|
53
|
+
Get the value of the objective function.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
*args: Positional arguments.
|
|
57
|
+
**kwargs: Keyword arguments.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
torch.Tensor: The value of the objective function.
|
|
61
|
+
"""
|
|
62
|
+
raise NotImplementedError("This method should be overridden by subclasses.")
|
|
63
|
+
|
|
64
|
+
def get_metrics(self) -> list[float]:
|
|
65
|
+
"""
|
|
66
|
+
Get custom metrics for display.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
list[float]: A list of metric values.
|
|
70
|
+
"""
|
|
71
|
+
return []
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def num_tasks(self) -> int:
|
|
75
|
+
"""
|
|
76
|
+
Get the number of tasks.
|
|
77
|
+
"""
|
|
78
|
+
return self.U.shape[0]
|
|
79
|
+
|
|
80
|
+
def pathlog_required(self):
|
|
81
|
+
return ['deformation']
|
|
82
|
+
|
|
83
|
+
def calculate_adjoint(self, *args, **kwargs) -> torch.Tensor:
|
|
84
|
+
"""
|
|
85
|
+
Calculate the adjoint variables.
|
|
86
|
+
"""
|
|
87
|
+
raise NotImplementedError("This method should be overridden by subclasses.")
|
|
88
|
+
|
|
89
|
+
def calculate_adjoint_problem(self, *args, **kwargs) -> torch.Tensor:
|
|
90
|
+
"""
|
|
91
|
+
Calculate the linear factor of the objective function.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def closure_JdU(U: torch.Tensor) -> torch.Tensor:
|
|
95
|
+
U0 = self.U
|
|
96
|
+
self.U = U
|
|
97
|
+
obj = self.get_objective()
|
|
98
|
+
self.U = U0
|
|
99
|
+
return obj
|
|
100
|
+
|
|
101
|
+
ADJFu_now: torch.Tensor = -torch.autograd.functional.jacobian(closure_JdU, self.U.detach().clone())
|
|
102
|
+
|
|
103
|
+
ADJu = []
|
|
104
|
+
K_sp_list = []
|
|
105
|
+
K_solver_list = []
|
|
106
|
+
for step_index in range(self.num_tasks):
|
|
107
|
+
|
|
108
|
+
# set the loads
|
|
109
|
+
morphopt.controller.params.feamodel.process_fea(fe=self.fe, step_index=step_index)
|
|
110
|
+
|
|
111
|
+
# region get the decomposed stiffness matrix
|
|
112
|
+
R, K_indices, K_values = self.fe.assembly.assemble_Stiffness_Matrix(GC=self.U[step_index].to(self.fe.assembly.device))
|
|
113
|
+
K_values = K_values.cpu().numpy()
|
|
114
|
+
K_indices = K_indices.cpu().numpy()
|
|
115
|
+
K_sp = sp.coo_matrix(
|
|
116
|
+
(K_values,
|
|
117
|
+
(K_indices[0], K_indices[1])), dtype=np.float64,
|
|
118
|
+
shape=(self.fe.assembly.GC.shape[0], self.fe.assembly.GC.shape[0])).tocsr()
|
|
119
|
+
K_solver = pypardiso.PyPardisoSolver()
|
|
120
|
+
K_solver.factorize(K_sp)
|
|
121
|
+
|
|
122
|
+
K_sp_list.append(K_sp)
|
|
123
|
+
K_solver_list.append(K_solver)
|
|
124
|
+
|
|
125
|
+
# endregion
|
|
126
|
+
|
|
127
|
+
# region calculate the adjoint variable
|
|
128
|
+
|
|
129
|
+
ADJu_now = torch.from_numpy(K_solver.solve(K_sp, ADJFu_now[step_index].cpu().numpy())).cpu()
|
|
130
|
+
ADJu.append(ADJu_now)
|
|
131
|
+
# endregion
|
|
132
|
+
|
|
133
|
+
self.ADJu = torch.stack(ADJu, dim=0).cpu()
|
|
134
|
+
self.K_sp = K_sp_list
|
|
135
|
+
self.K_solver = K_solver_list
|
|
136
|
+
|
|
137
|
+
def __str__(self) -> str:
|
|
138
|
+
|
|
139
|
+
result = ["FE_result Summary:"]
|
|
140
|
+
|
|
141
|
+
for i in range(self.num_tasks):
|
|
142
|
+
result.append(f"=================================Task {i+1}=================================")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# 格式化位移向量(一维)
|
|
146
|
+
u_vector = self.U[i][-6:].tolist()
|
|
147
|
+
u_str = " ".join([f"{x:.6f}" for x in u_vector])
|
|
148
|
+
result.append(f" Displacement U: {u_str}")
|
|
149
|
+
|
|
150
|
+
# # 格式化Jacobian矩阵(二维)
|
|
151
|
+
# if self.Udp is not None:
|
|
152
|
+
# matrix = self.Udp[i][:, -6:].T.tolist() # 二维矩阵
|
|
153
|
+
# result.append(f" Jacobian Udp:")
|
|
154
|
+
# # 格式化并添加矩阵每行
|
|
155
|
+
# formatted = format_matrix(matrix, indent=4)
|
|
156
|
+
# result.extend(formatted)
|
|
157
|
+
|
|
158
|
+
# # 格式化UdF矩阵(二维)
|
|
159
|
+
# if self.UdF is not None:
|
|
160
|
+
# matrix = self.UdF[i][:, -6:].tolist() # 二维矩阵
|
|
161
|
+
# result.append(f" UdF:")
|
|
162
|
+
# formatted = format_matrix(matrix, indent=4)
|
|
163
|
+
# result.extend(formatted)
|
|
164
|
+
|
|
165
|
+
result.append(f"============================================================================")
|
|
166
|
+
|
|
167
|
+
return "\n".join(result)
|
|
168
|
+
|
|
169
|
+
def __getitem__(self, key):
|
|
170
|
+
"""
|
|
171
|
+
Get the attribute with the given key.
|
|
172
|
+
|
|
173
|
+
Parameters:
|
|
174
|
+
key: The key of the attribute to get.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
The attribute value.
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
KeyError: If the key is not found.
|
|
181
|
+
"""
|
|
182
|
+
if hasattr(self, key):
|
|
183
|
+
return getattr(self, key)
|
|
184
|
+
else:
|
|
185
|
+
raise KeyError(f"'{key}' not found in FE_result")
|
|
186
|
+
|
|
187
|
+
def save(self, foldpath: str, iteration: int, insname: str = 'final_model', surface: str = 'surface_0_All') -> None:
|
|
188
|
+
"""
|
|
189
|
+
Save the figures of the FEA results.
|
|
190
|
+
|
|
191
|
+
Parameters:
|
|
192
|
+
foldpath (str): The path to save the figures.
|
|
193
|
+
iteration (int): The current iteration number.
|
|
194
|
+
"""
|
|
195
|
+
ins = self.fe.assembly.get_instance(insname)
|
|
196
|
+
surfaces = [surface]
|
|
197
|
+
surface_elements: list[torchfea.elements.BaseSurface] = []
|
|
198
|
+
for i in range(len(surfaces)):
|
|
199
|
+
surface_elements = surface_elements + ins.surfaces.get_elements(surfaces[i])
|
|
200
|
+
|
|
201
|
+
surface_connections = [surface_elements[i].surf_elems_circ.cpu().numpy() for i in range(len(surface_elements))]
|
|
202
|
+
|
|
203
|
+
for case in range(self.num_tasks):
|
|
204
|
+
deformed_nodes = (ins.nodes + self.fe.assembly._GC2RGC(self.U[case].to(ins.nodes.device))[ins._RGC_index]).detach().cpu().numpy()
|
|
205
|
+
|
|
206
|
+
import pyvista as pv
|
|
207
|
+
|
|
208
|
+
plotter = pv.Plotter(off_screen=True, window_size=(1200, 1200))
|
|
209
|
+
|
|
210
|
+
# Create faces list for pyvista
|
|
211
|
+
faces = []
|
|
212
|
+
for connection in surface_connections:
|
|
213
|
+
for face in connection:
|
|
214
|
+
if -1 not in face:
|
|
215
|
+
faces.append([len(face)] + list(face))
|
|
216
|
+
|
|
217
|
+
# Create pyvista mesh
|
|
218
|
+
mesh = pv.PolyData(deformed_nodes, faces)
|
|
219
|
+
plotter.add_mesh(mesh, color=(40.0/255, 120.0/255, 181.0/255), opacity=1.0)
|
|
220
|
+
|
|
221
|
+
x_min, x_max = deformed_nodes[:,0].min().item(), deformed_nodes[:,0].max().item()
|
|
222
|
+
y_min, y_max = deformed_nodes[:,1].min().item(), deformed_nodes[:,1].max().item()
|
|
223
|
+
z_min, z_max = deformed_nodes[:,2].min().item(), deformed_nodes[:,2].max().item()
|
|
224
|
+
|
|
225
|
+
# Add some padding to the bounds
|
|
226
|
+
padding = 0.05 * max(x_max-x_min, y_max-y_min, z_max-z_min)
|
|
227
|
+
|
|
228
|
+
plotter.show_bounds(xtitle='X', ytitle='Y', ztitle='Z', color='black',
|
|
229
|
+
bounds=[x_min-padding, x_max+padding,
|
|
230
|
+
y_min-padding, y_max+padding,
|
|
231
|
+
z_min-padding, z_max+padding])
|
|
232
|
+
|
|
233
|
+
# Approximate view
|
|
234
|
+
plotter.set_background('white')
|
|
235
|
+
plotter.enable_parallel_projection()
|
|
236
|
+
azimuth = 210
|
|
237
|
+
elevation = 20
|
|
238
|
+
plotter.view_vector((math.cos(math.radians(azimuth)) * math.cos(math.radians(elevation)),
|
|
239
|
+
math.sin(math.radians(azimuth)) * math.cos(math.radians(elevation)),
|
|
240
|
+
math.sin(math.radians(elevation))))
|
|
241
|
+
|
|
242
|
+
# Save the figure as a PNG file
|
|
243
|
+
plotter.screenshot(f"{foldpath}/{self.pathlog_required()[0]}/task_{case}_iter_{iteration}.png")
|
|
244
|
+
|
|
245
|
+
# Save the deformed mesh as an OBJ file
|
|
246
|
+
obj_filepath = f"{foldpath}/{self.pathlog_required()[0]}/task_{case}_iter_{iteration}.obj"
|
|
247
|
+
mesh.save(obj_filepath)
|
|
248
|
+
plotter.close()
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
|
|
2
|
+
import numpy as np
|
|
3
|
+
import torch
|
|
4
|
+
import copy
|
|
5
|
+
import torchfea
|
|
6
|
+
import multiprocessing as mp
|
|
7
|
+
|
|
8
|
+
from .modelparams import Params, FEAParams, Materials
|
|
9
|
+
|
|
10
|
+
from .baseobject import BaseObject
|
|
11
|
+
import morphopt
|
|
12
|
+
class MorphSolver(BaseObject):
|
|
13
|
+
"""
|
|
14
|
+
This class is responsible for solving the FEA and get the displacement of the soft robot.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def __init__(self, params: Params, num_process: int = 4, available_gpus: list[str] = None, task_index_list: list[list[int]] = None):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the Solver class with a list of pressure values.
|
|
21
|
+
|
|
22
|
+
Parameters:
|
|
23
|
+
params (Params):
|
|
24
|
+
num_process (int): The number of processes to use for parallel computation.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
super().__init__()
|
|
28
|
+
|
|
29
|
+
self.params: Params = params
|
|
30
|
+
"""
|
|
31
|
+
Pressures: An instance of the params of the optimization problem.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
self.num_process = num_process
|
|
35
|
+
"""
|
|
36
|
+
int: The number of processes to use for parallel computation.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
if available_gpus is None:
|
|
40
|
+
self.available_gpus = ['cuda:%d' % i for i in range(torch.cuda.device_count())]
|
|
41
|
+
else:
|
|
42
|
+
self.available_gpus = available_gpus
|
|
43
|
+
|
|
44
|
+
self.task_index_list = task_index_list
|
|
45
|
+
"""
|
|
46
|
+
list[list[int]]: A list of task indices for each process.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def initialize(self):
|
|
50
|
+
if self.task_index_list is None:
|
|
51
|
+
self.task_index_list = []
|
|
52
|
+
for i in range(morphopt.controller.params.feamodel.num_load_steps):
|
|
53
|
+
self.task_index_list.append([i])
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def reinitialize(self, iteration: int) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Reinitialize the solver for a new iteration.
|
|
60
|
+
|
|
61
|
+
Parameters:
|
|
62
|
+
iteration (int): The current iteration number.
|
|
63
|
+
"""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
def solve(self):
|
|
67
|
+
"""
|
|
68
|
+
Solve the optimization problem using the specified solver.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
tuple: the displacement field and its derivatives:
|
|
72
|
+
- fe (torchfea.FEAController): An instance of the FEA_Main class with the given input parameters.
|
|
73
|
+
- GC0 (list[torch.Tensor]): The displacement field at the reference point.
|
|
74
|
+
- Udp0 (list[torch.Tensor]): The displacement field at the reference point with respect to the pressure.
|
|
75
|
+
- GCv (list[torch.Tensor]): The first adjoint displacement field.
|
|
76
|
+
- GCw (list[torch.Tensor]): The second adjoint displacement field.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
# multiprocess FEA
|
|
80
|
+
|
|
81
|
+
fe_cpu = copy.deepcopy(morphopt.controller.objfun.fe)
|
|
82
|
+
fe_cpu.change_device(torch.device('cpu'))
|
|
83
|
+
|
|
84
|
+
# self._solve_FEA(fe=fe_cpu,
|
|
85
|
+
# feamodel=self.params.feamodel,
|
|
86
|
+
# task_index=self.task_index_list[0],
|
|
87
|
+
# available_gpus=self.available_gpus)
|
|
88
|
+
|
|
89
|
+
pools = morphopt.controller.pools
|
|
90
|
+
result = []
|
|
91
|
+
for i in range(len(self.task_index_list)):
|
|
92
|
+
result.append(
|
|
93
|
+
pools.apply_async(self._solve_FEA,
|
|
94
|
+
kwds={'fe': fe_cpu,
|
|
95
|
+
'feamodel': self.params.feamodel,
|
|
96
|
+
'task_index': self.task_index_list[i],
|
|
97
|
+
'available_gpus': self.available_gpus}))
|
|
98
|
+
|
|
99
|
+
# get the result
|
|
100
|
+
U0 = []
|
|
101
|
+
list_number = []
|
|
102
|
+
for i in range(len(result)):
|
|
103
|
+
U0 += result[i].get()
|
|
104
|
+
list_number += self.task_index_list[i]
|
|
105
|
+
list_number = np.array(list_number).flatten()
|
|
106
|
+
|
|
107
|
+
Uresult = torch.tensor(U0).to(torch.float64)[list_number]
|
|
108
|
+
del fe_cpu
|
|
109
|
+
return Uresult
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def _solve_FEA(cls, fe: torchfea.FEAController,
|
|
114
|
+
feamodel: FEAParams,
|
|
115
|
+
task_index: list[int],
|
|
116
|
+
available_gpus: list[str],
|
|
117
|
+
U_guess: np.ndarray = None):
|
|
118
|
+
import os
|
|
119
|
+
os.environ['KMP_DUPLICATE_LIB_OK']='True'
|
|
120
|
+
import sys
|
|
121
|
+
import torch
|
|
122
|
+
sys.path.append(os.getcwd())
|
|
123
|
+
|
|
124
|
+
current_process_name = mp.current_process().name
|
|
125
|
+
try:
|
|
126
|
+
pool_id = int(current_process_name[-1])
|
|
127
|
+
except:
|
|
128
|
+
pool_id = 0
|
|
129
|
+
|
|
130
|
+
if len(available_gpus) > 0:
|
|
131
|
+
cuda_now = (pool_id+1) % len(available_gpus)
|
|
132
|
+
torch.set_default_device(available_gpus[cuda_now])
|
|
133
|
+
device_now = available_gpus[cuda_now]
|
|
134
|
+
print("Process %s use GPU: %s" % (current_process_name, available_gpus[cuda_now]))
|
|
135
|
+
else:
|
|
136
|
+
torch.set_default_device('cpu')
|
|
137
|
+
device_now = 'cpu'
|
|
138
|
+
print("Process %s use CPU" % (current_process_name))
|
|
139
|
+
|
|
140
|
+
# torch.set_default_device(torch.device('cuda:0'))
|
|
141
|
+
torch.set_default_dtype(torch.float64)
|
|
142
|
+
torch.cuda.empty_cache()
|
|
143
|
+
# construct the FEA
|
|
144
|
+
fe.change_device(device_now)
|
|
145
|
+
|
|
146
|
+
if U_guess is not None:
|
|
147
|
+
U0 = torch.from_numpy(U_guess).to(torch.float64).to(fe.assembly.device)
|
|
148
|
+
else:
|
|
149
|
+
U0 = fe.assembly.GC.to(torch.get_default_device())
|
|
150
|
+
|
|
151
|
+
result_list = []
|
|
152
|
+
for i in range(len(task_index)):
|
|
153
|
+
feamodel.process_fea(fe=fe, step_index=task_index[i])
|
|
154
|
+
result: torchfea.solver.StaticResult = fe.solve(GC0=U0.to(torch.get_default_device()), if_initialize=False)
|
|
155
|
+
|
|
156
|
+
U0 = result.GC.detach()
|
|
157
|
+
Unow = U0.cpu().numpy()
|
|
158
|
+
result_list.append(Unow)
|
|
159
|
+
return result_list
|