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.
Files changed (55) hide show
  1. {pyMOTO-1.3.0 → pymoto-1.4.0}/PKG-INFO +7 -8
  2. {pyMOTO-1.3.0 → pymoto-1.4.0}/README.md +6 -7
  3. {pyMOTO-1.3.0 → pymoto-1.4.0}/pyMOTO.egg-info/PKG-INFO +7 -8
  4. {pyMOTO-1.3.0 → pymoto-1.4.0}/pyMOTO.egg-info/SOURCES.txt +12 -3
  5. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/__init__.py +17 -11
  6. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/common/domain.py +60 -5
  7. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/common/dyadcarrier.py +33 -4
  8. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/common/mma.py +83 -53
  9. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/core_objects.py +117 -113
  10. pymoto-1.4.0/pymoto/modules/aggregation.py +209 -0
  11. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/assembly.py +136 -10
  12. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/complex.py +3 -3
  13. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/filter.py +171 -24
  14. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/generic.py +12 -1
  15. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/io.py +22 -11
  16. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/linalg.py +21 -110
  17. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/scaling.py +4 -4
  18. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/routines.py +23 -9
  19. pymoto-1.4.0/pymoto/solvers/__init__.py +14 -0
  20. pymoto-1.4.0/pymoto/solvers/auto_determine.py +108 -0
  21. pyMOTO-1.3.0/pymoto/common/solvers_dense.py → pymoto-1.4.0/pymoto/solvers/dense.py +90 -70
  22. pymoto-1.4.0/pymoto/solvers/iterative.py +361 -0
  23. pymoto-1.4.0/pymoto/solvers/matrix_checks.py +56 -0
  24. pymoto-1.4.0/pymoto/solvers/solvers.py +253 -0
  25. pyMOTO-1.3.0/pymoto/common/solvers_sparse.py → pymoto-1.4.0/pymoto/solvers/sparse.py +41 -29
  26. pymoto-1.4.0/tests/test_aggregration.py +205 -0
  27. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_assembly.py +14 -0
  28. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_complex.py +19 -8
  29. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_core.py +18 -0
  30. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_domain.py +26 -0
  31. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_dyadcarrier.py +25 -0
  32. pymoto-1.4.0/tests/test_element_operations.py +185 -0
  33. pymoto-1.4.0/tests/test_filter.py +231 -0
  34. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_module_mathgeneral.py +48 -0
  35. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_solvers_dense.py +128 -75
  36. pymoto-1.4.0/tests/test_solvers_iterative.py +96 -0
  37. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_solvers_sparse.py +76 -36
  38. pyMOTO-1.3.0/pymoto/common/solvers.py +0 -236
  39. {pyMOTO-1.3.0 → pymoto-1.4.0}/LICENSE +0 -0
  40. {pyMOTO-1.3.0 → pymoto-1.4.0}/pyMOTO.egg-info/dependency_links.txt +0 -0
  41. {pyMOTO-1.3.0 → pymoto-1.4.0}/pyMOTO.egg-info/requires.txt +0 -0
  42. {pyMOTO-1.3.0 → pymoto-1.4.0}/pyMOTO.egg-info/top_level.txt +0 -0
  43. {pyMOTO-1.3.0 → pymoto-1.4.0}/pyMOTO.egg-info/zip-safe +0 -0
  44. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/modules/autodiff.py +0 -0
  45. {pyMOTO-1.3.0 → pymoto-1.4.0}/pymoto/utils.py +0 -0
  46. {pyMOTO-1.3.0 → pymoto-1.4.0}/pyproject.toml +0 -0
  47. {pyMOTO-1.3.0 → pymoto-1.4.0}/setup.cfg +0 -0
  48. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_automod.py +0 -0
  49. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_elmatrices.py +0 -0
  50. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_linsolve_sparse.py +0 -0
  51. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_module_concatsignal.py +0 -0
  52. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_module_eigensolve.py +0 -0
  53. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_module_einsum.py +0 -0
  54. {pyMOTO-1.3.0 → pymoto-1.4.0}/tests/test_scaling.py +0 -0
  55. {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.0
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
- [![10.5281/zenodo.7708738](https://zenodo.org/badge/DOI/10.5281/zenodo.7708738.svg)](https://doi.org/10.5281/zenodo.7708738)
28
+ [![10.5281/zenodo.8138859](https://zenodo.org/badge/DOI/10.5281/zenodo.8138859.svg)](https://doi.org/10.5281/zenodo.8138859)
29
29
  [![anaconda.org/aatmdelissen/pymoto](https://anaconda.org/aatmdelissen/pymoto/badges/version.svg)](https://anaconda.org/aatmdelissen/pymoto)
30
30
  [![pypi.org/project/pyMOTO](https://badge.fury.io/py/pyMOTO.svg)](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
- A local installation for development in `pyMOTO` can be done by first downloading the entire git repo, and then calling
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
- * **NumPy** - Dense linear algebra and solvers
69
- * **SciPy** - Sparse linear algebra and solvers
70
- * **SymPy** - Symbolic differentiation for `MathGeneral` module
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
- [![10.5281/zenodo.7708738](https://zenodo.org/badge/DOI/10.5281/zenodo.7708738.svg)](https://doi.org/10.5281/zenodo.7708738)
1
+ [![10.5281/zenodo.8138859](https://zenodo.org/badge/DOI/10.5281/zenodo.8138859.svg)](https://doi.org/10.5281/zenodo.8138859)
2
2
  [![anaconda.org/aatmdelissen/pymoto](https://anaconda.org/aatmdelissen/pymoto/badges/version.svg)](https://anaconda.org/aatmdelissen/pymoto)
3
3
  [![pypi.org/project/pyMOTO](https://badge.fury.io/py/pyMOTO.svg)](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
- A local installation for development in `pyMOTO` can be done by first downloading the entire git repo, and then calling
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
- * **NumPy** - Dense linear algebra and solvers
42
- * **SciPy** - Sparse linear algebra and solvers
43
- * **SymPy** - Symbolic differentiation for `MathGeneral` module
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.0
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
- [![10.5281/zenodo.7708738](https://zenodo.org/badge/DOI/10.5281/zenodo.7708738.svg)](https://doi.org/10.5281/zenodo.7708738)
28
+ [![10.5281/zenodo.8138859](https://zenodo.org/badge/DOI/10.5281/zenodo.8138859.svg)](https://doi.org/10.5281/zenodo.8138859)
29
29
  [![anaconda.org/aatmdelissen/pymoto](https://anaconda.org/aatmdelissen/pymoto/badges/version.svg)](https://anaconda.org/aatmdelissen/pymoto)
30
30
  [![pypi.org/project/pyMOTO](https://badge.fury.io/py/pyMOTO.svg)](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
- A local installation for development in `pyMOTO` can be done by first downloading the entire git repo, and then calling
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
- * **NumPy** - Dense linear algebra and solvers
69
- * **SciPy** - Sparse linear algebra and solvers
70
- * **SymPy** - Symbolic differentiation for `MathGeneral` module
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/common/solvers.py
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.3.0'
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
- from .common.solvers import matrix_is_complex, matrix_is_diagonal, matrix_is_symmetric, matrix_is_hermitian, LinearSolver, LDAWrapper
9
- from .common.solvers_dense import SolverDiagonal, SolverDenseQR, SolverDenseLU, SolverDenseCholesky, SolverDenseLDL
10
- from .common.solvers_sparse import SolverSparsePardiso, SolverSparseLU, SolverSparseCholeskyScikit, SolverSparseCholeskyCVXOPT
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
- 'matrix_is_complex', 'matrix_is_diagonal', 'matrix_is_symmetric', 'matrix_is_hermitian',
36
- 'LinearSolver', 'LDAWrapper',
37
- 'SolverDiagonal', 'SolverDenseQR', 'SolverDenseLU', 'SolverDenseCholesky', 'SolverDenseLDL',
38
- 'SolverSparsePardiso', 'SolverSparseLU', 'SolverSparseCholeskyScikit', 'SolverSparseCholeskyCVXOPT',
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
- "Scaling"
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 [idx * self.element_size[ii] for ii, idx in enumerate(ijk)]
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: 0 - No prints, 1 - Only convergence message, 2 - Convergence and iteration info, 3 - Extended info
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=0, **kwargs):
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 not correct ({} != {})".format(len(self.xmax), self.n))
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
- if len(self.move) == len(self.variables):
321
- movevals = self.move
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(len(movevals)):
324
- self.move[self.cumlens[i]:self.cumlens[i + 1]] = movevals[i]
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 not correct ({} != {})".format(len(self.move), self.n))
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
- try:
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[:] = xval[self.cumlens[i]:self.cumlens[i+1]]
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
- isscal = self.cumlens[i + 1] - self.cumlens[i] == 1
395
- msg = "{0:>10s} = ".format(s.tag)
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 += "[{0: .3e} ... {1: .3e}] ".format(min(s.state), max(s.state))
403
- for j, s_out in enumerate(self.responses):
404
- msg += "| {0:>10s}/{1:10s} = ".format("d"+s_out.tag, "d"+s.tag)
405
- if isscal:
406
- msg += " {0: .3e} ".format(df[j][self.cumlens[i]])
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 += "[{0: .3e} ... {1: .3e}] ".format(min(df[j][self.cumlens[i]:self.cumlens[i+1]]), max(df[j][self.cumlens[i]:self.cumlens[i+1]]))
409
- print(msg)
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
- print("It. {0: 4d}, {1}".format(self.iter, ", ".join(msgs)))
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
- # Print changes
543
- printstr = "Changes: "
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
- isscal = self.cumlens[i + 1] - self.cumlens[i] == 1
546
- if isscal:
547
- chg = abs(xval[self.cumlens[i]] - xmma[self.cumlens[i]])
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
- chg = np.average(abs(xval[self.cumlens[i]:self.cumlens[i + 1]] - xmma[self.cumlens[i]:self.cumlens[i + 1]]))
580
+ change_msgs.append(f"Δ({s.tag}) = {mintag}…{maxtag}")
550
581
 
551
- printstr += "{0:s} = {1:.3e} ".format("Δ_"+s.tag, chg)
552
- print(printstr)
582
+ print(f" | Changes: {', '.join(change_msgs)}")
553
583
 
554
- return xmma, change
584
+ return xmma, change