pyMOTO 1.3.0__tar.gz → 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyMOTO-1.3.0 → pymoto-1.4.0}/PKG-INFO +7 -8
- {pyMOTO-1.3.0 → pymoto-1.4.0}/README.md +6 -7
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pyMOTO.egg-info/PKG-INFO +7 -8
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pyMOTO.egg-info/SOURCES.txt +12 -3
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/__init__.py +17 -11
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/common/domain.py +60 -5
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/common/dyadcarrier.py +33 -4
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/common/mma.py +83 -53
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/core_objects.py +117 -113
- pymoto-1.4.0/pymoto/modules/aggregation.py +209 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/assembly.py +136 -10
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/complex.py +3 -3
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/filter.py +171 -24
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/generic.py +12 -1
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/io.py +22 -11
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/linalg.py +21 -110
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/scaling.py +4 -4
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/routines.py +23 -9
- pymoto-1.4.0/pymoto/solvers/__init__.py +14 -0
- pymoto-1.4.0/pymoto/solvers/auto_determine.py +108 -0
- pyMOTO-1.3.0/pymoto/common/solvers_dense.py → pymoto-1.4.0/pymoto/solvers/dense.py +90 -70
- pymoto-1.4.0/pymoto/solvers/iterative.py +361 -0
- pymoto-1.4.0/pymoto/solvers/matrix_checks.py +56 -0
- pymoto-1.4.0/pymoto/solvers/solvers.py +253 -0
- pyMOTO-1.3.0/pymoto/common/solvers_sparse.py → pymoto-1.4.0/pymoto/solvers/sparse.py +41 -29
- pymoto-1.4.0/tests/test_aggregration.py +205 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_assembly.py +14 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_complex.py +19 -8
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_core.py +18 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_domain.py +26 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_dyadcarrier.py +25 -0
- pymoto-1.4.0/tests/test_element_operations.py +185 -0
- pymoto-1.4.0/tests/test_filter.py +231 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_module_mathgeneral.py +48 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_solvers_dense.py +128 -75
- pymoto-1.4.0/tests/test_solvers_iterative.py +96 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_solvers_sparse.py +76 -36
- pyMOTO-1.3.0/pymoto/common/solvers.py +0 -236
- {pyMOTO-1.3.0 → pymoto-1.4.0}/LICENSE +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pyMOTO.egg-info/dependency_links.txt +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pyMOTO.egg-info/requires.txt +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pyMOTO.egg-info/top_level.txt +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pyMOTO.egg-info/zip-safe +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/autodiff.py +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/utils.py +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/pyproject.toml +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/setup.cfg +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_automod.py +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_elmatrices.py +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_linsolve_sparse.py +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_module_concatsignal.py +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_module_eigensolve.py +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_module_einsum.py +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_scaling.py +0 -0
- {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_static_condenstation.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pyMOTO
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.4.0
|
4
4
|
Summary: A modular approach for topology optimization
|
5
5
|
Home-page: https://github.com/aatmdelissen/pyMOTO
|
6
6
|
Author: Arnoud Delissen
|
@@ -25,7 +25,7 @@ Requires-Dist: scikit-sparse; extra == "dev"
|
|
25
25
|
Requires-Dist: pypardiso; extra == "dev"
|
26
26
|
Requires-Dist: jax[cpu]; extra == "dev"
|
27
27
|
|
28
|
-
[](https://doi.org/10.5281/zenodo.8138859)
|
29
29
|
[](https://anaconda.org/aatmdelissen/pymoto)
|
30
30
|
[](https://pypi.org/project/pyMOTO/)
|
31
31
|
|
@@ -61,15 +61,14 @@ automatically calculated.
|
|
61
61
|
4. Run the example by typing `python ex_....py` in the console
|
62
62
|
|
63
63
|
|
64
|
-
|
64
|
+
For development, a local installation of `pyMOTO` can be done by first downloading/cloning the entire git repo, and then calling
|
65
65
|
`pip install -e .` in the `pyMOTO` folder (of course from within your virtual environment).
|
66
66
|
|
67
67
|
## Dependencies
|
68
|
-
* **
|
69
|
-
* **
|
70
|
-
* **
|
71
|
-
* **Matplotlib** - Plotting and visualisation
|
72
|
-
* (optional) **SAO** - Sequential approximated optimizers
|
68
|
+
* [**numpy**](https://numpy.org/doc/stable/) - Dense linear algebra and solvers
|
69
|
+
* [**scipy**](https://docs.scipy.org/doc/scipy/) - Sparse linear algebra and solvers
|
70
|
+
* [**sympy**](https://docs.sympy.org/latest/index.html) - Symbolic differentiation for `MathGeneral` module
|
71
|
+
* [**Matplotlib**](https://matplotlib.org/stable/) - Plotting and visualisation
|
73
72
|
* (optional) [**opt_einsum**](https://optimized-einsum.readthedocs.io/en/stable/install.html) - Optimized function for `EinSum` module
|
74
73
|
|
75
74
|
For fast linear solvers for sparse matrices:
|
@@ -1,4 +1,4 @@
|
|
1
|
-
[](https://doi.org/10.5281/zenodo.8138859)
|
2
2
|
[](https://anaconda.org/aatmdelissen/pymoto)
|
3
3
|
[](https://pypi.org/project/pyMOTO/)
|
4
4
|
|
@@ -34,15 +34,14 @@ automatically calculated.
|
|
34
34
|
4. Run the example by typing `python ex_....py` in the console
|
35
35
|
|
36
36
|
|
37
|
-
|
37
|
+
For development, a local installation of `pyMOTO` can be done by first downloading/cloning the entire git repo, and then calling
|
38
38
|
`pip install -e .` in the `pyMOTO` folder (of course from within your virtual environment).
|
39
39
|
|
40
40
|
## Dependencies
|
41
|
-
* **
|
42
|
-
* **
|
43
|
-
* **
|
44
|
-
* **Matplotlib** - Plotting and visualisation
|
45
|
-
* (optional) **SAO** - Sequential approximated optimizers
|
41
|
+
* [**numpy**](https://numpy.org/doc/stable/) - Dense linear algebra and solvers
|
42
|
+
* [**scipy**](https://docs.scipy.org/doc/scipy/) - Sparse linear algebra and solvers
|
43
|
+
* [**sympy**](https://docs.sympy.org/latest/index.html) - Symbolic differentiation for `MathGeneral` module
|
44
|
+
* [**Matplotlib**](https://matplotlib.org/stable/) - Plotting and visualisation
|
46
45
|
* (optional) [**opt_einsum**](https://optimized-einsum.readthedocs.io/en/stable/install.html) - Optimized function for `EinSum` module
|
47
46
|
|
48
47
|
For fast linear solvers for sparse matrices:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pyMOTO
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.4.0
|
4
4
|
Summary: A modular approach for topology optimization
|
5
5
|
Home-page: https://github.com/aatmdelissen/pyMOTO
|
6
6
|
Author: Arnoud Delissen
|
@@ -25,7 +25,7 @@ Requires-Dist: scikit-sparse; extra == "dev"
|
|
25
25
|
Requires-Dist: pypardiso; extra == "dev"
|
26
26
|
Requires-Dist: jax[cpu]; extra == "dev"
|
27
27
|
|
28
|
-
[](https://doi.org/10.5281/zenodo.8138859)
|
29
29
|
[](https://anaconda.org/aatmdelissen/pymoto)
|
30
30
|
[](https://pypi.org/project/pyMOTO/)
|
31
31
|
|
@@ -61,15 +61,14 @@ automatically calculated.
|
|
61
61
|
4. Run the example by typing `python ex_....py` in the console
|
62
62
|
|
63
63
|
|
64
|
-
|
64
|
+
For development, a local installation of `pyMOTO` can be done by first downloading/cloning the entire git repo, and then calling
|
65
65
|
`pip install -e .` in the `pyMOTO` folder (of course from within your virtual environment).
|
66
66
|
|
67
67
|
## Dependencies
|
68
|
-
* **
|
69
|
-
* **
|
70
|
-
* **
|
71
|
-
* **Matplotlib** - Plotting and visualisation
|
72
|
-
* (optional) **SAO** - Sequential approximated optimizers
|
68
|
+
* [**numpy**](https://numpy.org/doc/stable/) - Dense linear algebra and solvers
|
69
|
+
* [**scipy**](https://docs.scipy.org/doc/scipy/) - Sparse linear algebra and solvers
|
70
|
+
* [**sympy**](https://docs.sympy.org/latest/index.html) - Symbolic differentiation for `MathGeneral` module
|
71
|
+
* [**Matplotlib**](https://matplotlib.org/stable/) - Plotting and visualisation
|
73
72
|
* (optional) [**opt_einsum**](https://optimized-einsum.readthedocs.io/en/stable/install.html) - Optimized function for `EinSum` module
|
74
73
|
|
75
74
|
For fast linear solvers for sparse matrices:
|
@@ -15,9 +15,7 @@ pymoto/utils.py
|
|
15
15
|
pymoto/common/domain.py
|
16
16
|
pymoto/common/dyadcarrier.py
|
17
17
|
pymoto/common/mma.py
|
18
|
-
pymoto/
|
19
|
-
pymoto/common/solvers_dense.py
|
20
|
-
pymoto/common/solvers_sparse.py
|
18
|
+
pymoto/modules/aggregation.py
|
21
19
|
pymoto/modules/assembly.py
|
22
20
|
pymoto/modules/autodiff.py
|
23
21
|
pymoto/modules/complex.py
|
@@ -26,13 +24,23 @@ pymoto/modules/generic.py
|
|
26
24
|
pymoto/modules/io.py
|
27
25
|
pymoto/modules/linalg.py
|
28
26
|
pymoto/modules/scaling.py
|
27
|
+
pymoto/solvers/__init__.py
|
28
|
+
pymoto/solvers/auto_determine.py
|
29
|
+
pymoto/solvers/dense.py
|
30
|
+
pymoto/solvers/iterative.py
|
31
|
+
pymoto/solvers/matrix_checks.py
|
32
|
+
pymoto/solvers/solvers.py
|
33
|
+
pymoto/solvers/sparse.py
|
34
|
+
tests/test_aggregration.py
|
29
35
|
tests/test_assembly.py
|
30
36
|
tests/test_automod.py
|
31
37
|
tests/test_complex.py
|
32
38
|
tests/test_core.py
|
33
39
|
tests/test_domain.py
|
34
40
|
tests/test_dyadcarrier.py
|
41
|
+
tests/test_element_operations.py
|
35
42
|
tests/test_elmatrices.py
|
43
|
+
tests/test_filter.py
|
36
44
|
tests/test_linsolve_sparse.py
|
37
45
|
tests/test_module_concatsignal.py
|
38
46
|
tests/test_module_eigensolve.py
|
@@ -40,5 +48,6 @@ tests/test_module_einsum.py
|
|
40
48
|
tests/test_module_mathgeneral.py
|
41
49
|
tests/test_scaling.py
|
42
50
|
tests/test_solvers_dense.py
|
51
|
+
tests/test_solvers_iterative.py
|
43
52
|
tests/test_solvers_sparse.py
|
44
53
|
tests/test_static_condenstation.py
|
@@ -1,25 +1,27 @@
|
|
1
|
-
__version__ = '1.
|
1
|
+
__version__ = '1.4.0'
|
2
2
|
|
3
3
|
from .common.domain import DomainDefinition
|
4
4
|
|
5
5
|
# Imports from common
|
6
6
|
from .common.dyadcarrier import DyadCarrier
|
7
7
|
from .common.mma import MMA
|
8
|
-
|
9
|
-
|
10
|
-
from .
|
8
|
+
|
9
|
+
# Import solvers
|
10
|
+
from . import solvers
|
11
11
|
|
12
12
|
# Modular inports
|
13
13
|
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
18
|
from .modules.autodiff import AutoMod
|
18
19
|
from .modules.complex import MakeComplex, RealPart, ImagPart, ComplexNorm
|
19
20
|
from .modules.filter import FilterConv, Filter, DensityFilter, OverhangFilter
|
20
21
|
from .modules.generic import MathGeneral, EinSum, ConcatSignal
|
21
|
-
from .modules.io import PlotDomain, PlotGraph, PlotIter, WriteToVTI
|
22
|
+
from .modules.io import FigModule, PlotDomain, PlotGraph, PlotIter, WriteToVTI
|
22
23
|
from .modules.linalg import Inverse, LinSolve, EigenSolve, SystemOfEquations, StaticCondensation
|
24
|
+
from .modules.aggregation import AggScaling, AggActiveSet, Aggregation, PNorm, SoftMinMax, KSFunction
|
23
25
|
from .modules.scaling import Scaling
|
24
26
|
|
25
27
|
# Further helper routines
|
@@ -28,21 +30,25 @@ from .routines import finite_difference, minimize_oc, minimize_mma
|
|
28
30
|
__all__ = [
|
29
31
|
'Signal', 'Module', 'Network', 'make_signals',
|
30
32
|
'finite_difference', 'minimize_oc', 'minimize_mma',
|
33
|
+
|
31
34
|
# Common
|
32
35
|
'MMA',
|
33
36
|
'DyadCarrier',
|
34
37
|
'DomainDefinition',
|
35
|
-
'
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
'solvers',
|
39
|
+
|
40
|
+
# Helpers
|
41
|
+
"AggScaling", "AggActiveSet",
|
42
|
+
|
39
43
|
# Modules
|
40
44
|
"MathGeneral", "EinSum", "ConcatSignal",
|
41
45
|
"Inverse", "LinSolve", "EigenSolve", "SystemOfEquations", "StaticCondensation",
|
42
46
|
"AssembleGeneral", "AssembleStiffness", "AssembleMass", "AssemblePoisson",
|
47
|
+
"ElementOperation", "Strain", "Stress",
|
43
48
|
"FilterConv", "Filter", "DensityFilter", "OverhangFilter",
|
44
|
-
"PlotDomain", "PlotGraph", "PlotIter", "WriteToVTI",
|
49
|
+
"FigModule", "PlotDomain", "PlotGraph", "PlotIter", "WriteToVTI",
|
45
50
|
"MakeComplex", "RealPart", "ImagPart", "ComplexNorm",
|
46
51
|
"AutoMod",
|
47
|
-
"
|
52
|
+
"Aggregation", "PNorm", "SoftMinMax", "KSFunction",
|
53
|
+
"Scaling",
|
48
54
|
]
|
@@ -5,6 +5,31 @@ import struct
|
|
5
5
|
import warnings
|
6
6
|
from typing import Union
|
7
7
|
import numpy as np
|
8
|
+
from matplotlib.patches import PathPatch
|
9
|
+
from matplotlib.path import Path
|
10
|
+
|
11
|
+
|
12
|
+
def plot_deformed_element(ax, x, y, **kwargs):
|
13
|
+
codes, verts = zip(*[
|
14
|
+
(Path.MOVETO, [x[0], y[0]]),
|
15
|
+
(Path.LINETO, [x[1], y[1]]),
|
16
|
+
(Path.LINETO, [x[3], y[3]]),
|
17
|
+
(Path.LINETO, [x[2], y[2]]),
|
18
|
+
(Path.CLOSEPOLY, [x[0], y[0]])])
|
19
|
+
path = Path(verts, codes)
|
20
|
+
patch = PathPatch(path, **kwargs)
|
21
|
+
ax.add_artist(patch)
|
22
|
+
return patch
|
23
|
+
|
24
|
+
|
25
|
+
def get_path(x, y):
|
26
|
+
codes, verts = zip(*[
|
27
|
+
(Path.MOVETO, [x[0], y[0]]),
|
28
|
+
(Path.LINETO, [x[1], y[1]]),
|
29
|
+
(Path.LINETO, [x[3], y[3]]),
|
30
|
+
(Path.LINETO, [x[2], y[2]]),
|
31
|
+
(Path.CLOSEPOLY, [x[0], y[0]])])
|
32
|
+
return Path(verts, codes)
|
8
33
|
|
9
34
|
|
10
35
|
class DomainDefinition:
|
@@ -100,6 +125,13 @@ class DomainDefinition:
|
|
100
125
|
self.conn = np.zeros((self.nel, self.elemnodes), dtype=int)
|
101
126
|
self.conn[el, :] = self.get_elemconnectivity(elx, ely, elz)
|
102
127
|
|
128
|
+
# 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
|
+
self.elements = self.get_elemnumber(eli, elj, elk)
|
131
|
+
|
132
|
+
ndi, ndj, ndk = np.meshgrid(np.arange(self.nelx+1), np.arange(self.nely+1), np.arange(self.nelz+1), indexing='ij')
|
133
|
+
self.nodes = self.get_nodenumber(ndi, ndj, ndk)
|
134
|
+
|
103
135
|
def get_elemnumber(self, eli: Union[int, np.ndarray], elj: Union[int, np.ndarray], elk: Union[int, np.ndarray] = 0):
|
104
136
|
""" Gets the element number(s) for element(s) with given Cartesian indices (i, j, k)
|
105
137
|
|
@@ -126,7 +158,7 @@ class DomainDefinition:
|
|
126
158
|
"""
|
127
159
|
return (nodk * (self.nely + 1) + nodj) * (self.nelx + 1) + nodi
|
128
160
|
|
129
|
-
def get_node_indices(self, nod_idx: Union[int, np.ndarray]):
|
161
|
+
def get_node_indices(self, nod_idx: Union[int, np.ndarray] = None):
|
130
162
|
""" Gets the Cartesian index (i, j, k) for given node number(s)
|
131
163
|
|
132
164
|
Args:
|
@@ -135,16 +167,18 @@ class DomainDefinition:
|
|
135
167
|
Returns:
|
136
168
|
i, j, k for requested node(s); k is only returned in 3D
|
137
169
|
"""
|
170
|
+
if nod_idx is None:
|
171
|
+
nod_idx = np.arange(self.nnodes)
|
138
172
|
nodi = nod_idx % (self.nelx + 1)
|
139
173
|
nodj = (nod_idx // (self.nelx + 1)) % (self.nely + 1)
|
140
174
|
if self.dim == 2:
|
141
|
-
return nodi, nodj
|
175
|
+
return np.stack([nodi, nodj], axis=0)
|
142
176
|
nodk = nod_idx // ((self.nelx + 1)*(self.nely + 1))
|
143
|
-
return nodi, nodj, nodk
|
177
|
+
return np.stack([nodi, nodj, nodk], axis=0)
|
144
178
|
|
145
|
-
def get_node_position(self, nod_idx: Union[int, np.ndarray]):
|
179
|
+
def get_node_position(self, nod_idx: Union[int, np.ndarray] = None):
|
146
180
|
ijk = self.get_node_indices(nod_idx)
|
147
|
-
return
|
181
|
+
return (self.element_size[:self.dim] * ijk.T).T
|
148
182
|
|
149
183
|
def get_elemconnectivity(self, i: Union[int, np.ndarray], j: Union[int, np.ndarray], k: Union[int, np.ndarray] = 0):
|
150
184
|
""" Get the connectivity for element identified with Cartesian indices (i, j, k)
|
@@ -230,6 +264,27 @@ class DomainDefinition:
|
|
230
264
|
dN_dx[i, :] *= np.array([n[i] for n in self.node_numbering]) # Flip +/- signs according to node position
|
231
265
|
return dN_dx
|
232
266
|
|
267
|
+
def plot(self, ax, deformation=None, scaling=None):
|
268
|
+
patches = []
|
269
|
+
for e in range(self.nel):
|
270
|
+
n = self.conn[e]
|
271
|
+
x, y = self.get_node_position(n)
|
272
|
+
u, v = deformation[n * 2], deformation[n * 2 + 1]
|
273
|
+
color = (1 - scaling[e], 1 - scaling[e], 1 - scaling[e]) if scaling is not None else 'grey'
|
274
|
+
patch = plot_deformed_element(ax, x + u, v + y, linewidth=0.1, color=color)
|
275
|
+
patches.append(patch)
|
276
|
+
return patches
|
277
|
+
|
278
|
+
def update_plot(self, patches, deformation=None, scaling=None):
|
279
|
+
for e in range(self.nel):
|
280
|
+
patch = patches[e]
|
281
|
+
n = self.conn[e]
|
282
|
+
x, y = self.get_node_position(n)
|
283
|
+
u, v = deformation[n * 2], deformation[n * 2 + 1]
|
284
|
+
color = (1 - scaling[e], 1 - scaling[e], 1 - scaling[e]) if scaling is not None else 'grey'
|
285
|
+
patch.set_color(color)
|
286
|
+
patch.set_path(self.get_path(x + u, y + v))
|
287
|
+
|
233
288
|
# flake8: noqa: C901
|
234
289
|
def write_to_vti(self, vectors: dict, filename="out.vti", scale=1.0, origin=(0.0, 0.0, 0.0)):
|
235
290
|
""" Write all given vectors to a Paraview (VTI) file
|
@@ -1,8 +1,8 @@
|
|
1
|
-
from typing import Union, Iterable
|
1
|
+
from typing import Union, Iterable, List
|
2
2
|
import warnings
|
3
3
|
import numpy as np
|
4
4
|
from numpy.typing import NDArray
|
5
|
-
from scipy.sparse import spmatrix
|
5
|
+
from scipy.sparse import spmatrix, coo_matrix
|
6
6
|
from ..utils import _parse_to_list
|
7
7
|
try: # Import fast optimized einsum
|
8
8
|
from opt_einsum import contract as einsum
|
@@ -31,7 +31,7 @@ def isnullslice(x):
|
|
31
31
|
|
32
32
|
|
33
33
|
class DyadCarrier(object):
|
34
|
-
""" Efficient storage for dyadic or rank-N matrix
|
34
|
+
r""" Efficient storage for dyadic or rank-N matrix
|
35
35
|
|
36
36
|
Stores only the vectors instead of creating a full rank-N matrix
|
37
37
|
:math:`\mathbf{A} = \sum_k^N \mathbf{u}_k\otimes\mathbf{v}_k`
|
@@ -67,7 +67,7 @@ class DyadCarrier(object):
|
|
67
67
|
return self.ulen * self.vlen
|
68
68
|
|
69
69
|
def add_dyad(self, u: Iterable, v: Iterable = None, fac: float = None):
|
70
|
-
""" Adds a list of vectors to the dyad carrier
|
70
|
+
r""" Adds a list of vectors to the dyad carrier
|
71
71
|
|
72
72
|
Checks for conforming sizes of `u` and `v`. The data inside the vectors are copied.
|
73
73
|
|
@@ -373,6 +373,31 @@ class DyadCarrier(object):
|
|
373
373
|
|
374
374
|
return val
|
375
375
|
|
376
|
+
def contract_multi(self, mats: List[spmatrix], dtype=None):
|
377
|
+
""" Faster version of contraction for a list of sparse matrices """
|
378
|
+
if dtype is None:
|
379
|
+
dtype = np.result_type(self.dtype, mats[0].dtype)
|
380
|
+
val = np.zeros(len(mats), dtype=dtype)
|
381
|
+
|
382
|
+
if len(self.u) == 0 or len(self.v) == 0:
|
383
|
+
return val
|
384
|
+
U = np.array(self.u).T
|
385
|
+
V = np.array(self.v).T
|
386
|
+
|
387
|
+
for i, m in enumerate(mats):
|
388
|
+
if m is None:
|
389
|
+
vali = 0.0
|
390
|
+
else:
|
391
|
+
try:
|
392
|
+
if not isinstance(m, coo_matrix):
|
393
|
+
warnings.warn("Inefficiency: Matrix must be converted to coo_matrix for contraction")
|
394
|
+
mat_coo = m.tocoo()
|
395
|
+
vali = np.einsum('ij,i,ij->', U[mat_coo.row, :], mat_coo.data, V[mat_coo.col, :])
|
396
|
+
except AttributeError:
|
397
|
+
vali = self.contract(m)
|
398
|
+
val[i] = vali
|
399
|
+
return val
|
400
|
+
|
376
401
|
def todense(self):
|
377
402
|
""" Returns a full (dense) matrix from the DyadCarrier matrix """
|
378
403
|
warning_size = 100e+6 # Bytes
|
@@ -387,6 +412,10 @@ class DyadCarrier(object):
|
|
387
412
|
|
388
413
|
return val
|
389
414
|
|
415
|
+
def toarray(self):
|
416
|
+
""" Convert to array, same as todense(). To be consistent with scipy.sparse """
|
417
|
+
return self.todense()
|
418
|
+
|
390
419
|
def iscomplex(self):
|
391
420
|
""" Check if the DyadCarrier is of complex type """
|
392
421
|
return np.iscomplexobj(np.array([], dtype=self.dtype))
|
@@ -219,11 +219,16 @@ class MMA:
|
|
219
219
|
xmin: Minimum design variable (can be a vector)
|
220
220
|
xmax: Maximum design variable (can be a vector)
|
221
221
|
fn_callback: A function that is called just before calling the response() in each iteration
|
222
|
-
verbosity:
|
222
|
+
verbosity: Level of information to print
|
223
|
+
0 - No prints
|
224
|
+
1 - Only convergence message
|
225
|
+
2 - Convergence and iteration info (default)
|
226
|
+
3 - Additional info on variables
|
227
|
+
4 - Additional info on sensitivity information
|
223
228
|
|
224
229
|
"""
|
225
230
|
|
226
|
-
def __init__(self, function, variables, responses, tolx=1e-4, tolf=0.0, move=0.1, maxit=100, xmin=0.0, xmax=1.0, fn_callback=None, verbosity=
|
231
|
+
def __init__(self, function, variables, responses, tolx=1e-4, tolf=0.0, move=0.1, maxit=100, xmin=0.0, xmax=1.0, fn_callback=None, verbosity=2, **kwargs):
|
227
232
|
self.funbl = function
|
228
233
|
self.verbosity = verbosity
|
229
234
|
|
@@ -301,8 +306,7 @@ class MMA:
|
|
301
306
|
self.xmin[self.cumlens[i]:self.cumlens[i+1]] = xminvals[i]
|
302
307
|
|
303
308
|
if len(self.xmin) != self.n:
|
304
|
-
raise RuntimeError(
|
305
|
-
"Length of the xmin vector not correct ({} != {})".format(len(self.xmin), self.n))
|
309
|
+
raise RuntimeError(f"Length of the xmin vector ({len(self.xmin)}) should be equal to # design variables ({self.n})")
|
306
310
|
|
307
311
|
if not hasattr(self.xmax, '__len__'):
|
308
312
|
self.xmax = self.xmax * np.ones_like(xval)
|
@@ -313,17 +317,19 @@ class MMA:
|
|
313
317
|
self.xmax[self.cumlens[i]:self.cumlens[i + 1]] = xmaxvals[i]
|
314
318
|
|
315
319
|
if len(self.xmax) != self.n:
|
316
|
-
raise RuntimeError("Length of the xmax vector
|
320
|
+
raise RuntimeError(f"Length of the xmax vector ({len(self.xmax)}) should be equal to # design variables ({self.n})")
|
317
321
|
|
318
|
-
# Set movelimit in case of multiple
|
319
322
|
if hasattr(self.move, '__len__'):
|
320
|
-
|
321
|
-
|
323
|
+
# Set movelimit in case of multiple are given
|
324
|
+
move_input = np.asarray(self.move).copy()
|
325
|
+
if move_input.size == len(self.variables):
|
322
326
|
self.move = np.zeros_like(xval)
|
323
|
-
for i in range(
|
324
|
-
self.move[self.cumlens[i]:self.cumlens[i + 1]] =
|
327
|
+
for i in range(move_input.size):
|
328
|
+
self.move[self.cumlens[i]:self.cumlens[i + 1]] = move_input[i]
|
325
329
|
elif len(self.move) != self.n:
|
326
|
-
raise RuntimeError("Length of the move vector
|
330
|
+
raise RuntimeError(f"Length of the move vector ({len(self.move)}) should be equal to number of "
|
331
|
+
f"design variable signals ({len(self.variables)}) or "
|
332
|
+
f"total number of design variables ({self.n}).")
|
327
333
|
|
328
334
|
fcur = 0.0
|
329
335
|
while self.iter < self.maxIt:
|
@@ -333,12 +339,9 @@ class MMA:
|
|
333
339
|
# Set the new states
|
334
340
|
for i, s in enumerate(self.variables):
|
335
341
|
if self.cumlens[i+1]-self.cumlens[i] == 1:
|
336
|
-
|
337
|
-
s.state[:] = xval[self.cumlens[i]]
|
338
|
-
except TypeError:
|
339
|
-
s.state = xval[self.cumlens[i]]
|
342
|
+
s.state = xval[self.cumlens[i]]
|
340
343
|
else:
|
341
|
-
s.state
|
344
|
+
s.state = xval[self.cumlens[i]:self.cumlens[i+1]]
|
342
345
|
|
343
346
|
if self.fn_callback is not None:
|
344
347
|
self.fn_callback()
|
@@ -346,19 +349,11 @@ class MMA:
|
|
346
349
|
# Calculate response
|
347
350
|
self.funbl.response()
|
348
351
|
|
349
|
-
# Update the states
|
350
|
-
for i, s in enumerate(self.variables):
|
351
|
-
if self.cumlens[i+1]-self.cumlens[i] == 1:
|
352
|
-
try:
|
353
|
-
xval[self.cumlens[i]] = s.state[:]
|
354
|
-
except (TypeError, IndexError):
|
355
|
-
xval[self.cumlens[i]] = s.state
|
356
|
-
else:
|
357
|
-
xval[self.cumlens[i]:self.cumlens[i+1]] = s.state[:]
|
358
|
-
|
359
352
|
# Save response
|
360
353
|
f = ()
|
361
354
|
for s in self.responses:
|
355
|
+
if not np.isscalar(s.state):
|
356
|
+
raise TypeError("State of responses must be scalar.")
|
362
357
|
f += (s.state, )
|
363
358
|
|
364
359
|
# Check function change convergence criterion
|
@@ -388,27 +383,48 @@ class MMA:
|
|
388
383
|
# Reset sensitivities for the next response
|
389
384
|
self.funbl.reset()
|
390
385
|
|
391
|
-
# Display info on variables
|
392
386
|
if self.verbosity >= 3:
|
387
|
+
# Display info on variables
|
388
|
+
show_sensitivities = self.verbosity >= 4
|
389
|
+
msg = ""
|
393
390
|
for i, s in enumerate(self.variables):
|
394
|
-
|
395
|
-
|
396
|
-
if isscal:
|
397
|
-
try:
|
398
|
-
msg += " {0: .3e} ".format(s.state)
|
399
|
-
except TypeError:
|
400
|
-
msg += " {0: .3e} ".format(s.state[0])
|
391
|
+
if show_sensitivities:
|
392
|
+
msg += "{0:>10s} = ".format(s.tag[:10])
|
401
393
|
else:
|
402
|
-
msg += "
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
394
|
+
msg += f"{s.tag} = "
|
395
|
+
|
396
|
+
# Display value range
|
397
|
+
fmt = '% .2e'
|
398
|
+
minval, maxval = np.min(s.state), np.max(s.state)
|
399
|
+
mintag, maxtag = fmt % minval, fmt % maxval
|
400
|
+
if mintag == maxtag:
|
401
|
+
if show_sensitivities:
|
402
|
+
msg += f" {mintag} "
|
407
403
|
else:
|
408
|
-
msg += "
|
409
|
-
|
404
|
+
msg += f" {mintag}"
|
405
|
+
else:
|
406
|
+
sep = '…' if len(s.state) > 2 else ','
|
407
|
+
msg += f"[{mintag}{sep}{maxtag}]"
|
408
|
+
if show_sensitivities:
|
409
|
+
msg += " "
|
410
|
+
|
411
|
+
if show_sensitivities:
|
412
|
+
# Display info on sensivity values
|
413
|
+
for j, s_out in enumerate(self.responses):
|
414
|
+
msg += "| {0:s}/{1:11s} = ".format("d" + s_out.tag, "d" + s.tag[:10])
|
415
|
+
minval = np.min(df[j][self.cumlens[i]:self.cumlens[i+1]])
|
416
|
+
maxval = np.max(df[j][self.cumlens[i]:self.cumlens[i+1]])
|
417
|
+
mintag, maxtag = fmt % minval, fmt % maxval
|
418
|
+
if mintag == maxtag:
|
419
|
+
msg += f" {mintag} "
|
420
|
+
else:
|
421
|
+
sep = '…' if self.cumlens[i + 1] - self.cumlens[i] > 2 else ','
|
422
|
+
msg += f"[{mintag}{sep}{maxtag}] "
|
423
|
+
msg += '\n'
|
424
|
+
elif i != len(self.variables)-1:
|
425
|
+
msg += ', '
|
426
|
+
print(msg)
|
410
427
|
|
411
|
-
self.iter += 1
|
412
428
|
xnew, change = self.mmasub(xval.copy(), np.hstack(f), np.vstack(df))
|
413
429
|
|
414
430
|
# Stopping criteria on step size
|
@@ -419,6 +435,7 @@ class MMA:
|
|
419
435
|
break
|
420
436
|
|
421
437
|
xval = xnew
|
438
|
+
self.iter += 1
|
422
439
|
|
423
440
|
def mmasub(self, xval, g, dg):
|
424
441
|
if self.dx is None:
|
@@ -535,20 +552,33 @@ class MMA:
|
|
535
552
|
change = np.average(abs(xval - xmma))
|
536
553
|
|
537
554
|
if self.verbosity >= 2:
|
555
|
+
# Display iteration status message
|
538
556
|
msgs = ["g{0:d}({1:s}): {2:+.4e}".format(i, s.tag, g[i]) for i, s in enumerate(self.responses)]
|
539
|
-
|
557
|
+
max_infeasibility = max(g[1:])
|
558
|
+
is_feasible = max_infeasibility <= 0
|
559
|
+
|
560
|
+
feasibility_tag = 'f' if is_feasible else ' '
|
561
|
+
print("It. {0: 4d}, [{1:1s}] {2}".format(self.iter, feasibility_tag, ", ".join(msgs)))
|
540
562
|
|
541
|
-
if self.verbosity >=3:
|
542
|
-
#
|
543
|
-
|
563
|
+
if self.verbosity >= 3:
|
564
|
+
# Report design feasibility
|
565
|
+
iconst_max = np.argmax(g[1:])
|
566
|
+
print(f" | {np.sum(g[1:]>0)} / {len(g)-1} violated constraints, "
|
567
|
+
f"max. violation ({self.responses[iconst_max+1].tag}) = {'%.2g'%g[iconst_max+1]}")
|
568
|
+
|
569
|
+
# Print design changes
|
570
|
+
change_msgs = []
|
544
571
|
for i, s in enumerate(self.variables):
|
545
|
-
|
546
|
-
|
547
|
-
|
572
|
+
minchg = np.min(abs(xval[self.cumlens[i]:self.cumlens[i + 1]] - xmma[self.cumlens[i]:self.cumlens[i + 1]]))
|
573
|
+
maxchg = np.max(abs(xval[self.cumlens[i]:self.cumlens[i + 1]] - xmma[self.cumlens[i]:self.cumlens[i + 1]]))
|
574
|
+
fmt = '%.2g'
|
575
|
+
mintag, maxtag = fmt % minchg, fmt % maxchg
|
576
|
+
|
577
|
+
if mintag == maxtag:
|
578
|
+
change_msgs.append(f"Δ({s.tag}) = {mintag}")
|
548
579
|
else:
|
549
|
-
|
580
|
+
change_msgs.append(f"Δ({s.tag}) = {mintag}…{maxtag}")
|
550
581
|
|
551
|
-
|
552
|
-
print(printstr)
|
582
|
+
print(f" | Changes: {', '.join(change_msgs)}")
|
553
583
|
|
554
|
-
return xmma, change
|
584
|
+
return xmma, change
|