Exact connections with the null-space method#
The domains of a MultiDomain assembly were originally connected
with penalty stiffnesses, see MultiDomain.get_kC_conn() and
[castro2017Multidomain], still available with conn_method='penalty'.
The penalty method adds, for each quantity \(q_k\)
that must be continuous across a connection, the energy:
where \(k_k\) is the translation or rotation penalty constant, 'kt' or
'kr', and the integral is along the connected edges, or over the area of
the connection 'SB'. The continuity is then only approximate. Low penalty
constants make the assembly too flexible, and high ones make the stiffness
matrix ill-conditioned. The default constants of calc_kt_kr() are a
compromise, and they can still lead to errors of a few percent, see
Comparison with the penalty method.
With conn_method='null-space', the default, the connections are imposed
exactly, as with penalty constants going to infinity, without any penalty
constant:
md = MultiDomain(panels, conn) # conn_method='null-space'
and the penalty method is selected with:
md = MultiDomain(panels, conn, conn_method='penalty')
The connections that can be imposed with the null-space method are given in
nullspace.NULL_SPACE_FUNCS: 'SSxcte', 'SSycte',
'BFxcte', 'BFycte', 'BFxycte' and 'SB', for all the models, see
Quantities imposed. The damaged connection 'SB_TSL' represents
the physical stiffness of a cohesive interface, see Cohesive zone model of the multi-domain approach,
and it keeps its penalty stiffness also with conn_method='null-space'.
Theory#
The quantities \(q_k\) of Eq. (1) are linear combinations of the Ritz constants \(\boldsymbol{c}\) of the two domains. Since Bardell’s functions are polynomials, see Bardell’s hierarchical functions, the difference \(q_k^{(1)} - q_k^{(2)}\) is a polynomial along the connection, of the natural coordinate \(\xi\) or \(\eta\) shared by the two domains. It vanishes everywhere when it vanishes at as many points as the number of its coefficients, and the connections become the homogeneous linear constraints:
where each row of \(\boldsymbol{B}\) is \(q_k^{(1)} - q_k^{(2)}\) at one Gauss-Legendre
point along the connection, or over the area of 'SB', see
nullspace.constraint_matrix(). The first four functions of Bardell
are cubic and the function \(i \ge 4\) has the order \(i\), so that
\(\max(m_1, m_2, 4)\) points are used along \(x\) and \(\max(n_1, n_2, 4)\) along
\(y\). The zero energy of Eq. (1) and the constraints of
Eq. (2) are equivalent: the vectors with zero penalty energy
are exactly those that satisfy the constraints, which is verified for every
connection and model in tests/multidomain/test_null_space.py.
Why not Lagrange multipliers#
The constraints could be imposed with Lagrange multipliers \(\boldsymbol{\lambda}\), which add one unknown per constraint and give the system:
This matrix is indefinite. For the linear buckling and frequency analyses
the geometric stiffness matrix and the mass matrix have zero rows and
columns for the multipliers, giving infinite eigenvalues that the
shift-and-invert eigenvalue solvers of structsolve do not handle. Instead
of adding unknowns, the null-space method removes them, giving the same
solution.
Elimination of the constraints#
All the vectors that satisfy Eq. (2) are written with a basis \(\boldsymbol{Z}\) of the null space of \(\boldsymbol{B}\), \(\boldsymbol{B} \boldsymbol{Z} = \boldsymbol{0}\):
where \(\boldsymbol{c}_r\) are the independent Ritz constants. Substituting Eq. (3) into the total potential energy gives the reduced system:
where \(\boldsymbol{K}\), \(\boldsymbol{K}_G\), \(\boldsymbol{M}\) and \(\boldsymbol{F}\) are the matrices and vectors of the
domains, which do not contain the connections imposed exactly. The reduced
stiffness matrix is symmetric and positive definite whenever the connected
assembly has no rigid-body motion, like the stiffness matrix of the penalty
method, and the static, linear buckling, frequency and non-linear solvers of
structsolve are used unchanged. The constraints are linear in \(\boldsymbol{c}\),
therefore \(\boldsymbol{Z}\) does not change during a non-linear analysis:
\(\boldsymbol{Z}\) is built by nullspace.null_space_basis(), one connection at a
time:
the rows of \(\boldsymbol{B}\) of the connection are normalized to a unit norm and written in the independent Ritz constants of the connections already processed, \(\boldsymbol{B} \boldsymbol{Z}\);
the rank of the constraints is revealed with a QR decomposition with column pivoting, \(\boldsymbol{B} \boldsymbol{Z} \boldsymbol{P} = \boldsymbol{Q} \boldsymbol{U}\), with the permutation \(\boldsymbol{P}\), the orthogonal factor \(\boldsymbol{Q}\) and the upper triangular factor \(\boldsymbol{U}\), and each pivot larger than the tolerance
tolgives one independent constraint;the Ritz constants of the pivot columns are eliminated, \(\boldsymbol{c}_e = -\boldsymbol{U}_{11}^{-1} \boldsymbol{U}_{12} \boldsymbol{c}_k\), where \(\boldsymbol{c}_k\) are the remaining constants, and \(\boldsymbol{Z}\) is updated.
Redundant constraints are found in step 2 and dropped. They happen for
instance at a corner shared by four domains, around a closed cylinder, or
where a boundary condition flag already removes the term that a constraint
would remove. Bardell’s functions of order 5 and higher vanish together with
their slope at the ends of the domain, \(\xi = \pm 1\), so the constraints of
an edge involve only the first four functions across it, and \(\boldsymbol{Z}\) has very
few non-zero terms: the elimination is similar to the assembly of the
hierarchical elements of the p-version of the finite element method. A
connection along an interior line, e.g. 'BFycte' at the middle of the
base, involves all the functions across the line.
The terms removed by the boundary condition flags are zero rows and columns of the matrices of the domains, as with the penalty method, and remain in \(\boldsymbol{c}_r\) with zero rows and columns in the reduced matrices.
\(\boldsymbol{Z}\) is stored in MultiDomain.Z and reused while the connections, the
number of terms and the boundary condition flags do not change, see
MultiDomain.get_Z().
Quantities imposed#
The quantities imposed by each connection are those of the corresponding
penalty kernels, with p1 and p2 the first and second panels of the
connection. For 'SB', the two panels share the axes \(x, y, z\), \(z\) being
normal to their mid-surfaces, p1 is the panel on the positive side of the
interface along \(z\) and p2 the one on the negative side, e.g. a stiffener
base on the side of positive \(z\) of a skin is p1 and the skin p2,
see MultiDomain.get_kC_conn():
Connection |
Classical laminated plate theory (3 DOFs) |
Shear deformation theories (5 DOFs) |
|---|---|---|
|
\(u, v, w, w_{,x}\), see |
\(u, v, w, \phi_x, \Phi_y\), and \(w_{,x}\) for the TSDT, see
|
|
\(u, v, w, w_{,y}\), see |
\(u, v, w, \phi_x, \Phi_y\), and \(w_{,y}\) for the TSDT, see
|
|
\(u_1 = u_2\), \(v_1 = w_2\), \(w_1 = -v_2\), \(w_{1,y} - v_1/r_1 =
w_{2,y} - v_2/r_2\), see |
\(u_1 = u_2\), \(v_1 = w_2\), \(w_1 = -v_2\), \(\Phi_{y1} = \Phi_{y2}\) |
|
\(u_1 = w_2\), \(v_1 = v_2\), \(w_1 = -u_2\), \(w_{1,x} = w_{2,x}\), see
|
\(u_1 = w_2\), \(v_1 = v_2\), \(w_1 = -u_2\), \(\phi_{x1} = \phi_{x2}\) |
|
\(u_1 = -\cos\theta \, v_2 + \sin\theta \, w_2\), \(v_1 = u_2\), \(w_1 =
\sin\theta \, v_2 + \cos\theta \, w_2\), \(-w_{1,x} = w_{2,y} -
v_2/r_2\), with the angle \(\theta\) of the key |
the same displacements, \(\phi_{x1} = -\Phi_{y2}\) |
|
\(u_1(z_1) = u_2(z_2)\), \(v_1(z_1) = v_2(z_2)\), \(w_1 = w_2\) at the
interface, \(z_1 = d_1 - h_1/2\) of |
\(u, v, w\) at the interface, \(z = d_1 - h_1/2\) of |
where \(\Phi_y = \phi_y + v/r\) with the Sanders-Koiter kinematics and \(\Phi_y
= \phi_y\) otherwise, and the terms \(v/r\) and \(z/r\) of the classical
laminated plate theory are present only with the Sanders-Koiter kinematics.
The quantities of 'SB' are the same for all the models, the
displacements of the faces in contact, see
kCsdt._sb_components(). The keys
'kt' and 'kr' of the connections are not used, apart from 'kr'
of 'SB' with the shear deformation theories, which tells whether the
rotations are connected. The rotations of the classical laminated plate
theory, \(-w_{,x}\) and \(-w_{,y} + v/r\), follow from \(w\), already tied by \(w_1
= w_2\): tying them too adds redundant constraints, or, with the
Sanders-Koiter kinematics and distinct reference surfaces, the constraint
\((z_1 - z_2) w_{,y} = 0\), i.e. \(w_{,y} = 0\) over the whole area, see
kCsdt._sb_components().
Usage#
The matrices and vectors computed by MultiDomain have the size of
all the Ritz constants and do not contain the connections imposed exactly:
solving them directly would leave the domains disconnected. They are reduced
with MultiDomain.reduce() before solving, and the solutions are expanded to
all the Ritz constants with MultiDomain.expand(), which are used by
the post-processing methods, e.g. MultiDomain.uvw(),
MultiDomain.stress() and MultiDomain.plot(). With
conn_method='penalty', \(\boldsymbol{Z}\) is the identity matrix, so that the same
code runs with both methods.
Linear static analysis:
from structsolve import solve
md = MultiDomain(panels, conn)
kC = md.calc_kC()
fext = md.calc_fext()
c = md.expand(solve(md.reduce(kC), md.reduce(fext)))
Linear buckling analysis with the pre-buckling state of a static analysis:
from structsolve import lb, static
kC = md.reduce(md.calc_kC())
incs, cs = static(kC, md.reduce(md.calc_fext()))
c = md.expand(cs[0])
kG = md.reduce(md.calc_kG(c=c))
eigvals, eigvecs = lb(kC, kG)
eigvecs = md.expand(eigvecs)
Frequency analysis:
from structsolve import freq
eigvals, eigvecs = freq(md.reduce(md.calc_kC()), md.reduce(md.calc_kM()))
eigvecs = md.expand(eigvecs)
Geometrically non-linear analysis, with the functions of the reduced problem
given by MultiDomain.get_reduced_functions():
from structsolve import Analysis
an = Analysis(*md.get_reduced_functions())
an.static(NLgeom=True)
cs = [md.expand(c_r) for c_r in an.cs]
The functions create_cylinder(),
create_cylinder_blade_stiffened(), the analyses of
panels.multidomain.cylinder and
panels.multidomain.cylinder_blade_stiffened, and
tstiff2d_1stiff_freq(), tstiff2d_1stiff_compression() and
tstiff2d_1stiff_flutter() take the argument conn_method.
Comparison with the penalty method#
The results below are verified in tests/multidomain/test_null_space.py.
The penalty method is more flexible than the exact connections, and its
error decreases with the inverse of the penalty constants. For a thin plate
divided in 2x2 domains, with the classical laminated plate theory, the error
of the first frequencies is 1.3% with the default constants of
calc_kt_kr(), and it becomes 0.13%, 0.013% and 0.0013% with 10, 100 and
1000 times higher constants. The condition number of the stiffness matrix
grows with the penalty constants: for the 4-domain plate of
tests/multidomain/test_conn_4panels_kt_kr.py, the reduced stiffness matrix
of the null-space method has a condition number about 600 times lower than
the stiffness matrix of the penalty method with the default constants, and
\(6 \times 10^4\) times lower than with 100 times higher constants.
Case |
Penalty, default constants |
Penalty, higher constants |
Null-space |
|---|---|---|---|
Frequencies of a thick plate divided in 2x2 domains, FSDT, error with respect to the single domain (Noor 1973) |
\(7.8 \times 10^{-2}\) |
\(9.5 \times 10^{-5}\) (1000 times) |
\(3 \times 10^{-12}\) |
Laminates \([0/90]\) over \([0/90]\) with |
\(8 \times 10^{-6}\) (\(10^5\) times) |
\(6 \times 10^{-13}\) |
|
Same, large deflection with the von Kármán kinematics |
\(10^{-15}\) |
||
Blade-stiffened cylinder, buckling load with the pre-buckling state of a static analysis, \(N_{xx}\) (N/m) |
-39993.8 |
-41694.9 (1000 times) |
-41696.7 |
Closed cylinder, buckling load \(N_{xx}\) (N/m) |
-47056.0 |
-47113.0 |
|
T-stiffened panel, first frequency (rad/s) |
48.2733 |
48.3858 (100 times) |
48.3875 |
The default penalty constants underestimate the buckling load of the blade-stiffened cylinder by 4%, whereas the higher constants converge to the result of the null-space method.
Prescribed displacements imposed exactly#
The prescribed displacements of Shell.add_point_pd(),
Shell.add_distr_pd_fixed_x() and Shell.add_distr_pd_fixed_y()
are imposed by default with the penalty method: their forces \(k u_p\) enter
the external force vector and the penalty stiffnesses, e.g. of
fkCpd(), fkCld_xcte() and fkCld_ycte(), for the models
with 3 and 5 degrees of freedom per term, must be added to the stiffness
matrix by the user, symmetrized with
structsolve.sparseutils.finalize_symmetric_matrix():
@pytest.mark.parametrize('model', ['plate_clpt_donnell', 'plate_fsdt_donnell',
'plate_tsdt_donnell'])
def test_penalty_reaches_prescribed_values(model):
r"""A simply supported plate with `u, v, w` prescribed at a point, `w`
along a line `x = x_l` and `v` along a line `y = y_l`, imposed with the
penalty stiffnesses of the kernels and the forces of :func:`.shell_fext`,
reaches the prescribed values, as with the null-space method"""
a, b = 0.4, 0.3
xp, yp = 0.13, 0.21
xl, yl = 0.25, 0.09
up, vp, wp = 1e-5, -2e-5, 1e-3
wl = lambda y: 2e-3*4*y*(b - y)/b**2
vl = lambda x: 3e-5*4*x*(a - x)/a**2
#NOTE the error of the penalty method decreases with 1/k
k = 1e4*70e9*1e-3
p = plate(a, b, m=8, n=8, model=model)
p.add_point_pd(xp, yp, k, up, k, vp, k, wp)
p.add_distr_pd_fixed_x(xl, kw=k, funcw=wl)
p.add_distr_pd_fixed_y(yl, kv=k, funcv=vl)
md = MultiDomain([p], conn=[])
size = md.get_size()
kCp = finalize_symmetric_matrix(
fkCpd(k, k, k, p, xp, yp, size, p.row_start, p.col_start)
+ fkCld_xcte(0., 0., k, p, xl, size, p.row_start, p.col_start)
+ fkCld_ycte(0., k, 0., p, yl, size, p.row_start, p.col_start))
c = solve(md.calc_kC() + kCp, md.calc_fext(), silent=True)
c_ns = MultiDomain([p], conn=[], pd_method='null-space').linear_static()
ys = np.linspace(0, b, 7)
xs = np.linspace(0, a, 7)
for ci, tol in ((c_ns, 1e-10), (c, 1e-2)):
u, v, w = displacements(p, ci, [xp], [yp])
assert np.allclose([u[0], v[0], w[0]], [up, vp, wp], rtol=tol)
w = displacements(p, ci, np.full_like(ys, xl), ys)[2]
assert np.allclose(w, wl(ys), rtol=0, atol=tol*2e-3)
v = displacements(p, ci, xs, np.full_like(xs, yl))[1]
assert np.allclose(v, vl(xs), rtol=0, atol=tol*3e-5)
assert np.linalg.norm(c - c_ns) < 1e-2*np.linalg.norm(c_ns)
With pd_method='null-space' they are
imposed exactly, as inhomogeneous linear constraints eliminated together
with the connections, without any penalty constant:
md = MultiDomain(panels, conn, pd_method='null-space')
A single panel is wrapped in a MultiDomain without connections,
MultiDomain([shell], conn=[], pd_method='null-space'). Both settings are
independent: the connections may be imposed with either conn_method.
The default pd_method='penalty' keeps the previous behaviour.
The same methods of Shell define the prescribed displacements,
the in-plane ones at the reference surface or at the thickness coordinate
of the keyword z, e.g. at the faces of a face-clamped edge, see
Face-clamped edges.
With pd_method='null-space' the values of the penalty constants are not
used: a component is prescribed when its penalty constant is neither
None nor zero and its value is given, the same rule as for the penalty
method, where a null penalty constant prescribes nothing. The rotations
of the normal at the reference surface \(z = 0\) can also be prescribed:
\(-w_{,x}\) and \(-w_{,y}\) for the classical laminated plate theory, and for
the shear deformation theories \(\theta_x\) and \(\theta_y\) of Eq.
(5), the rotations also tied by the connections, which
are the fields \(\phi_x\) and \(\phi_y\) for the FSDT and for the TSDT without
offset; the rotation about \(x\) includes \(v/r\) with the Sanders-Koiter
kinematics. With an offset \(d\), the TSDT rotations \(\phi_x\) and \(\phi_y\)
are those at the mid-surface, \(z = d\), and the prescribed rotations at \(z =
0\) include the transverse shear, \(\theta_x = \phi_x - (4 d^2/h^2)(\phi_x +
w_{,x})\), see Prescribed rotations. The rotations are
prescribed only with pd_method='null-space', through the keyword
arguments kphix,
phixp, kphiy and phiyp of Shell.add_point_pd(), or
kphix, funcphix, kphiy and funcphiy of the distributed ones.
For example, a uniform end-shortening \(\delta\) of a plate, incremented with
the load factor, with the lateral contraction free:
p.add_distr_pd_fixed_x(0., ku=1., funcu=lambda y: 0.)
p.add_distr_pd_fixed_x(p.a, ku=1., funcu=lambda y: -delta, cte=False)
p.add_point_pd(0., 0., 0, None, 1., 0., 0, None) # v = 0 at a corner
Theory#
The connections give the homogeneous constraints \(\boldsymbol{B}_h \boldsymbol{c} = \boldsymbol{0}\) of Eq. (2), and the prescribed displacements the inhomogeneous constraints:
where \(\bar{\boldsymbol{d}}_0\) collects the constant prescribed values, cte=True, and
\(\bar{\boldsymbol{d}}_1\) those scaled by the load factor \(\lambda\), cte=False. Each row
of \(\boldsymbol{B}_d\) is the prescribed component at a point, or at a Gauss-Legendre
point along a prescribed line, with max(n, 4) points along \(y\) for the
lines x = cte and max(m, 4) along \(x\) for the lines y = cte, see
nullspace.pd_constraint_matrix(). As for the connections, the
component along the line is a polynomial with that many coefficients: the
constraint holds along the whole line when the prescribed function is a
polynomial of degree up to max(n, 4) - 1, e.g. a uniform or a linear
end-shortening; otherwise it holds at the Gauss-Legendre points, the
prescribed function being interpolated by the approximation functions.
With \(\boldsymbol{B} = [\boldsymbol{B}_h; \boldsymbol{B}_d]\), every admissible configuration is
where \(\boldsymbol{Z}\) spans the null space of \(\boldsymbol{B}\) and \(\boldsymbol{c}_{p0}\), \(\boldsymbol{c}_{p1}\)
are particular solutions, \(\boldsymbol{B} \boldsymbol{c}_{p0} = \{\boldsymbol{0}; \bar{\boldsymbol{d}}_0\}\) and \(\boldsymbol{B}
\boldsymbol{c}_{p1} = \{\boldsymbol{0}; \bar{\boldsymbol{d}}_1\}\), see MultiDomain.get_cp(). They are
obtained in the same elimination as \(\boldsymbol{Z}\), see
nullspace.Elimination: the constraints are eliminated one block
at a time, the connections first, each block written in the independent
Ritz constants left by the previous ones, \(\boldsymbol{B} (\boldsymbol{Z} \boldsymbol{c}_r + \boldsymbol{c}_p) =
\boldsymbol{b}\), with the right-hand side \(\boldsymbol{b}\) of the block, and
decomposed with a QR decomposition with column pivoting, which
gives both the new \(\boldsymbol{Z}\) and the correction of \(\boldsymbol{c}_p\) for the
right-hand side \(\boldsymbol{b} - \boldsymbol{B} \boldsymbol{c}_p\). The data of the decompositions are
kept, such that \(\boldsymbol{c}_{p0}\) and \(\boldsymbol{c}_{p1}\) are computed again without
any decomposition when only the prescribed values change, e.g. in a loop
over the end-shortening; \(\boldsymbol{Z}\) changes only with the positions and the
components prescribed.
Substituting Eq. (6) into the residual of the unconstrained
system, \(\boldsymbol{R}(\boldsymbol{c}, \lambda) = \boldsymbol{F}_{int}(\boldsymbol{c}, \lambda) - \lambda \boldsymbol{F}_{ext}\),
and projecting it with \(\boldsymbol{Z}^\top\) gives the reduced problem solved by
structsolve:
\(\boldsymbol{K}_{T, r}\) is the exact Jacobian, and \(-\partial \boldsymbol{R}_r/\partial \lambda\)
depends on the configuration and on the load factor. structsolve 0.7.0
passes the load factor to calc_fext when it declares the keyword
argument lbd, and uses \(-\partial \boldsymbol{R}_r/\partial \lambda\) in the predictors
and in the constraint of the arc-length methods, and \(\lambda \partial
\boldsymbol{R}_r/\partial \lambda\) as a reference of the convergence criteria, the
relative one and Crisfield’s, the default since structsolve 0.9.0,
because under a prescribed displacement alone both \(\boldsymbol{Z}^\top \boldsymbol{F}_{ext}\) and
\(\boldsymbol{Z}^\top \boldsymbol{F}_{int}\) vanish at equilibrium, see
MultiDomain.get_reduced_functions().
The linear static analysis solves
see MultiDomain.linear_static(), the loads of calc_fext(inc=1.)
being scaled by \(\lambda\) as in the analyses of structsolve. The linear
buckling analysis about the pre-buckling state \(\hat{\boldsymbol{c}}(\lambda) =
\hat{\boldsymbol{c}}_0 + \lambda \hat{\boldsymbol{c}}_1\), with \(\hat{\boldsymbol{c}}_0\) due to the
constant prescribed displacements and \(\hat{\boldsymbol{c}}_1\) due to the load and
the incremented ones, solves \((\boldsymbol{K}_r + \boldsymbol{K}_{G, r}(\hat{\boldsymbol{c}}_0) + \lambda
\boldsymbol{K}_{G, r}(\hat{\boldsymbol{c}}_1)) \boldsymbol{\phi}_r = \boldsymbol{0}\), see
MultiDomain.linear_buckling(); the constant stress resultants
Nxx, Nyy and Nxy of the panels are part of the constant state,
in \(\boldsymbol{K}_{G, r}(\hat{\boldsymbol{c}}_0)\) only. The modes satisfy the
homogeneous constraints, \(\boldsymbol{\phi} = \boldsymbol{Z} \boldsymbol{\phi}_r\): under displacement
control the prescribed components of the modes are zero, whereas under the
equivalent load they are free. For a flat plate the modes of the linear
buckling analysis are out of plane and both critical loads are equal, for a
cylinder, whose modes couple the membrane and bending displacements, the
critical end-shortening of a cylinder of 5 panels is 3.8% higher than the
end-shortening of the critical axial load with x2u=1.
Reactions#
With \(\boldsymbol{r}(\boldsymbol{c}, \lambda) = \boldsymbol{F}_{int}(\boldsymbol{c}) - \lambda \boldsymbol{F}_{ext}\) the residual of the unconstrained system, the force applied by the supports on the structure, the generalized reaction work-conjugate to the set \(j\) of prescribed displacements, the constant or the incremented ones, is
see MultiDomain.reaction_pd(). It does not depend on the choice of
the particular solution, since \(\boldsymbol{Z}^\top \boldsymbol{r} = \boldsymbol{0}\) at equilibrium.
\(P_j \delta\) is the work of the reactions for the prescribed values
multiplied by \(\delta\): for a uniform end-shortening \(\delta\) of an edge
with the total axial reaction \(N\), \(P_1 = N \delta\), and \(P_1 = \partial
U/\partial \lambda\) is the force conjugate to the load factor of a
displacement-controlled analysis.
The resultant of the reactions along selected prescribed lines or points,
in the direction \(u\), \(v\) or \(w\) of the panels, is \(F_u = \boldsymbol{c}_u^\top \boldsymbol{r}\),
with \(\boldsymbol{c}_u\) a solution of the constraints for a unit translation \(u = 1\)
of the selected prescriptions that prescribe \(u\), all the other prescribed
values being zero, see MultiDomain.reaction_resultant(). A line whose
ends are shared with a prescription that is not selected, directly or
through a connection, has no resultant of its own, the force at the shared
corner being undetermined, and an error is raised: for instance, the total
axial force at the end of a cylinder made of several panels is the
resultant of all the lines of that end. The sign is that of the force
applied by the support on the structure, \(F_u = -N\) at the shortened end.
Redundant and inconsistent prescriptions#
As for the connections, a constraint that is a linear combination of the
previous ones is redundant and dropped, e.g. at the corner where a
prescribed edge meets a connection or another prescribed edge, or on a term
already removed by a boundary condition flag. A redundant constraint is
dropped only when its right-hand side is consistent, i.e. satisfied by the
particular solution within rhs_tol of MultiDomain.get_Z(),
\(10^{-8}\) by default, relative to the largest prescribed value. Otherwise
MultiDomain.get_cp() raises a ValueError that names the
prescription, the panel, the boundary condition flags that restrain it, and
the connections and prescriptions imposed before it on the same or
connected panels:
a prescribed edge meeting another one with a different value at a corner, or across a connection;
a non-zero value on a term removed by a boundary condition flag, e.g. \(u = \delta\) along
x = awithx2u = 0: the message tells to set the flag to 1, whereas \(u = 0\) is consistent and dropped;the constant and the incremented sets must each be consistent by themselves, since the constraints hold for every \(\lambda\): the same line cannot have a constant and an incremented prescription of the same component;
a function that is not a polynomial, prescribed along a line whose end is restrained by a flag, is consistent only within the interpolation error, which may exceed
rhs_tolwith few terms.
API#
MultiDomain(..., pd_method='null-space')MultiDomain.get_Z()andMultiDomain.get_cp(): \(\boldsymbol{Z}\), \(\boldsymbol{c}_{p0}\) and \(\boldsymbol{c}_{p1}\), also in the attributesZ,c_p0andc_p1MultiDomain.expand():expand(c_r, lbd)returns the configuration of Eq. (6),expand(c_r)returns \(\boldsymbol{Z} \boldsymbol{c}_r\), e.g. for the modesMultiDomain.get_reduced_functions(): the callables ofstructsolve.Analysisfor the non-linear analyses, Newton-Raphson and the arc-length methods, requiringstructsolve>=0.7.0. The linear static analysis ofAnalysis.static(NLgeom=False)cannot contain the constant prescribed displacements and raises an error, seeMultiDomain.linear_static()MultiDomain.linear_static()andMultiDomain.linear_buckling()MultiDomain.reaction_pd()andMultiDomain.reaction_resultant()MultiDomain.calc_fext()excludes the forces of the prescribed displacements withpd_method='null-space'
Example#
A simply supported plate under a uniform end-shortening, with the lateral edges free in-plane: linear buckling, then the post-buckling path under displacement control, with a small lateral load as imperfection:
import numpy as np
from structsolve import Analysis
from panels.shell import Shell
from panels.multidomain import MultiDomain
a, b, h = 0.3, 0.2, 1e-3
p = Shell(a=a, b=b, m=8, n=8, model='plate_clpt_donnell', stack=[0],
plyt=h, laminaprop=(70e9, 0.3))
for edge in ('x1', 'x2', 'y1', 'y2'):
setattr(p, edge + 'u', 1.) # u and v free at the edges, w = 0
setattr(p, edge + 'v', 1.)
md = MultiDomain([p], conn=[], pd_method='null-space')
def prescribe(delta):
p.clear_disps()
p.add_distr_pd_fixed_x(0., ku=1., funcu=lambda y: 0.)
p.add_distr_pd_fixed_x(a, ku=1., funcu=lambda y: -delta, cte=False)
p.add_point_pd(0., 0., 0, None, 1., 0., 0, None)
# linear buckling: the eigenvalue is the critical end-shortening
prescribe(1.)
eigvals, modes = md.linear_buckling(num_eigvalues=3)
delta_cr = eigvals[0]
# post-buckling up to three times the critical end-shortening
prescribe(3*delta_cr)
p.add_point_load(a/2, b/2, 0., 0., 2.)
an = Analysis(*md.get_reduced_functions())
an.NL_method = 'arc_length_riks'
an.maxArcLength = 100.
an.static(NLgeom=True)
cs = [md.expand(c_r, lbd) for c_r, lbd in zip(an.cs, an.increments)]
N = [-md.reaction_resultant(c, [(p, 'x', a)], lbd)[0]
for c, lbd in zip(cs, an.increments)]
The arc length of structsolve is scaled by the linear solution, which
has no out-of-plane displacement under an end-shortening, therefore the
post-buckling path is long in these units, and maxArcLength may need to
be increased. The tests of tests/multidomain/test_null_space_pd.py give
more examples, among them:
@pytest.mark.parametrize('layout', ['single', '2x2'])
@pytest.mark.parametrize('model', ['plate_clpt_donnell', 'plate_fsdt_donnell'])
def test_membrane_patch_test_isotropic(layout, model):
r"""Uniform end-shortening `\delta`, with the lateral contraction free:
exact uniform strain, `u = -\delta x/a`, `v = \nu \delta y/a`, and the
reactions `P = N \delta` and `F_u = -N`, `N = E h b \delta/a`"""
a, b, h, delta = 1., 0.6, 2e-3, 1e-3
if layout == 'single':
ps = [plate(a, b, h, model=model)]
conn = []
loaded, fixed = ps, ps
else:
ps, conn = two_by_two(a, b, h=h, model=model)
loaded, fixed = (ps[1], ps[3]), (ps[0], ps[2])
for p in fixed:
end_shortening(p, delta, x2=False)
for p in loaded:
end_shortening(p, delta, x1=False)
ps[0].add_point_pd(0., 0., 0, None, 1., 0., 0, None)
md = MultiDomain(ps, conn, pd_method='null-space')
c = md.linear_static()
for p in ps:
X, Y, f = fields(p, c)
assert np.allclose(f['u'], -delta*X/a, rtol=0, atol=1e-12*delta)
assert np.allclose(f['v'], NU*delta*Y/a, rtol=0, atol=1e-12*delta)
# w is decoupled, but its round-off is the membrane round-off
# amplified by the stiffness ratio, of order (a/h)**2
assert np.allclose(f['w'], 0, rtol=0, atol=1e-8*delta)
N = E*h*b*delta/a
P = md.reaction_pd(c, NLgeom=False)
assert np.isclose(P['inc'], N*delta, rtol=1e-10)
assert P['cte'] == 0.
F = md.reaction_resultant(c, [(p, 'x', p.a) for p in loaded],
NLgeom=False)
assert np.isclose(F[0], -N, rtol=1e-10)
assert np.all(np.isnan(F[1:]))
F = md.reaction_resultant(c, [(p, 'x', 0.) for p in fixed], NLgeom=False)
assert np.isclose(F[0], N, rtol=1e-10)
@pytest.mark.parametrize('method', ['NR', 'arc_length_riks',
'arc_length_crisfield'])
@pytest.mark.slow
def test_nonlinear_displacement_control(method):
r"""Post-buckling under displacement control: every converged increment
satisfies `\|\boldsymbol{Z}^\top \boldsymbol{r}\| \leq` ``relTOL`` `\|\lambda \partial \boldsymbol{R}_r/\partial
\lambda\|`, the methods agree, and the reaction is below the linear one"""
md, p, delta = plate_postbuckling()
an = Analysis(*md.get_reduced_functions())
an.NL_method = method
an.initialInc = 0.2
#NOTE the arc length is scaled by the linear solution, without the
# out-of-plane deflection of the post-buckling path
an.maxArcLength = 100.
an.static(NLgeom=True, silent=True)
assert np.isclose(an.increments[-1], 1.)
fext, fint, kC, kG = md.get_reduced_functions()
Z = md.get_Z()
for c_r, lbd in zip(an.cs, an.increments):
c = md.expand(c_r, lbd)
r = md._reaction_residual(c, lbd, True)
q = fext(inc=1., c=c_r, lbd=lbd)
assert np.linalg.norm(Z.T @ r) <= 1.01e-6*np.linalg.norm(lbd*q)
P = md.reaction_pd(c, 1.)['inc']/delta
N_lin = E*p.plyt*p.b*delta/p.a
assert 0.5 < P/N_lin < 0.7
assert np.isclose(P, 2420., rtol=2e-3)
Penalty vs null-space displacement control#
The penalty method imposes a prescribed displacement \(u_p\) with the energy
\(\frac{1}{2} k \int (u - u_p)^2\), i.e. a spring of stiffness \(k\) between the
structure and the support: the structure is more flexible, the prescribed
value is not reached, and the error decreases with \(1/k\), while the
condition number of the stiffness matrix grows with \(k\). The null-space
method has no penalty constant. The cases below were run with the models of
tests/multidomain/test_null_space_pd.py, the penalty stiffnesses being
those of fkCld_xcte() and fkCpd(); \(E h\) is the membrane
stiffness of the isotropic plate and \(A_{11}\) that of the laminate of the
cylinder.
Accuracy and conditioning, linear static. Plate under a uniform
end-shortening with the lateral contraction restrained along \(y = 0, b\),
m = n = 10, 300 Ritz constants, 280 independent ones with the null-space
method. Errors of the Ritz constants and of the axial reaction, and condition
number of the stiffness matrix; the reduced stiffness matrix of the
null-space method has the condition number \(1.4 \times 10^{11}\):
\(k / (E h)\) |
\(10\) |
\(10^2\) |
\(10^4\) |
\(10^6\) |
\(10^8\) |
\(10^{10}\) |
|---|---|---|---|---|---|---|
error of \(\boldsymbol{c}\) |
\(1.6 \times 10^{-1}\) |
\(2.0 \times 10^{-2}\) |
\(2.0 \times 10^{-4}\) |
\(2.0 \times 10^{-6}\) |
\(2.0 \times 10^{-8}\) |
\(3.2 \times 10^{-9}\) |
error of the reaction |
\(-1.8 \times 10^{-1}\) |
\(-2.2 \times 10^{-2}\) |
\(-2.2 \times 10^{-4}\) |
\(-2.2 \times 10^{-6}\) |
\(4.4 \times 10^{-9}\) |
\(-2.3 \times 10^{-6}\) |
condition number |
\(2.5 \times 10^{12}\) |
\(2.0 \times 10^{13}\) |
\(2.0 \times 10^{15}\) |
\(2.0 \times 10^{17}\) |
\(2.0 \times 10^{19}\) |
\(1.9 \times 10^{21}\) |
The reaction of the penalty method, \(k \int (u_p - u)\), here integrated with 40 Gauss-Legendre points along \(x = a\), is the product of a large constant and a small difference, and loses accuracy by cancellation for the highest constants: from \(k/(E h) = 10^8\) its error no longer decreases as \(1/k\), and depends on the round-off, e.g. on the number of integration points.
Linear buckling. Cylinder of 5 panels under a uniform end-shortening,
see test_cylinder_end_shortening. The error of the critical
end-shortening of the penalty method is \(1.4 \times 10^{-1}\), \(1.3 \times
10^{-2}\), \(1.3 \times 10^{-3}\), \(1.3 \times 10^{-4}\) and \(1.3 \times
10^{-6}\) for \(k/A_{11} = 10, 10^2, 10^3, 10^4, 10^6\), from above, since the
penalty springs shorten the cylinder less than the prescribed value. Both
methods take about 0.5 s, from the creation of the model to the critical
end-shortening. With \(k/A_{11} = 10^6\) the sparse eigenvalue solver of
structsolve 0.8.0 failed its verification of the residuals of the
eigenpairs, because of the conditioning, and structsolve.lb fell back to
the dense solver with a DenseFallbackWarning. Since structsolve
0.9.0 the relative residual of the verification is at least
RESIDUAL_FLOOR = 1e-8 times a scale of the norms of the matrices, and
the eigenpairs of the sparse solver are accepted.
Non-linear analysis. Post-buckling of the plate at twice the critical
end-shortening, m = n = 8, with a lateral point load of 30 N at the
centre as imperfection, as in test_nonlinear_reaction_matches_penalty,
Newton-Raphson with 8 increments. Error of the axial reaction of the penalty
method, relative to that of the null-space method, with Crisfield’s
convergence criterion, Analysis.convergence = 'crisfield', the default of
structsolve 0.9.0 with relTOL = 1e-9, and with the relative
criterion, 'relative', the only one up to structsolve 0.8.0, whose
default was relTOL = 1e-6:
criterion, \(k / (E h)\) |
\(10^2\) |
\(10^4\) |
\(10^6\) |
\(10^8\) |
|---|---|---|---|---|
|
\(-3.6 \times 10^{-2}\) |
\(-3.8 \times 10^{-4}\) |
\(-3.8 \times 10^{-6}\) |
\(-5.6 \times 10^{-8}\) |
|
\(-3.6 \times 10^{-2}\) |
\(-4.0 \times 10^{-4}\) |
\(-1.9 \times 10^{-5}\) |
\(-1.5 \times 10^{-5}\) |
|
\(-3.6 \times 10^{-2}\) |
\(-5.5 \times 10^{-4}\) |
\(-2.1 \times 10^{-1}\) |
\(1.1\) |
|
\(-3.6 \times 10^{-2}\) |
\(-3.8 \times 10^{-4}\) |
\(-1.9 \times 10^{-5}\) |
\(-1.7 \times 10^{-4}\) |
Both criteria compare the norm of the residual with the largest norm of the
references \(\lambda \boldsymbol{F}_{ext}\), \(\boldsymbol{F}_{int}\) and, when they apply, the
external force vector of the increment of the arc-length methods and
\(\lambda \partial \boldsymbol{R}/\partial \lambda\), see structsolve.convergence. For
the penalty method \(\lambda \boldsymbol{F}_{ext}\) and \(\boldsymbol{F}_{int}\) contain the
penalty forces \(k u_p\). The relative criterion measures them with the
Euclidean norm over all the degrees of freedom, which grows with \(k\): with
relTOL = 1e-6 the highest penalty constants give the largest errors,
21% and 110%, after 12 and 8 evaluations of the internal force vector in
the 8 increments, against 28 for \(k/(E h) = 10^2\), and even with relTOL =
1e-10 the error grows again from \(k/(E h) = 10^6\) to \(10^8\). Crisfield’s
criterion uses the norm \(\|\boldsymbol{v}\|_{\boldsymbol{d}} = (\sum_j v_j^2/|d_j|)^{1/2}\)
over the free degrees of freedom, the entries \(d_j\) of the diagonal scaling
vector \(\boldsymbol{d}\) being the diagonal of the linear stiffness matrix
calc_kC(), which contains the penalty stiffness, such that the scaled
norm of the penalty forces grows with \(\sqrt{k}\), by a factor of 10 for
each factor of 100 of \(k\) in this case: with relTOL = 1e-9 the error
decreases as \(1/k\) up to \(k/(E h) = 10^8\), whereas with relTOL = 1e-6 it
stops decreasing at about \(2 \times 10^{-5}\). The null-space method has no
such trade-off, its reference being \(\lambda \partial \boldsymbol{R}_r/\partial \lambda\),
see Prescribed displacements imposed exactly: its reaction is the same to \(3 \times 10^{-10}\)
with the four combinations of the table.
Run time. For the linear analyses both methods have the same cost: the
elimination of the constraints is a small fraction of the assembly. For the
non-linear analysis above the run time is dominated by the assembly of the
tangent stiffness matrix. With panels 0.14.0 and structsolve 0.9.0,
on the same machine, medians of 15 runs of Analysis.static: 0.60 s with
the null-space method and Crisfield’s criterion with relTOL = 1e-9, and
0.50 s and 0.66 s with the relative criterion with relTOL = 1e-6 and
1e-10, against 0.46 s, 0.24 s and 0.45 s with the penalty method and
\(k/(E h) = 10^4\). With
Crisfield’s criterion the null-space method factorizes the tangent stiffness
matrix 29 times, against 25 for the penalty method. Its \(\partial
\boldsymbol{R}_r/\partial \lambda\) requires the product \(\boldsymbol{K}_T \boldsymbol{c}_{p1}\), computed
without assembling \(\boldsymbol{K}_T\), see Tangent stiffness matrix and its product by a vector, at the cost of one
evaluation of the internal force vector, and structsolve, since 0.8.0,
evaluates it only together with the tangent stiffness matrix. With
panels 0.12.0 before these two improvements and the tabulation of the
approximation functions, see Approximation functions tabulated at the integration points, the same analyses with the
relative criterion and relTOL = 1e-6 and 1e-10 took 12.6 s and
15.8 s against 4.4 s and 9.2 s.
When to use each method. The null-space method is exact, needs no
penalty constant, keeps the conditioning of the model, and gives reactions
that do not depend on any constant; it is recommended for displacement
control, e.g. the end-shortening of panels and shells, and for linear
buckling under prescribed displacements. The penalty method remains the
default for backward compatibility, and it is needed when the prescribed
displacement represents a physical stiffness, e.g. a load cell or a loading
fixture, or for models that rely on the penalty forms, e.g. the cohesive zone
examples of Cohesive zone model of the multi-domain approach, whose reaction is given by
MultiDomain.reaction_line_pd_xcte().
Limitations#
A connection imposed exactly constrains the domains exactly as the penalty method does with infinite penalty constants, which may constrain the approximation more than the penalty method with the default constants does:
When the two domains have a different number of terms along the connection, the terms of the trace of the richer domain that the other domain cannot represent are forced to vanish.
The boundary condition flags at the ends of an edge of one domain are imposed on the other domain.
With
'SB'and the classical laminated plate theory, \(u_1 - z_1 w_{,x} = u_2 - z_2 w_{,x}\), \(z_1\) and \(z_2\) being the faces in contact measured from the reference surface of each domain. When the reference surfaces of the two domains are apart, \(z_1 \ne z_2\), e.g. without offsets, restraining the normal in-plane displacement of an edge in both domains, e.g. \(u = 0\) at \(x = 0\), also restrains the rotation \(w_{,x}\) at that edge, which is clamped. With offsets that place both reference surfaces at the same height, \(z_1 = z_2\) and \(u_1 = u_2\). The in-plane displacements of the bonded laminates must be restrained consistently with the single laminate that they represent, e.g. restraining only the tangential displacement of the edges, as intest_skin_base_nonlinear_equals_single_laminate.
The prescribed displacements of Shell.add_point_pd(),
Shell.add_distr_pd_fixed_x() and Shell.add_distr_pd_fixed_y()
remain penalty stiffnesses by default, and are imposed exactly with
pd_method='null-space', see Prescribed displacements imposed exactly. The
traction-separation law of 'SB_TSL' remains a penalty stiffness.
The rank of the constraints is found with the tolerance tol of
MultiDomain.get_Z(), 1e-9 by default, which applies to the
constraints normalized to a unit norm. The independent constraints have
pivots many orders of magnitude above the round-off errors of the redundant
ones, and a warning is issued when this gap is smaller than \(10^4\).
API#
The methods of MultiDomain for the null-space method are
MultiDomain.get_Z(), MultiDomain.reduce(),
MultiDomain.expand() and MultiDomain.get_reduced_functions(),
and for the prescribed displacements MultiDomain.get_cp(),
MultiDomain.linear_static(), MultiDomain.linear_buckling(),
MultiDomain.reaction_pd() and MultiDomain.reaction_resultant(),
see Prescribed displacements imposed exactly.