Multi-domain assemblies#
A MultiDomain assembles several Shell domains, each one
with its own approximation functions, see Bardell’s hierarchical functions, and
imposes the compatibility of their displacements along edges or over areas,
following Castro and Donadon [castro2017Multidomain]. Assemblies model
cylinders and panels divided in domains, stiffened panels whose stiffeners
are made of plate domains, panels with debonding defects and, with cohesive
connections, delaminations.
Assembly#
The domains are given as a list, and the vector of the Ritz constants of the
assembly is the concatenation of those of the domains, in that order: the
constructor sets the attributes row_start, col_start, row_end
and col_end of each domain. The matrices of the assembly,
MultiDomain.calc_kC(), MultiDomain.calc_kG(),
MultiDomain.calc_kM(), MultiDomain.calc_kT(), and the vectors,
MultiDomain.calc_fext(), MultiDomain.calc_fint(), are the sums
of those of the domains at these positions, see Plates and cylindrical shells, plus
the connections. The boundary conditions and the loads are those of each
domain. The attribute group of the domains collects them for the plots
and the field outputs, and x0, y0 place a domain in the plane of its
group, see Field outputs, plots and files.
Connections#
Each connection is a dictionary of the list conn, with the two domains
p1 and p2, the key func and the position of the connection in
each domain:
|
connection |
position |
|---|---|---|
|
two skins along an edge of constant \(x\) |
|
|
two skins along an edge of constant \(y\) |
|
|
base, or skin, |
|
|
base, or skin, |
|
|
edge of constant \(x\) of |
|
|
skin and base over their common area |
|
|
|
as |
The kernels and the conventions of each connection are described in
panels.multidomain.connections. The connections of the models with
5 degrees of freedom per term, FSDT and TSDT, are those of
panels.multidomain.connections.kCsdt, and two connected domains must
have the same number of degrees of freedom per term; 'SB_TSL' is
available for the classical laminated plate theory only, see
Cohesive zone model of the multi-domain approach.
Each domain of an edge connection needs at least 4 terms across the joint
(n for an edge of constant \(y\), m for an edge of constant \(x\), thus
m of p1 and n of p2 for 'BFxycte') when its
connected edge and its opposite edge are released, as for a blade flange
with a free edge: only the first four of Bardell’s functions carry the
values and the slopes at the edges, and with 3 terms all of them have a
zero slope at \(\eta = +1\), so that no field varies linearly across the
joint. The rigid rotations of the flange in its plane (\(u\)) and about the
joint (\(w\)) are then missing, and a cantilever T-beam with 3 terms across
its blade is 137 times too stiff in bending, a wing-box with blade
stringers about 100 times. MultiDomain issues a
ConnectionTermsWarning in this case, see the Notes of
MultiDomain and MultiDomain.check_conn_terms().
Imposing the connections#
With conn_method='null-space', the default, the connections are linear
constraints \(\boldsymbol{B}\boldsymbol{c} = \boldsymbol{0}\), imposed exactly with a basis \(\boldsymbol{Z}\) of the
null space of \(\boldsymbol{B}\), MultiDomain.get_Z():
The matrices and vectors are reduced with MultiDomain.reduce()
before solving, and the solution is expanded with
MultiDomain.expand(); MultiDomain.get_reduced_functions()
gives the reduced functions for the non-linear analyses of
structsolve.Analysis. The theory and the verification are in
Exact connections with the null-space method.
With conn_method='penalty', penalty stiffnesses are added to the
stiffness matrix by MultiDomain.get_kC_conn(), with the constants
'kt' and 'kr' of each connection, by default those of
calc_kt_kr(), and the matrices are solved directly. The continuity
is then approximate, with an error that decreases with the penalty
constants, while the conditioning of the stiffness matrix worsens.
The prescribed displacements of the domains are imposed with
pd_method, see Boundary conditions.
MultiDomain.linear_static() and MultiDomain.linear_buckling()
solve the reduced problems, with the prescribed displacements when
pd_method='null-space', in which case
MultiDomain.reaction_pd() and MultiDomain.reaction_resultant()
give the reactions.
Ready-made assemblies#
The functions of panels.multidomain build and analyse complete
assemblies, and show how to build others:
create_cylinder(),create_cylinder_blade_stiffened()and the analyses ofpanels.multidomain.cylinderandpanels.multidomain.cylinder_blade_stiffened, see Cylinders modeled as multi-domain assemblies;the panel with one T-stiffener of
panels.multidomain.tstiff2d_1stiff_compression,panels.multidomain.tstiff2d_1stiff_freqandpanels.multidomain.tstiff2d_1stiff_flutter, see Panels with a T-stiffener modeled as multi-domain assemblies;the rectangular wing-box of
panels.multidomain.wingbox, with skins, spars, ribs and optionally blade stringers, see Wing-box modeled as a multi-domain assembly.
Example#
A square plate divided in two or four domains, simply supported on the
outer edges, with the connections 'SSxcte' and 'SSycte':
def domains(model, a, h, stack, laminaprop, layout, xs=0.4, ys=0.6, m=10,
n=10, factor=1.e3):
r"""Square plate divided in domains, with all the outer edges simply
supported
``layout`` is ``'x'`` for two domains divided at `x = x_s a`, ``'y'``
for two domains divided at `y = y_s a`, or ``'2x2'`` for four domains
divided at both, with an interior corner.
"""
def plate(x0, y0, dx, dy, bcs):
return make_plate(model, dx, dy, h, stack, laminaprop, bcs, m=m, n=n,
x0=x0, y0=y0)
if layout == 'x':
p1 = plate(0, 0, xs*a, a, 'SSFS')
p2 = plate(xs*a, 0, (1 - xs)*a, a, 'FSSS')
conn = [dict(p1=p1, p2=p2, func='SSxcte', xcte1=p1.a, xcte2=0,
**penalties(p1, p2, 'xcte', factor))]
return [p1, p2], conn
if layout == 'y':
p1 = plate(0, 0, a, ys*a, 'SSSF')
p2 = plate(0, ys*a, a, (1 - ys)*a, 'SFSS')
conn = [dict(p1=p1, p2=p2, func='SSycte', ycte1=p1.b, ycte2=0,
**penalties(p1, p2, 'ycte', factor))]
return [p1, p2], conn
assert layout == '2x2'
p00 = plate(0, 0, xs*a, ys*a, 'SSFF')
p10 = plate(xs*a, 0, (1 - xs)*a, ys*a, 'FSSF')
p01 = plate(0, ys*a, xs*a, (1 - ys)*a, 'SFFS')
p11 = plate(xs*a, ys*a, (1 - xs)*a, (1 - ys)*a, 'FFSS')
conn = []
for pA, pB in ((p00, p10), (p01, p11)):
conn.append(dict(p1=pA, p2=pB, func='SSxcte', xcte1=pA.a, xcte2=0,
**penalties(pA, pB, 'xcte', factor)))
for pA, pB in ((p00, p01), (p10, p11)):
conn.append(dict(p1=pA, p2=pB, func='SSycte', ycte1=pA.b, ycte2=0,
**penalties(pA, pB, 'ycte', factor)))
return [p00, p10, p01, p11], conn
The deflection of the plate under a sinusoidal pressure, solved with the null-space method, reduced and expanded back, agrees with the solution of Pagano (1970) given in the test:
@pytest.mark.parametrize('model', [FSDT, TSDT])
def test_bending_divided_plate_pagano(model):
r"""Pagano (1970), deflection at the interior corner of four domains,
solved with the reduced system and expanded back"""
a = 1.
h = a/10
q0 = 1.e3
panels, conn = domains(model, a, h, [0, 90, 90, 0],
orthotropic(25, G12=0.5, G13=0.5, G23=0.2), '2x2',
xs=0.5, ys=0.5)
md = MultiDomain(panels, conn, conn_method='null-space')
fext = sum(sinusoidal_load(p, q0, a, md.get_size()) for p in panels)
c_r = solve(md.reduce(md.calc_kC()), md.reduce(fext), silent=True)
c = md.expand(c_r)
assert c.shape == (md.get_size(),)
for p in panels:
res = md.uvw(c, group=None, eval_panel=p, gridx=2, gridy=2)
w = res['w'][0][int(p.y0 == 0), int(p.x0 == 0)]
wbar = 100*w*E2*h**3/(q0*a**4)
assert np.isclose(wbar, PAGANO[model], rtol=2e-4)
Two plates in series under tension, connected with the penalty method, and
the section force computed by MultiDomain.force():
@pytest.fixture(scope='module')
def tension():
"""Two plates in series under uniform tension ``N``
::
u = 0 ________________________
| | | |
| | p1 | p2 | --> N
| |__________|____________|
The stress state is exactly uniform, so the post-processing of every
panel must give ``Nxx = N``, ``exx = N/(E t)`` and ``eyy = -nu exx``.
"""
def plate(x0, a):
p = Shell(group='g', x0=x0, y0=0, a=a, b=B, r=0., m=8, n=6,
stack=[0], plyt=t, laminaprop=(E, nu),
model='plate_clpt_donnell')
for edge in ('x1', 'x2', 'y1', 'y2'):
for dof in ('u', 'v', 'w'):
setattr(p, edge + dof, 1.)
setattr(p, edge + dof + 'r', 1.)
return p
p1 = plate(0., 0.4)
p2 = plate(0.4, 0.6)
p1.x1u = p1.x1w = p1.x1wr = 0.
p2.x2w = 0.
# removing the rigid body motion along y
p1.add_point_pd(0., 0., 0., 0., 1e9, 0., 0., 0.)
p2.add_distr_load_fixed_x(p2.a, funcx=lambda y: N)
kt, kr = calc_kt_kr(p1, p2, 'xcte')
conn = [dict(p1=p1, p2=p2, func='SSxcte', xcte1=p1.a, xcte2=0.,
kt=kt, kr=kr)]
md = MultiDomain([p1, p2], conn, conn_method='penalty')
c = solve(md.calc_kC(), md.calc_fext(), silent=True)
return md, c
@pytest.mark.parametrize('panel', [0, 1])
def test_force_equals_applied_load(tension, panel):
"""The section force along x = cte is the applied load N*b"""
md, c = tension
p = md.panels[panel]
res = md.force(c, 'g', eval_panel=p, x_cte_force=p.a/2, nr_y_gauss=4,
gridx=11)
assert np.isclose(res['Fxx'], N*B)
assert np.isclose(res['Fxy'], 0., atol=1e-8*N*B)
assert np.isclose(res['Fyy'], 0., atol=1e-8*N*B)