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:

func

connection

position

'SSxcte'

two skins along an edge of constant \(x\)

xcte1, xcte2

'SSycte'

two skins along an edge of constant \(y\)

ycte1, ycte2

'BFycte'

base, or skin, p1 and flange p2 at constant \(y\)

ycte1, ycte2

'BFxcte'

base, or skin, p1 and flange p2 at constant \(x\)

xcte1, xcte2

'BFxycte'

edge of constant \(x\) of p1 and of constant \(y\) of p2

xcte1, ycte2, 'angle'

'SB'

skin and base over their common area

p1 on the positive side of \(z\)

'SB_TSL'

'SB' with a cohesive traction-separation law

as 'SB'

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():

\[\boldsymbol{c} = \boldsymbol{Z}\boldsymbol{c}_r, \qquad \boldsymbol{K}_r = \boldsymbol{Z}^\top \boldsymbol{K} \boldsymbol{Z}, \qquad \boldsymbol{F}_r = \boldsymbol{Z}^\top \boldsymbol{F}\]

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:

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)