pyMOTO 1.4.0__py3-none-any.whl → 1.5.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.
- {pyMOTO-1.4.0.dist-info → pyMOTO-1.5.1.dist-info}/METADATA +2 -2
- pyMOTO-1.5.1.dist-info/RECORD +29 -0
- {pyMOTO-1.4.0.dist-info → pyMOTO-1.5.1.dist-info}/WHEEL +1 -1
- pymoto/__init__.py +5 -5
- pymoto/common/domain.py +3 -1
- pymoto/common/dyadcarrier.py +56 -27
- pymoto/common/mma.py +70 -79
- pymoto/core_objects.py +103 -47
- pymoto/modules/assembly.py +146 -28
- pymoto/modules/io.py +80 -6
- pymoto/modules/linalg.py +84 -23
- pymoto/modules/scaling.py +2 -1
- pymoto/routines.py +13 -2
- pymoto/solvers/__init__.py +2 -2
- pymoto/solvers/auto_determine.py +1 -1
- pymoto/solvers/iterative.py +20 -16
- pymoto/solvers/matrix_checks.py +7 -3
- pymoto/solvers/solvers.py +44 -13
- pymoto/solvers/sparse.py +11 -0
- pyMOTO-1.4.0.dist-info/RECORD +0 -29
- {pyMOTO-1.4.0.dist-info → pyMOTO-1.5.1.dist-info}/LICENSE +0 -0
- {pyMOTO-1.4.0.dist-info → pyMOTO-1.5.1.dist-info}/top_level.txt +0 -0
- {pyMOTO-1.4.0.dist-info → pyMOTO-1.5.1.dist-info}/zip-safe +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pyMOTO
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.5.1
|
4
4
|
Summary: A modular approach for topology optimization
|
5
5
|
Home-page: https://github.com/aatmdelissen/pyMOTO
|
6
6
|
Author: Arnoud Delissen
|
@@ -51,7 +51,7 @@ automatically calculated.
|
|
51
51
|
|
52
52
|
# Quick start installation
|
53
53
|
1. Make sure you have Python running in some kind of virtual environment (e.g.
|
54
|
-
[conda](https://docs.conda.io/projects/conda/en/stable/), [miniconda](https://docs.conda.io/en/latest/miniconda.html),
|
54
|
+
[uv](https://docs.astral.sh/uv/guides/install-python/), [conda](https://docs.conda.io/projects/conda/en/stable/), [miniconda](https://docs.conda.io/en/latest/miniconda.html),
|
55
55
|
[venv](https://realpython.com/python-virtual-environments-a-primer/))
|
56
56
|
2. Install the pymoto Python package (and its dependencies)
|
57
57
|
- Option A (conda): If you are working with Conda, install by `conda install -c aatmdelissen pymoto`
|
@@ -0,0 +1,29 @@
|
|
1
|
+
pymoto/__init__.py,sha256=i4B3bc954raAJs0lh0vycgWQ3n6FQCL5oKllmaHtt8M,2058
|
2
|
+
pymoto/core_objects.py,sha256=JDY5OBnzSnmXp35-gY_Gqawjh6C2x8dL1cEV8KAW944,27343
|
3
|
+
pymoto/routines.py,sha256=inz5GbSnvmbD3WKmaNKATTc_SthKsAMPuRc8Jaub4xs,15568
|
4
|
+
pymoto/utils.py,sha256=YJ-PNLJLc12Yx6TYCrEechS2aaBRx0o4mTM1soeeyz0,1122
|
5
|
+
pymoto/common/domain.py,sha256=1peaQJuePBZCWjW2oMhRxlhOl1FuhthEE1aWW9dMumk,18168
|
6
|
+
pymoto/common/dyadcarrier.py,sha256=Q0MCIP4b-W_RuYxynvY-voCHfeZ4oF12ykAuVUnmLq4,19433
|
7
|
+
pymoto/common/mma.py,sha256=SaFdZhpL5uoQRLWxQrElcjRMzGG4CL3BcPc77z3eMVE,24612
|
8
|
+
pymoto/modules/aggregation.py,sha256=Oi17hIJ6dic4lOPw16zmjbdC72MjB6XK34H80bnbWAI,7580
|
9
|
+
pymoto/modules/assembly.py,sha256=o_HZ4bcyK6cMNWVsrmxvv2z6oDgQcrBmB6dquQCWGC8,23216
|
10
|
+
pymoto/modules/autodiff.py,sha256=WAfoAOHBSozf7jbr9gQz9Vw4a_2G9wGJxLMMqUQP0Co,1684
|
11
|
+
pymoto/modules/complex.py,sha256=B_Obk-ABdV66lEudZ5s8o6qG9NsmYlBsX-PbWvbphhc,4429
|
12
|
+
pymoto/modules/filter.py,sha256=6X9FaQMWYZ_TpHVTFiEibzlmAwmSWbydYM93LFrJ0Wo,25490
|
13
|
+
pymoto/modules/generic.py,sha256=YzsGZ8J0oLCORt78Bf2p0v4GuqpWRI77NLoCk7gqidw,10666
|
14
|
+
pymoto/modules/io.py,sha256=BHJgS5IvFTiYYH4cXqRwSid1bfB58HOQs_jhFeLbb3A,13639
|
15
|
+
pymoto/modules/linalg.py,sha256=GIMqcIfVsMib5nIwrhx4sPT6-auLiHBJjbKCAdulLLE,22005
|
16
|
+
pymoto/modules/scaling.py,sha256=uq88HHW9rP16XLz7UGc3CNBBpY2Z1glo8yjYxZEnXUg,2327
|
17
|
+
pymoto/solvers/__init__.py,sha256=9JUeD2SgZbkYFullA7s7s6SuAVv0onqAqJ8hFvNOs2g,1033
|
18
|
+
pymoto/solvers/auto_determine.py,sha256=X8MEG7h6jLfAV1inpja45_-suG8qQFMfLMDfW2ryQqQ,5134
|
19
|
+
pymoto/solvers/dense.py,sha256=9fKPCwNxRKAEk5k1A7fdLrr9ngeVssGlw-sbjWCm4iU,11235
|
20
|
+
pymoto/solvers/iterative.py,sha256=hfMRw2LChupr4sQf8qUKG6OHjhpeVAp6C9N7M4-845M,13179
|
21
|
+
pymoto/solvers/matrix_checks.py,sha256=bbrfjpTSWWnuQW3xY0_CYE8yrh5gA9K5b1LzHEOFAxI,1663
|
22
|
+
pymoto/solvers/solvers.py,sha256=Srn44oRonZIjJq-XVpJY9KoTWV0R7zBWFaSmXKWsUCw,11870
|
23
|
+
pymoto/solvers/sparse.py,sha256=BPw-2Q4lgbmtjCl8eLEmDdWhjqD7RSLrVYy_kQN_LdU,17144
|
24
|
+
pyMOTO-1.5.1.dist-info/LICENSE,sha256=ZXMC2Txpzs-dBwz9Me4_1rQCSVl4P1B27MomNi43F30,1072
|
25
|
+
pyMOTO-1.5.1.dist-info/METADATA,sha256=_ZZ3mHjsLTWtdHuM2k8VcO2t3xF6gt67gFVFHibXMJ8,5062
|
26
|
+
pyMOTO-1.5.1.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
27
|
+
pyMOTO-1.5.1.dist-info/top_level.txt,sha256=EdvAUSmFMaiqhuEZW8jxANMiK-LdPtlmDWL6SfmCdUU,7
|
28
|
+
pyMOTO-1.5.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
29
|
+
pyMOTO-1.5.1.dist-info/RECORD,,
|
pymoto/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
__version__ = '1.
|
1
|
+
__version__ = '1.5.1'
|
2
2
|
|
3
3
|
from .common.domain import DomainDefinition
|
4
4
|
|
@@ -14,12 +14,12 @@ from .core_objects import Signal, Module, Network, make_signals
|
|
14
14
|
|
15
15
|
# Import modules
|
16
16
|
from .modules.assembly import AssembleGeneral, AssembleStiffness, AssembleMass, AssemblePoisson
|
17
|
-
from .modules.assembly import ElementOperation, Strain, Stress
|
17
|
+
from .modules.assembly import ElementOperation, Strain, Stress, ElementAverage, NodalOperation, ThermoMechanical
|
18
18
|
from .modules.autodiff import AutoMod
|
19
19
|
from .modules.complex import MakeComplex, RealPart, ImagPart, ComplexNorm
|
20
20
|
from .modules.filter import FilterConv, Filter, DensityFilter, OverhangFilter
|
21
21
|
from .modules.generic import MathGeneral, EinSum, ConcatSignal
|
22
|
-
from .modules.io import FigModule, PlotDomain, PlotGraph, PlotIter, WriteToVTI
|
22
|
+
from .modules.io import FigModule, PlotDomain, PlotGraph, PlotIter, WriteToVTI, ScalarToFile
|
23
23
|
from .modules.linalg import Inverse, LinSolve, EigenSolve, SystemOfEquations, StaticCondensation
|
24
24
|
from .modules.aggregation import AggScaling, AggActiveSet, Aggregation, PNorm, SoftMinMax, KSFunction
|
25
25
|
from .modules.scaling import Scaling
|
@@ -44,9 +44,9 @@ __all__ = [
|
|
44
44
|
"MathGeneral", "EinSum", "ConcatSignal",
|
45
45
|
"Inverse", "LinSolve", "EigenSolve", "SystemOfEquations", "StaticCondensation",
|
46
46
|
"AssembleGeneral", "AssembleStiffness", "AssembleMass", "AssemblePoisson",
|
47
|
-
"ElementOperation", "Strain", "Stress",
|
47
|
+
"ElementOperation", "Strain", "Stress", "ElementAverage", "NodalOperation", "ThermoMechanical",
|
48
48
|
"FilterConv", "Filter", "DensityFilter", "OverhangFilter",
|
49
|
-
"FigModule", "PlotDomain", "PlotGraph", "PlotIter", "WriteToVTI",
|
49
|
+
"FigModule", "PlotDomain", "PlotGraph", "PlotIter", "WriteToVTI", "ScalarToFile",
|
50
50
|
"MakeComplex", "RealPart", "ImagPart", "ComplexNorm",
|
51
51
|
"AutoMod",
|
52
52
|
"Aggregation", "PNorm", "SoftMinMax", "KSFunction",
|
pymoto/common/domain.py
CHANGED
@@ -91,6 +91,7 @@ class DomainDefinition:
|
|
91
91
|
if self.nelz is None:
|
92
92
|
self.nelz = 0
|
93
93
|
self.unitx, self.unity, self.unitz = unitx, unity, unitz
|
94
|
+
self.origin = np.array([0.0, 0.0, 0.0])
|
94
95
|
|
95
96
|
self.dim = 1 if (self.nelz == 0 and self.nely == 0) else (2 if self.nelz == 0 else 3)
|
96
97
|
|
@@ -126,9 +127,10 @@ class DomainDefinition:
|
|
126
127
|
self.conn[el, :] = self.get_elemconnectivity(elx, ely, elz)
|
127
128
|
|
128
129
|
# Helper for element slicing
|
129
|
-
eli, elj, elk = np.meshgrid(np.arange(self.nelx), np.arange(self.nely), np.arange(self.nelz), indexing='ij')
|
130
|
+
eli, elj, elk = np.meshgrid(np.arange(self.nelx), np.arange(self.nely), np.arange(max(self.nelz, 1)), indexing='ij')
|
130
131
|
self.elements = self.get_elemnumber(eli, elj, elk)
|
131
132
|
|
133
|
+
# Helper for node slicing
|
132
134
|
ndi, ndj, ndk = np.meshgrid(np.arange(self.nelx+1), np.arange(self.nely+1), np.arange(self.nelz+1), indexing='ij')
|
133
135
|
self.nodes = self.get_nodenumber(ndi, ndj, ndk)
|
134
136
|
|
pymoto/common/dyadcarrier.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Union, Iterable, List
|
1
|
+
from typing import Union, Iterable, List, Tuple
|
2
2
|
import warnings
|
3
3
|
import numpy as np
|
4
4
|
from numpy.typing import NDArray
|
@@ -37,19 +37,20 @@ class DyadCarrier(object):
|
|
37
37
|
:math:`\mathbf{A} = \sum_k^N \mathbf{u}_k\otimes\mathbf{v}_k`
|
38
38
|
or in index notation :math:`A_{ij} = \sum_k^N u_{ki} v_{kj}`. This saves a lot of memory for low :math:`N`.
|
39
39
|
|
40
|
-
Args:
|
41
|
-
u
|
42
|
-
v
|
40
|
+
Keyword Args:
|
41
|
+
u: List of vectors
|
42
|
+
v: List of vectors (if ``u`` is given and ``v`` not, a symmetric dyad is assumed with ``v = u``)
|
43
|
+
shape: Shape of the matrix
|
43
44
|
"""
|
44
45
|
|
45
46
|
__array_priority__ = 11.0 # For overriding numpy's ufuncs
|
46
47
|
ndim = 2 # Number of dimensions
|
47
48
|
|
48
|
-
def __init__(self, u: Iterable = None, v: Iterable = None):
|
49
|
+
def __init__(self, u: Iterable = None, v: Iterable = None, shape: Tuple[int, int] = (-1, -1)):
|
49
50
|
self.u = []
|
50
51
|
self.v = []
|
51
|
-
self.ulen =
|
52
|
-
self.vlen =
|
52
|
+
self.ulen = shape[0]
|
53
|
+
self.vlen = shape[1]
|
53
54
|
self.dtype = np.dtype('float64') # Standard data type
|
54
55
|
self.add_dyad(u, v)
|
55
56
|
|
@@ -66,6 +67,12 @@ class DyadCarrier(object):
|
|
66
67
|
else:
|
67
68
|
return self.ulen * self.vlen
|
68
69
|
|
70
|
+
@property
|
71
|
+
def n_dyads(self):
|
72
|
+
""" Number of dyads stored """
|
73
|
+
assert len(self.u) == len(self.v)
|
74
|
+
return len(self.u)
|
75
|
+
|
69
76
|
def add_dyad(self, u: Iterable, v: Iterable = None, fac: float = None):
|
70
77
|
r""" Adds a list of vectors to the dyad carrier
|
71
78
|
|
@@ -92,9 +99,7 @@ class DyadCarrier(object):
|
|
92
99
|
if len(ulist) != len(vlist):
|
93
100
|
raise TypeError("Number of vectors in u ({}) and v({}) should be equal".format(len(ulist), len(vlist)))
|
94
101
|
|
95
|
-
|
96
|
-
|
97
|
-
for i, ui, vi in zip(range(n), ulist, vlist):
|
102
|
+
for i, (ui, vi) in enumerate(zip(ulist, vlist)):
|
98
103
|
# Make sure they are numpy arrays
|
99
104
|
if not isinstance(ui, np.ndarray):
|
100
105
|
ui = np.array(ui)
|
@@ -141,14 +146,22 @@ class DyadCarrier(object):
|
|
141
146
|
|
142
147
|
def __getitem__(self, subscript):
|
143
148
|
assert len(subscript) == self.ndim, "Invalid number of slices, must be 2"
|
144
|
-
|
145
|
-
|
149
|
+
if self.shape[0] < 0 and self.shape[1] < 0:
|
150
|
+
return DyadCarrier()
|
151
|
+
|
152
|
+
usample = np.zeros(self.shape[0])[subscript[0]]
|
153
|
+
vsample = np.zeros(self.shape[1])[subscript[1]]
|
146
154
|
|
147
|
-
is_uni_slice = isscalarlike(
|
155
|
+
is_uni_slice = isscalarlike(usample) or isscalarlike(vsample)
|
148
156
|
is_np_slice = isinstance(subscript[0], np.ndarray) and isinstance(subscript[1], np.ndarray)
|
157
|
+
|
149
158
|
if is_np_slice and subscript[0].shape != subscript[1].shape:
|
150
159
|
raise IndexError(f"shape mismatch: indexing arrays could not be broadcast together "
|
151
160
|
f"with shapes {subscript[0].shape} {subscript[1].shape}")
|
161
|
+
|
162
|
+
usub = [ui[subscript[0]] for ui in self.u]
|
163
|
+
vsub = [vi[subscript[1]] for vi in self.v]
|
164
|
+
|
152
165
|
if is_uni_slice or is_np_slice:
|
153
166
|
res = 0
|
154
167
|
for (ui, vi) in zip(usub, vsub):
|
@@ -156,7 +169,7 @@ class DyadCarrier(object):
|
|
156
169
|
|
157
170
|
return res
|
158
171
|
else:
|
159
|
-
return DyadCarrier(usub, vsub)
|
172
|
+
return DyadCarrier(usub, vsub, shape=(np.size(usample), np.size(vsample)))
|
160
173
|
|
161
174
|
def __setitem__(self, subscript, value):
|
162
175
|
assert len(subscript) == self.ndim, "Invalid number of slices, must be 2"
|
@@ -171,10 +184,10 @@ class DyadCarrier(object):
|
|
171
184
|
vi[subscript[1]] = value
|
172
185
|
|
173
186
|
def __pos__(self):
|
174
|
-
return
|
187
|
+
return self.copy()
|
175
188
|
|
176
189
|
def __neg__(self):
|
177
|
-
return DyadCarrier([-uu for uu in self.u], self.v)
|
190
|
+
return DyadCarrier([-uu for uu in self.u], self.v, shape=self.shape)
|
178
191
|
|
179
192
|
def __iadd__(self, other):
|
180
193
|
self.add_dyad(other.u, other.v)
|
@@ -189,7 +202,7 @@ class DyadCarrier(object):
|
|
189
202
|
elif isdyad(other):
|
190
203
|
if other.shape != self.shape and (self.size > 0 and other.size > 0):
|
191
204
|
raise ValueError(f"Inconsistent shapes {self.shape} and {other.shape}")
|
192
|
-
return
|
205
|
+
return self.copy().__iadd__(other)
|
193
206
|
elif isdense(other):
|
194
207
|
other = np.broadcast_to(other, self.shape)
|
195
208
|
return other + self.todense()
|
@@ -219,28 +232,44 @@ class DyadCarrier(object):
|
|
219
232
|
return NotImplemented
|
220
233
|
|
221
234
|
def __rmul__(self, other): # other * self
|
222
|
-
return DyadCarrier([other*ui for ui in self.u], self.v)
|
235
|
+
return DyadCarrier([other*ui for ui in self.u], self.v, shape=self.shape)
|
223
236
|
|
224
237
|
def __mul__(self, other): # self * other
|
225
|
-
return DyadCarrier(self.u, [vi*other for vi in self.v])
|
238
|
+
return DyadCarrier(self.u, [vi*other for vi in self.v], shape=self.shape)
|
226
239
|
|
227
240
|
def copy(self):
|
228
241
|
""" Returns a deep copy of the DyadCarrier """
|
229
|
-
return DyadCarrier(self.u, self.v)
|
242
|
+
return DyadCarrier(self.u, self.v, shape=self.shape)
|
230
243
|
|
231
244
|
def conj(self):
|
232
245
|
""" Returns (a deep copied) complex conjugate of the DyadCarrier """
|
233
|
-
return DyadCarrier([u.conj() for u in self.u], [v.conj() for v in self.v])
|
246
|
+
return DyadCarrier([u.conj() for u in self.u], [v.conj() for v in self.v], shape=self.shape)
|
234
247
|
|
235
248
|
@property
|
236
249
|
def real(self):
|
237
250
|
""" Returns a deep copy of the real part of the DyadCarrier """
|
238
|
-
return DyadCarrier([*[u.real for u in self.u], *[-u.imag for u in self.u]], [*[v.real for v in self.v], *[v.imag for v in self.v]])
|
251
|
+
return DyadCarrier([*[u.real for u in self.u], *[-u.imag for u in self.u]], [*[v.real for v in self.v], *[v.imag for v in self.v]], shape=self.shape)
|
239
252
|
|
240
253
|
@property
|
241
254
|
def imag(self):
|
242
255
|
""" Returns a deep copy of the imaginary part of the DyadCarrier """
|
243
|
-
return DyadCarrier([*[u.real for u in self.u], *[u.imag for u in self.u]], [*[v.imag for v in self.v], *[v.real for v in self.v]])
|
256
|
+
return DyadCarrier([*[u.real for u in self.u], *[u.imag for u in self.u]], [*[v.imag for v in self.v], *[v.real for v in self.v]], shape=self.shape)
|
257
|
+
|
258
|
+
def min(self):
|
259
|
+
minval = 0.0
|
260
|
+
for u, v in zip(self.u, self.v):
|
261
|
+
minval += u.min() * v.min()
|
262
|
+
if len(self.u) >= 2:
|
263
|
+
warnings.warn("The minimum is an approximation")
|
264
|
+
return minval
|
265
|
+
|
266
|
+
def max(self):
|
267
|
+
maxval = 0.0
|
268
|
+
for u, v in zip(self.u, self.v):
|
269
|
+
maxval += u.max() * v.max()
|
270
|
+
if len(self.u) >= 2:
|
271
|
+
warnings.warn("The maximum is an approximation")
|
272
|
+
return maxval
|
244
273
|
|
245
274
|
# flake8: noqa: C901
|
246
275
|
def contract(self, mat: Union[NDArray, spmatrix] = None, rows: NDArray[int] = None, cols: NDArray[int] = None):
|
@@ -364,7 +393,7 @@ class DyadCarrier(object):
|
|
364
393
|
exprvars = (rowvar, colvar) if mat is None else (rowvar, matvar, colvar)
|
365
394
|
expr = ','.join(exprvars) + '->' + batchvar
|
366
395
|
|
367
|
-
val = 0.0 if batchsize is None else np.zeros(batchsize)
|
396
|
+
val = 0.0 if batchsize is None else np.zeros(batchsize, dtype=np.result_type(mat, self.dtype))
|
368
397
|
for ui, vi in zip(self.u, self.v):
|
369
398
|
uarg = ui if rows is None else ui[rows]
|
370
399
|
varg = vi if cols is None else vi[cols]
|
@@ -446,7 +475,7 @@ class DyadCarrier(object):
|
|
446
475
|
|
447
476
|
def transpose(self):
|
448
477
|
""" Returns a deep copy of the transposed DyadCarrier matrix"""
|
449
|
-
return DyadCarrier(self.v, self.u)
|
478
|
+
return DyadCarrier(self.v, self.u, shape=(self.shape[1], self.shape[0]))
|
450
479
|
|
451
480
|
def dot(self, other):
|
452
481
|
""" Inner product """
|
@@ -474,9 +503,9 @@ class DyadCarrier(object):
|
|
474
503
|
if other.ndim == 1:
|
475
504
|
return self.__dot__(other)
|
476
505
|
|
477
|
-
return DyadCarrier(self.u, [vi@other for vi in self.v])
|
506
|
+
return DyadCarrier(self.u, [vi@other for vi in self.v], shape=(self.shape[0], other.shape[1]))
|
478
507
|
|
479
508
|
def __rmatmul__(self, other): # other @ self
|
480
509
|
if other.ndim == 1:
|
481
510
|
return self.__rdot__(other)
|
482
|
-
return DyadCarrier([other@ui for ui in self.u], self.v)
|
511
|
+
return DyadCarrier([other@ui for ui in self.u], self.v, shape=(other.shape[0], self.shape[1]))
|
pymoto/common/mma.py
CHANGED
@@ -28,20 +28,50 @@ def residual(x, y, z, lam, xsi, eta, mu, zet, s, upp, low, P0, P1, Q0, Q1, epsi,
|
|
28
28
|
])
|
29
29
|
|
30
30
|
|
31
|
-
def subsolv(epsimin, low, upp, alfa, beta, P, Q, a0, a, b, c, d):
|
32
|
-
""" This function
|
33
|
-
|
34
|
-
|
35
|
-
subject to
|
36
|
-
|
37
|
-
|
38
|
-
|
31
|
+
def subsolv(epsimin, low, upp, alfa, beta, P, Q, a0, a, b, c, d, x0=None):
|
32
|
+
r""" This function solves the MMA subproblem
|
33
|
+
|
34
|
+
minimize f_0(\vec{x}) + a_0*z + \sum_i^m[ c_i*y_i + 1/2*d_i*y_i^2 ],
|
35
|
+
subject to f_i(\vec{x}) - a_i*z - y_i <= b_i, for i = 1, ..., m
|
36
|
+
alfa_j <= x_j <= beta_j, for j = 1, ..., n
|
37
|
+
y_i >= 0, for i = 1, ..., m
|
38
|
+
z >= 0.
|
39
|
+
|
40
|
+
where:
|
41
|
+
MMA approximation: :math:`f_i(\vec{x}) = \sum_j\left( p_{ij}/(upp_j-x_j) + q_{ij}/(x_j-low_j) \right)`
|
42
|
+
m: The number of general constraints
|
43
|
+
n: The number of variables in :math:`\vec{x}`
|
44
|
+
|
45
|
+
Args:
|
46
|
+
epsimin: Solution tolerance on maximum residual
|
47
|
+
low: Column vector with the lower asymptotes
|
48
|
+
upp: Column vector with the upper asymptotes
|
49
|
+
alfa: Vector with the lower bounds for the variables :math:`\vec{x}`
|
50
|
+
beta: Vector with the upper bounds for the variables :math:`\vec{x}`
|
51
|
+
P: Upper asymptotic amplitudes
|
52
|
+
Q: Lower asymptotic amplitudes
|
53
|
+
a0: The constants :math:`a_0` in the term :math:`a_0\cdot z`
|
54
|
+
a: Vector with the constants :math:`a_i` in the terms :math:`a_i \cdot z`
|
55
|
+
c: Vector with the constants :math:`c_i` in the terms :math:`c_i \cdot y_i`
|
56
|
+
d: Vector with the constants :math:`d_i` in the terms :math:`0.5 \cdot d_i \cdot y_i^2`
|
57
|
+
x0 (optional): Initial guess, in case not given :math:`x_0 = (\alpha + \beta)/2` is used
|
58
|
+
|
59
|
+
Returns:
|
60
|
+
x: Vector with the optimal values of the variables :math:`\vec{x}` in the current MMA subproblem
|
61
|
+
y: Vector with the optimal values of the variables :math:`y_i` in the current MMA subproblem
|
62
|
+
z: Scalar with the optimal value of the variable :math:`z` in the current MMA subproblem
|
63
|
+
lam: Lagrange multipliers for the :math:`m` general MMA constraints
|
64
|
+
xsi: Lagrange multipliers for the :math:'n' constraints :math:`alfa_j - x_j <= 0`
|
65
|
+
eta: Lagrange multipliers for the :math:'n' constraints :math:`x_j - beta_j <= 0`
|
66
|
+
mu: Lagrange multipliers for the :math:`m` constraints :math:`-y_i <= 0`
|
67
|
+
zet: Lagrange multiplier for the single constraint :math:`-z <= 0`
|
68
|
+
s: Slack variables for the m general MMA constraints
|
39
69
|
"""
|
40
70
|
|
41
71
|
n, m = len(alfa), len(a)
|
42
72
|
epsi = 1.0
|
43
73
|
maxittt = 400
|
44
|
-
x = 0.5 * (alfa + beta)
|
74
|
+
x = 0.5 * (alfa + beta) if x0 is None else np.clip(x0, alfa+1e-10, beta-1e-10) # Design variables
|
45
75
|
y = np.ones(m)
|
46
76
|
z = 1.0
|
47
77
|
lam = np.ones(m)
|
@@ -168,8 +198,7 @@ def subsolv(epsimin, low, upp, alfa, beta, P, Q, a0, a, b, c, d):
|
|
168
198
|
sold = s.copy()
|
169
199
|
|
170
200
|
# Do linesearch
|
171
|
-
itto
|
172
|
-
while itto < maxittt:
|
201
|
+
for itto in range(maxittt):
|
173
202
|
# Find new set of variables with stepsize
|
174
203
|
x[:] = xold + steg * dx
|
175
204
|
y[:] = yold + steg * dy
|
@@ -184,14 +213,13 @@ def subsolv(epsimin, low, upp, alfa, beta, P, Q, a0, a, b, c, d):
|
|
184
213
|
residu = residual(x, y, z, lam, xsi, eta, mu, zet, s, upp, low, P0, P1, Q0, Q1, epsi, a0, a, b, c, d, alfa, beta)
|
185
214
|
if np.linalg.norm(residu) < residunorm:
|
186
215
|
break
|
187
|
-
itto += 1
|
188
216
|
steg /= 2 # Reduce stepsize
|
189
217
|
|
190
218
|
residunorm = np.linalg.norm(residu)
|
191
219
|
residumax = np.max(np.abs(residu))
|
192
220
|
|
193
221
|
if ittt > maxittt - 2:
|
194
|
-
print(f"MMA Subsolver: itt = {ittt}, at epsi = {epsi}")
|
222
|
+
print(f"MMA Subsolver: itt = {ittt}, at epsi = {'%.3e'%epsi}")
|
195
223
|
# decrease epsilon with factor 10
|
196
224
|
epsi /= 10
|
197
225
|
|
@@ -200,8 +228,7 @@ def subsolv(epsimin, low, upp, alfa, beta, P, Q, a0, a, b, c, d):
|
|
200
228
|
|
201
229
|
|
202
230
|
class MMA:
|
203
|
-
"""
|
204
|
-
Block for the MMA algorithm
|
231
|
+
r""" Class for the MMA optimization algorithm
|
205
232
|
The design variables are set by keyword <variables> accepting a list of variables.
|
206
233
|
The responses are set by keyword <responses> accepting a list of signals.
|
207
234
|
If none are given, the internal sig_in and sig_out are used.
|
@@ -251,19 +278,15 @@ class MMA:
|
|
251
278
|
|
252
279
|
self.a0 = kwargs.get("a0", 1.0)
|
253
280
|
|
254
|
-
self.epsimin = kwargs.get("epsimin", 1e-
|
255
|
-
self.raa0 = kwargs.get("raa0", 1e-5)
|
256
|
-
|
281
|
+
self.epsimin = kwargs.get("epsimin", 1e-10) # Or 1e-7 ?? witout sqrt(m+n) or 1e-9
|
257
282
|
self.cCoef = kwargs.get("cCoef", 1e3) # Svanberg uses 1e3 in example? Old code had 1e7
|
258
283
|
|
259
|
-
# Not used
|
260
|
-
self.dxmin = kwargs.get("dxmin", 1e-5)
|
261
|
-
|
262
284
|
self.albefa = kwargs.get("albefa", 0.1)
|
263
285
|
self.asyinit = kwargs.get("asyinit", 0.5)
|
264
286
|
self.asyincr = kwargs.get("asyincr", 1.2)
|
265
287
|
self.asydecr = kwargs.get("asydecr", 0.7)
|
266
288
|
self.asybound = kwargs.get("asybound", 10.0)
|
289
|
+
self.mmaversion = kwargs.get("mmaversion", "Svanberg2007") # Options are Svanberg1987, Svanberg2007
|
267
290
|
|
268
291
|
self.ittomax = kwargs.get("ittomax", 400)
|
269
292
|
|
@@ -282,12 +305,15 @@ class MMA:
|
|
282
305
|
|
283
306
|
# Setting up for constriants
|
284
307
|
self.m = len(self.responses) - 1
|
285
|
-
self.a = np.zeros(self.m)
|
286
|
-
|
308
|
+
self.a = kwargs.get("a", np.zeros(self.m))
|
309
|
+
if len(self.a) != self.m:
|
310
|
+
raise RuntimeError(f"Length of the a vector ({len(self.a)}) should be equal to # constraints ({self.m}).")
|
311
|
+
self.c = kwargs.get("c", np.full(self.m, self.cCoef, dtype=float))
|
312
|
+
if len(self.c) != self.m:
|
313
|
+
raise RuntimeError(f"Length of the c vector ({len(self.c)}) should be equal to # constraints ({self.m}).")
|
287
314
|
self.d = np.ones(self.m)
|
288
315
|
self.gold1 = np.zeros(self.m + 1)
|
289
316
|
self.gold2 = self.gold1.copy()
|
290
|
-
self.rho = self.raa0 * np.ones(self.m + 1)
|
291
317
|
|
292
318
|
def response(self):
|
293
319
|
change = 1
|
@@ -349,11 +375,15 @@ class MMA:
|
|
349
375
|
# Calculate response
|
350
376
|
self.funbl.response()
|
351
377
|
|
378
|
+
xval, _ = _concatenate_to_array([s.state for s in self.variables])
|
379
|
+
|
352
380
|
# Save response
|
353
381
|
f = ()
|
354
382
|
for s in self.responses:
|
355
|
-
if
|
383
|
+
if np.size(s.state) != 1:
|
356
384
|
raise TypeError("State of responses must be scalar.")
|
385
|
+
if np.iscomplexobj(s.state):
|
386
|
+
raise TypeError("Responses must be real-valued.")
|
357
387
|
f += (s.state, )
|
358
388
|
|
359
389
|
# Check function change convergence criterion
|
@@ -443,57 +473,6 @@ class MMA:
|
|
443
473
|
if self.offset is None:
|
444
474
|
self.offset = self.asyinit * np.ones(self.n)
|
445
475
|
|
446
|
-
# Minimize f_0(x) + a_0*z + sum( c_i*y_i + 0.5*d_i*(y_i)^2 )
|
447
|
-
# subject to f_i(x) - a_i*z - y_i <= 0, i = 1,...,m
|
448
|
-
# xmin_j <= x_j <= xmax_j, j = 1,...,n
|
449
|
-
# z >= 0, y_i >= 0, i = 1,...,m
|
450
|
-
# *** INPUT:
|
451
|
-
#
|
452
|
-
# m = The number of general constraints.
|
453
|
-
# n = The number of variables x_j.
|
454
|
-
# iter = Current iteration number ( =1 the first time mmasub is called).
|
455
|
-
# xval = Column vector with the current values of the variables x_j.
|
456
|
-
# xmin = Column vector with the lower bounds for the variables x_j.
|
457
|
-
# xmax = Column vector with the upper bounds for the variables x_j.
|
458
|
-
# xold1 = xval, one iteration ago (provided that iter>1).
|
459
|
-
# xold2 = xval, two iterations ago (provided that iter>2).
|
460
|
-
# f0val = The value of the objective function f_0 at xval.
|
461
|
-
# df0dx = Column vector with the derivatives of the objective function
|
462
|
-
# f_0 with respect to the variables x_j, calculated at xval.
|
463
|
-
# fval = Column vector with the values of the constraint functions f_i,
|
464
|
-
# calculated at xval.
|
465
|
-
# dfdx = (m x n)-matrix with the derivatives of the constraint functions
|
466
|
-
# f_i with respect to the variables x_j, calculated at xval.
|
467
|
-
# dfdx(i,j) = the derivative of f_i with respect to x_j.
|
468
|
-
# low = Column vector with the lower asymptotes from the previous
|
469
|
-
# iteration (provided that iter>1).
|
470
|
-
# upp = Column vector with the upper asymptotes from the previous
|
471
|
-
# iteration (provided that iter>1).
|
472
|
-
# a0 = The constants a_0 in the term a_0*z.
|
473
|
-
# a = Column vector with the constants a_i in the terms a_i*z.
|
474
|
-
# c = Column vector with the constants c_i in the terms c_i*y_i.
|
475
|
-
# d = Column vector with the constants d_i in the terms 0.5*d_i*(y_i)^2.
|
476
|
-
#
|
477
|
-
|
478
|
-
# *** OUTPUT:
|
479
|
-
#
|
480
|
-
# xmma = Column vector with the optimal values of the variables x_j
|
481
|
-
# in the current MMA subproblem.
|
482
|
-
# ymma = Column vector with the optimal values of the variables y_i
|
483
|
-
# in the current MMA subproblem.
|
484
|
-
# zmma = Scalar with the optimal value of the variable z
|
485
|
-
# in the current MMA subproblem.
|
486
|
-
# lam = Lagrange multipliers for the m general MMA constraints.
|
487
|
-
# xsi = Lagrange multipliers for the n constraints alfa_j - x_j <= 0.
|
488
|
-
# eta = Lagrange multipliers for the n constraints x_j - beta_j <= 0.
|
489
|
-
# mu = Lagrange multipliers for the m constraints -y_i <= 0.
|
490
|
-
# zet = Lagrange multiplier for the single constraint -z <= 0.
|
491
|
-
# s = Slack variables for the m general MMA constraints.
|
492
|
-
# low = Column vector with the lower asymptotes, calculated and used
|
493
|
-
# in the current MMA subproblem.
|
494
|
-
# upp = Column vector with the upper asymptotes, calculated and used
|
495
|
-
# in the current MMA subproblem.
|
496
|
-
|
497
476
|
# # ASYMPTOTES
|
498
477
|
# Calculation of the asymptotes low and upp :
|
499
478
|
# For iter = 1,2 the asymptotes are fixed depending on asyinit
|
@@ -536,16 +515,27 @@ class MMA:
|
|
536
515
|
# # APPROXIMATE CONVEX SEPARABLE FUNCTIONS
|
537
516
|
# Calculations of p0, q0, P, Q and b.
|
538
517
|
# calculate the constant factor in calculations of pij and qij
|
518
|
+
# From: Svanberg(2007) - MMA and GCMMA, two methods for nonlinear optimization
|
519
|
+
dg_plus = np.maximum(+dg, 0)
|
520
|
+
dg_min = np.maximum(-dg, 0)
|
539
521
|
dx2 = shift**2
|
540
|
-
|
541
|
-
|
522
|
+
if '1987' in self.mmaversion:
|
523
|
+
# Original version
|
524
|
+
P = dx2 * dg_plus
|
525
|
+
Q = dx2 * dg_min
|
526
|
+
elif '2007' in self.mmaversion:
|
527
|
+
# Improved version -> Allows to use higher epsimin to get design variables closer to the bound.
|
528
|
+
P = dx2 * (1.001*dg_plus + 0.001*dg_min + 1e-5/self.dx)
|
529
|
+
Q = dx2 * (0.001*dg_plus + 1.001*dg_min + 1e-5/self.dx)
|
530
|
+
else:
|
531
|
+
raise ValueError("Only \"Svanberg1987\" or \"Svanberg2007\" are valid options")
|
542
532
|
|
543
533
|
rhs = np.dot(P, 1 / shift) + np.dot(Q, 1 / shift) - g
|
544
534
|
b = rhs[1:]
|
545
535
|
|
546
536
|
# Solving the subproblem by a primal-dual Newton method
|
547
537
|
epsimin_scaled = self.epsimin*np.sqrt(self.m + self.n)
|
548
|
-
xmma, ymma, zmma, lam, xsi, eta, mu, zet, s = subsolv(epsimin_scaled, self.low, self.upp, alfa, beta, P, Q, self.a0, self.a, b, self.c, self.d)
|
538
|
+
xmma, ymma, zmma, lam, xsi, eta, mu, zet, s = subsolv(epsimin_scaled, self.low, self.upp, alfa, beta, P, Q, self.a0, self.a, b, self.c, self.d, x0=xval)
|
549
539
|
|
550
540
|
self.gold2, self.gold1 = self.gold1, g.copy()
|
551
541
|
self.xold2, self.xold1 = self.xold1, xval.copy()
|
@@ -582,3 +572,4 @@ class MMA:
|
|
582
572
|
print(f" | Changes: {', '.join(change_msgs)}")
|
583
573
|
|
584
574
|
return xmma, change
|
575
|
+
|