Tria3DSG - Triangular element with discrete shear gap (pyfe3d.tria3dsg)#
Three-node triangular shell element with 6 degrees-of-freedom per node, \(u\), \(v\), \(w\), \(r_x\), \(r_y\), \(r_z\), first-order shear deformation theory, and a transverse shear field obtained with the discrete shear gap (DSG) method:
Bletzinger, K.-U., Bischoff, M., and Ramm, E., 2000, “A unified approach for shear-locking-free triangular and rectangular shell finite elements”, Computers & Structures, 75(3), pp. 321-334. https://doi.org/10.1016/S0045-7949(99)00140-6
Why this element exists#
pyfe3d.tria3r.Tria3R takes its transverse shear from a single point
at the centroid, which still locks, and cures the locking by dividing the
transverse shear stiffnesses by \(1 + \alpha \ell^2/h^2\) with
\(\alpha = 0.7\), following Lyly, Stenberg and Vihinen (1993). That works for
plate bending, where the error it introduces is \(O(1/n^2)\) in the number of
elements per span, but it leaves the element unable to reproduce a state of
constant transverse shear: measured on an 8 by 8 patch, its stiffness
against a transverse displacement field with all rotations at zero is
\(3 \times 10^{-1}\), \(2 \times 10^{-2}\) and \(1 \times 10^{-3}\) of the
pyfe3d.quad4.Quad4 value at \(\ell/h\) of 1.25, 6.25 and 25. A
Donnell geometric stiffness matrix works on \(\partial w/\partial x\), which
is exactly that subspace, so thin-shell buckling suffers badly. See
tests/test_transverse_shear_stiffness.py.
The DSG removes the locking in the operator instead of in the constitutive matrix, so no stabilisation parameter is needed and the transverse shear stiffness is used as the laminate gives it.
The algebraic stabilisation of Bischoff and Bletzinger (2004) and Castro et
al. (2019) is nevertheless available through the alpha_shear_locking
attribute, which is 0. by default and then has no effect whatsoever. It
is there because in those papers the factor is applied on top of a discrete
shear gap field exactly like this one, so setting it reproduces the element
they describe, and because on a coarse mesh of a thin plate it does improve
the answer: \(\alpha = 0.07\), the lower bound of the range recommended by
Castro et al., takes the first natural frequency of a \(5 \times 7\) mesh from
\(12.06\) to \(7.93\) per cent above the analytical value and the buckling load
of a thin plate from \(+1.71\) to \(+0.11\) per cent. That is the opposite of
its role in pyfe3d.tria3r.Tria3R, where the same factor is applied
to a centroid-sampled shear field and has to do the unlocking itself, which
is why that element defaults to \(\alpha = 0.7\).
The default here is zero because the factor is unbounded in \(\ell/h\), so any
non-zero \(\alpha\) removes nearly all of the transverse shear stiffness on a
practical shell mesh and with it the three properties listed above. The
alpha_shear_locking attribute documentation carries the measurements and
the recommended value.
The discrete shear gap#
With \(\pmb{\phi} = (-r_y, r_x)\) the transverse shear strains of this element’s convention are
that is \(\pmb{\gamma} = \nabla w - \pmb{\phi}\). The shear gap \(\Delta w\) is the function whose gradient is the shear strain, and its nodal values are obtained by integrating along the straight edges from node 1, with \(\pmb{\phi}\) interpolated linearly,
The assumed shear strain is then the gradient of the interpolated gap,
which is constant over the element because the shape functions are linear.
Four properties follow, all of them verified symbolically and then
numerically in tests/test_tria3dsg.py:
the three rigid-body modes that involve \(w\) give \(\pmb{\gamma} = 0\) exactly;
a state of constant transverse shear is reproduced exactly, which is what Tria3R cannot do;
a state of constant curvature gives \(\pmb{\gamma} = 0\) exactly, so the element develops no parasitic shear in pure bending. This is the locking-free property, and it is the reason no stabilisation parameter appears anywhere in this element;
the operator is a linear functional of the nodal values only and is constant over the element, so it is formed once per element.
The gap is integrated away from one node, which makes the plain operator
depend on which node that is: measured on an irregular triangle, the element
matrix moves by 14 and 17 per cent under the two cyclic relabellings, against
\(3 \times 10^{-16}\) for pyfe3d.tria3r.Tria3R, whose shear comes from
a symmetric centroid evaluation. A mesh generator orders the nodes of a
triangle as it pleases, so this element averages the three operators, one per
starting node. The averaging costs nothing: each of the three is separately
exact for all of the properties listed above, and those are linear in the
operator, so the mean inherits them, which the tests confirm. What it buys is
invariance, measured at \(6 \times 10^{-16}\) in
tests/test_tria3dsg.py::test_node_numbering_invariance.
Membrane, bending and drilling#
The in-plane and curvature fields are the constant-strain ones of the linear
triangle, so no locking arises there and one point integrates them exactly.
The drilling rotation \(r_z\) is given stiffness with the regularisation of
Hughes and Brezzi (1989), tying \(r_z\) to the in-plane rotation
\(\theta_z = (v_{,x} - u_{,y})/2\), exactly as in pyfe3d.quad4.Quad4
and pyfe3d.tria3r.Tria3R:
Hughes, T. J. R., and Brezzi, F., 1989, “On drilling degrees of freedom,” Computer Methods in Applied Mechanics and Engineering, 72(1), pp. 105-121. https://doi.org/10.1016/0045-7825(89)90124-2
drilling_model = 0, the default, uses the physics-based coefficient
\(\gamma_{r_z} = A_{66}\), a modulus of the laminate and not an adjustable
parameter. Any other value uses the fictitious penalty
\(\gamma_{r_z} = K6ROT \cdot 10^{-6} A_{66}\).
Note
This element does not carry the hierarchical edge-mode
enrichment of Allman (1984) that pyfe3d.tria3r.Tria3R and
pyfe3d.quad4.Quad4 apply to the in-plane displacement field, so
its membrane response in in-plane bending is the stiff one of the
constant-strain triangle. The element is aimed at the transverse shear
behaviour; for in-plane dominated problems prefer the enriched elements.
Geometrically nonlinear analysis#
The element carries the von Karman membrane strains,
with the same split of the tangent stiffness as
pyfe3d.quad4.Quad4 and pyfe3d.tria3r.Tria3R,
which is the exact Jacobian of the internal forces of
Tria3DSG.update_fint() called with nonlinear=1. \([K_G]\) is left
homogeneous of degree one in the displacements so that a linear buckling
analysis can use it directly, the stress of the nonlinear membrane strain
being collected in \([K_{CNL}]\) instead. See Tria3DSG.update_KCNL().
Two things are simpler here than in the quadrilateral. First, the transverse shear strains of first-order shear deformation theory have no von Karman terms, so the discrete shear gap operator appears in \([K_{C_0}]\) alone and the nonlinear methods never touch it. Second, every operator of this element is constant over the triangle, so \([K_{CNL}]\), its internal forces and \([K_G]\) are all integrated exactly by a single multiplication by the area, with no quadrature loop and no quadrature to keep consistent between the matrix and the residual. The drilling term is the only one that needs more than one point, and it is linear.
- class pyfe3d.tria3dsg.Tria3DSG#
Three-node triangular shell element with a discrete shear gap
- Attributes:
- eid,int
Element identification number.
- pid,int
Property identification number.
- area,double
Element area.
- n1, n2, n3int
Node identification numbers.
- c1, c2, c3int
Position of each node’s first degree-of-freedom in the global displacement vector.
- init_k_KC0, init_k_KCNL, init_k_KG, init_k_Mint
Position of this element’s first entry in the sparse vectors.
- drilling_model,int
0, the default, uses \(\gamma_{r_z} = A_{66}\) of Hughes and Brezzi. Any other value uses \(K6ROT \cdot 10^{-6} A_{66}\).- gamma_rz,double
When non-negative and
drilling_model = 0, overrides \(A_{66}\) as the regularisation coefficient, which is what makes the sensitivity study recommended in the literature possible. Negative means use \(A_{66}\).- K6ROT,double
Coefficient of the fictitious penalty, used when
drilling_model != 0.- alpha_shear_locking,double
Optional algebraic stabilisation of the transverse shear stiffness, \(\hat{A}_{ts} = A_{ts}/(1 + \alpha \ell^2/h^2)\) with \(\ell\) the longest edge and \(h\) the total thickness, applied after the rotation into the element coordinate system and after the shear correction. The default
0.disables it entirely and is the element as described above: the discrete shear gap is already locking-free, so nothing has to be rescaled.A non-zero value reproduces the element of Bischoff and Bletzinger (2004) and Castro et al. (2019) as those papers actually define it, that is the stabilisation applied on top of a discrete shear gap field, which is not how
pyfe3d.tria3r.Tria3Ruses the same symbol. There the factor is applied to a centroid-sampled shear field and has to do the unlocking itself, which is why its default is 0.7, seven times the literature value, and why it is load bearing there and merely helpful here.Recommended value.
0.07, the lower bound of the range recommended by Castro et al. (2019), on a thin plate discretised coarsely, and only there. Measured with this element, with the remaining columns fromtests/test_tria3dsg.pyand the study described in the module documentation:
Methods
update_KC0(self, long[, long[, double[, ...)Update the sparse vectors of the constitutive stiffness matrix
update_KCNL(self, long[, long[, double[, ...)Update the sparse vectors of the nonlinear constitutive stiffness matrix
update_KG(self, long[, long[, double[, ...)Update the geometric stiffness from the current displacements
update_KG_given_stress(self, double Nxx, ...)Update the sparse vectors of the geometric stiffness matrix
update_M(self, long[, long[, double[, ...)Update the sparse vectors of the mass matrix
update_area(self)Update the element area from the probe coordinates
update_fint(self, double[, ShellProp prop, ...)Update the internal force vector
update_probe_finte(self, ShellProp prop, ...)Update the internal force vector of the probe
update_probe_ue(self, double[)Update the element-frame nodal displacements in the probe
update_probe_xe(self, double[)Update the element-frame nodal coordinates in the probe
update_rotation_matrix(self, double[, ...)Update the rotation matrix of the element
- K6ROT#
K6ROT: ‘double’
- alpha_shear_locking#
alpha_shear_locking: ‘double’
- area#
area: ‘double’
- c1#
c1: ‘int’
- c2#
c2: ‘int’
- c3#
c3: ‘int’
- drilling_model#
drilling_model: ‘int’
- eid#
eid: ‘int’
- gamma_rz#
gamma_rz: ‘double’
- init_k_KC0#
init_k_KC0: ‘int’
- init_k_KCNL#
init_k_KCNL: ‘int’
- init_k_KG#
init_k_KG: ‘int’
- init_k_M#
init_k_M: ‘int’
- m11#
m11: ‘double’
- m12#
m12: ‘double’
- m21#
m21: ‘double’
- m22#
m22: ‘double’
- n1#
n1: ‘int’
- n2#
n2: ‘int’
- n3#
n3: ‘int’
- pid#
pid: ‘int’
- probe#
probe: pyfe3d.tria3dsg.Tria3DSGProbe
- r11#
r11: ‘double’
- r12#
r12: ‘double’
- r13#
r13: ‘double’
- r21#
r21: ‘double’
- r22#
r22: ‘double’
- r23#
r23: ‘double’
- r31#
r31: ‘double’
- r32#
r32: ‘double’
- r33#
r33: ‘double’
- update_KC0(self, long[: :1] KC0r, long[: :1] KC0c, double[: :1] KC0v, ShellProp prop, int update_KC0v_only=0) void#
Update the sparse vectors of the constitutive stiffness matrix
The element-frame matrix is built by
_update_probe_KC0ve()into theKC0veattribute of the probe and rotated to global coordinates here.Before this method is called,
update_probe_xe()must have been used to set the element-frame coordinates of the probe.- Parameters:
- KC0r, KC0carray-like
Row and column positions of the sparse values.
- KC0varray-like
Sparse values.
- prop
pyfe3d.shellprop.ShellProp Shell property.
- update_KC0v_onlyint, optional
0, the default, also fillsKC0randKC0c.
- update_KCNL(self, long[: :1] KCNLr, long[: :1] KCNLc, double[: :1] KCNLv, ShellProp prop, int update_KCNLv_only=0) void#
Update the sparse vectors of the nonlinear constitutive stiffness matrix
Assuming that KCNL = KC0L + KCL0 + KCLL + KGNL, built from the von Karman membrane strains
\[\epsilon_{xx} = u_{,x} + \frac{1}{2} w_{,x}^2, \quad \epsilon_{yy} = v_{,y} + \frac{1}{2} w_{,y}^2, \quad \gamma_{xy} = u_{,y} + v_{,x} + w_{,x} w_{,y}\]whose nonlinear part is \(\{\epsilon_{NL}\} = \frac{1}{2} [B_{mL}] \{u_e\}\), with \([B_{mL}]\) its variation. With \([B_m]\) and \([B_b]\) the linear membrane and bending strain-displacement matrices, \([G]\) the gradient of \(w\), and \([A]\), \([B]\) the laminate matrices:
KC0L = \([B_m]^T [A] [B_{mL}] + [B_b]^T [B] [B_{mL}]\)
KCL0 = KC0L`^T`
KCLL = \([B_{mL}]^T [A] [B_{mL}]\)
KGNL = \([G]^T [N_{NL}] [G]\), with \(\{N_{NL}\} = [A] \{\epsilon_{NL}\}\)
The first three groups are the constitutive terms coupling the linear and the nonlinear parts of the membrane strain. KGNL is geometric, carrying the stress of the nonlinear membrane strain. It is collected here so that
update_KG()stays homogeneous of degree one in the displacements, which is what a linear buckling analysis needs. With it here,\[K_T = K_{C0} + K_{CNL}(u) + K_G(u)\]is the exact Jacobian of the internal forces of
update_fint()withnonlinear=1, and a Newton-Raphson iteration built on them converges quadratically.Note
The transverse shear strains of first-order shear deformation theory carry no von Karman terms, so the discrete shear gap operator of this element enters \(K_{C0}\) alone and needs nothing here. The membrane rows have no \(r_z\) entries, this element having no Allman enrichment, so the drilling regularisation is likewise untouched. Both are unlike
pyfe3d.quad4.Quad4, whose enriched membrane rows have to be carried into its nonlinear tangent.Before this method is called,
update_probe_xe()must have been used with the node coordinates andupdate_probe_ue()with the current displacements.- Parameters:
- KCNLr, KCNLcarray-like
Row and column positions of the sparse values.
- KCNLvarray-like
Sparse values.
- prop
pyfe3d.shellprop.ShellProp Shell property.
- update_KCNLv_onlyint, optional
0, the default, also fillsKCNLrandKCNLc.
- update_KG(self, long[: :1] KGr, long[: :1] KGc, double[: :1] KGv, ShellProp prop, int update_KGv_only=0) void#
Update the geometric stiffness from the current displacements
The membrane stress resultants are recovered from the probe displacements, which
update_probe_ue()must have updated, using the same strain operators and the same membrane constitutive relation thatupdate_fint()uses, and then handed toupdate_KG_given_stress(). Both the strains and the curvatures are constant over the triangle, so the resulting stress state is constant too, which is what that method assumes.Only the linear strains contribute, which leaves this matrix homogeneous of degree one in the displacements, as a linear buckling analysis needs. The stress of the von Karman terms of the membrane strain is carried by
update_KCNL()instead.- Parameters:
- KGr, KGcarray-like
Row and column positions of the sparse values.
- KGvarray-like
Sparse values.
- prop
pyfe3d.shellprop.ShellProp Shell property.
- update_KGv_onlyint, optional
0, the default, also fillsKGrandKGc.
- update_KG_given_stress(self, double Nxx, double Nyy, double Nxy, long[: :1] KGr, long[: :1] KGc, double[: :1] KGv, int update_KGv_only=0) void#
Update the sparse vectors of the geometric stiffness matrix
A constant membrane stress state \(N_{xx}, N_{yy}, N_{xy}\) is assumed within the element. The operator is the Donnell one, involving only the gradients of the element-frame \(w\), which are taken from the
Gwx, Gwyrows of the probe and turned into global translations with the third row of the rotation matrix, so only the translational degrees-of-freedom appear.Warning
The stress resultants are in the element coordinate system, and for a triangle that is easy to get wrong. The element \(x\) axis runs from node 1 to node 2, so in a structured mesh whose cells are split along a diagonal the first triangle of each cell has its frame along the mesh while the second has it along the diagonal, some 45 degrees away. Handing the same \((N_{xx}, 0, 0)\) to both then loads half the elements along the diagonal, and the answer comes to depend on how the mesh generator happened to order the nodes of each triangle.
A uniaxial state \(N\) along a global direction \(\{d\}\) has to be rotated. With \([R]\) the element-to-global rotation of this object, \(\{a\} = [R]^T \{d\}\) is that direction in element coordinates and the state \(N \{a\} \{a\}^T\) has components
\[N^e_{xx} = N a_x^2, \quad N^e_{yy} = N a_y^2, \quad N^e_{xy} = N a_x a_y\]which reduces to \((N, 0, 0)\) when the element \(x\) axis is along \(\{d\}\), so a quadrilateral mesh aligned with the load needs nothing. Measured on the cylinder of
tests/test_quad4_linear_buckling_cylinder_displ.py, whose cells are nearly square so the diagonal sits at 44.3 degrees, the three cyclic numberings of one triangle give buckling loads spanning a factor of fifteen without this rotation and agree to 2e-3 with it. This is checked bytest_buckling_load_does_not_depend_on_triangle_node_numberingintests/test_tria3dsg.py.update_KG()is free of this, recovering the stress from the element’s own strains in its own frame.- Parameters:
- Nxx, Nyy, Nxydouble
Membrane stress resultants in the element coordinate system.
- KGr, KGcarray-like
Row and column positions of the sparse values.
- KGvarray-like
Sparse values.
- update_KGv_onlyint, optional
0, the default, also fillsKGrandKGc.
- update_M(self, long[: :1] Mr, long[: :1] Mc, double[: :1] Mv, ShellProp prop, int mtype=0) void#
Update the sparse vectors of the mass matrix
- Parameters:
- Mr, Mcarray-like
Row and column positions of the sparse values.
- Mvarray-like
Sparse values.
- prop
pyfe3d.shellprop.ShellProp Shell property.
- mtypeint, optional
0for the consistent mass matrix, for which \(\int N_i N_j dA\) is \(A/6\) when \(i = j\) and \(A/12\) otherwise, and2for the lumped one, which puts \(A/3\) on each node. Any other value is treated as0.
- update_area(self) void#
Update the element area from the probe coordinates
- update_fint(self, double[: :1] fint, ShellProp prop, int nonlinear=0) void#
Update the internal force vector
- Parameters:
- fintarray-like
Array updated in place with the internal forces, in global coordinates.
update_probe_finte()is called to obtain the element-frame internal forces, available afterwards as.probe.finte.- prop
pyfe3d.shellprop.ShellProp Shell property.
- nonlinearint, optional
The default
0gives the linear internal forces,KC0*u. Any other value adds the geometrically nonlinear terms of the von Karman strains, for which the exact Jacobian of the internal forces isKC0 + KCNL + KG, seeupdate_KCNL().
- update_probe_finte(self, ShellProp prop, int nonlinear=0) void#
Update the internal force vector of the probe
The attribute
finteof theTria3DSGProbeis updated with the internal forces in element coordinates. Mind that the probe can be shared amongst more than one finite element, so it always holds the values of the last update.Before this method is called,
update_probe_xe()andupdate_probe_ue()must have been used.- Parameters:
- prop
pyfe3d.shellprop.ShellProp Shell property.
- nonlinearint, optional
The default
0gives the linear internal forces,KC0*u. Any other value adds the geometrically nonlinear terms of the von Karman strains, for which the exact Jacobian of the internal forces isKC0 + KCNL + KG, seeupdate_KCNL().
- prop
- update_probe_ue(self, double[: :1] u) void#
Update the element-frame nodal displacements in the probe
- Parameters:
- uarray-like
Global displacement vector.
- update_probe_xe(self, double[: :1] x) void#
Update the element-frame nodal coordinates in the probe
- Parameters:
- xarray-like
Global nodal coordinates.
- update_rotation_matrix(self, double[: :1] x, double xmati=0., double xmatj=0., double xmatk=0.) void#
Update the rotation matrix of the element
The element \(x\) axis runs from node 1 to node 2 and the element \(z\) axis is the triangle normal, which is the same construction used by
pyfe3d.tria3r.Tria3R.- Parameters:
- xarray-like
Global nodal coordinates,
x_1, y_1, z_1, ..., x_M, y_M, z_M.- xmati, xmatj, xmatkdouble, optional
Material direction in global coordinates, projected onto the element. Leaving it at zero means the material and element directions coincide.
- class pyfe3d.tria3dsg.Tria3DSGData#
Sizes needed to allocate the sparse matrices
- Attributes:
- KC0_SPARSE_SIZE,int
KC0_SPARSE_SIZE = 324, the full 18 by 18 element matrix stored row by row.- KCNL_SPARSE_SIZE,int
KCNL_SPARSE_SIZE = 324, also the full element matrix, since the nonlinear constitutive terms couple every degree-of-freedom.- KG_SPARSE_SIZE,int
KG_SPARSE_SIZE = 81, the 9 by 9 translational block, the Donnell geometric stiffness involving only the gradients of \(w\).- M_SPARSE_SIZE,int
M_SPARSE_SIZE = 324
- KC0_SPARSE_SIZE#
KC0_SPARSE_SIZE: ‘int’
- KCNL_SPARSE_SIZE#
KCNL_SPARSE_SIZE: ‘int’
- KG_SPARSE_SIZE#
KG_SPARSE_SIZE: ‘int’
- M_SPARSE_SIZE#
M_SPARSE_SIZE: ‘int’
- class pyfe3d.tria3dsg.Tria3DSGProbe#
Probe carrying the element-frame coordinates, displacements and operators
The idea behind using a probe is to avoid allocating one set of buffers per finite element. The buffers belong to the probe, and one probe can be shared amongst many elements, the contents being updated and read on demand. Mind that the probe always holds the values of the last update.
- Attributes:
- xe,array-like
Element-frame nodal coordinates,
x1, y1, z1, x2, ..., z3.- ue,array-like
Element-frame nodal displacements, 18 values, in the order \(u_1, v_1, w_1, {r_x}_1, {r_y}_1, {r_z}_1, u_2, \ldots, {r_z}_3\).
- finte,array-like
Element-frame internal forces corresponding to
ue, 18 values, updated byTria3DSG.update_probe_finte().- KC0ve,array-like
The 18 by 18 element-frame constitutive stiffness matrix stored row by row, 324 values.
- KCNLve,array-like
The 18 by 18 element-frame nonlinear constitutive stiffness matrix stored row by row, see
Tria3DSG.update_KCNL().- BLexx, BLeyy, BLgxyarray-like
Rows of 18 values giving the membrane strains \(\epsilon_{xx}, \epsilon_{yy}, \gamma_{xy}\).
- BLkxx, BLkyy, BLkxyarray-like
Rows of 18 values giving the curvatures \(\kappa_{xx}, \kappa_{yy}, \kappa_{xy}\).
- BLgxz, BLgyzarray-like
Rows of 18 values giving the transverse shear strains \(\gamma_{xz}, \gamma_{yz}\) of the discrete shear gap, symmetrised over the three starting nodes.
BLdrillingarray-likeBLdrilling: ‘double[::1]’
- Gwx, Gwyarray-like
Rows of 18 values giving \(w_{,x}\) and \(w_{,y}\), used by the geometrically nonlinear terms.
- .. note:: Every one of these operators is constant over the triangle,
because all three shape functions are linear, so each is formed once per element and no quadrature loop appears anywhere in this module except for the drilling term.
- BLdrilling#
BLdrilling: ‘double[::1]’
- BLexx#
BLexx: ‘double[::1]’
- BLeyy#
BLeyy: ‘double[::1]’
- BLgxy#
BLgxy: ‘double[::1]’
- BLgxz#
BLgxz: ‘double[::1]’
- BLgyz#
BLgyz: ‘double[::1]’
- BLkxx#
BLkxx: ‘double[::1]’
- BLkxy#
BLkxy: ‘double[::1]’
- BLkyy#
BLkyy: ‘double[::1]’
- Gwx#
Gwx: ‘double[::1]’
- Gwy#
Gwy: ‘double[::1]’
- KC0ve#
KC0ve: ‘double[::1]’
- KCNLve#
KCNLve: ‘double[::1]’
- finte#
finte: ‘double[::1]’
- ue#
ue: ‘double[::1]’
- xe#
xe: ‘double[::1]’