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,110 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
from ..modelparams.base_params import BaseParams
|
|
5
|
+
from ..modelparams.params import Params
|
|
6
|
+
from . import optimizer
|
|
7
|
+
from ..baseobject import BaseObject
|
|
8
|
+
|
|
9
|
+
class BaseUpdater(BaseObject):
|
|
10
|
+
"""
|
|
11
|
+
Base class for all Updaters.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, params: Params, *args, **kwargs):
|
|
15
|
+
"""
|
|
16
|
+
Initialize the BaseUpdater.
|
|
17
|
+
"""
|
|
18
|
+
self.max_step_iter = 50
|
|
19
|
+
"""Maximum number of iterations for the sub-optimization process.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
self.obj_funcs: dict[str, callable] = {}
|
|
23
|
+
"""A dictionary of objective functions to be optimized.
|
|
24
|
+
The keys are the names of the functions, and the values are the functions themselves.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
self.params_update: BaseParams = None
|
|
28
|
+
"""The parameters of the optimization process.
|
|
29
|
+
This should be set to an instance of a subclass of BaseParams.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
self.params: Params = params
|
|
33
|
+
"""The parameters of optimization process.
|
|
34
|
+
This should be set to an instance of the Params class.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
self.optimizer: optimizer.BaseOpt
|
|
38
|
+
"""The optimization algorithm used to update the design variables.
|
|
39
|
+
This should be set to an instance of a subclass of Optimizer.BaseOpt.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
self.iteration_total = 0
|
|
43
|
+
"""
|
|
44
|
+
The total number of iterations performed in the optimization process.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
self.U_dim: list[int] = []
|
|
48
|
+
"""U_dim: The dimensions of the interest for the optimization problem.
|
|
49
|
+
This is a list of integers representing the dimensions of the displacement vector.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def reinitialize(self, iter_now: int, sensitivity: list[torch.Tensor]) -> None:
|
|
53
|
+
"""
|
|
54
|
+
reInitialize the updater with the current iteration and sensitivity.
|
|
55
|
+
"""
|
|
56
|
+
raise NotImplementedError("This method should be overridden by subclasses.")
|
|
57
|
+
|
|
58
|
+
def initialize(self) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Initialize the updater.
|
|
61
|
+
"""
|
|
62
|
+
raise NotImplementedError("This method should be overridden by subclasses.")
|
|
63
|
+
|
|
64
|
+
def _get_sensitivity(self) -> tuple[torch.Tensor, list[torch.Tensor]]:
|
|
65
|
+
"""
|
|
66
|
+
Get the sensitivity of the elements based on the provided objective function.
|
|
67
|
+
|
|
68
|
+
Parameters:
|
|
69
|
+
fe_result (FE_result): The finite element analysis result containing the displacement and Jacobian.
|
|
70
|
+
obj_fun (callable): The objective function to be used for sensitivity analysis.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
tuple: A tuple containing:
|
|
74
|
+
- Loss (torch.Tensor): The loss value.
|
|
75
|
+
- sensitivity (list[torch.Tensor]): A list of sensitivity tensors for each surface.
|
|
76
|
+
"""
|
|
77
|
+
raise NotImplementedError("This method should be overridden by subclasses.")
|
|
78
|
+
|
|
79
|
+
def _refine_sensitivity(self, iter_now: int, sensitivity: list[torch.Tensor]) -> list[torch.Tensor]:
|
|
80
|
+
"""
|
|
81
|
+
Refine the sensitivity values based on the current iteration.
|
|
82
|
+
|
|
83
|
+
Parameters:
|
|
84
|
+
iter_now (int): The current iteration number.
|
|
85
|
+
sensitivity (list[torch.Tensor]): The sensitivity tensors to be refined.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
list[torch.Tensor]: The refined sensitivity tensors.
|
|
89
|
+
"""
|
|
90
|
+
# This method can be overridden by subclasses if needed
|
|
91
|
+
return sensitivity
|
|
92
|
+
|
|
93
|
+
def closure(self, x: torch.Tensor, return_list: bool = False) -> torch.Tensor:
|
|
94
|
+
"""
|
|
95
|
+
This method is called to update the design variables based on the optimization algorithm used.
|
|
96
|
+
|
|
97
|
+
Parameters:
|
|
98
|
+
x (torch.Tensor): The current design variables.
|
|
99
|
+
return_list (bool): If True, returns a list of objective function values; otherwise, returns their sum.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
torch.Tensor: The objective function value(s).
|
|
103
|
+
"""
|
|
104
|
+
raise NotImplementedError("This method should be overridden by subclasses.")
|
|
105
|
+
|
|
106
|
+
def update_variables(self, dx: torch.Tensor) -> None:
|
|
107
|
+
"""
|
|
108
|
+
Update the variables of the surfaces.
|
|
109
|
+
"""
|
|
110
|
+
pass
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BaseConstraints():
|
|
5
|
+
"""
|
|
6
|
+
Base class for objective functions in MorphOpt.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
"""
|
|
11
|
+
Initialize the objective function.
|
|
12
|
+
"""
|
|
13
|
+
self.scaler: list[torch.Tensor] = []
|
|
14
|
+
"""
|
|
15
|
+
The scaler to process the objective function.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def initialize(self, r0: list[torch.Tensor], rdu0: list[torch.Tensor], rdu20: list[torch.Tensor], sensitivity: list[torch.Tensor], weights: list[torch.Tensor], *args, **kwargs):
|
|
20
|
+
|
|
21
|
+
self.scaler = []
|
|
22
|
+
for i in range(len(sensitivity)):
|
|
23
|
+
sen_now = sensitivity[i].reshape([-1, 3]).norm(dim=1)
|
|
24
|
+
if (sen_now != 0).any():
|
|
25
|
+
sen_now[sen_now==0] = sen_now[sen_now!=0].min()
|
|
26
|
+
else:
|
|
27
|
+
sen_now = torch.ones_like(sen_now) # Fallback if all zeros
|
|
28
|
+
self.scaler.append(sen_now * weights[i])
|
|
29
|
+
|
|
30
|
+
def __call__(self, r: list[torch.Tensor], rdu: list[torch.Tensor], rdu2: list[torch.Tensor], weights: list[torch.Tensor], *args, **kwargs) -> float:
|
|
31
|
+
"""
|
|
32
|
+
Call the objective function.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
weight (list[torch.Tensor]): The weights for each point.
|
|
36
|
+
r (list[torch.Tensor]): The point coordinates of the surfaces.
|
|
37
|
+
rdu (list[torch.Tensor]): The partial derivatives of the surfaces.
|
|
38
|
+
rdu2 (list[torch.Tensor]): The second partial derivatives of the surfaces.
|
|
39
|
+
*args: Positional arguments.
|
|
40
|
+
**kwargs: Keyword arguments.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
The value of the objective function.
|
|
44
|
+
"""
|
|
45
|
+
raise NotImplementedError("Objective function not implemented.")
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def barrier_function(f: torch.Tensor, f_max: torch.Tensor|float, ratio: float, p: int):
|
|
49
|
+
"""
|
|
50
|
+
Apply a barrier function to the objective function.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
f (torch.Tensor): The objective function value.
|
|
54
|
+
f_max (float or torch.Tensor): The maximum value of the objective function.
|
|
55
|
+
ratio (float): The ratio for the barrier function.
|
|
56
|
+
p (float): The exponent for the barrier function.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
index (torch.Tensor): The indices of the elements that are greater than the barrier.
|
|
60
|
+
fnew (torch.Tensor): The new objective function value after applying the barrier function.
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
if type(f_max) != torch.Tensor:
|
|
64
|
+
f_max = torch.tensor(f_max).repeat(f.shape)
|
|
65
|
+
index = torch.where(f > f_max * ratio)[0]
|
|
66
|
+
|
|
67
|
+
if index.numel() > 0:
|
|
68
|
+
f = f[index]
|
|
69
|
+
f_max = f_max[index]
|
|
70
|
+
fnew = ((f - f_max * ratio) / (f_max - f_max * ratio))**(p)
|
|
71
|
+
else:
|
|
72
|
+
f = f[index]
|
|
73
|
+
fnew = torch.zeros_like(f)
|
|
74
|
+
return index, fnew
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class BaseObjective():
|
|
78
|
+
"""
|
|
79
|
+
Base class for objective functions in MorphOpt.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self):
|
|
83
|
+
"""
|
|
84
|
+
Initialize the objective function.
|
|
85
|
+
"""
|
|
86
|
+
self.sensitivity: list[torch.Tensor] = []
|
|
87
|
+
"""
|
|
88
|
+
Sensitivity of the shape derivative with respect to the contact forces.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def initialize(self, r0: list[torch.Tensor], rdu0: list[torch.Tensor], rdu20: list[torch.Tensor], weights: list[torch.Tensor], *args, **kwargs) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Initialize the objective function with the given parameters.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
weights (list[torch.Tensor]): The weights for each point.
|
|
97
|
+
*args: Positional arguments.
|
|
98
|
+
**kwargs: Keyword arguments.
|
|
99
|
+
"""
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
def __call__(self, r: list[torch.Tensor], rdu: list[torch.Tensor], rdu2: list[torch.Tensor], *args, **kwargs) -> float:
|
|
103
|
+
"""
|
|
104
|
+
Call the objective function.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
weight (list[torch.Tensor]): The weights for each point.
|
|
108
|
+
r (list[torch.Tensor]): The point coordinates of the surfaces.
|
|
109
|
+
rdu (list[torch.Tensor]): The partial derivatives of the surfaces.
|
|
110
|
+
rdu2 (list[torch.Tensor]): The second partial derivatives of the surfaces.
|
|
111
|
+
*args: Positional arguments.
|
|
112
|
+
**kwargs: Keyword arguments.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
The value of the objective function.
|
|
116
|
+
"""
|
|
117
|
+
raise NotImplementedError("Objective function not implemented.")
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
|
|
2
|
+
import torch
|
|
3
|
+
from .basefuncs import BaseConstraints
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MinRadius(BaseConstraints):
|
|
7
|
+
"""
|
|
8
|
+
Cylinder boundary objective function for MorphOpt.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, radius: float) -> None:
|
|
12
|
+
"""
|
|
13
|
+
Initialize the Cylinder objective function.
|
|
14
|
+
"""
|
|
15
|
+
super().__init__()
|
|
16
|
+
|
|
17
|
+
self._radius = radius
|
|
18
|
+
"""
|
|
19
|
+
The radius of the cylinder.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def __call__(self, r, rdu, rdu2, *args, **kwargs):
|
|
24
|
+
|
|
25
|
+
thre = 0.05
|
|
26
|
+
degree = 3
|
|
27
|
+
|
|
28
|
+
loss = torch.zeros(1, device=r[0].device, dtype=r[0].dtype)
|
|
29
|
+
for i in range(len(r)):
|
|
30
|
+
|
|
31
|
+
length_r = -(r[i][:, 0]**2 + r[i][:, 1]**2).sqrt() + self._radius
|
|
32
|
+
|
|
33
|
+
index_r, loss_r = self.barrier_function(length_r, thre, 0., degree)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if len(index_r) > 0:
|
|
37
|
+
loss = loss + (self.scaler[i][index_r] * loss_r).sum()
|
|
38
|
+
|
|
39
|
+
return loss
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Cylinder(BaseConstraints):
|
|
43
|
+
"""
|
|
44
|
+
Cylinder boundary objective function for MorphOpt.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, radius: float, height: float, bottom: float) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Initialize the Cylinder objective function.
|
|
50
|
+
"""
|
|
51
|
+
super().__init__()
|
|
52
|
+
|
|
53
|
+
self._radius = radius
|
|
54
|
+
"""
|
|
55
|
+
The radius of the cylinder.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
self._height = height + 1
|
|
59
|
+
"""
|
|
60
|
+
The height of the cylinder.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
self._bottom = bottom - 1
|
|
64
|
+
"""
|
|
65
|
+
The bottom of the cylinder.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __call__(self, r, rdu, rdu2, *args, **kwargs):
|
|
69
|
+
|
|
70
|
+
thre = 0.05
|
|
71
|
+
degree = 3
|
|
72
|
+
|
|
73
|
+
loss = torch.zeros(1, device=r[0].device, dtype=r[0].dtype)
|
|
74
|
+
for i in range(len(r)):
|
|
75
|
+
|
|
76
|
+
length_r = (r[i][:, 0]**2 + r[i][:, 1]**2).sqrt() - self._radius
|
|
77
|
+
length_bottom = -r[i][:, 2] + self._bottom
|
|
78
|
+
length_top = r[i][:, 2] - self._height
|
|
79
|
+
|
|
80
|
+
index_r, loss_r = self.barrier_function(length_r, thre, 0., degree)
|
|
81
|
+
index_bottom, loss_bottom = self.barrier_function(length_bottom, thre, 0., degree)
|
|
82
|
+
index_top, loss_top = self.barrier_function(length_top, thre, 0., degree)
|
|
83
|
+
|
|
84
|
+
if len(index_r) > 0:
|
|
85
|
+
loss = loss + (self.scaler[i][index_r] * loss_r).sum()
|
|
86
|
+
if len(index_bottom) > 0:
|
|
87
|
+
loss = loss + (self.scaler[i][index_bottom] * loss_bottom).sum()
|
|
88
|
+
if len(index_top) > 0:
|
|
89
|
+
loss = loss + (self.scaler[i][index_top] * loss_top).sum()
|
|
90
|
+
|
|
91
|
+
return loss
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
|
|
2
|
+
import torch
|
|
3
|
+
from .basefuncs import BaseConstraints
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
class Distance(BaseConstraints):
|
|
7
|
+
"""
|
|
8
|
+
Distance objective function for MorphOpt.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, min_distance: list[list[float]]):
|
|
12
|
+
"""
|
|
13
|
+
Initialize the Distance objective function with a name.
|
|
14
|
+
"""
|
|
15
|
+
super().__init__()
|
|
16
|
+
|
|
17
|
+
self.neighbor_points: torch.Tensor
|
|
18
|
+
"""
|
|
19
|
+
The neighbor points of the surfaces that need to be checked for distance.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
self.points_surface_index: torch.Tensor
|
|
23
|
+
"""
|
|
24
|
+
The index of the surfaces for each point.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
self.neighbor_mindist: torch.Tensor
|
|
28
|
+
"""
|
|
29
|
+
The allowable minimum distance between the points.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
self.min_distance = torch.tensor(min_distance).to(torch.float32)
|
|
33
|
+
"""
|
|
34
|
+
The minimum distance between the surfaces.
|
|
35
|
+
[i, j] the minimum distance between surface i and surface j.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
self.distance_threshold = max(self.min_distance.flatten()).item() * 1.5
|
|
39
|
+
"""
|
|
40
|
+
The distance threshold for the distance penalty.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def initialize(self, r0, rdu0, *args, **kwargs) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Initialize the Distance objective function with the surface points and their derivatives.
|
|
46
|
+
Parameters:
|
|
47
|
+
r0 (list[torch.Tensor]): List of surface points.
|
|
48
|
+
rdu0 (list[torch.Tensor]): List of surface point derivatives.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
super().initialize(r0=r0, rdu0=rdu0, *args, **kwargs)
|
|
52
|
+
|
|
53
|
+
# prepare the normal vector for each surface
|
|
54
|
+
normal0 = []
|
|
55
|
+
for i in range(len(r0)):
|
|
56
|
+
normal_vector = torch.cross(rdu0[i][:, :, 1], rdu0[i][:, :, 0], dim=1)
|
|
57
|
+
normal_vector /= normal_vector.norm(dim=1, keepdim=True)
|
|
58
|
+
normal0.append(normal_vector.detach().clone())
|
|
59
|
+
|
|
60
|
+
num_points_total = []
|
|
61
|
+
index_Surf = []
|
|
62
|
+
for i in range(len(r0)):
|
|
63
|
+
index_Surf.append(torch.ones(r0[i].shape[0]) * i)
|
|
64
|
+
num_points_total += [r0[i].shape[0]]
|
|
65
|
+
|
|
66
|
+
normal0 = torch.cat(normal0, dim=0).type(torch.float32)
|
|
67
|
+
|
|
68
|
+
self.points_surface_index = torch.cat(index_Surf,
|
|
69
|
+
dim=0).type(torch.int64)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# get the neighbor points
|
|
73
|
+
import scipy.spatial
|
|
74
|
+
kdtree = scipy.spatial.KDTree(torch.cat(r0, dim=0).cpu().numpy())
|
|
75
|
+
self.neighbor_points = kdtree.query_pairs(r=self.distance_threshold, output_type='ndarray').T
|
|
76
|
+
|
|
77
|
+
self.neighbor_points = torch.from_numpy(self.neighbor_points).type(torch.int64).to(r0[0].device)
|
|
78
|
+
|
|
79
|
+
# determine the minimum distance between the neighbor points
|
|
80
|
+
self.neighbor_mindist = torch.zeros(self.neighbor_points.shape[1], dtype=torch.float32)
|
|
81
|
+
for i in range(self.min_distance.shape[0]):
|
|
82
|
+
for j in range(self.min_distance.shape[1]):
|
|
83
|
+
# Find indices where points are from surface i and j
|
|
84
|
+
index = torch.where(
|
|
85
|
+
(self.points_surface_index[self.neighbor_points[0]] == i) &
|
|
86
|
+
(self.points_surface_index[self.neighbor_points[1]] == j))[0]
|
|
87
|
+
|
|
88
|
+
if len(index) > 0:
|
|
89
|
+
if i == j: # Same surface case
|
|
90
|
+
# Use normal vector direction to determine distance for same surface
|
|
91
|
+
self.neighbor_mindist[index] = self.min_distance[i, j] * (-normal0[self.neighbor_points[0][index], :] *
|
|
92
|
+
normal0[self.neighbor_points[1][index], :]).sum(dim=1)
|
|
93
|
+
else:
|
|
94
|
+
# Different surfaces
|
|
95
|
+
self.neighbor_mindist[index] = self.min_distance[i, j]
|
|
96
|
+
|
|
97
|
+
index_pos = self.neighbor_mindist > 0
|
|
98
|
+
self.neighbor_points = self.neighbor_points[:, index_pos]
|
|
99
|
+
self.neighbor_mindist = self.neighbor_mindist[index_pos]
|
|
100
|
+
|
|
101
|
+
logging.debug(f"Distance Objective Function: {self.neighbor_points.shape[1]} point pairs within threshold {self.distance_threshold}")
|
|
102
|
+
|
|
103
|
+
def __call__(self, r, *args, **kwargs):
|
|
104
|
+
|
|
105
|
+
R = torch.cat(r, dim=0).type(torch.float32)
|
|
106
|
+
thre = 0.02
|
|
107
|
+
degree = 5
|
|
108
|
+
loss_distance = torch.tensor(0.0, dtype=torch.float32)
|
|
109
|
+
weight_flatten = torch.cat(self.scaler, dim=0).type(torch.float32)
|
|
110
|
+
if torch.numel(self.neighbor_points) != 0:
|
|
111
|
+
|
|
112
|
+
deltaR = R[self.neighbor_points[0], :] - R[self.neighbor_points[1], :]
|
|
113
|
+
distance = torch.sqrt(((deltaR)**2).sum(dim=1))
|
|
114
|
+
|
|
115
|
+
dist = self.neighbor_mindist - distance + thre
|
|
116
|
+
|
|
117
|
+
indexl, l = self.barrier_function(dist, thre, 0, degree)
|
|
118
|
+
|
|
119
|
+
if len(indexl) > 0:
|
|
120
|
+
loss_distance += (l * weight_flatten[self.neighbor_points[0, indexl]] *
|
|
121
|
+
weight_flatten[self.neighbor_points[1, indexl]]).sum()
|
|
122
|
+
|
|
123
|
+
return loss_distance
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from scipy import interpolate
|
|
4
|
+
import torch
|
|
5
|
+
from .basefuncs import BaseConstraints
|
|
6
|
+
import morphopt
|
|
7
|
+
|
|
8
|
+
class ShapeDerivativeDisplacement(BaseConstraints):
|
|
9
|
+
"""
|
|
10
|
+
Shape derivative for contact forces.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
|
|
15
|
+
super().__init__()
|
|
16
|
+
|
|
17
|
+
self._r0: list[torch.Tensor] = []
|
|
18
|
+
"""
|
|
19
|
+
Initial reference configuration.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def initialize(self, r0: list[torch.Tensor], *args, **kwargs):
|
|
23
|
+
|
|
24
|
+
objfun = morphopt.controller.objfun
|
|
25
|
+
fe = objfun.fe
|
|
26
|
+
|
|
27
|
+
self._r0 = [r0[i].detach().clone() for i in range(len(r0))]
|
|
28
|
+
|
|
29
|
+
ins = fe.assembly.get_instance('final_model')
|
|
30
|
+
part = fe.assembly.get_part('final_model')
|
|
31
|
+
grad_pos = torch.zeros_like(ins.nodes)
|
|
32
|
+
|
|
33
|
+
def closure_work(nodes_diff: torch.Tensor):
|
|
34
|
+
nodes0 = part.nodes
|
|
35
|
+
part.nodes = nodes_diff
|
|
36
|
+
fe.initialize()
|
|
37
|
+
|
|
38
|
+
# compute the sensitivity of the displacement
|
|
39
|
+
work = objfun.get_objective().to(part.nodes.device)
|
|
40
|
+
for i in range(len(objfun.U)):
|
|
41
|
+
GC0 = objfun.U[i].to(part.nodes.device)
|
|
42
|
+
fe.assembly.GC = GC0
|
|
43
|
+
fe.assembly.RGC = fe.assembly._GC2RGC(GC0)
|
|
44
|
+
morphopt.controller.params.feamodel.process_fea(fe=fe, step_index=i)
|
|
45
|
+
|
|
46
|
+
R = fe.assembly.assemble_Stiffness_Matrix(GC=GC0)[0]
|
|
47
|
+
ADJu = objfun.ADJu[i].to(part.nodes.device)
|
|
48
|
+
work = work + (R*ADJu).sum()
|
|
49
|
+
part.nodes = nodes0
|
|
50
|
+
fe.initialize()
|
|
51
|
+
return work
|
|
52
|
+
grad_pos += torch.autograd.functional.jacobian(closure_work, part.nodes.detach().clone())
|
|
53
|
+
|
|
54
|
+
i=0
|
|
55
|
+
|
|
56
|
+
interpolate_points = self._get_interpolate_points()
|
|
57
|
+
|
|
58
|
+
self.sensitivity = self._sensitivity_interpolation(Ldot=grad_pos, points_request=part.nodes, interpolated_points=interpolate_points)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def show_sensitivity(self, ind: int) -> None:
|
|
62
|
+
"""
|
|
63
|
+
Show the shape sensitivity
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
import pyvista as pv
|
|
67
|
+
import numpy as np
|
|
68
|
+
|
|
69
|
+
plotter = pv.Plotter(window_size=(1000, 1000))
|
|
70
|
+
plotter.set_background('white')
|
|
71
|
+
|
|
72
|
+
r = self._r0[ind].detach().cpu().numpy()
|
|
73
|
+
n = self.sensitivity[ind].detach().cpu().numpy()
|
|
74
|
+
|
|
75
|
+
# r and n are [N, 3]
|
|
76
|
+
points = r
|
|
77
|
+
vectors = n
|
|
78
|
+
|
|
79
|
+
# Debug: print statistics
|
|
80
|
+
print(f"Points shape: {points.shape}")
|
|
81
|
+
print(f"Vectors shape: {vectors.shape}")
|
|
82
|
+
print(f"Vector magnitude range: [{np.linalg.norm(vectors, axis=1).min():.6e}, {np.linalg.norm(vectors, axis=1).max():.6e}]")
|
|
83
|
+
|
|
84
|
+
# Filter out zero vectors
|
|
85
|
+
vector_norms = np.linalg.norm(vectors, axis=1)
|
|
86
|
+
valid_mask = vector_norms > 1e-10
|
|
87
|
+
|
|
88
|
+
if valid_mask.sum() == 0:
|
|
89
|
+
print("Warning: All sensitivity vectors are zero!")
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
points_filtered = points[valid_mask]
|
|
93
|
+
vectors_filtered = vectors[valid_mask]
|
|
94
|
+
|
|
95
|
+
# Auto-scale magnitude based on model size
|
|
96
|
+
model_size = np.max(points_filtered.max(axis=0) - points_filtered.min(axis=0))
|
|
97
|
+
vector_mag = np.linalg.norm(vectors_filtered, axis=1).mean()
|
|
98
|
+
mag_scale = model_size / vector_mag * 0.01 # Scale to 1% of model size
|
|
99
|
+
|
|
100
|
+
print(f"Using {valid_mask.sum()} non-zero vectors out of {len(valid_mask)}")
|
|
101
|
+
print(f"Auto-scaled magnitude: {mag_scale:.6e}")
|
|
102
|
+
|
|
103
|
+
# Add surface mesh for context
|
|
104
|
+
plotter.add_points(points_filtered, color='blue', point_size=5, render_points_as_spheres=True)
|
|
105
|
+
|
|
106
|
+
# Add arrows with auto-scaled magnitude
|
|
107
|
+
plotter.add_arrows(points_filtered, vectors_filtered, mag=mag_scale, color='red')
|
|
108
|
+
|
|
109
|
+
plotter.show_axes()
|
|
110
|
+
plotter.show()
|
|
111
|
+
|
|
112
|
+
def __call__(self, r: list[torch.Tensor], *args, **kwargs):
|
|
113
|
+
loss_objective = 0.0
|
|
114
|
+
|
|
115
|
+
for i in range(len(self.sensitivity)):
|
|
116
|
+
# r[i], self._r0[i], self.sensitivity[i] are all [p, 3]
|
|
117
|
+
# Compute inner product: sum over all points and dimensions
|
|
118
|
+
loss_objective += ((r[i] - self._r0[i]) * self.sensitivity[i]).sum()
|
|
119
|
+
|
|
120
|
+
return loss_objective
|
|
121
|
+
|
|
122
|
+
def _get_interpolate_points(self, *args, **kwargs):
|
|
123
|
+
"""
|
|
124
|
+
Get the interpolated points for the design variables.
|
|
125
|
+
|
|
126
|
+
Parameters:
|
|
127
|
+
*args: Additional arguments.
|
|
128
|
+
**kwargs: Additional keyword arguments.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
list[torch.Tensor]: The interpolated points for the design variables.
|
|
132
|
+
"""
|
|
133
|
+
surfaces = morphopt.controller.params.geometry
|
|
134
|
+
r0 = surfaces.get_geometry_values()[0]
|
|
135
|
+
interpolated_points = []
|
|
136
|
+
for sf in range(surfaces.num_surface):
|
|
137
|
+
interpolated_points.append(r0[sf].cpu().numpy())
|
|
138
|
+
return interpolated_points
|
|
139
|
+
|
|
140
|
+
def _sensitivity_interpolation(
|
|
141
|
+
self, Ldot: torch.Tensor, points_request: torch.Tensor,
|
|
142
|
+
interpolated_points: list[torch.Tensor]) -> list[torch.Tensor]:
|
|
143
|
+
"""
|
|
144
|
+
Interpolate the sensitivity into the structural grids.
|
|
145
|
+
|
|
146
|
+
Parameters:
|
|
147
|
+
Ldot (torch.Tensor): The sensitivity values for elements gaussian points.
|
|
148
|
+
[num_points, 3]
|
|
149
|
+
points_request (torch.Tensor): The coordinates of the elements.
|
|
150
|
+
[num_points, 3]
|
|
151
|
+
interpolated_points (list[torch.Tensor]): The coordinates of the interpolation points.
|
|
152
|
+
[num_surface, 3]
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
list[torch.Tensor]: The interpolated sensitivity values for the structural grids.
|
|
156
|
+
[num_surface, sensitivity]
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
# interpolate the sensitivity into the structral grids
|
|
160
|
+
output_senNodes = []
|
|
161
|
+
|
|
162
|
+
for surf_index in range(len(interpolated_points)):
|
|
163
|
+
|
|
164
|
+
output_senNodes.append(torch.zeros(interpolated_points[surf_index].shape))
|
|
165
|
+
|
|
166
|
+
for i in range(3):
|
|
167
|
+
Part_B = interpolate.griddata(
|
|
168
|
+
points_request.reshape([-1, 3]).detach().cpu().numpy(),
|
|
169
|
+
Ldot[:, i].flatten().detach().cpu().numpy(),
|
|
170
|
+
(interpolated_points[surf_index][:, 0],
|
|
171
|
+
interpolated_points[surf_index][:, 1],
|
|
172
|
+
interpolated_points[surf_index][:, 2]),
|
|
173
|
+
method='nearest',
|
|
174
|
+
fill_value=0,
|
|
175
|
+
rescale=True)
|
|
176
|
+
|
|
177
|
+
output_senNodes[-1][:, i] = torch.tensor((Part_B).tolist())
|
|
178
|
+
|
|
179
|
+
return output_senNodes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from .basefuncs import BaseConstraints
|
|
3
|
+
from ....modelparams import GeometryParams
|
|
4
|
+
from . import ShapeDerivativeDisplacement
|
|
5
|
+
class Fairness(BaseConstraints):
|
|
6
|
+
"""
|
|
7
|
+
Fairness objective function for MorphOpt.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, surfaces: GeometryParams, sensitivity: ShapeDerivativeDisplacement):
|
|
11
|
+
"""
|
|
12
|
+
Initialize the fairness objective function with a name.
|
|
13
|
+
"""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.surfaces = surfaces
|
|
16
|
+
"""
|
|
17
|
+
The surfaces object that contains the design variables.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
self.sensitivity = sensitivity
|
|
21
|
+
"""the sensitivity to anchor the scaler"""
|
|
22
|
+
|
|
23
|
+
def __call__(self, r: list[torch.Tensor], rdu: list[torch.Tensor], rdu2: list[torch.Tensor], *args, **kwargs) -> float:
|
|
24
|
+
"""
|
|
25
|
+
Call the fairness objective function.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
r (list[torch.Tensor]): The point coordinates of the surfaces.
|
|
29
|
+
rdu (list[torch.Tensor]): The partial derivatives of the surfaces.
|
|
30
|
+
rdu2 (list[torch.Tensor]): The second partial derivatives of the surfaces.
|
|
31
|
+
*args: Positional arguments.
|
|
32
|
+
**kwargs: Keyword arguments.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
The value of the fairness objective function.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Implement the fairness objective function here
|
|
40
|
+
return sum(self.surfaces.get_penalty_fairness(self.scaler, r, rdu, rdu2))
|