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,347 @@
|
|
|
1
|
+
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
import torch
|
|
5
|
+
from .. import optimizer
|
|
6
|
+
|
|
7
|
+
from ...modelparams.params import Params
|
|
8
|
+
from ...modelparams import GeometryParams, FEAParams, Materials
|
|
9
|
+
from tabulate import tabulate
|
|
10
|
+
from ..base_updater import BaseUpdater
|
|
11
|
+
|
|
12
|
+
class UpdaterGeometries(BaseUpdater):
|
|
13
|
+
"""
|
|
14
|
+
The Updater class is responsible for updating the parameters of the optimization process.
|
|
15
|
+
It contains methods to update the parameters based on the optimization algorithm used.
|
|
16
|
+
"""
|
|
17
|
+
from . import objectivefuncs
|
|
18
|
+
|
|
19
|
+
def __init__(self, params: Params, max_step_iter: int = 50, max_step_length: float = 0.5, reset_sensitivity_scaler_per_iter: int = 1) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Initialize the Updater class with the given parameters.
|
|
22
|
+
|
|
23
|
+
Parameters:
|
|
24
|
+
surfaces (Surfaces): The surfaces object that contains the design variables.
|
|
25
|
+
max_step_iter (int): The maximum number of iterations for the sub-optimization process.
|
|
26
|
+
max_step_length_surf (list[float]): The maximum step length for each surface in the optimization process.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
super().__init__(params)
|
|
30
|
+
|
|
31
|
+
self.max_step_iter = max_step_iter
|
|
32
|
+
"""
|
|
33
|
+
The maximum number of iterations for the sub-optimization process.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
self.constraints_funcs: dict[str, UpdaterGeometries.objectivefuncs.BaseConstraints] = {}
|
|
37
|
+
"""
|
|
38
|
+
A list of penalty functions to be optimized. \n
|
|
39
|
+
L = sum_{i=1}^{n} w_i * f_i(x)
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
self.obj_funcs: dict[str, UpdaterGeometries.objectivefuncs.BaseObjective] = {}
|
|
43
|
+
"""
|
|
44
|
+
A list of objective functions to be optimized. \n
|
|
45
|
+
L = sum_{i=1}^{n} w_i * f_i(x)
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
self._delta_control_points_previous: list[np.ndarray] = None
|
|
49
|
+
"""
|
|
50
|
+
The previous change in control points for each surface.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
self._weight_points: list[torch.Tensor] = []
|
|
54
|
+
"""
|
|
55
|
+
The weights for the points in the optimization process.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
self.params_update: GeometryParams = params.geometry
|
|
59
|
+
"""
|
|
60
|
+
The surfaces object that contains the design variables.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
self._reset_sensitivity_scaler_per_iter = reset_sensitivity_scaler_per_iter
|
|
64
|
+
"""
|
|
65
|
+
The number of iterations after which the scaler is reset.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
self._max_step_length_max: float = max_step_length
|
|
69
|
+
"""
|
|
70
|
+
The maximum step length for each surface in the optimization process.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
self._max_step_length: list[torch.Tensor] = []
|
|
74
|
+
"""
|
|
75
|
+
The current maximum step length for each surface in the optimization process.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
self._step_length_min_ratio: float = 0.1
|
|
79
|
+
"""
|
|
80
|
+
The minimum ratio for the step length relative to the maximum step length.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
self._step_length_decay: float = 0.5
|
|
84
|
+
"""
|
|
85
|
+
The decay factor for the step length relative to the maximum step length.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
self._step_length_increase: float = 1.5
|
|
89
|
+
"""
|
|
90
|
+
The increase factor for the step length relative to the maximum step length.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
self.if_update: list[float] = None
|
|
94
|
+
"""
|
|
95
|
+
A list indicating whether each surface needs to be updated.
|
|
96
|
+
True means the surface needs to be updated, False means it does not.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def pathlog_required(self):
|
|
100
|
+
return ['geometryupdater']
|
|
101
|
+
|
|
102
|
+
def add_constraints(self,
|
|
103
|
+
obj_func: objectivefuncs.basefuncs,
|
|
104
|
+
name: str = None) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Add an objective function to the list of objective functions.
|
|
107
|
+
|
|
108
|
+
Parameters:
|
|
109
|
+
obj_func (ObjectiveFuncs.BaseObj): The objective function to be added.
|
|
110
|
+
"""
|
|
111
|
+
if name is None:
|
|
112
|
+
name = obj_func.__class__.__name__
|
|
113
|
+
|
|
114
|
+
extra_num = 0
|
|
115
|
+
while name + '_%d' % extra_num in self.constraints_funcs.keys():
|
|
116
|
+
extra_num += 1
|
|
117
|
+
|
|
118
|
+
name = name + '_%d' % extra_num
|
|
119
|
+
self.constraints_funcs[name] = obj_func
|
|
120
|
+
|
|
121
|
+
def add_objective_function(self,
|
|
122
|
+
obj_func: objectivefuncs.basefuncs,
|
|
123
|
+
name: str = None) -> None:
|
|
124
|
+
"""
|
|
125
|
+
Add an objective function to the list of objective functions.
|
|
126
|
+
|
|
127
|
+
Parameters:
|
|
128
|
+
obj_func (ObjectiveFuncs.BaseObj): The objective function to be added.
|
|
129
|
+
"""
|
|
130
|
+
if name is None:
|
|
131
|
+
name = obj_func.__class__.__name__
|
|
132
|
+
|
|
133
|
+
extra_num = 0
|
|
134
|
+
while name + '_%d' % extra_num in self.obj_funcs.keys():
|
|
135
|
+
extra_num += 1
|
|
136
|
+
|
|
137
|
+
name = name + '_%d' % extra_num
|
|
138
|
+
self.obj_funcs[name] = obj_func
|
|
139
|
+
|
|
140
|
+
def reinitialize(self, *args, **kwargs) -> None:
|
|
141
|
+
"""
|
|
142
|
+
Initialize the parameters of the optimization process.
|
|
143
|
+
|
|
144
|
+
Parameters:
|
|
145
|
+
iter_now (int): The current iteration number.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
# get the weights for the points in the optimization process
|
|
149
|
+
self._weight_points = self.params_update.get_points_weight()
|
|
150
|
+
|
|
151
|
+
# initialize the objective function
|
|
152
|
+
r0, rdu0, rdu20 = self.params_update.get_geometry_values()
|
|
153
|
+
|
|
154
|
+
# initialize the shape derivative sensitivity
|
|
155
|
+
self._initialize_objectives(r0=r0, rdu0=rdu0, rdu20=rdu20)
|
|
156
|
+
|
|
157
|
+
# get the total sensitivity
|
|
158
|
+
sensitivity = self._get_total_sensitivity()
|
|
159
|
+
|
|
160
|
+
# initialize the constraint functions
|
|
161
|
+
self._initialize_constraints(r0=r0, rdu0=rdu0, rdu20=rdu20, sensitivity=sensitivity)
|
|
162
|
+
|
|
163
|
+
# initialize the optimizer
|
|
164
|
+
self._initialize_optimizer()
|
|
165
|
+
|
|
166
|
+
# initialize the step length
|
|
167
|
+
for i in range(len(self._max_step_length)):
|
|
168
|
+
if (self._max_step_length[i].shape[0] != self.params.geometry.surface_list[i].control_points.flatten().shape[0] // 3):
|
|
169
|
+
self._max_step_length[i] = self._max_step_length[i].mean().repeat(self.params.geometry.surface_list[i].num_variables // 3)
|
|
170
|
+
|
|
171
|
+
# save the sensitivity for the next iteration
|
|
172
|
+
self.sensitivity_previous = sensitivity
|
|
173
|
+
|
|
174
|
+
def initialize(self):
|
|
175
|
+
|
|
176
|
+
if self.if_update is None:
|
|
177
|
+
self.if_update = [True for _ in range(self.params.geometry.num_surface)]
|
|
178
|
+
|
|
179
|
+
if len(self._max_step_length) == 0:
|
|
180
|
+
for i in range(self.params_update.num_surface):
|
|
181
|
+
self._max_step_length.append(torch.ones(self.params.geometry.surface_list[i].num_variables // 3) * self._max_step_length_max * 0.5)
|
|
182
|
+
|
|
183
|
+
for i in range(len(self._max_step_length)):
|
|
184
|
+
if not self.if_update[i]:
|
|
185
|
+
self._max_step_length[i] *= 0.0
|
|
186
|
+
|
|
187
|
+
def _initialize_objectives(self, r0: list[torch.Tensor], rdu0: list[torch.Tensor], rdu20: list[torch.Tensor]) -> None:
|
|
188
|
+
for obj_func in self.obj_funcs.values():
|
|
189
|
+
obj_func.initialize(r0=r0, rdu0=rdu0, rdu20=rdu20, weights=self._weight_points)
|
|
190
|
+
|
|
191
|
+
def _get_total_sensitivity(self) -> None:
|
|
192
|
+
# get the total sensitivity
|
|
193
|
+
sensitivity_all = []
|
|
194
|
+
for obj_func in self.obj_funcs.values():
|
|
195
|
+
sensitivity_all.append(obj_func.sensitivity)
|
|
196
|
+
sensitivity: list[torch.Tensor] = sensitivity_all[0]
|
|
197
|
+
for surf_ind in range(len(sensitivity)):
|
|
198
|
+
for obj_ind in range(1, len(sensitivity_all)):
|
|
199
|
+
sensitivity[surf_ind] += sensitivity_all[obj_ind][surf_ind]
|
|
200
|
+
return sensitivity
|
|
201
|
+
|
|
202
|
+
def _initialize_constraints(self, r0: list[torch.Tensor], rdu0: list[torch.Tensor], rdu20: list[torch.Tensor], sensitivity: list[torch.Tensor]) -> None:
|
|
203
|
+
for constraints in self.constraints_funcs.values():
|
|
204
|
+
constraints.initialize(r0=r0, rdu0=rdu0, rdu20=rdu20, sensitivity=sensitivity, weights=self._weight_points)
|
|
205
|
+
|
|
206
|
+
def _initialize_optimizer(self) -> None:
|
|
207
|
+
self.optimizer = optimizer.LBFGS(closure=self.closure, num_limit=20, tol_error=1e-10)
|
|
208
|
+
self.iteration_total = 0
|
|
209
|
+
|
|
210
|
+
def _update_step_length(self, delta_control_points: list[np.ndarray]) -> None:
|
|
211
|
+
|
|
212
|
+
# update the step length based on the number of variables
|
|
213
|
+
if self._delta_control_points_previous is not None:
|
|
214
|
+
for i in range(len(self._max_step_length)):
|
|
215
|
+
# check if the mesh has improved
|
|
216
|
+
if (self._delta_control_points_previous[i].shape != self.params.geometry.surface_list[i].control_points.shape):
|
|
217
|
+
self._max_step_length[i] = self._max_step_length[i].mean().repeat(self.params.geometry.surface_list[i].num_variables // 3)
|
|
218
|
+
else:
|
|
219
|
+
delta_difference: np.ndarray = np.sum(self._delta_control_points_previous[i] * delta_control_points[i], axis=-1) / (np.linalg.norm(delta_control_points[i], axis=-1)) / np.linalg.norm(self._delta_control_points_previous[i], axis=-1)
|
|
220
|
+
delta_difference[np.isnan(delta_difference)] = 0.0
|
|
221
|
+
|
|
222
|
+
index_increase = (delta_difference > -0.5).flatten()
|
|
223
|
+
index_decrease = (delta_difference <= -0.5).flatten()
|
|
224
|
+
self._max_step_length[i][index_increase] = torch.clamp(self._max_step_length[i][index_increase] * self._step_length_increase,
|
|
225
|
+
max=self._max_step_length_max)
|
|
226
|
+
self._max_step_length[i][index_decrease] = torch.clamp(self._max_step_length[i][index_decrease] * self._step_length_decay,
|
|
227
|
+
min=self._max_step_length_max * self._step_length_min_ratio)
|
|
228
|
+
for i in range(len(self._max_step_length)):
|
|
229
|
+
if not self.if_update[i]:
|
|
230
|
+
self._max_step_length[i] *= 0.0
|
|
231
|
+
|
|
232
|
+
def closure(self, x: torch.Tensor, return_list=False) -> float:
|
|
233
|
+
"""
|
|
234
|
+
The closure function for the optimization process.
|
|
235
|
+
|
|
236
|
+
Parameters:
|
|
237
|
+
x (torch.Tensor): The current point in the optimization process.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
float: The objective function value at the current point.
|
|
241
|
+
"""
|
|
242
|
+
# save the current point
|
|
243
|
+
x0 = self.params_update.get_parameters()
|
|
244
|
+
|
|
245
|
+
# Set the design variables to the current point
|
|
246
|
+
self.params_update.update_variables(x_change=x, max_step_length=self._max_step_length)
|
|
247
|
+
|
|
248
|
+
# Calculate the objective function value
|
|
249
|
+
r, rdu, rdu2 = self.params_update.get_geometry_values()
|
|
250
|
+
|
|
251
|
+
constraints_value: list[torch.Tensor] = []
|
|
252
|
+
for constraints in self.constraints_funcs.values():
|
|
253
|
+
constraints_value.append(constraints(r=r, rdu=rdu, rdu2=rdu2))
|
|
254
|
+
|
|
255
|
+
obj_value: list[torch.Tensor] = []
|
|
256
|
+
for obj_func in self.obj_funcs.values():
|
|
257
|
+
obj_value.append(obj_func(r=r, rdu=rdu, rdu2=rdu2))
|
|
258
|
+
|
|
259
|
+
# enroll the design variables
|
|
260
|
+
self.params_update.set_parameters(xlist=x0)
|
|
261
|
+
|
|
262
|
+
if return_list:
|
|
263
|
+
return obj_value, constraints_value
|
|
264
|
+
else:
|
|
265
|
+
return sum(obj_value) + sum(constraints_value)
|
|
266
|
+
|
|
267
|
+
def update(self) -> torch.Tensor:
|
|
268
|
+
"""
|
|
269
|
+
Update the parameters of the optimization process.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
# initialize the optimizer
|
|
273
|
+
self.reinitialize()
|
|
274
|
+
|
|
275
|
+
# update the objective function
|
|
276
|
+
variables = self.params_update.get_variables().detach().clone()
|
|
277
|
+
|
|
278
|
+
# print the information
|
|
279
|
+
print("\n\n")
|
|
280
|
+
print("Start updating the surfaces...")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
low_step_length_iter = 0
|
|
284
|
+
gk_new = None
|
|
285
|
+
for iteration in range(self.max_step_iter):
|
|
286
|
+
self.iteration_total += 1
|
|
287
|
+
|
|
288
|
+
# get the current variables of the surfaces
|
|
289
|
+
alpha, delta_var, gk_new = self.optimizer.step(x_now=variables, gk_now= gk_new)
|
|
290
|
+
variables.data += delta_var * alpha
|
|
291
|
+
|
|
292
|
+
# check if the step length is too small
|
|
293
|
+
if abs(alpha) < 1e-10:
|
|
294
|
+
low_step_length_iter += 1
|
|
295
|
+
|
|
296
|
+
if low_step_length_iter > 10:
|
|
297
|
+
print(
|
|
298
|
+
f"Low step length detected ({low_step_length_iter} iterations), stopping optimization."
|
|
299
|
+
)
|
|
300
|
+
# break
|
|
301
|
+
|
|
302
|
+
# get current objective function value
|
|
303
|
+
if self.iteration_total % 10 == 0:
|
|
304
|
+
with torch.no_grad():
|
|
305
|
+
obj_values, constraints_values = self.closure(x=variables, return_list=True)
|
|
306
|
+
|
|
307
|
+
# print the objective function value
|
|
308
|
+
# Print a pretty table showing objective values and iteration progress
|
|
309
|
+
# Clear previous output (move cursor up and clear lines)
|
|
310
|
+
if iteration > 0:
|
|
311
|
+
print("\033[F\033[K" * 4, end="\r")
|
|
312
|
+
|
|
313
|
+
headers = ["Iteration"] + ["Total"] + list(self.obj_funcs.keys()) + list(self.constraints_funcs.keys())
|
|
314
|
+
data = [[f"{iteration+1}/{self.max_step_iter}"] +
|
|
315
|
+
[f"{sum(obj_values).item():.6e}"] +
|
|
316
|
+
[f"{val.item():.6e}" for val in obj_values] +
|
|
317
|
+
[f"{val.item():.6e}" for val in constraints_values]]
|
|
318
|
+
|
|
319
|
+
string = tabulate(data, headers=headers, tablefmt="grid")
|
|
320
|
+
print(string, end="\r")
|
|
321
|
+
|
|
322
|
+
return variables.detach().clone()
|
|
323
|
+
|
|
324
|
+
def update_variables(self, dx: torch.Tensor) -> None:
|
|
325
|
+
"""
|
|
326
|
+
Update the variables of the surfaces.
|
|
327
|
+
"""
|
|
328
|
+
control_points0 = self.params_update.get_control_points_list()
|
|
329
|
+
control_points0 = [cp.detach().clone().cpu().numpy() for cp in control_points0]
|
|
330
|
+
|
|
331
|
+
self.params_update.update_variables(x_change=dx, max_step_length=self._max_step_length)
|
|
332
|
+
|
|
333
|
+
control_points_new = self.params_update.get_control_points_list()
|
|
334
|
+
control_points_new = [cp.detach().clone().cpu().numpy() for cp in control_points_new]
|
|
335
|
+
delta_control_points = [control_points_new[i] - control_points0[i] for i in range(len(control_points0))]
|
|
336
|
+
self._update_step_length(delta_control_points=delta_control_points)
|
|
337
|
+
|
|
338
|
+
self._delta_control_points_previous = [cp.copy() for cp in delta_control_points]
|
|
339
|
+
|
|
340
|
+
def save(self, foldpath, iteration):
|
|
341
|
+
step_length_numpy = [self._max_step_length[i].detach().cpu().numpy() for i in range(len(self._max_step_length))]
|
|
342
|
+
data = np.savez(foldpath + self.pathlog_required()[0] + f"/step_length_{iteration}.npz", *step_length_numpy)
|
|
343
|
+
|
|
344
|
+
def load(self, foldpath, iteration):
|
|
345
|
+
data = np.load(foldpath + self.pathlog_required()[0] + f"/step_length_{iteration}.npz")
|
|
346
|
+
for i in range(len(self._max_step_length)):
|
|
347
|
+
self._max_step_length[i] = torch.tensor(data['arr_%d' % i]).to(self.params_update.surface_list[i].model.control_points.device)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BaseOpt():
|
|
5
|
+
def __init__(self, closure: callable):
|
|
6
|
+
self.closure = closure
|
|
7
|
+
"""
|
|
8
|
+
the objective function to be optimized, which should return the objective value
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
self.grad = torch.func.jacrev(self.closure)
|
|
12
|
+
""" the gradient of the objective function, which should return the gradient vector
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# params for line search
|
|
16
|
+
self._c1 = 1e-4
|
|
17
|
+
self._rou1 = 0.2
|
|
18
|
+
|
|
19
|
+
def set_line_search_params(self, c1: float = None, rou1: float = None):
|
|
20
|
+
"""
|
|
21
|
+
Set the parameters for the line search method.
|
|
22
|
+
|
|
23
|
+
Parameters:
|
|
24
|
+
c1 (float): The parameter for the Wolfe condition.
|
|
25
|
+
rou1 (float): The parameter for the backtracking line search.
|
|
26
|
+
"""
|
|
27
|
+
if c1 is not None:
|
|
28
|
+
self._c1 = c1
|
|
29
|
+
if rou1 is not None:
|
|
30
|
+
self._rou1 = rou1
|
|
31
|
+
|
|
32
|
+
def _LineSearchBacktracking(self, x0: torch.Tensor, dx: torch.Tensor, direction: torch.Tensor, alpha0: float = 1.0, obj0: float = None):
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
Backtracking line search to find the step length for the optimization process.
|
|
36
|
+
|
|
37
|
+
Parameters:
|
|
38
|
+
x0 (torch.Tensor): The current point in the optimization process.
|
|
39
|
+
dx (torch.Tensor): The gradient vector at the current point.
|
|
40
|
+
direction (torch.Tensor): The search direction.
|
|
41
|
+
alpha0 (float): The initial step length.
|
|
42
|
+
c1 (float): The parameter for the Wolfe condition.
|
|
43
|
+
rou1 (float): The parameter for the backtracking line search.
|
|
44
|
+
obj0 (float): The initial objective value.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
float: The step length that satisfies the Wolfe condition.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
if obj0 is None:
|
|
51
|
+
obj0 = self.closure(x0)
|
|
52
|
+
|
|
53
|
+
alpha = alpha0
|
|
54
|
+
while True:
|
|
55
|
+
x_new = x0 + alpha * direction
|
|
56
|
+
with torch.no_grad():
|
|
57
|
+
obj_new = self.closure(x_new)
|
|
58
|
+
if ~torch.isnan(obj_new) and ~torch.isinf(
|
|
59
|
+
obj_new
|
|
60
|
+
) and obj_new < obj0 + self._c1 * alpha * dx.dot(direction):
|
|
61
|
+
return alpha, obj_new
|
|
62
|
+
alpha *= self._rou1
|
|
63
|
+
if (alpha * direction).abs().max() < 1e-14:
|
|
64
|
+
return 0., obj0
|
|
65
|
+
|
|
66
|
+
def step(x_now: torch.Tensor, gk_now: torch.Tensor=None) -> torch.Tensor:
|
|
67
|
+
"""
|
|
68
|
+
Perform a single optimization step.
|
|
69
|
+
"""
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
class LBFGS(BaseOpt):
|
|
73
|
+
def __init__(self,
|
|
74
|
+
closure: callable,
|
|
75
|
+
num_limit:int = 10,
|
|
76
|
+
tol_error:float = 1e-10) -> None:
|
|
77
|
+
super(LBFGS, self).__init__(closure=closure)
|
|
78
|
+
self.SK = []
|
|
79
|
+
self.YK = []
|
|
80
|
+
self.rhok = []
|
|
81
|
+
|
|
82
|
+
self.num_limit = num_limit
|
|
83
|
+
"""
|
|
84
|
+
the number of pairs of Hessian and gradient stored in the memory
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
self.tol_error = tol_error
|
|
88
|
+
"""
|
|
89
|
+
the tolerance for the convergence of the optimization process
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def Hg_loop(self, dv: torch.Tensor) -> torch.Tensor:
|
|
93
|
+
"""
|
|
94
|
+
Compute the product of the inverse Hessian matrix and the gradient vector.
|
|
95
|
+
This is done using the BFGS update formula.
|
|
96
|
+
|
|
97
|
+
Parameters:
|
|
98
|
+
dv (torch.Tensor): The gradient vector.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
torch.Tensor: The product of the inverse Hessian matrix and the gradient vector.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
q = dv.clone()
|
|
105
|
+
alpha = torch.zeros(len(self.SK))
|
|
106
|
+
for i in range(len(self.SK) - 1, -1, -1):
|
|
107
|
+
alpha[i] = self.rhok[i] * self.SK[i].dot(q)
|
|
108
|
+
q = q - alpha[i] * self.YK[i]
|
|
109
|
+
|
|
110
|
+
y = q / (self.rhok[-1] * self.YK[-1].dot(self.YK[-1]))
|
|
111
|
+
|
|
112
|
+
for i in range(len(self.SK)):
|
|
113
|
+
beta = self.rhok[i] * self.YK[i].dot(y)
|
|
114
|
+
y = y + (alpha[i] - beta) * self.SK[i]
|
|
115
|
+
|
|
116
|
+
return y
|
|
117
|
+
|
|
118
|
+
def step(self, x_now: torch.Tensor, gk_now: torch.Tensor=None):
|
|
119
|
+
|
|
120
|
+
obj_now = self.closure(x_now)
|
|
121
|
+
if gk_now is None:
|
|
122
|
+
gk_now: torch.Tensor = self.grad(x_now).flatten()
|
|
123
|
+
|
|
124
|
+
gk_now.view(-1)[gk_now.view(-1).isnan()] = 0
|
|
125
|
+
|
|
126
|
+
# if the first iteration, use steepest descent direction
|
|
127
|
+
if len(self.SK) == 0:
|
|
128
|
+
dk = -gk_now
|
|
129
|
+
else:
|
|
130
|
+
dk = self.Hg_loop(-gk_now)
|
|
131
|
+
if dk.view(-1).isnan().any():
|
|
132
|
+
dk = -gk_now
|
|
133
|
+
self.SK = []
|
|
134
|
+
self.YK = []
|
|
135
|
+
self.rhok = []
|
|
136
|
+
|
|
137
|
+
# if the gradient is not positive, use steepest descent direction
|
|
138
|
+
if (dk * gk_now).sum() > 0:
|
|
139
|
+
dk = -dk
|
|
140
|
+
|
|
141
|
+
# line search
|
|
142
|
+
alpha, obj_new = self._LineSearchBacktracking(x0=x_now, dx=gk_now, direction=dk, alpha0=1., obj0=obj_now)
|
|
143
|
+
|
|
144
|
+
# if the step length is too small, stop the iteration
|
|
145
|
+
if abs(alpha) <= self.tol_error:
|
|
146
|
+
self.SK = []
|
|
147
|
+
self.YK = []
|
|
148
|
+
self.rhok = []
|
|
149
|
+
|
|
150
|
+
x_new = x_now + alpha * dk
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if obj_new>obj_now:
|
|
154
|
+
self.SK = []
|
|
155
|
+
self.YK = []
|
|
156
|
+
self.rhok = []
|
|
157
|
+
return 0., torch.zeros_like(x_now), gk_now
|
|
158
|
+
|
|
159
|
+
gk_new: torch.Tensor =self.grad(x_new).flatten()
|
|
160
|
+
|
|
161
|
+
yk = gk_new.flatten() - gk_now.flatten()
|
|
162
|
+
sk = alpha * dk.flatten()
|
|
163
|
+
|
|
164
|
+
# BFGS method
|
|
165
|
+
if yk.norm() > 1e-14 and sk.norm() > 1e-14:
|
|
166
|
+
self.SK.append(sk)
|
|
167
|
+
self.YK.append(yk)
|
|
168
|
+
self.rhok.append(1 / yk.dot(sk))
|
|
169
|
+
|
|
170
|
+
if len(self.SK) > self.num_limit:
|
|
171
|
+
self.SK = self.SK[1:]
|
|
172
|
+
self.YK = self.YK[1:]
|
|
173
|
+
self.rhok = self.rhok[1:]
|
|
174
|
+
|
|
175
|
+
return alpha, dk, gk_new
|
|
176
|
+
|
|
177
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
|
|
2
|
+
import torch
|
|
3
|
+
|
|
4
|
+
from ..baseobject import BaseObject
|
|
5
|
+
|
|
6
|
+
from .geometry.update_geometry import UpdaterGeometries
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Updaters(BaseObject):
|
|
10
|
+
"""
|
|
11
|
+
This class is responsible for updating the morphologies of the neurons.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, surfaces:UpdaterGeometries=None, *args, **kwargs):
|
|
15
|
+
"""
|
|
16
|
+
Initialize the Updaters class with a neuron object.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
surfaces (UpdaterSurfaces, optional): An instance of the UpdaterSurfaces class for updating the surfaces.
|
|
20
|
+
loads (UpdaterLoads, optional): An instance of the UpdaterLoads class for updating the loads.
|
|
21
|
+
materials (UpdaterMaterials, optional): An instance of the UpdaterMaterials class for updating the materials.
|
|
22
|
+
"""
|
|
23
|
+
self._surface: UpdaterGeometries = None
|
|
24
|
+
"""
|
|
25
|
+
UpdaterSurfaces: An instance of the UpdaterSurfaces class for updating the surfaces.
|
|
26
|
+
"""
|
|
27
|
+
self.if_update_surface = False
|
|
28
|
+
"""
|
|
29
|
+
if_update_surface: A flag indicating whether the surface needs to be updated.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
if surfaces is not None:
|
|
33
|
+
self._surface = surfaces
|
|
34
|
+
self.if_update_surface = True
|
|
35
|
+
self._var_surface: torch.Tensor = None
|
|
36
|
+
"""
|
|
37
|
+
var_surface: The updated surface variables.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def reinitialize(self, iteration: int) -> None:
|
|
41
|
+
"""
|
|
42
|
+
reInitialize the Updaters class.
|
|
43
|
+
This method reinitializes the surfaces, loads, and materials if they are present.
|
|
44
|
+
"""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
def initialize(self) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Initialize the Updaters class.
|
|
50
|
+
This method initializes the surfaces, loads, and materials if they are present.
|
|
51
|
+
"""
|
|
52
|
+
if self.if_update_surface:
|
|
53
|
+
self._surface.initialize()
|
|
54
|
+
|
|
55
|
+
def update(self) -> torch.Tensor:
|
|
56
|
+
"""
|
|
57
|
+
Update the morphology of the neuron.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
if self.if_update_surface:
|
|
61
|
+
self._var_surface = self._surface.update()
|
|
62
|
+
|
|
63
|
+
def update_variables(self) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Update the variables of the surfaces and loads.
|
|
66
|
+
"""
|
|
67
|
+
if self.if_update_surface:
|
|
68
|
+
self._surface.update_variables(dx=self._var_surface)
|
|
69
|
+
|
|
70
|
+
def save(self, foldpath: str, iteration: int) -> None:
|
|
71
|
+
"""
|
|
72
|
+
Save the updater state to a file.
|
|
73
|
+
"""
|
|
74
|
+
if self.if_update_surface:
|
|
75
|
+
self._surface.save(foldpath=foldpath, iteration=iteration)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def pathlog_required(self):
|
|
79
|
+
paths = []
|
|
80
|
+
if self.if_update_surface:
|
|
81
|
+
paths += self._surface.pathlog_required()
|
|
82
|
+
return paths
|