pyMOTO 1.1.0__tar.gz → 1.2.1__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.1.0 → pyMOTO-1.2.1}/PKG-INFO +1 -1
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pyMOTO.egg-info/PKG-INFO +1 -1
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pyMOTO.egg-info/SOURCES.txt +4 -1
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/__init__.py +15 -12
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/common/domain.py +3 -3
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/modules/assembly.py +14 -4
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/modules/linalg.py +90 -7
- pyMOTO-1.2.1/tests/test_assembly.py +91 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/tests/test_automod.py +4 -4
- pyMOTO-1.2.1/tests/test_linsolve_sparse.py +479 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/tests/test_solvers_sparse.py +7 -208
- pyMOTO-1.2.1/tests/test_static_condenstation.py +55 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/LICENSE +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/README.md +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pyMOTO.egg-info/dependency_links.txt +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pyMOTO.egg-info/requires.txt +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pyMOTO.egg-info/top_level.txt +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pyMOTO.egg-info/zip-safe +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/common/dyadcarrier.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/common/mma.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/common/solvers.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/common/solvers_dense.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/common/solvers_sparse.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/core_objects.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/modules/autodiff.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/modules/complex.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/modules/filter.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/modules/generic.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/modules/io.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/modules/scaling.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/routines.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pymoto/utils.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/pyproject.toml +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/setup.cfg +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/tests/test_complex.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/tests/test_core.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/tests/test_dyadcarrier.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/tests/test_module_concatsignal.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/tests/test_module_eigensolve.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/tests/test_module_einsum.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/tests/test_module_mathgeneral.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/tests/test_scaling.py +0 -0
- {pyMOTO-1.1.0 → pyMOTO-1.2.1}/tests/test_solvers_dense.py +0 -0
@@ -26,14 +26,17 @@ pymoto/modules/generic.py
|
|
26
26
|
pymoto/modules/io.py
|
27
27
|
pymoto/modules/linalg.py
|
28
28
|
pymoto/modules/scaling.py
|
29
|
+
tests/test_assembly.py
|
29
30
|
tests/test_automod.py
|
30
31
|
tests/test_complex.py
|
31
32
|
tests/test_core.py
|
32
33
|
tests/test_dyadcarrier.py
|
34
|
+
tests/test_linsolve_sparse.py
|
33
35
|
tests/test_module_concatsignal.py
|
34
36
|
tests/test_module_eigensolve.py
|
35
37
|
tests/test_module_einsum.py
|
36
38
|
tests/test_module_mathgeneral.py
|
37
39
|
tests/test_scaling.py
|
38
40
|
tests/test_solvers_dense.py
|
39
|
-
tests/test_solvers_sparse.py
|
41
|
+
tests/test_solvers_sparse.py
|
42
|
+
tests/test_static_condenstation.py
|
@@ -1,27 +1,30 @@
|
|
1
|
-
__version__ = '1.1
|
1
|
+
__version__ = '1.2.1'
|
2
2
|
|
3
|
-
from .
|
4
|
-
from .routines import finite_difference, minimize_oc, minimize_mma
|
3
|
+
from .common.domain import DomainDefinition
|
5
4
|
|
6
5
|
# Imports from common
|
7
6
|
from .common.dyadcarrier import DyadCarrier
|
8
|
-
from .common.domain import DomainDefinition
|
9
7
|
from .common.mma import MMA
|
10
|
-
from .common.solvers import matrix_is_complex, matrix_is_diagonal, matrix_is_symmetric, matrix_is_hermitian,
|
11
|
-
LinearSolver, LDAWrapper
|
8
|
+
from .common.solvers import matrix_is_complex, matrix_is_diagonal, matrix_is_symmetric, matrix_is_hermitian, LinearSolver, LDAWrapper
|
12
9
|
from .common.solvers_dense import SolverDiagonal, SolverDenseQR, SolverDenseLU, SolverDenseCholesky, SolverDenseLDL
|
13
10
|
from .common.solvers_sparse import SolverSparsePardiso, SolverSparseLU, SolverSparseCholeskyScikit, SolverSparseCholeskyCVXOPT
|
14
11
|
|
15
|
-
#
|
16
|
-
from .
|
17
|
-
|
12
|
+
# Modular inports
|
13
|
+
from .core_objects import Signal, Module, Network, make_signals
|
14
|
+
|
15
|
+
# Import modules
|
18
16
|
from .modules.assembly import AssembleGeneral, AssembleStiffness, AssembleMass
|
17
|
+
from .modules.autodiff import AutoMod
|
18
|
+
from .modules.complex import MakeComplex, RealPart, ImagPart, ComplexNorm
|
19
19
|
from .modules.filter import FilterConv, Filter, DensityFilter, OverhangFilter
|
20
|
+
from .modules.generic import MathGeneral, EinSum, ConcatSignal
|
20
21
|
from .modules.io import PlotDomain, PlotGraph, PlotIter, WriteToVTI
|
21
|
-
from .modules.
|
22
|
-
from .modules.autodiff import AutoMod
|
22
|
+
from .modules.linalg import Inverse, LinSolve, EigenSolve, SystemOfEquations, StaticCondensation
|
23
23
|
from .modules.scaling import Scaling
|
24
24
|
|
25
|
+
# Further helper routines
|
26
|
+
from .routines import finite_difference, minimize_oc, minimize_mma
|
27
|
+
|
25
28
|
__all__ = [
|
26
29
|
'Signal', 'Module', 'Network', 'make_signals',
|
27
30
|
'finite_difference', 'minimize_oc', 'minimize_mma',
|
@@ -35,7 +38,7 @@ __all__ = [
|
|
35
38
|
'SolverSparsePardiso', 'SolverSparseLU', 'SolverSparseCholeskyScikit', 'SolverSparseCholeskyCVXOPT',
|
36
39
|
# Modules
|
37
40
|
"MathGeneral", "EinSum", "ConcatSignal",
|
38
|
-
"Inverse", "LinSolve", "EigenSolve", "SystemOfEquations",
|
41
|
+
"Inverse", "LinSolve", "EigenSolve", "SystemOfEquations", "StaticCondensation",
|
39
42
|
"AssembleGeneral", "AssembleStiffness", "AssembleMass",
|
40
43
|
"FilterConv", "Filter", "DensityFilter", "OverhangFilter",
|
41
44
|
"PlotDomain", "PlotGraph", "PlotIter", "WriteToVTI",
|
@@ -184,10 +184,10 @@ class DomainDefinition:
|
|
184
184
|
"""
|
185
185
|
v = np.prod(self.element_size[:self.dim])
|
186
186
|
assert v > 0.0, 'Element volume needs to be positive'
|
187
|
-
|
187
|
+
shapefn = np.ones(self.elemnodes)/v
|
188
188
|
for i in range(self.dim):
|
189
|
-
|
190
|
-
return
|
189
|
+
shapefn *= np.array([self.element_size[i]/2 + n[i]*pos[i] for n in self.node_numbering])
|
190
|
+
return shapefn
|
191
191
|
|
192
192
|
def eval_shape_fun_der(self, pos: np.ndarray):
|
193
193
|
""" Evaluates the shape function derivatives in x, y, and optionally z-direction.
|
@@ -1,9 +1,12 @@
|
|
1
1
|
""" Assembly modules for finite element analysis """
|
2
2
|
import sys
|
3
|
-
from
|
3
|
+
from typing import Union
|
4
|
+
|
4
5
|
import numpy as np
|
5
6
|
from scipy.sparse import csc_matrix
|
6
|
-
|
7
|
+
|
8
|
+
from pymoto import Module, DyadCarrier, DomainDefinition
|
9
|
+
|
7
10
|
try:
|
8
11
|
from opt_einsum import contract as einsum
|
9
12
|
except ModuleNotFoundError:
|
@@ -33,8 +36,11 @@ class AssembleGeneral(Module):
|
|
33
36
|
bcdiagval (optional): Value to put on the diagonal of the matrix at dofs where boundary conditions are active.
|
34
37
|
matrix_type (optional): The matrix type to construct. This is a constructor which must accept the arguments
|
35
38
|
``matrix_type((vals, (row_idx, col_idx)), shape=(n, n))``
|
39
|
+
add_constant (optional): A constant (e.g. matrix) to add.
|
36
40
|
"""
|
37
|
-
|
41
|
+
|
42
|
+
def _prepare(self, domain: DomainDefinition, element_matrix: np.ndarray, bc=None, bcdiagval=None,
|
43
|
+
matrix_type=csc_matrix, add_constant=None):
|
38
44
|
self.elmat = element_matrix
|
39
45
|
self.ndof = self.elmat.shape[-1] // domain.elemnodes # Number of dofs per node
|
40
46
|
self.n = self.ndof * domain.nnodes # Matrix size
|
@@ -59,6 +65,8 @@ class AssembleGeneral(Module):
|
|
59
65
|
else:
|
60
66
|
self.bcselect = None
|
61
67
|
|
68
|
+
self.add_constant = add_constant
|
69
|
+
|
62
70
|
def _response(self, xscale: np.ndarray):
|
63
71
|
scaled_el = ((self.elmat.flatten()[np.newaxis]).T * xscale).flatten(order='F')
|
64
72
|
|
@@ -76,6 +84,8 @@ class AssembleGeneral(Module):
|
|
76
84
|
"scipy.sparse.csrmatrix are supported"
|
77
85
|
.format(self.matrix_type)).with_traceback(sys.exc_info()[2]) from None
|
78
86
|
|
87
|
+
if self.add_constant is not None:
|
88
|
+
mat += self.add_constant
|
79
89
|
return mat
|
80
90
|
|
81
91
|
def _sensitivity(self, dgdmat: Union[DyadCarrier, np.ndarray]):
|
@@ -232,6 +242,7 @@ class AssembleMass(AssembleGeneral):
|
|
232
242
|
bcdiagval: The value to put on the diagonal in case of boundary conditions (bc)
|
233
243
|
**kwargs : Other keyword-arguments are passed to AssembleGeneral
|
234
244
|
"""
|
245
|
+
|
235
246
|
def _prepare(self, domain: DomainDefinition, *args, rho: float = 1.0, bcdiagval=0.0, **kwargs):
|
236
247
|
# Element mass matrix
|
237
248
|
# 1/36 Mass of one element
|
@@ -259,4 +270,3 @@ class AssembleMass(AssembleGeneral):
|
|
259
270
|
else:
|
260
271
|
raise RuntimeError("Only for 2D and 3D")
|
261
272
|
super()._prepare(domain, ME, *args, bcdiagval=bcdiagval, **kwargs)
|
262
|
-
|
@@ -13,6 +13,71 @@ from pymoto import SolverSparseLU, SolverSparseCholeskyCVXOPT, SolverSparsePardi
|
|
13
13
|
from pymoto import matrix_is_symmetric, matrix_is_hermitian, matrix_is_diagonal
|
14
14
|
|
15
15
|
|
16
|
+
class StaticCondensation(Module):
|
17
|
+
r"""Static condensation of a linear system of equations
|
18
|
+
|
19
|
+
The partitioned system of equations
|
20
|
+
|
21
|
+
:math:`\begin{bmatrix} \mathbf{A}_\text{mm} & \mathbf{A}_\text{ms} \\ \mathbf{A}_\text{sm} & \mathbf{A}_\text{ss}
|
22
|
+
\end{bmatrix}
|
23
|
+
\begin{bmatrix} \mathbf{x}_\text{m} \\ \mathbf{x}_\text{s} \end{bmatrix} =
|
24
|
+
\begin{bmatrix} \mathbf{b}_\text{m} \\ \mathbf{b}_\text{s} \end{bmatrix}
|
25
|
+
,`
|
26
|
+
with subscripts ``(m)`` and ``(s)`` referring to the main and secondary dofs, respectively.
|
27
|
+
|
28
|
+
The system is solved in two steps:
|
29
|
+
|
30
|
+
:math:`\begin{aligned}
|
31
|
+
\mathbf{A}_\text{ss} \mathbf{x}_\text{sm} &= \mathbf{A}_\text{sm} \\
|
32
|
+
\tilde{\mathbf{A}} &= \mathbf{A}_\text{mm} - \mathbf{A}_\text{ms} \mathbf{x}_\text{sm}.
|
33
|
+
\end{aligned}`
|
34
|
+
|
35
|
+
Assumptions:
|
36
|
+
(i) It is assumed the prescribed DOFs (all dof - main dof - free dof) are prescribed to zero.
|
37
|
+
(ii) It is assumed the applied load on the free DOFs is zero; there is no reduced load.
|
38
|
+
|
39
|
+
Implemented by @artofscience (s.koppen@tudelft.nl).
|
40
|
+
|
41
|
+
References:
|
42
|
+
|
43
|
+
Koppen, S., Langelaar, M., & van Keulen, F. (2022).
|
44
|
+
Efficient multi-partition topology optimization.
|
45
|
+
Computer Methods in Applied Mechanics and Engineering, 393, 114829.
|
46
|
+
DOI: https://doi.org/10.1016/j.cma.2022.114829
|
47
|
+
|
48
|
+
Input Signals:
|
49
|
+
- ``A`` (`dense or sparse matrix`): The system matrix :math:`\mathbf{A}` of size ``(n, n)``
|
50
|
+
|
51
|
+
Output Signal:
|
52
|
+
- ``Ared`` (`dense or sparse matrix`): The reduced system matrix :math:`\tilde{\mathbf{A}}` of size ``(m, m)``
|
53
|
+
|
54
|
+
Args:
|
55
|
+
free: The indices corresponding to the free degrees of freedom
|
56
|
+
main: The indices corresponding to the main degrees of freedom
|
57
|
+
**kwargs: See `pymoto.LinSolve`, as they are directly passed into the `LinSolve` module
|
58
|
+
"""
|
59
|
+
|
60
|
+
def _prepare(self, main, free, **kwargs):
|
61
|
+
self.module_LinSolve = LinSolve([self.sig_in[0], Signal()], **kwargs)
|
62
|
+
self.module_LinSolve.use_lda_solver = False
|
63
|
+
self.m = main
|
64
|
+
self.f = free
|
65
|
+
|
66
|
+
def _response(self, A):
|
67
|
+
self.n = np.shape(A)[0]
|
68
|
+
self.module_LinSolve.sig_in[0].state = A[self.f, ...][..., self.f]
|
69
|
+
self.module_LinSolve.sig_in[1].state = A[self.f, ...][..., self.m].todense()
|
70
|
+
self.module_LinSolve.response()
|
71
|
+
self.X = self.module_LinSolve.sig_out[0].state
|
72
|
+
return A[self.m, ...][..., self.m] - A[self.m, ...][..., self.f] @ self.X
|
73
|
+
|
74
|
+
def _sensitivity(self, dfdB):
|
75
|
+
C = np.zeros((self.n, len(self.m)), dtype=float)
|
76
|
+
C[self.m, ...] = np.eye(len(self.m))
|
77
|
+
C[self.f, ...] = -self.X
|
78
|
+
return C @ dfdB @ C.T if isinstance(dfdB, DyadCarrier) else DyadCarrier(list(C.T), list(np.asarray(dfdB @ C.T)))
|
79
|
+
|
80
|
+
|
16
81
|
class SystemOfEquations(Module):
|
17
82
|
r""" Solve a partitioned linear system of equations
|
18
83
|
|
@@ -63,6 +128,7 @@ class SystemOfEquations(Module):
|
|
63
128
|
assert bf.shape[0] + xp.shape[0] == A.shape[0], "Dimensions of applied force and displacement must match matrix"
|
64
129
|
assert bf.ndim == xp.ndim, "Number of loadcases for applied force and displacement must match"
|
65
130
|
self.n = np.shape(A)[0]
|
131
|
+
self.dim = xp.ndim
|
66
132
|
|
67
133
|
if self.f is None:
|
68
134
|
all_dofs = np.arange(self.n)
|
@@ -73,7 +139,8 @@ class SystemOfEquations(Module):
|
|
73
139
|
assert self.f.size + self.p.size == self.n, "Size of free and prescribed indices must match the matrix size"
|
74
140
|
|
75
141
|
# create empty output
|
76
|
-
self.x = np.zeros((self.n, *bf.shape[1:]), dtype=
|
142
|
+
self.x = np.zeros((self.n, *bf.shape[1:]), dtype=complex) if np.iscomplexobj(A) else np.zeros(
|
143
|
+
(self.n, *bf.shape[1:]), dtype=float)
|
77
144
|
self.x[self.p, ...] = xp
|
78
145
|
|
79
146
|
b = np.zeros_like(self.x)
|
@@ -97,13 +164,19 @@ class SystemOfEquations(Module):
|
|
97
164
|
return self.x, b
|
98
165
|
|
99
166
|
def _sensitivity(self, dgdx, dgdb):
|
100
|
-
adjoint_load =
|
167
|
+
adjoint_load = np.zeros_like(self.x[self.f, ...])
|
168
|
+
|
169
|
+
if dgdx is not None:
|
170
|
+
adjoint_load += dgdx[self.f, ...]
|
171
|
+
if dgdb is not None:
|
172
|
+
adjoint_load += self.Afp * dgdb[self.p, ...]
|
101
173
|
|
102
|
-
# adjoint equation
|
103
174
|
lam = np.zeros_like(self.x)
|
104
175
|
lamf = -1.0 * self.module_LinSolve.solver.adjoint(adjoint_load)
|
105
176
|
lam[self.f, ...] = lamf
|
106
|
-
|
177
|
+
|
178
|
+
if dgdb is not None:
|
179
|
+
lam[self.p, ...] = dgdb[self.p, ...]
|
107
180
|
|
108
181
|
# sensitivities to system matrix
|
109
182
|
if self.x.ndim > 1:
|
@@ -112,10 +185,19 @@ class SystemOfEquations(Module):
|
|
112
185
|
dgdA = DyadCarrier(lam, self.x)
|
113
186
|
|
114
187
|
# sensitivities to applied load and prescribed state
|
115
|
-
|
116
|
-
dgdup =
|
188
|
+
dgdbf = np.zeros_like(adjoint_load)
|
189
|
+
dgdup = np.zeros_like(self.x[self.p, ...])
|
190
|
+
dgdbf -= lam[self.f, ...]
|
191
|
+
dgdup += self.Afp.T * lam[self.f, ...]
|
192
|
+
|
193
|
+
if dgdx is not None:
|
194
|
+
dgdup += dgdx[self.p, ...]
|
117
195
|
|
118
|
-
|
196
|
+
if dgdb is not None:
|
197
|
+
dgdbf += dgdb[self.f, ...]
|
198
|
+
dgdup += self.App * dgdb[self.p, ...]
|
199
|
+
|
200
|
+
return dgdA, dgdbf, dgdup
|
119
201
|
|
120
202
|
|
121
203
|
class Inverse(Module):
|
@@ -257,6 +339,7 @@ class LinSolve(Module):
|
|
257
339
|
Attributes:
|
258
340
|
use_lda_solver: Use the linear-dependency-aware solver :class:`LDAWrapper` to prevent redundant computations
|
259
341
|
"""
|
342
|
+
|
260
343
|
use_lda_solver = True
|
261
344
|
|
262
345
|
def _prepare(self, dep_tol=1e-5, hermitian=None, symmetric=None, solver=None):
|
@@ -0,0 +1,91 @@
|
|
1
|
+
import unittest
|
2
|
+
import numpy as np
|
3
|
+
import pymoto as pym
|
4
|
+
import numpy.testing as npt
|
5
|
+
|
6
|
+
|
7
|
+
class TestAssembleStiffness(unittest.TestCase):
|
8
|
+
def test_FEA_pure_tensile_2d_one_element(self):
|
9
|
+
Lx, Ly, Lz = 0.1, 0.2, 0.3
|
10
|
+
domain = pym.DomainDefinition(1, 1, unitx=Lx, unity=Ly, unitz=Lz)
|
11
|
+
nodidx_left = domain.get_nodenumber(0, np.arange(domain.nely + 1))
|
12
|
+
# Fixed at bottom, roller at the top in y-direction
|
13
|
+
nodidx_right = domain.get_nodenumber(domain.nelx, np.arange(domain.nely + 1))
|
14
|
+
dofidx_left = np.concatenate([nodidx_left*2, np.array([nodidx_left[0]*2 + 1])])
|
15
|
+
|
16
|
+
E, nu = 210e+9, 0.3
|
17
|
+
|
18
|
+
s_x = pym.Signal('x', state=np.ones(domain.nel))
|
19
|
+
|
20
|
+
# Assemble stiffness matrix
|
21
|
+
m_K = pym.AssembleStiffness(s_x, domain=domain, bc=dofidx_left, e_modulus=E, poisson_ratio=nu, plane='stress')
|
22
|
+
s_K = m_K.sig_out[0]
|
23
|
+
|
24
|
+
m_K.response()
|
25
|
+
F = 1.5
|
26
|
+
f = np.zeros(domain.nnodes*2)
|
27
|
+
f[nodidx_right * 2] = F/nodidx_right.size
|
28
|
+
x = np.linalg.solve(s_K.state.toarray(), f)
|
29
|
+
|
30
|
+
# Bottom y displacement should be zero
|
31
|
+
npt.assert_allclose(x[nodidx_right[0]*2+1], 0, atol=1e-10)
|
32
|
+
|
33
|
+
# Analytical axial displacement using stiffness k = EA/L
|
34
|
+
ux_chk = F * Lx / (E * Ly * Lz)
|
35
|
+
npt.assert_allclose(x[nodidx_right*2], ux_chk, rtol=1e-10)
|
36
|
+
|
37
|
+
# Transverse displacement using Poisson's effect
|
38
|
+
e_xx = ux_chk / Lx # Strain in x-direction
|
39
|
+
e_yy = - nu * e_xx
|
40
|
+
uy_chk = e_yy * Ly
|
41
|
+
npt.assert_allclose(x[nodidx_right[1]*2+1], uy_chk, rtol=1e-10)
|
42
|
+
|
43
|
+
def test_FEA_pure_tensile_3d_one_element(self):
|
44
|
+
Lx, Ly, Lz = 0.1, 0.2, 0.3
|
45
|
+
domain = pym.DomainDefinition(1, 1, 1, unitx=Lx, unity=Ly, unitz=Lz)
|
46
|
+
nodidx_left = domain.get_nodenumber(*np.meshgrid(0, range(domain.nely + 1), range(domain.nelz + 1))).flatten()
|
47
|
+
# Fixed at (0,0,0), roller in z-direction at (0, 1, 0), roller in y-direction at (0, 0, 1)
|
48
|
+
nod_00 = domain.get_nodenumber(0, 0, 0)
|
49
|
+
nod_10 = domain.get_nodenumber(0, 1, 0)
|
50
|
+
nod_01 = domain.get_nodenumber(0, 0, 1)
|
51
|
+
dofidx_left = np.concatenate([nodidx_left * 3, np.array([nod_00, nod_01]) * 3 + 1, np.array([nod_00, nod_10]) * 3 + 2])
|
52
|
+
nodidx_right = domain.get_nodenumber(*np.meshgrid(1, range(domain.nely + 1), range(domain.nelz + 1))).flatten()
|
53
|
+
|
54
|
+
E, nu = 210e+9, 0.3
|
55
|
+
|
56
|
+
s_x = pym.Signal('x', state=np.ones(domain.nel))
|
57
|
+
|
58
|
+
# Assemble stiffness matrix
|
59
|
+
m_K = pym.AssembleStiffness(s_x, domain=domain, bc=dofidx_left, e_modulus=E, poisson_ratio=nu)
|
60
|
+
s_K = m_K.sig_out[0]
|
61
|
+
|
62
|
+
m_K.response()
|
63
|
+
F = 1.5e+3
|
64
|
+
f = np.zeros(domain.nnodes * 3)
|
65
|
+
f[nodidx_right * 3] = F / nodidx_right.size
|
66
|
+
x = np.linalg.solve(s_K.state.toarray(), f)
|
67
|
+
|
68
|
+
# y and z displacements at (1, 0, 0) should be zero
|
69
|
+
npt.assert_allclose(x[domain.get_nodenumber(1, 0, 0) * 3 + 1], 0, atol=1e-10)
|
70
|
+
npt.assert_allclose(x[domain.get_nodenumber(1, 0, 0) * 3 + 2], 0, atol=1e-10)
|
71
|
+
|
72
|
+
# Z displacement at (1, 1, 0) should be zero
|
73
|
+
npt.assert_allclose(x[domain.get_nodenumber(1, 1, 0) * 3 + 2], 0, atol=1e-10)
|
74
|
+
|
75
|
+
# Y displacement at (1, 0, 1) should be zero
|
76
|
+
npt.assert_allclose(x[domain.get_nodenumber(1, 0, 1) * 3 + 1], 0, atol=1e-10)
|
77
|
+
|
78
|
+
# Analytical axial displacement using stiffness k = EA/L
|
79
|
+
ux_chk = F * Lx / (E * Ly * Lz)
|
80
|
+
npt.assert_allclose(x[nodidx_right * 3], ux_chk, rtol=1e-10)
|
81
|
+
|
82
|
+
# Transverse displacement using Poisson's effect
|
83
|
+
e_xx = ux_chk / Lx # Strain in x-direction
|
84
|
+
e_trans = - nu * e_xx
|
85
|
+
uy_chk = e_trans * Ly
|
86
|
+
npt.assert_allclose(x[domain.get_nodenumber(1, 1, 0) * 3 + 1], uy_chk, rtol=1e-10)
|
87
|
+
npt.assert_allclose(x[domain.get_nodenumber(1, 1, 1) * 3 + 1], uy_chk, rtol=1e-10)
|
88
|
+
|
89
|
+
uz_chk = e_trans * Lz
|
90
|
+
npt.assert_allclose(x[domain.get_nodenumber(1, 0, 1) * 3 + 2], uz_chk, rtol=1e-10)
|
91
|
+
npt.assert_allclose(x[domain.get_nodenumber(1, 1, 1) * 3 + 2], uz_chk, rtol=1e-10)
|
@@ -6,10 +6,10 @@ import pymoto as pym
|
|
6
6
|
class TestAutoMod(unittest.TestCase):
|
7
7
|
@classmethod
|
8
8
|
def setUpClass(cls) -> None:
|
9
|
-
""" Skip test if
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
""" Skip test if JAX is not installed """
|
10
|
+
try:
|
11
|
+
import jax
|
12
|
+
except ImportError:
|
13
13
|
raise unittest.SkipTest(f"Skipping test {cls}")
|
14
14
|
|
15
15
|
def test_automod_scalar(self):
|
@@ -0,0 +1,479 @@
|
|
1
|
+
import unittest
|
2
|
+
import numpy as np
|
3
|
+
import pymoto as pym
|
4
|
+
import numpy.testing as npt
|
5
|
+
from scipy.sparse import csc_matrix
|
6
|
+
|
7
|
+
|
8
|
+
class DynamicMatrix(pym.Module):
|
9
|
+
alpha = 0.5
|
10
|
+
beta = 0.5
|
11
|
+
|
12
|
+
def _response(self, K, M, omega):
|
13
|
+
return K + 1j * omega * (self.alpha * M + self.beta * K) - omega ** 2 * M
|
14
|
+
|
15
|
+
def _sensitivity(self, dZ):
|
16
|
+
K, M, omega = [s.state for s in self.sig_in]
|
17
|
+
dK = np.real(dZ) - (omega * self.beta) * np.imag(dZ)
|
18
|
+
dM = (-omega ** 2) * np.real(dZ) - (omega * self.alpha) * np.imag(dZ)
|
19
|
+
dZrM = np.real(dZ).contract(M)
|
20
|
+
dZiK = np.imag(dZ).contract(K)
|
21
|
+
dZiM = np.imag(dZ).contract(M)
|
22
|
+
domega = -self.beta * dZiK - self.alpha * dZiM - 2 * omega * dZrM
|
23
|
+
return dK, dM, domega
|
24
|
+
|
25
|
+
|
26
|
+
class TestLinSolveModuleSparse(unittest.TestCase):
|
27
|
+
# # ------------- Symmetric -------------
|
28
|
+
def test_symmetric_real_compliance2d(self):
|
29
|
+
""" Test symmetric real sparse matrix (compliance in 2D)"""
|
30
|
+
N = 10 # Number of elements
|
31
|
+
dom = pym.DomainDefinition(N, N)
|
32
|
+
sx = pym.Signal('x', np.random.rand(dom.nel))
|
33
|
+
fixed_nodes = dom.get_nodenumber(0, np.arange(0, N+1))
|
34
|
+
bc = np.concatenate((fixed_nodes*2, fixed_nodes*2+1))
|
35
|
+
# Setup different rhs types
|
36
|
+
iforce_x = dom.get_nodenumber(N, np.arange(0, N + 1)) * 2 # Force in x-direction
|
37
|
+
iforce_y = dom.get_nodenumber(N, np.arange(0, N + 1)) * 2 + 1 # Force in y-direction
|
38
|
+
|
39
|
+
force_vecs = dict()
|
40
|
+
|
41
|
+
# Single force
|
42
|
+
f = np.zeros(dom.nnodes*2)
|
43
|
+
f[iforce_x] = 1.0
|
44
|
+
force_vecs['single_real'] = f
|
45
|
+
|
46
|
+
# Multiple rhs
|
47
|
+
f = np.zeros((dom.nnodes * 2, 2))
|
48
|
+
f[iforce_x, 0] = 1.0
|
49
|
+
f[iforce_y, 1] = 1.0
|
50
|
+
force_vecs['multiple_real'] = f
|
51
|
+
|
52
|
+
for k, f in force_vecs.items():
|
53
|
+
with self.subTest(f"RHS-{k}"):
|
54
|
+
sf = pym.Signal('f', f)
|
55
|
+
|
56
|
+
fn = pym.Network()
|
57
|
+
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), dom, bc=bc))
|
58
|
+
su = fn.append(pym.LinSolve([sK, sf], pym.Signal('u')))
|
59
|
+
|
60
|
+
fn.response()
|
61
|
+
|
62
|
+
self.assertTrue(np.allclose(sK.state@su.state, sf.state)) # Check residual
|
63
|
+
# Check finite difference
|
64
|
+
# def tfn(x0, dx, df_an, df_fd): np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5)
|
65
|
+
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5))
|
66
|
+
pym.finite_difference(fn, [sx, sf], su, test_fn=tfn, dx=1e-5, tol=1e-4, verbose=False)
|
67
|
+
|
68
|
+
def test_symmetric_real_compliance3d(self):
|
69
|
+
""" Test symmetric real sparse matrix (compliance in 3D)"""
|
70
|
+
N = 3 # Number of elements
|
71
|
+
dom = pym.DomainDefinition(N, N, N)
|
72
|
+
sx = pym.Signal('x', np.random.rand(dom.nel))
|
73
|
+
jfix, kfix = np.meshgrid(np.arange(0, N+1), np.arange(0, N+1), indexing='ij')
|
74
|
+
fixed_nodes = dom.get_nodenumber(0, jfix, kfix).flatten()
|
75
|
+
bc = np.concatenate((fixed_nodes*3, fixed_nodes*3+1, fixed_nodes*3+2))
|
76
|
+
iforce = dom.get_nodenumber(N, np.arange(0, N+1), np.arange(0, N+1))*3 + 1
|
77
|
+
sf = pym.Signal('f', np.zeros(dom.nnodes*3))
|
78
|
+
sf.state[iforce] = 1.0
|
79
|
+
|
80
|
+
fn = pym.Network()
|
81
|
+
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), dom, bc=bc))
|
82
|
+
su = fn.append(pym.LinSolve([sK, sf], pym.Signal('u')))
|
83
|
+
|
84
|
+
fn.response()
|
85
|
+
|
86
|
+
self.assertTrue(np.allclose(sK.state@su.state, sf.state)) # Check residual
|
87
|
+
# Check finite difference
|
88
|
+
# def tfn(x0, dx, df_an, df_fd): np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5)
|
89
|
+
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=2e-3, atol=1e-5))
|
90
|
+
pym.finite_difference(fn, [sx, sf], su, test_fn=tfn, dx=1e-5, tol=1e-4, verbose=False)
|
91
|
+
|
92
|
+
def test_symmetric_complex_dyncompliance2d(self):
|
93
|
+
""" Test symmetric complex sparse matrix (dynamic compliance in 2D)"""
|
94
|
+
N = 5 # Number of elements
|
95
|
+
dom = pym.DomainDefinition(N, N)
|
96
|
+
sx = pym.Signal('x', np.random.rand(dom.nel))
|
97
|
+
fixed_nodes = dom.get_nodenumber(0, np.arange(0, N+1))
|
98
|
+
bc = np.concatenate((fixed_nodes*2, fixed_nodes*2+1))
|
99
|
+
iforce = dom.get_nodenumber(N, np.arange(0, N+1))*2 + 1
|
100
|
+
sf = pym.Signal('f', np.zeros(dom.nnodes*2))
|
101
|
+
sf.state[iforce] = 1.0
|
102
|
+
|
103
|
+
sOmega = pym.Signal('omega', 0.1)
|
104
|
+
fn = pym.Network()
|
105
|
+
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), dom, bc=bc))
|
106
|
+
sM = fn.append(pym.AssembleMass(sx, pym.Signal('M'), dom, bc=bc))
|
107
|
+
sZ = fn.append(DynamicMatrix([sK, sM, sOmega], pym.Signal('Z')))
|
108
|
+
|
109
|
+
su = fn.append(pym.LinSolve([sZ, sf], pym.Signal('u')))
|
110
|
+
|
111
|
+
fn.response()
|
112
|
+
|
113
|
+
# spspla.eigsh(sK.state, M=sM.state, k=6, sigma=0.0)
|
114
|
+
|
115
|
+
self.assertTrue(np.allclose(sZ.state@su.state, sf.state)) # Check residual
|
116
|
+
# Check finite difference
|
117
|
+
# def tfn(x0, dx, df_an, df_fd): np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5)
|
118
|
+
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5))
|
119
|
+
pym.finite_difference(fn, [sx, sf, sOmega], su, test_fn=tfn, dx=1e-7, tol=1e-4, verbose=False)
|
120
|
+
|
121
|
+
|
122
|
+
class TestAssemblyAddValues(unittest.TestCase):
|
123
|
+
def test_finite_difference(self):
|
124
|
+
N = 2
|
125
|
+
# Set up the domain
|
126
|
+
domain = pym.DomainDefinition(N, N)
|
127
|
+
nodes_left = domain.get_nodenumber(0, np.arange(N + 1))
|
128
|
+
nodes_right = domain.get_nodenumber(N, np.arange(N + 1))
|
129
|
+
|
130
|
+
dofs_left = np.repeat(nodes_left * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
131
|
+
dofs_right = np.repeat(nodes_right * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
132
|
+
dofs_left_x = dofs_left[0::2]
|
133
|
+
dofs_left_y = dofs_left[1::2]
|
134
|
+
dof_input = dofs_left_y[0] # Input dofs for mechanism
|
135
|
+
dof_output = dofs_left_y[-1] # Output dofs for mechanism
|
136
|
+
|
137
|
+
prescribed_dofs = np.union1d(dofs_left_x, dofs_right)
|
138
|
+
|
139
|
+
# Setup rhs for two loadcases
|
140
|
+
f = np.zeros(domain.nnodes * 2, dtype=float)
|
141
|
+
f[dof_input] = 1.0
|
142
|
+
|
143
|
+
# Initial design
|
144
|
+
sx = pym.Signal('x', np.random.rand(domain.nel))
|
145
|
+
signal_force = pym.Signal('f', state=f)
|
146
|
+
# Setup optimization problem
|
147
|
+
network = pym.Network()
|
148
|
+
|
149
|
+
# Assembly
|
150
|
+
istiff = np.array([dof_input, dof_output])
|
151
|
+
sstiff = np.array([10.0, 10.0])
|
152
|
+
|
153
|
+
K_const = csc_matrix((sstiff, (istiff, istiff)), shape=(domain.nnodes * 2, domain.nnodes * 2))
|
154
|
+
signal_stiffness = network.append(pym.AssembleStiffness(sx, domain=domain, bc=prescribed_dofs, add_constant=K_const))
|
155
|
+
su = network.append(pym.LinSolve([signal_stiffness, signal_force], pym.Signal('u')))
|
156
|
+
sc = network.append(pym.EinSum(su, expression='i->'))
|
157
|
+
network.response()
|
158
|
+
|
159
|
+
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5))
|
160
|
+
|
161
|
+
pym.finite_difference(network, [sx], sc, test_fn=tfn, dx=1e-5, tol=1e-4, verbose=False)
|
162
|
+
|
163
|
+
def test_added_stiffness_on_ground(self):
|
164
|
+
N = 2
|
165
|
+
# Set up the domain
|
166
|
+
domain = pym.DomainDefinition(N, N)
|
167
|
+
nodes_left = domain.get_nodenumber(0, np.arange(N + 1))
|
168
|
+
nodes_right = domain.get_nodenumber(N, np.arange(N + 1))
|
169
|
+
|
170
|
+
dofs_left = np.repeat(nodes_left * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
171
|
+
dofs_right = np.repeat(nodes_right * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
172
|
+
dofs_left_x = dofs_left[0::2]
|
173
|
+
dofs_left_y = dofs_left[1::2]
|
174
|
+
dof_input = dofs_left_y[0] # Input dofs for mechanism
|
175
|
+
dof_output = dofs_left_y[-1] # Output dofs for mechanism
|
176
|
+
|
177
|
+
prescribed_dofs = np.union1d(dofs_left_x, dofs_right)
|
178
|
+
|
179
|
+
# Setup rhs for two loadcases
|
180
|
+
f = np.zeros(domain.nnodes * 2, dtype=float)
|
181
|
+
f[dof_input] = 1.0
|
182
|
+
|
183
|
+
# Initial design
|
184
|
+
sx = pym.Signal('x', np.random.rand(domain.nel))
|
185
|
+
signal_force = pym.Signal('f', state=f)
|
186
|
+
# Setup optimization problem
|
187
|
+
network = pym.Network()
|
188
|
+
|
189
|
+
# Assembly
|
190
|
+
istiff = np.array([dof_input, dof_output])
|
191
|
+
sstiff = np.array([10.0, 10.0])
|
192
|
+
|
193
|
+
K_const = csc_matrix((sstiff, (istiff, istiff)), shape=(domain.nnodes * 2, domain.nnodes * 2))
|
194
|
+
signal_stiffness = network.append(
|
195
|
+
pym.AssembleStiffness(sx, domain=domain, bc=prescribed_dofs, add_constant=K_const))
|
196
|
+
su = network.append(pym.LinSolve([signal_stiffness, signal_force], pym.Signal('u')))
|
197
|
+
sc = network.append(pym.EinSum(su, expression='i->'))
|
198
|
+
|
199
|
+
network.response()
|
200
|
+
|
201
|
+
network2 = pym.Network()
|
202
|
+
# Assembly
|
203
|
+
istiff = np.array([dof_input, dof_output, 4, 5])
|
204
|
+
sstiff = np.array([10.0, 10.0, 100.0, 100.0])
|
205
|
+
|
206
|
+
K_const1 = csc_matrix((sstiff, (istiff, istiff)), shape=(domain.nnodes * 2, domain.nnodes * 2))
|
207
|
+
signal_stiffness = network2.append(
|
208
|
+
pym.AssembleStiffness(sx, domain=domain, bc=prescribed_dofs, add_constant=K_const1))
|
209
|
+
su = network2.append(pym.LinSolve([signal_stiffness, signal_force], pym.Signal('u')))
|
210
|
+
sc2 = network2.append(pym.EinSum(su, expression='i->'))
|
211
|
+
|
212
|
+
network2.response()
|
213
|
+
|
214
|
+
npt.assert_allclose(sc.state, sc2.state)
|
215
|
+
|
216
|
+
|
217
|
+
class TestSystemOfEquations(unittest.TestCase):
|
218
|
+
def test_sparse_symmetric_real_compliance2d_single_load(self):
|
219
|
+
""" Test symmetric real sparse matrix (compliance in 2D)"""
|
220
|
+
N=10
|
221
|
+
# Set up the domain
|
222
|
+
domain = pym.DomainDefinition(N, N)
|
223
|
+
|
224
|
+
# node groups
|
225
|
+
nodes_left = domain.get_nodenumber(0, np.arange(N + 1))
|
226
|
+
nodes_right = domain.get_nodenumber(N, np.arange(N + 1))
|
227
|
+
|
228
|
+
# dof groups
|
229
|
+
dofs_left = np.repeat(nodes_left * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
230
|
+
dofs_right = np.repeat(nodes_right * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
231
|
+
dofs_left_horizontal = dofs_left[0::2]
|
232
|
+
dofs_left_vertical = dofs_left[1::2]
|
233
|
+
|
234
|
+
# free and prescribed dofs
|
235
|
+
all_dofs = np.arange(0, 2 * domain.nnodes)
|
236
|
+
prescribed_dofs = np.unique(np.hstack([dofs_left_horizontal, dofs_right, dofs_left_vertical]))
|
237
|
+
free_dofs = np.setdiff1d(all_dofs, prescribed_dofs)
|
238
|
+
|
239
|
+
# Setup solution vectors and rhs
|
240
|
+
ff = np.zeros_like(free_dofs, dtype=float)
|
241
|
+
ff[:] = np.random.rand(len(free_dofs))
|
242
|
+
u = np.zeros_like(all_dofs, dtype=float)
|
243
|
+
|
244
|
+
u[dofs_left_vertical] = np.random.rand(len(dofs_left_vertical))
|
245
|
+
up = u[prescribed_dofs]
|
246
|
+
|
247
|
+
sff = pym.Signal('ff', ff)
|
248
|
+
sup = pym.Signal('up', up)
|
249
|
+
|
250
|
+
fn = pym.Network()
|
251
|
+
sx = pym.Signal('x', np.random.rand(domain.nel))
|
252
|
+
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), domain))
|
253
|
+
su = fn.append(pym.SystemOfEquations([sK, sff, sup], free=free_dofs, prescribed=prescribed_dofs))
|
254
|
+
sc = fn.append(pym.EinSum([su[0], su[1]], expression='i,i->'))
|
255
|
+
fn.response()
|
256
|
+
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5))
|
257
|
+
pym.finite_difference(fn, [sx, sff, sup], sc, test_fn=tfn, dx=1e-5, tol=1e-4, verbose=False)
|
258
|
+
|
259
|
+
def test_sparse_symmetric_real_compliance2d_single_load_u(self):
|
260
|
+
""" Test symmetric real sparse matrix (compliance in 2D)"""
|
261
|
+
N = 10
|
262
|
+
# Set up the domain
|
263
|
+
domain = pym.DomainDefinition(N, N)
|
264
|
+
|
265
|
+
# node groups
|
266
|
+
nodes_left = domain.get_nodenumber(0, np.arange(N + 1))
|
267
|
+
nodes_right = domain.get_nodenumber(N, np.arange(N + 1))
|
268
|
+
|
269
|
+
# dof groups
|
270
|
+
dofs_left = np.repeat(nodes_left * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
271
|
+
dofs_right = np.repeat(nodes_right * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
272
|
+
dofs_left_horizontal = dofs_left[0::2]
|
273
|
+
dofs_left_vertical = dofs_left[1::2]
|
274
|
+
|
275
|
+
# free and prescribed dofs
|
276
|
+
all_dofs = np.arange(0, 2 * domain.nnodes)
|
277
|
+
prescribed_dofs = np.unique(np.hstack([dofs_left_horizontal, dofs_right, dofs_left_vertical]))
|
278
|
+
free_dofs = np.setdiff1d(all_dofs, prescribed_dofs)
|
279
|
+
|
280
|
+
# Setup solution vectors and rhs
|
281
|
+
ff = np.zeros_like(free_dofs, dtype=float)
|
282
|
+
ff[:] = np.random.rand(len(free_dofs))
|
283
|
+
u = np.zeros_like(all_dofs, dtype=float)
|
284
|
+
|
285
|
+
u[dofs_left_vertical] = np.random.rand(len(dofs_left_vertical))
|
286
|
+
up = u[prescribed_dofs]
|
287
|
+
|
288
|
+
sff = pym.Signal('ff', ff)
|
289
|
+
sup = pym.Signal('up', up)
|
290
|
+
|
291
|
+
fn = pym.Network()
|
292
|
+
sx = pym.Signal('x', np.random.rand(domain.nel))
|
293
|
+
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), domain))
|
294
|
+
su = fn.append(pym.SystemOfEquations([sK, sff, sup], free=free_dofs, prescribed=prescribed_dofs))
|
295
|
+
sc = fn.append(pym.EinSum([su[0]], expression='i->'))
|
296
|
+
fn.response()
|
297
|
+
|
298
|
+
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5))
|
299
|
+
|
300
|
+
pym.finite_difference(fn, [sx, sff, sup], sc, test_fn=tfn, dx=1e-5, tol=1e-4, verbose=False)
|
301
|
+
|
302
|
+
def test_sparse_symmetric_real_compliance2d_single_load_f(self):
|
303
|
+
""" Test symmetric real sparse matrix (compliance in 2D)"""
|
304
|
+
N = 10
|
305
|
+
# Set up the domain
|
306
|
+
domain = pym.DomainDefinition(N, N)
|
307
|
+
|
308
|
+
# node groups
|
309
|
+
nodes_left = domain.get_nodenumber(0, np.arange(N + 1))
|
310
|
+
nodes_right = domain.get_nodenumber(N, np.arange(N + 1))
|
311
|
+
|
312
|
+
# dof groups
|
313
|
+
dofs_left = np.repeat(nodes_left * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
314
|
+
dofs_right = np.repeat(nodes_right * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
315
|
+
dofs_left_horizontal = dofs_left[0::2]
|
316
|
+
dofs_left_vertical = dofs_left[1::2]
|
317
|
+
|
318
|
+
# free and prescribed dofs
|
319
|
+
all_dofs = np.arange(0, 2 * domain.nnodes)
|
320
|
+
prescribed_dofs = np.unique(np.hstack([dofs_left_horizontal, dofs_right, dofs_left_vertical]))
|
321
|
+
free_dofs = np.setdiff1d(all_dofs, prescribed_dofs)
|
322
|
+
|
323
|
+
# Setup solution vectors and rhs
|
324
|
+
ff = np.zeros_like(free_dofs, dtype=float)
|
325
|
+
ff[:] = np.random.rand(len(free_dofs))
|
326
|
+
u = np.zeros_like(all_dofs, dtype=float)
|
327
|
+
|
328
|
+
u[dofs_left_vertical] = np.random.rand(len(dofs_left_vertical))
|
329
|
+
up = u[prescribed_dofs]
|
330
|
+
|
331
|
+
sff = pym.Signal('ff', ff)
|
332
|
+
sup = pym.Signal('up', up)
|
333
|
+
|
334
|
+
fn = pym.Network()
|
335
|
+
sx = pym.Signal('x', np.random.rand(domain.nel))
|
336
|
+
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), domain))
|
337
|
+
su = fn.append(pym.SystemOfEquations([sK, sff, sup], free=free_dofs, prescribed=prescribed_dofs))
|
338
|
+
sc = fn.append(pym.EinSum([su[1]], expression='i->'))
|
339
|
+
fn.response()
|
340
|
+
|
341
|
+
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5))
|
342
|
+
|
343
|
+
pym.finite_difference(fn, [sx, sff, sup], sc, test_fn=tfn, dx=1e-5, tol=1e-4, verbose=False)
|
344
|
+
|
345
|
+
def test_sparse_symmetric_real_compliance2d_single_multi_load(self):
|
346
|
+
""" Test symmetric real sparse matrix (compliance in 2D)"""
|
347
|
+
N=10
|
348
|
+
# Set up the domain
|
349
|
+
domain = pym.DomainDefinition(N, N)
|
350
|
+
|
351
|
+
# node groups
|
352
|
+
nodes_left = domain.get_nodenumber(0, np.arange(N + 1))
|
353
|
+
nodes_right = domain.get_nodenumber(N, np.arange(N + 1))
|
354
|
+
|
355
|
+
# dof groups
|
356
|
+
dofs_left = np.repeat(nodes_left * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
357
|
+
dofs_right = np.repeat(nodes_right * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
358
|
+
dofs_left_horizontal = dofs_left[0::2]
|
359
|
+
dofs_left_vertical = dofs_left[1::2]
|
360
|
+
|
361
|
+
# free and prescribed dofs
|
362
|
+
all_dofs = np.arange(0, 2 * domain.nnodes)
|
363
|
+
prescribed_dofs = np.unique(np.hstack([dofs_left_horizontal, dofs_right, dofs_left_vertical]))
|
364
|
+
free_dofs = np.setdiff1d(all_dofs, prescribed_dofs)
|
365
|
+
|
366
|
+
# Setup solution vectors and rhs
|
367
|
+
ff = np.zeros((len(free_dofs), 2), dtype=float)
|
368
|
+
ff[:, :] = np.random.rand(np.shape(ff)[0], np.shape(ff)[1])
|
369
|
+
u = np.zeros((len(all_dofs), 2), dtype=float)
|
370
|
+
|
371
|
+
u[dofs_left_vertical, 0] = np.random.rand(len(dofs_left_vertical))
|
372
|
+
u[dofs_left_horizontal, 1] = np.random.rand(len(dofs_left_vertical))
|
373
|
+
up = u[prescribed_dofs, :]
|
374
|
+
|
375
|
+
sff = pym.Signal('ff', ff)
|
376
|
+
sup = pym.Signal('up', up)
|
377
|
+
|
378
|
+
fn = pym.Network()
|
379
|
+
sx = pym.Signal('x', np.random.rand(domain.nel))
|
380
|
+
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), domain))
|
381
|
+
su = fn.append(pym.SystemOfEquations([sK, sff, sup], free=free_dofs, prescribed=prescribed_dofs))
|
382
|
+
sc1 = fn.append(pym.EinSum([su[0][:, 0], su[1][:, 0]], expression='i,i->'))
|
383
|
+
sc2 = fn.append(pym.EinSum([su[0][:, 1], su[1][:, 1]], expression='i,i->'))
|
384
|
+
sc = fn.append(pym.MathGeneral([sc1, sc2], expression='inp0 + inp1'))
|
385
|
+
fn.response()
|
386
|
+
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5))
|
387
|
+
pym.finite_difference(fn, [sx, sff, sup], sc, test_fn=tfn, dx=1e-5, tol=1e-4, verbose=False)
|
388
|
+
|
389
|
+
def test_sparse_symmetric_real_compliance2d_single_multi_load_u(self):
|
390
|
+
""" Test symmetric real sparse matrix (compliance in 2D)"""
|
391
|
+
N = 10
|
392
|
+
# Set up the domain
|
393
|
+
domain = pym.DomainDefinition(N, N)
|
394
|
+
|
395
|
+
# node groups
|
396
|
+
nodes_left = domain.get_nodenumber(0, np.arange(N + 1))
|
397
|
+
nodes_right = domain.get_nodenumber(N, np.arange(N + 1))
|
398
|
+
|
399
|
+
# dof groups
|
400
|
+
dofs_left = np.repeat(nodes_left * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
401
|
+
dofs_right = np.repeat(nodes_right * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
402
|
+
dofs_left_horizontal = dofs_left[0::2]
|
403
|
+
dofs_left_vertical = dofs_left[1::2]
|
404
|
+
|
405
|
+
# free and prescribed dofs
|
406
|
+
all_dofs = np.arange(0, 2 * domain.nnodes)
|
407
|
+
prescribed_dofs = np.unique(np.hstack([dofs_left_horizontal, dofs_right, dofs_left_vertical]))
|
408
|
+
free_dofs = np.setdiff1d(all_dofs, prescribed_dofs)
|
409
|
+
|
410
|
+
# Setup solution vectors and rhs
|
411
|
+
ff = np.zeros((len(free_dofs), 2), dtype=float)
|
412
|
+
ff[:, :] = np.random.rand(np.shape(ff)[0], np.shape(ff)[1])
|
413
|
+
u = np.zeros((len(all_dofs), 2), dtype=float)
|
414
|
+
|
415
|
+
u[dofs_left_vertical, 0] = np.random.rand(len(dofs_left_vertical))
|
416
|
+
u[dofs_left_horizontal, 1] = np.random.rand(len(dofs_left_vertical))
|
417
|
+
up = u[prescribed_dofs, :]
|
418
|
+
|
419
|
+
sff = pym.Signal('ff', ff)
|
420
|
+
sup = pym.Signal('up', up)
|
421
|
+
|
422
|
+
fn = pym.Network()
|
423
|
+
sx = pym.Signal('x', np.random.rand(domain.nel))
|
424
|
+
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), domain))
|
425
|
+
su = fn.append(pym.SystemOfEquations([sK, sff, sup], free=free_dofs, prescribed=prescribed_dofs))
|
426
|
+
sc = fn.append(pym.EinSum(su[0], expression='ij->'))
|
427
|
+
fn.response()
|
428
|
+
|
429
|
+
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5))
|
430
|
+
|
431
|
+
pym.finite_difference(fn, [sx, sff, sup], sc, test_fn=tfn, dx=1e-5, tol=1e-4, verbose=False)
|
432
|
+
|
433
|
+
def test_sparse_symmetric_real_compliance2d_single_multi_load_f(self):
|
434
|
+
""" Test symmetric real sparse matrix (compliance in 2D)"""
|
435
|
+
N = 10
|
436
|
+
# Set up the domain
|
437
|
+
domain = pym.DomainDefinition(N, N)
|
438
|
+
|
439
|
+
# node groups
|
440
|
+
nodes_left = domain.get_nodenumber(0, np.arange(N + 1))
|
441
|
+
nodes_right = domain.get_nodenumber(N, np.arange(N + 1))
|
442
|
+
|
443
|
+
# dof groups
|
444
|
+
dofs_left = np.repeat(nodes_left * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
445
|
+
dofs_right = np.repeat(nodes_right * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
446
|
+
dofs_left_horizontal = dofs_left[0::2]
|
447
|
+
dofs_left_vertical = dofs_left[1::2]
|
448
|
+
|
449
|
+
# free and prescribed dofs
|
450
|
+
all_dofs = np.arange(0, 2 * domain.nnodes)
|
451
|
+
prescribed_dofs = np.unique(np.hstack([dofs_left_horizontal, dofs_right, dofs_left_vertical]))
|
452
|
+
free_dofs = np.setdiff1d(all_dofs, prescribed_dofs)
|
453
|
+
|
454
|
+
# Setup solution vectors and rhs
|
455
|
+
ff = np.zeros((len(free_dofs), 2), dtype=float)
|
456
|
+
ff[:, :] = np.random.rand(np.shape(ff)[0], np.shape(ff)[1])
|
457
|
+
u = np.zeros((len(all_dofs), 2), dtype=float)
|
458
|
+
|
459
|
+
u[dofs_left_vertical, 0] = np.random.rand(len(dofs_left_vertical))
|
460
|
+
u[dofs_left_horizontal, 1] = np.random.rand(len(dofs_left_vertical))
|
461
|
+
up = u[prescribed_dofs, :]
|
462
|
+
|
463
|
+
sff = pym.Signal('ff', ff)
|
464
|
+
sup = pym.Signal('up', up)
|
465
|
+
|
466
|
+
fn = pym.Network()
|
467
|
+
sx = pym.Signal('x', np.random.rand(domain.nel))
|
468
|
+
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), domain))
|
469
|
+
su = fn.append(pym.SystemOfEquations([sK, sff, sup], free=free_dofs, prescribed=prescribed_dofs))
|
470
|
+
sc = fn.append(pym.EinSum(su[1], expression='ij->'))
|
471
|
+
fn.response()
|
472
|
+
|
473
|
+
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5))
|
474
|
+
|
475
|
+
pym.finite_difference(fn, [sx, sff, sup], sc, test_fn=tfn, dx=1e-5, tol=1e-4, verbose=False)
|
476
|
+
|
477
|
+
|
478
|
+
if __name__ == '__main__':
|
479
|
+
unittest.main()
|
@@ -1,14 +1,16 @@
|
|
1
|
+
import inspect
|
2
|
+
import pathlib # For importing files
|
3
|
+
import sys
|
1
4
|
import unittest
|
5
|
+
from math import isclose
|
6
|
+
|
2
7
|
import numpy as np
|
3
8
|
import scipy as sp
|
4
|
-
from scipy.io import mmread # For importing files
|
5
|
-
import pathlib # For importing files
|
6
9
|
import scipy.sparse as spsp
|
7
10
|
import scipy.sparse.linalg as spspla
|
8
|
-
import
|
11
|
+
from scipy.io import mmread # For importing files
|
12
|
+
|
9
13
|
import pymoto as pym
|
10
|
-
import sys
|
11
|
-
import inspect
|
12
14
|
|
13
15
|
try:
|
14
16
|
import cvxopt
|
@@ -408,208 +410,5 @@ class TestPardiso(GenericTestSolvers):
|
|
408
410
|
]
|
409
411
|
|
410
412
|
|
411
|
-
""" ------------------ TEST LINSOLVE MODULE -------------------- """
|
412
|
-
|
413
|
-
|
414
|
-
class DynamicMatrix(pym.Module):
|
415
|
-
alpha = 0.5
|
416
|
-
beta = 0.5
|
417
|
-
|
418
|
-
def _response(self, K, M, omega):
|
419
|
-
return K + 1j * omega * (self.alpha * M + self.beta * K) - omega ** 2 * M
|
420
|
-
|
421
|
-
def _sensitivity(self, dZ):
|
422
|
-
K, M, omega = [s.state for s in self.sig_in]
|
423
|
-
dK = np.real(dZ) - (omega * self.beta) * np.imag(dZ)
|
424
|
-
dM = (-omega ** 2) * np.real(dZ) - (omega * self.alpha) * np.imag(dZ)
|
425
|
-
dZrM = np.real(dZ).contract(M)
|
426
|
-
dZiK = np.imag(dZ).contract(K)
|
427
|
-
dZiM = np.imag(dZ).contract(M)
|
428
|
-
domega = -self.beta * dZiK - self.alpha * dZiM - 2 * omega * dZrM
|
429
|
-
return dK, dM, domega
|
430
|
-
|
431
|
-
class TestLinSolveModule_sparse(unittest.TestCase):
|
432
|
-
# # ------------- Symmetric -------------
|
433
|
-
def test_symmetric_real_compliance2d(self):
|
434
|
-
""" Test symmetric real sparse matrix (compliance in 2D)"""
|
435
|
-
N = 10 # Number of elements
|
436
|
-
dom = pym.DomainDefinition(N, N)
|
437
|
-
sx = pym.Signal('x', np.random.rand(dom.nel))
|
438
|
-
fixed_nodes = dom.get_nodenumber(0, np.arange(0, N+1))
|
439
|
-
bc = np.concatenate((fixed_nodes*2, fixed_nodes*2+1))
|
440
|
-
# Setup different rhs types
|
441
|
-
iforce_x = dom.get_nodenumber(N, np.arange(0, N + 1)) * 2 # Force in x-direction
|
442
|
-
iforce_y = dom.get_nodenumber(N, np.arange(0, N + 1)) * 2 + 1 # Force in y-direction
|
443
|
-
|
444
|
-
force_vecs = dict()
|
445
|
-
|
446
|
-
# Single force
|
447
|
-
f = np.zeros(dom.nnodes*2)
|
448
|
-
f[iforce_x] = 1.0
|
449
|
-
force_vecs['single_real'] = f
|
450
|
-
|
451
|
-
# Multiple rhs
|
452
|
-
f = np.zeros((dom.nnodes * 2, 2))
|
453
|
-
f[iforce_x, 0] = 1.0
|
454
|
-
f[iforce_y, 1] = 1.0
|
455
|
-
force_vecs['multiple_real'] = f
|
456
|
-
|
457
|
-
for k, f in force_vecs.items():
|
458
|
-
with self.subTest(f"RHS-{k}"):
|
459
|
-
sf = pym.Signal('f', f)
|
460
|
-
|
461
|
-
fn = pym.Network()
|
462
|
-
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), dom, bc=bc))
|
463
|
-
su = fn.append(pym.LinSolve([sK, sf], pym.Signal('u')))
|
464
|
-
|
465
|
-
fn.response()
|
466
|
-
|
467
|
-
self.assertTrue(np.allclose(sK.state@su.state, sf.state)) # Check residual
|
468
|
-
# Check finite difference
|
469
|
-
# def tfn(x0, dx, df_an, df_fd): np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5)
|
470
|
-
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5))
|
471
|
-
pym.finite_difference(fn, [sx, sf], su, test_fn=tfn, dx=1e-5, tol=1e-4, verbose=False)
|
472
|
-
|
473
|
-
def test_symmetric_real_compliance3d(self):
|
474
|
-
""" Test symmetric real sparse matrix (compliance in 3D)"""
|
475
|
-
N = 3 # Number of elements
|
476
|
-
dom = pym.DomainDefinition(N, N, N)
|
477
|
-
sx = pym.Signal('x', np.random.rand(dom.nel))
|
478
|
-
jfix, kfix = np.meshgrid(np.arange(0, N+1), np.arange(0, N+1), indexing='ij')
|
479
|
-
fixed_nodes = dom.get_nodenumber(0, jfix, kfix).flatten()
|
480
|
-
bc = np.concatenate((fixed_nodes*3, fixed_nodes*3+1, fixed_nodes*3+2))
|
481
|
-
iforce = dom.get_nodenumber(N, np.arange(0, N+1), np.arange(0, N+1))*3 + 1
|
482
|
-
sf = pym.Signal('f', np.zeros(dom.nnodes*3))
|
483
|
-
sf.state[iforce] = 1.0
|
484
|
-
|
485
|
-
fn = pym.Network()
|
486
|
-
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), dom, bc=bc))
|
487
|
-
su = fn.append(pym.LinSolve([sK, sf], pym.Signal('u')))
|
488
|
-
|
489
|
-
fn.response()
|
490
|
-
|
491
|
-
self.assertTrue(np.allclose(sK.state@su.state, sf.state)) # Check residual
|
492
|
-
# Check finite difference
|
493
|
-
# def tfn(x0, dx, df_an, df_fd): np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5)
|
494
|
-
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=2e-3, atol=1e-5))
|
495
|
-
pym.finite_difference(fn, [sx, sf], su, test_fn=tfn, dx=1e-5, tol=1e-4, verbose=False)
|
496
|
-
|
497
|
-
def test_symmetric_complex_dyncompliance2d(self):
|
498
|
-
""" Test symmetric complex sparse matrix (dynamic compliance in 2D)"""
|
499
|
-
N = 5 # Number of elements
|
500
|
-
dom = pym.DomainDefinition(N, N)
|
501
|
-
sx = pym.Signal('x', np.random.rand(dom.nel))
|
502
|
-
fixed_nodes = dom.get_nodenumber(0, np.arange(0, N+1))
|
503
|
-
bc = np.concatenate((fixed_nodes*2, fixed_nodes*2+1))
|
504
|
-
iforce = dom.get_nodenumber(N, np.arange(0, N+1))*2 + 1
|
505
|
-
sf = pym.Signal('f', np.zeros(dom.nnodes*2))
|
506
|
-
sf.state[iforce] = 1.0
|
507
|
-
|
508
|
-
sOmega = pym.Signal('omega', 0.1)
|
509
|
-
fn = pym.Network()
|
510
|
-
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), dom, bc=bc))
|
511
|
-
sM = fn.append(pym.AssembleMass(sx, pym.Signal('M'), dom, bc=bc))
|
512
|
-
sZ = fn.append(DynamicMatrix([sK, sM, sOmega], pym.Signal('Z')))
|
513
|
-
|
514
|
-
su = fn.append(pym.LinSolve([sZ, sf], pym.Signal('u')))
|
515
|
-
|
516
|
-
fn.response()
|
517
|
-
|
518
|
-
# spspla.eigsh(sK.state, M=sM.state, k=6, sigma=0.0)
|
519
|
-
|
520
|
-
self.assertTrue(np.allclose(sZ.state@su.state, sf.state)) # Check residual
|
521
|
-
# Check finite difference
|
522
|
-
# def tfn(x0, dx, df_an, df_fd): np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5)
|
523
|
-
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5))
|
524
|
-
pym.finite_difference(fn, [sx, sf, sOmega], su, test_fn=tfn, dx=1e-7, tol=1e-4, verbose=False)
|
525
|
-
|
526
|
-
|
527
|
-
class TestSystemOfEquations(unittest.TestCase):
|
528
|
-
def test_sparse_symmetric_real_compliance2d_single_load(self):
|
529
|
-
""" Test symmetric real sparse matrix (compliance in 2D)"""
|
530
|
-
N=10
|
531
|
-
# Set up the domain
|
532
|
-
domain = pym.DomainDefinition(N, N)
|
533
|
-
|
534
|
-
# node groups
|
535
|
-
nodes_left = domain.get_nodenumber(0, np.arange(N + 1))
|
536
|
-
nodes_right = domain.get_nodenumber(N, np.arange(N + 1))
|
537
|
-
|
538
|
-
# dof groups
|
539
|
-
dofs_left = np.repeat(nodes_left * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
540
|
-
dofs_right = np.repeat(nodes_right * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
541
|
-
dofs_left_horizontal = dofs_left[0::2]
|
542
|
-
dofs_left_vertical = dofs_left[1::2]
|
543
|
-
|
544
|
-
# free and prescribed dofs
|
545
|
-
all_dofs = np.arange(0, 2 * domain.nnodes)
|
546
|
-
prescribed_dofs = np.unique(np.hstack([dofs_left_horizontal, dofs_right, dofs_left_vertical]))
|
547
|
-
free_dofs = np.setdiff1d(all_dofs, prescribed_dofs)
|
548
|
-
|
549
|
-
# Setup solution vectors and rhs
|
550
|
-
ff = np.zeros_like(free_dofs, dtype=float)
|
551
|
-
ff[:] = np.random.rand(len(free_dofs))
|
552
|
-
u = np.zeros_like(all_dofs, dtype=float)
|
553
|
-
|
554
|
-
u[dofs_left_vertical] = np.random.rand(len(dofs_left_vertical))
|
555
|
-
up = u[prescribed_dofs]
|
556
|
-
|
557
|
-
sff = pym.Signal('ff', ff)
|
558
|
-
sup = pym.Signal('up', up)
|
559
|
-
|
560
|
-
fn = pym.Network()
|
561
|
-
sx = pym.Signal('x', np.random.rand(domain.nel))
|
562
|
-
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), domain))
|
563
|
-
su = fn.append(pym.SystemOfEquations([sK, sff, sup], free=free_dofs, prescribed=prescribed_dofs))
|
564
|
-
sc = fn.append(pym.EinSum([su[0], su[1]], expression='i,i->'))
|
565
|
-
fn.response()
|
566
|
-
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5))
|
567
|
-
pym.finite_difference(fn, [sx, sff, sup], sc, test_fn=tfn, dx=1e-5, tol=1e-4, verbose=False)
|
568
|
-
|
569
|
-
def test_sparse_symmetric_real_compliance2d_single_multi_load(self):
|
570
|
-
""" Test symmetric real sparse matrix (compliance in 2D)"""
|
571
|
-
N=10
|
572
|
-
# Set up the domain
|
573
|
-
domain = pym.DomainDefinition(N, N)
|
574
|
-
|
575
|
-
# node groups
|
576
|
-
nodes_left = domain.get_nodenumber(0, np.arange(N + 1))
|
577
|
-
nodes_right = domain.get_nodenumber(N, np.arange(N + 1))
|
578
|
-
|
579
|
-
# dof groups
|
580
|
-
dofs_left = np.repeat(nodes_left * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
581
|
-
dofs_right = np.repeat(nodes_right * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
582
|
-
dofs_left_horizontal = dofs_left[0::2]
|
583
|
-
dofs_left_vertical = dofs_left[1::2]
|
584
|
-
|
585
|
-
# free and prescribed dofs
|
586
|
-
all_dofs = np.arange(0, 2 * domain.nnodes)
|
587
|
-
prescribed_dofs = np.unique(np.hstack([dofs_left_horizontal, dofs_right, dofs_left_vertical]))
|
588
|
-
free_dofs = np.setdiff1d(all_dofs, prescribed_dofs)
|
589
|
-
|
590
|
-
# Setup solution vectors and rhs
|
591
|
-
ff = np.zeros((len(free_dofs), 2), dtype=float)
|
592
|
-
ff[:, :] = np.random.rand(np.shape(ff)[0], np.shape(ff)[1])
|
593
|
-
u = np.zeros((len(all_dofs), 2), dtype=float)
|
594
|
-
|
595
|
-
u[dofs_left_vertical, 0] = np.random.rand(len(dofs_left_vertical))
|
596
|
-
u[dofs_left_horizontal, 1] = np.random.rand(len(dofs_left_vertical))
|
597
|
-
up = u[prescribed_dofs, :]
|
598
|
-
|
599
|
-
sff = pym.Signal('ff', ff)
|
600
|
-
sup = pym.Signal('up', up)
|
601
|
-
|
602
|
-
fn = pym.Network()
|
603
|
-
sx = pym.Signal('x', np.random.rand(domain.nel))
|
604
|
-
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), domain))
|
605
|
-
su = fn.append(pym.SystemOfEquations([sK, sff, sup], prescribed=prescribed_dofs))
|
606
|
-
sc1 = fn.append(pym.EinSum([su[0][:, 0], su[1][:, 0]], expression='i,i->'))
|
607
|
-
sc2 = fn.append(pym.EinSum([su[0][:, 1], su[1][:, 1]], expression='i,i->'))
|
608
|
-
sc = fn.append(pym.MathGeneral([sc1, sc2], expression='inp0 + inp1'))
|
609
|
-
fn.response()
|
610
|
-
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5))
|
611
|
-
pym.finite_difference(fn, [sx, sff, sup], sc, test_fn=tfn, dx=1e-5, tol=1e-4, verbose=False)
|
612
|
-
|
613
|
-
|
614
413
|
if __name__ == '__main__':
|
615
414
|
unittest.main()
|
@@ -0,0 +1,55 @@
|
|
1
|
+
import unittest
|
2
|
+
import numpy as np
|
3
|
+
import pymoto as pym
|
4
|
+
import numpy.testing as npt
|
5
|
+
|
6
|
+
np.random.seed(0)
|
7
|
+
|
8
|
+
|
9
|
+
class TestStaticCondensation(unittest.TestCase):
|
10
|
+
def test_real_symmetric(self):
|
11
|
+
""" Test symmetric real sparse matrix (compliance in 2D)"""
|
12
|
+
N = 20
|
13
|
+
# Set up the domain
|
14
|
+
domain = pym.DomainDefinition(N, N)
|
15
|
+
|
16
|
+
# node groups
|
17
|
+
nodes_left = domain.get_nodenumber(0, np.arange(N + 1))
|
18
|
+
nodes_right = domain.get_nodenumber(N, np.arange(N + 1))
|
19
|
+
|
20
|
+
# dof groups
|
21
|
+
dofs_left = np.repeat(nodes_left * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
22
|
+
dofs_right = np.repeat(nodes_right * 2, 2, axis=-1) + np.tile(np.arange(2), N + 1)
|
23
|
+
|
24
|
+
# free and prescribed dofs
|
25
|
+
all_dofs = np.arange(0, 2 * domain.nnodes)
|
26
|
+
prescribed_dofs = dofs_right
|
27
|
+
main_dofs = dofs_left[0::2]
|
28
|
+
free_dofs = np.setdiff1d(all_dofs, np.unique(np.hstack([main_dofs, prescribed_dofs])))
|
29
|
+
|
30
|
+
fn = pym.Network()
|
31
|
+
sx = pym.Signal('x', np.random.rand(domain.nel))
|
32
|
+
sK = fn.append(pym.AssembleStiffness(sx, pym.Signal('K'), domain))
|
33
|
+
su = fn.append(pym.StaticCondensation([sK], free=free_dofs, main=main_dofs))
|
34
|
+
sc = fn.append(pym.EinSum([su], expression='ij->'))
|
35
|
+
fn.response()
|
36
|
+
|
37
|
+
# Check result
|
38
|
+
fn1 = pym.Network()
|
39
|
+
sK1 = fn1.append(pym.AssembleStiffness(sx, domain=domain, bc=np.concatenate([main_dofs[1:], prescribed_dofs])))
|
40
|
+
sf = pym.Signal('f', np.zeros(domain.nnodes*2))
|
41
|
+
sf.state[main_dofs[0]] = 1.0
|
42
|
+
su1 = fn1.append(pym.LinSolve([sK1, sf]))
|
43
|
+
suKu1 = fn1.append(pym.EinSum([su1, sf], expression='i,i->'))
|
44
|
+
fn1.response()
|
45
|
+
|
46
|
+
npt.assert_allclose(suKu1.state, 1/su.state[0, 0])
|
47
|
+
|
48
|
+
def tfn(x0, dx, df_an, df_fd): self.assertTrue(np.allclose(df_an, df_fd, rtol=1e-3, atol=1e-5))
|
49
|
+
|
50
|
+
pym.finite_difference(fn, [sx], sc, test_fn=tfn, dx=1e-5, tol=1e-4, verbose=False)
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
if __name__ == '__main__':
|
55
|
+
unittest.main()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|