import numpy as np
from math import pi

from utils.utils import delta1, delta2, delta3, delta31, delta4, delta41
from utils.utils import delta5, delta66, delta67, delta68, delta69
from utils.utils import delta91, delta92
from utils.utils import kronecker, get_odd_even, get_pairs

class Panel(object):

    def __init__(self, panelID = None, length = None, width = None, 
    laminate = None, boundary_condition = None):
        """Initialize the Panel instance.
        
        Parameters
        ----------
        panelID : str
            Panel id (required).

        length : float
            Panel length, along x direction (required).

        width : float
            Panel width, along y direction (required)
.
        laminate : laminate.Laminate
            Panel laminate (required).

        boundary_condition : str
            Panel boundary condition (required).
            
            SSSS : All sides simply supported.
        
        """

        self.panelID = str(panelID)
        self.length = float(length)
        self.width = float(width)
        self.laminate = laminate
        self.boundary_condition = str(boundary_condition)
        self._number_of_terms = 1
        self._invalidate()

    @property
    def number_of_terms(self):
        """Number of terms used in analysis.

        """

        return self._number_of_terms

    @number_of_terms.setter
    def number_of_terms(self, val):
        """Number of terms, must be even. If odd specified, set to odd+1."""

        self._number_of_terms = val
        self._invalidate()

    @classmethod
    def fromDict(cls, **kwargs):
        """Initialize Panel from kwargs dict.
        
        Parameters
        ----------
        
        kwargs : dict
            {"panelID": str, "length": float, "width": float, "laminate": Laminate,
            "boundary_condition": str}

        Returns
        -------

        cls : Panel
        
        """

        panelID = kwargs.get("panelID")
        length = kwargs.get("length")
        width = kwargs.get("width")
        laminate = kwargs.get("laminate")
        boundary_condition = kwargs.get("boundary_condition")
        return cls(panelID, length, width, laminate, boundary_condition)

    def _invalidate(self):
        """Invalidates all the computed attributes of the Panel object."""
        
        self._odd = None
        self._even = None
        self._K11_odd = None
        self._K11_even = None
        self._K12_odd = None
        self._K12_even = None
        self._K14_odd = None
        self._K14_even = None
        self._K15_odd = None
        self._K15_even = None
        self._K21_odd = None
        self._K21_even = None
        self._K22_odd = None
        self._K22_even = None
        self._K24_odd = None
        self._K24_even = None
        self._K25_odd = None
        self._K25_even = None
        self._K33_odd = None
        self._K33_even = None
        self._K34_odd = None
        self._K34_even = None
        self._K35_odd = None
        self._K35_even = None
        self._K42_odd = None
        self._K42_even = None
        self._K43_odd = None
        self._K43_even = None
        self._K44_odd = None
        self._K44_even = None
        self._K45_odd = None
        self._K45_even = None
        self._K51_odd = None
        self._K51_even = None
        self._K53_odd = None
        self._K53_even = None
        self._K54_odd = None
        self._K54_even = None
        self._K55_odd = None
        self._K55_even = None

        self._X_odd = None
        self._Y_odd = None
        self._Yp_odd = None
        self._XInv_odd = None
        self._YpXInvY_odd = None
        self._K_odd = None

        self._X_even = None
        self._Y_even = None
        self._Yp_even = None
        self._XInv_even = None
        self._YpXInvY_even = None
        self._K_even = None

        self._M_odd = None
        self._M_even = None

        self._pairs = None
        self._K11 = None
        self._K12 = None
        self._K14 = None
        self._K15 = None
        self._K21 = None
        self._K22 = None
        self._K24 = None
        self._K25 = None
        self._K33 = None
        self._K34 = None
        self._K35 = None
        self._K42 = None
        self._K43 = None
        self._K44 = None
        self._K45 = None
        self._K51 = None
        self._K53 = None
        self._K54 = None
        self._K55 = None

        self._X = None
        self._Y = None
        self._Yp = None
        self._XInv = None
        self._YpXInvY = None
        self._K = None

        self._M = None

        self._KFull = None

    @property
    def odd(self):
        """List of (i, j) such as i+j is odd given that i = 1, 2, ... M
        and j = 1, 2, ..., M, where M = number of terms in the analysis.

        Returns
        -------

        odd : [(1,2), (1,4), ...]
        
        """

        if self._odd is not None:
            return self._odd

        self._odd, self._even = get_odd_even(self.number_of_terms)
        return self._odd

    @property
    def even(self):
        """List of (i, j) such as i+j is even given that i = 1, 2, ... M
        and j = 1, 2, ..., M, where M = number of terms in the analysis.

        Returns
        -------

        even : [(1,1), (1,3), ...]
        
        """

        if self._even is not None:
            return self._even

        self._odd, self._even = get_odd_even(self.number_of_terms)
        return self._even

    @property
    def pairs(self):
        """List of (i, j) for i, j = 1, 2, ... M, where M = number of terms.
        
        Returns
        -------
        
        pairs : [(1,1), (1,2), ...]
        
        """

        if self._pairs is not None:
            return self._pairs

        self._pairs = get_pairs(self.number_of_terms)
        return self._pairs

    @property
    def K11_odd(self):
        """K11 matrix for odd pairs.
        
        Returns
        -------
        
        K11_odd : numpy.array
        
        """

        if self._K11_odd is not None:
            return self._K11_odd

        self._K11_odd = [[ self._calculate_K11(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K11_odd = np.array(self._K11_odd)
        return self._K11_odd

    @property
    def K11_even(self):
        """K11 matrix for even pairs.
        
        Returns
        -------
        
        K11_even : numpy.array
        
        """

        if self._K11_even is not None:
            return self._K11_even

        self._K11_even = [[ self._calculate_K11(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K11_even = np.array(self._K11_even)
        return self._K11_even

    @property
    def K11(self):
        """K11 matrix for all pairs.
        
        Returns
        -------
        
        K11 : numpy.array
        
        """

        if self._K11 is not None:
            return self._K11

        self._K11 = [[ self._calculate_K11(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K11 = np.array(self._K11)
        return self._K11

    def _calculate_K11(self, m, n, p, q):
        """Calculate K11 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        A11 = self.laminate.A[0,0]
        A16 = self.laminate.A[0,2]
        A66 = self.laminate.A[2,2]
        val = (0.25*(A11*(a**2) + A66*(b**2))*L*W*kronecker(m,p)*
        kronecker(n,q) - 4*A16*delta2(m,n,p,q))
        return val

    @property
    def K12_odd(self):
        """K12 matrix for odd pairs.
        
        Returns
        -------
        
        K12_odd : numpy.array
        
        """

        if self._K12_odd is not None:
            return self._K12_odd

        self._K12_odd = [[ self._calculate_K12(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K12_odd = np.array(self._K12_odd)
        return self._K12_odd

    @property
    def K12_even(self):
        """K12 matrix for even pairs.
        
        Returns
        -------
        
        K12_even : numpy.array
        
        """

        if self._K12_even is not None:
            return self._K12_even

        self._K12_even = [[ self._calculate_K12(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K12_even = np.array(self._K12_even)
        return self._K12_even

    @property
    def K12(self):
        """K12 matrix for all pairs.
        
        Returns
        -------
        
        K12 : numpy.array
        
        """

        if self._K12 is not None:
            return self._K12

        self._K12 = [[ self._calculate_K12(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K12 = np.array(self._K12)
        return self._K12

    def _calculate_K12(self, m, n, p, q):
        """Calculates K12 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        A12 = self.laminate.A[0,1]
        A16 = self.laminate.A[0,2]
        A26 = self.laminate.A[1,2]
        A66 = self.laminate.A[2,2]
        val = (0.25*(A12+A66)*a*b*L*W*kronecker(m,p)*kronecker(n,q) - 
        (4.0*A16*W/L)*delta3(m,n,p,q) - (4.0*A26*L/W)*delta4(m,n,p,q))
        return val

    @property
    def K14_odd(self):
        """K14 matrix for odd pairs.
        
        Returns
        -------
        
        K14_odd : numpy.array
        
        """

        if self._K14_odd is not None:
            return self._K14_odd

        self._K14_odd = [[ self._calculate_K14(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K14_odd = np.array(self._K14_odd)
        return self._K14_odd

    @property
    def K14_even(self):
        """K14 matrix for even pairs.
        
        Returns
        -------
        
        K14_even : numpy.array
        
        """

        if self._K14_even is not None:
            return self._K14_even

        self._K14_even = [[ self._calculate_K14(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K14_even = np.array(self._K14_even)
        return self._K14_even

    @property
    def K14(self):
        """K14 matrix for all pairs.
        
        Returns
        -------
        
        K14 : numpy.array
        
        """

        if self._K14 is not None:
            return self._K14

        self._K14 = [[ self._calculate_K14(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K14 = np.array(self._K14)
        return self._K14

    def _calculate_K14(self, m, n, p, q):
        """Calculates K14 entries for a given m, n, p, q."""
        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        B11 = self.laminate.B[0,0]
        B16 = self.laminate.B[0,2]
        B66 = self.laminate.B[2,2]
        val = (0.25*(B11*(a**2) + B66*(b**2))*L*W*kronecker(m,p)*
        kronecker(n,q) - 4.0*B16*delta2(m,n,p,q))
        return val

    @property
    def K15_odd(self):
        """K15 matrix for odd pairs.
        
        Returns
        -------
        
        K15_odd : numpy.array
        
        """

        if self._K15_odd is not None:
            return self._K15_odd

        self._K15_odd = [[ self._calculate_K15(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K15_odd = np.array(self._K15_odd)
        return self._K15_odd

    @property
    def K15_even(self):
        """K15 matrix for even pairs.
        
        Returns
        -------
        
        K15_even : numpy.array
        
        """

        if self._K15_even is not None:
            return self._K15_even

        self._K15_even = [[ self._calculate_K15(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K15_even = np.array(self._K15_even)
        return self._K15_even

    @property
    def K15(self):
        """K15 matrix for all pairs.
        
        Returns
        -------
        
        K15 : numpy.array
        
        """

        if self._K15 is not None:
            return self._K15

        self._K15 = [[ self._calculate_K15(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K15 = np.array(self._K15)
        return self._K15

    def _calculate_K15(self, m, n, p, q):
        """Calculates K15 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        B12 = self.laminate.B[0,1]
        B16 = self.laminate.B[0,2]
        B26 = self.laminate.B[1,2]
        B66 = self.laminate.B[2,2]
        val = (0.25*(B12+B66)*a*b*L*W*kronecker(m,p)*kronecker(n,q) -
        (4.0*B16*W/L)*delta3(m,n,p,q) - (4.0*B26*L/W)*delta4(m,n,p,q))
        return val

    @property
    def K21_odd(self):
        """K21 matrix for odd pairs.
        
        Returns
        -------
        
        K21_odd : numpy.array
        
        """

        if self._K21_odd is not None:
            return self._K21_odd

        self._K21_odd = [[ self._calculate_K21(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K21_odd = np.array(self._K21_odd)
        return self._K21_odd

    @property
    def K21_even(self):
        """K21 matrix for even pairs.
        
        Returns
        -------
        
        K21_even : numpy.array
        
        """

        if self._K21_even is not None:
            return self._K21_even

        self._K21_even = [[ self._calculate_K21(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K21_even = np.array(self._K21_even)
        return self._K21_even

    @property
    def K21(self):
        """K21 matrix for all pairs.
        
        Returns
        -------
        
        K21 : numpy.array
        
        """

        if self._K21 is not None:
            return self._K21

        self._K21 = [[ self._calculate_K21(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K21 = np.array(self._K21)
        return self._K21

    def _calculate_K21(self, m, n, p, q):
        """Calculates K21 entries for a given m, n, p, q."""
        
        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        A12 = self.laminate.A[0,1]
        A16 = self.laminate.A[0,2]
        A26 = self.laminate.A[1,2]
        A66 = self.laminate.A[2,2]
        val = (0.25*(A12+A66)*a*b*L*W*kronecker(m,p)*kronecker(n,q) -
        (4.0*A16*W/L)*delta31(m,n,p,q) - (4.0*A26*L/W)*delta41(m,n,p,q))
        return val

    @property
    def K22_odd(self):
        """K22 matrix for odd pairs.
        
        Returns
        -------
        
        K22_odd : numpy.array
        
        """

        if self._K22_odd is not None:
            return self._K22_odd

        self._K22_odd = [[ self._calculate_K22(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K22_odd = np.array(self._K22_odd)
        return self._K22_odd

    @property
    def K22_even(self):
        """K22 matrix for even pairs.
        
        Returns
        -------
        
        K22_even : numpy.array
        
        """

        if self._K22_even is not None:
            return self._K22_even

        self._K22_even = [[ self._calculate_K22(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K22_even = np.array(self._K22_even)
        return self._K22_even

    @property
    def K22(self):
        """K22 matrix for all pairs.
        
        Returns
        -------
        
        K22 : numpy.array
        
        """

        if self._K22 is not None:
            return self._K22

        self._K22 = [[ self._calculate_K22(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K22 = np.array(self._K22)
        return self._K22

    def _calculate_K22(self, m, n, p, q):
        """Calculate K22 entries for m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        A22 = self.laminate.A[1,1]
        A26 = self.laminate.A[1,2]
        A66 = self.laminate.A[2,2]
        val = (0.25*(A66*(a**2) + A22*(b**2))*L*W*kronecker(m,p)*
        kronecker(n,q) - (4.0*A26)*delta5(m,n,p,q))
        return val

    @property
    def K24_odd(self):
        """K24 matrix for odd pairs.
        
        Returns
        -------
        
        K24_odd : numpy.array
        
        """

        if self._K24_odd is not None:
            return self._K24_odd

        self._K24_odd = [[ self._calculate_K24(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K24_odd = np.array(self._K24_odd)
        return self._K24_odd

    @property
    def K24_even(self):
        """K24 matrix for even pairs.
        
        Returns
        -------
        
        K24_even : numpy.array
        
        """

        if self._K24_even is not None:
            return self._K24_even

        self._K24_even = [[ self._calculate_K24(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K24_even = np.array(self._K24_even)
        return self._K24_even

    @property
    def K24(self):
        """K24 matrix for all pairs.
        
        Returns
        -------
        
        K24 : numpy.array
        
        """

        if self._K24 is not None:
            return self._K24

        self._K24 = [[ self._calculate_K24(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K24 = np.array(self._K24)
        return self._K24

    def _calculate_K24(self, m, n, p, q):
        """Calculate K24 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        B12 = self.laminate.B[0,1]
        B16 = self.laminate.B[0,2]
        B26 = self.laminate.B[1,2]
        B66 = self.laminate.B[2,2]
        val = (0.25*(B12+B66)*a*b*L*W*kronecker(m,p)*kronecker(n,q) -
        (4.0*B16*W/L)*delta31(m,n,p,q) - (4.0*B26*L/W)*delta41(m,n,p,q))
        return val

    @property
    def K25_odd(self):
        """K25 matrix for odd pairs.
        
        Returns
        -------
        
        K25_odd : numpy.array
        
        """

        if self._K25_odd is not None:
            return self._K25_odd

        self._K25_odd = [[ self._calculate_K25(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K25_odd = np.array(self._K25_odd)
        return self._K25_odd

    @property
    def K25_even(self):
        """K25 matrix for even pairs.
        
        Returns
        -------
        
        K25_even : numpy.array
        
        """

        if self._K25_even is not None:
            return self._K25_even

        self._K25_even = [[ self._calculate_K25(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K25_even = np.array(self._K25_even)
        return self._K25_even

    @property
    def K25(self):
        """K25 matrix for all pairs.
        
        Returns
        -------
        
        K25 : numpy.array
        
        """

        if self._K25 is not None:
            return self._K25

        self._K25 = [[ self._calculate_K25(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K25 = np.array(self._K25)
        return self._K25

    def _calculate_K25(self, m, n, p, q):
        """Calculate K25 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        B22 = self.laminate.B[1,1]
        B26 = self.laminate.B[1,2]
        B66 = self.laminate.B[2,2]
        val = (0.25*(B66*(a**2) + B22*(b**2))*L*W*kronecker(m,p)*
        kronecker(n,q) - (4.0*B26)*delta5(m,n,p,q))
        return val

    @property
    def K33_odd(self):
        """K33 matrix for odd pairs.
        
        Returns
        -------
        
        K33_odd : numpy.array
        
        """

        if self._K33_odd is not None:
            return self._K33_odd

        self._K33_odd = [[ self._calculate_K33(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K33_odd = np.array(self._K33_odd)
        return self._K33_odd

    @property
    def K33_even(self):
        """K33 matrix for even pairs.
        
        Returns
        -------
        
        K33_even : numpy.array
        
        """

        if self._K33_even is not None:
            return self._K33_even

        self._K33_even = [[ self._calculate_K33(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K33_even = np.array(self._K33_even)
        return self._K33_even

    @property
    def K33(self):
        """K33 matrix for all pairs.
        
        Returns
        -------
        
        K33 : numpy.array
        
        """

        if self._K33 is not None:
            return self._K33

        self._K33 = [[ self._calculate_K33(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K33 = np.array(self._K33)
        return self._K33

    def _calculate_K33(self, m, n, p, q):
        """Calculate K33 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        A44 = self.laminate.A44
        A45 = self.laminate.A45
        A55 = self.laminate.A55
        val = (0.25*(A55*(a**2) + A44*(b**2))*L*W*kronecker(m,p)*
        kronecker(n,q) - (8.0*A45)*delta1(m,n,p,q))
        return val

    @property
    def K34_odd(self):
        """K34 matrix for odd pairs.
        
        Returns
        -------
        
        K34_odd : numpy.array
        
        """

        if self._K34_odd is not None:
            return self._K34_odd

        self._K34_odd = [[ self._calculate_K34(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K34_odd = np.array(self._K34_odd)
        return self._K34_odd

    @property
    def K34_even(self):
        """K34 matrix for even pairs.
        
        Returns
        -------
        
        K34_even : numpy.array
        
        """

        if self._K34_even is not None:
            return self._K34_even

        self._K34_even = [[ self._calculate_K34(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K34_even = np.array(self._K34_even)
        return self._K34_even

    @property
    def K34(self):
        """K34 matrix for all pairs.
        
        Returns
        -------
        
        K34 : numpy.array
        
        """

        if self._K34 is not None:
            return self._K34

        self._K34 = [[ self._calculate_K34(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K34 = np.array(self._K34)
        return self._K34

    def _calculate_K34(self, m, n, p, q):
        """Calculate K34 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        A45 = self.laminate.A45
        A55 = self.laminate.A55
        val = (0.25*(A55*a)*L*W*kronecker(m,p)*kronecker(n,q) -
        (4.0*A45*L/pi)*delta66(m,n,p,q))
        return val

    @property
    def K35_odd(self):
        """K35 matrix for odd pairs.
        
        Returns
        -------
        
        K35_odd : numpy.array
        
        """

        if self._K35_odd is not None:
            return self._K35_odd

        self._K35_odd = [[ self._calculate_K35(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K35_odd = np.array(self._K35_odd)
        return self._K35_odd

    @property
    def K35_even(self):
        """K35 matrix for even pairs.
        
        Returns
        -------
        
        K35_even : numpy.array
        
        """

        if self._K35_even is not None:
            return self._K35_even

        self._K35_even = [[ self._calculate_K35(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K35_even = np.array(self._K35_even)
        return self._K35_even

    @property
    def K35(self):
        """K35 matrix for all pairs.
        
        Returns
        -------
        
        K35 : numpy.array
        
        """

        if self._K35 is not None:
            return self._K35

        self._K35 = [[ self._calculate_K35(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K35 = np.array(self._K35)
        return self._K35

    def _calculate_K35(self, m, n, p, q):
        """Calculate K35 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        A44 = self.laminate.A44
        A45 = self.laminate.A45
        val = (0.25*(A44*b)*L*W*kronecker(m,p)*kronecker(n,q) -
        (4.0*A45*W/pi)*delta67(m,n,p,q))
        return val

    @property
    def K42_odd(self):
        """K42 matrix for odd pairs.
        
        Returns
        -------
        
        K42_odd : numpy.array
        
        """

        if self._K42_odd is not None:
            return self._K42_odd

        self._K42_odd = [[ self._calculate_K42(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K42_odd = np.array(self._K42_odd)
        return self._K42_odd

    @property
    def K42_even(self):
        """K42 matrix for even pairs.
        
        Returns
        -------
        
        K42_even : numpy.array
        
        """

        if self._K42_even is not None:
            return self._K42_even

        self._K42_even = [[ self._calculate_K42(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K42_even = np.array(self._K42_even)
        return self._K42_even

    @property
    def K42(self):
        """K42 matrix for all pairs.
        
        Returns
        -------
        
        K42 : numpy.array
        
        """

        if self._K42 is not None:
            return self._K42

        self._K42 = [[ self._calculate_K42(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K42 = np.array(self._K42)
        return self._K42

    def _calculate_K42(self, m, n, p, q):
        """Calculate K42 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        B12 = self.laminate.B[0,1]
        B16 = self.laminate.B[0,2]
        B26 = self.laminate.B[1,2]
        B66 = self.laminate.B[2,2]
        val = (0.25*(B12+B66)*a*b*L*W*kronecker(m,p)*kronecker(n,q) -
        (4.0*B16*W/L)*delta3(m,n,p,q) - (4.0*B26*L/W)*delta4(m,n,p,q))
        return val

    @property
    def K43_odd(self):
        """K43 matrix for odd pairs.
        
        Returns
        -------
        
        K43_odd : numpy.array
        
        """

        if self._K43_odd is not None:
            return self._K43_odd

        self._K43_odd = [[ self._calculate_K43(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K43_odd = np.array(self._K43_odd)
        return self._K43_odd

    @property
    def K43_even(self):
        """K43 matrix for even pairs.
        
        Returns
        -------
        
        K43_even : numpy.array
        
        """

        if self._K43_even is not None:
            return self._K43_even

        self._K43_even = [[ self._calculate_K43(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K43_even = np.array(self._K43_even)
        return self._K43_even

    @property
    def K43(self):
        """K43 matrix for all pairs.
        
        Returns
        -------
        
        K43 : numpy.array
        
        """

        if self._K43 is not None:
            return self._K43

        self._K43 = [[ self._calculate_K43(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K43 = np.array(self._K43)
        return self._K43

    def _calculate_K43(self, m, n, p, q):
        """Calculate K43 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        A45 = self.laminate.A45
        A55 = self.laminate.A55
        val = (0.25*(A55*a)*L*W*kronecker(m,p)*kronecker(n,q) -
        (4.0*A45*L/pi)*delta68(m,n,p,q))
        return val

    @property
    def K44_odd(self):
        """K44 matrix for odd pairs.
        
        Returns
        -------
        
        K44_odd : numpy.array
        
        """

        if self._K44_odd is not None:
            return self._K44_odd

        self._K44_odd = [[ self._calculate_K44(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K44_odd = np.array(self._K44_odd)
        return self._K44_odd

    @property
    def K44_even(self):
        """K44 matrix for even pairs.
        
        Returns
        -------
        
        K44_even : numpy.array
        
        """

        if self._K44_even is not None:
            return self._K44_even

        self._K44_even = [[ self._calculate_K44(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K44_even = np.array(self._K44_even)
        return self._K44_even

    @property
    def K44(self):
        """K44 matrix for all pairs.
        
        Returns
        -------
        
        K44 : numpy.array
        
        """

        if self._K44 is not None:
            return self._K44

        self._K44 = [[ self._calculate_K44(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K44 = np.array(self._K44)
        return self._K44

    def _calculate_K44(self, m, n, p, q):
        """Calculate K44 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        A55 = self.laminate.A55
        D11 = self.laminate.D[0,0]
        D16 = self.laminate.D[0,2]
        D66 = self.laminate.D[2,2]
        val = (0.25*(A55 + D11*(a**2) + D66*(b**2))*L*W*kronecker(m,p)*
        kronecker(n,q) - (4.0*D16)*delta2(m,n,p,q))
        return val

    @property
    def K45_odd(self):
        """K45 matrix for odd pairs.
        
        Returns
        -------
        
        K45_odd : numpy.array
        
        """

        if self._K45_odd is not None:
            return self._K45_odd

        self._K45_odd = [[ self._calculate_K45(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K45_odd = np.array(self._K45_odd)
        return self._K45_odd

    @property
    def K45_even(self):
        """K45 matrix for even pairs.
        
        Returns
        -------
        
        K45_even : numpy.array
        
        """
        
        if self._K45_even is not None:
            return self._K45_even

        self._K45_even = [[ self._calculate_K45(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K45_even = np.array(self._K45_even)
        return self._K45_even

    @property
    def K45(self):
        """K45 matrix for all pairs.
        
        Returns
        -------
        
        K45 : numpy.array
        
        """

        if self._K45 is not None:
            return self._K45

        self._K45 = [[ self._calculate_K45(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K45 = np.array(self._K45)
        return self._K45

    def _calculate_K45(self, m, n, p, q):
        """Calculate K45 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        A45 = self.laminate.A45
        D12 = self.laminate.D[0,1]
        D16 = self.laminate.D[0,2]
        D26 = self.laminate.D[1,2]
        D66 = self.laminate.D[2,2]
        val = (0.25*(D12+D66)*a*b*L*W*kronecker(m,p)*kronecker(n,q) -
        (4.0*D16*W/L)*delta3(m,n,p,q) - (4.0*D26*L/W)*delta4(m,n,p,q) -
        (4.0*A45*L*W/(pi**2))*delta92(m,n,p,q))
        return val

    @property
    def K51_odd(self):
        """K51 matrix for odd pairs.
        
        Returns
        -------
        
        K51_odd : numpy.array
        
        """

        if self._K51_odd is not None:
            return self._K51_odd

        self._K51_odd = [[ self._calculate_K51(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K51_odd = np.array(self._K51_odd)
        return self._K51_odd

    @property
    def K51_even(self):
        """K51 matrix for even pairs.
        
        Returns
        -------
        
        K51_even : numpy.array
        
        """

        if self._K51_even is not None:
            return self._K51_even

        self._K51_even = [[ self._calculate_K51(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K51_even = np.array(self._K51_even)
        return self._K51_even

    @property
    def K51(self):
        """K51 matrix for all pairs.
        
        Returns
        -------
        
        K51 : numpy.array
        
        """

        if self._K51 is not None:
            return self._K51

        self._K51 = [[ self._calculate_K51(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K51 = np.array(self._K51)
        return self._K51

    def _calculate_K51(self, m, n, p, q):
        """Calculate K51 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        B12 = self.laminate.B[0,1]
        B16 = self.laminate.B[0,2]
        B26 = self.laminate.B[1,2]
        B66 = self.laminate.B[2,2]
        val = (0.25*(B12+B66)*a*b*L*W*kronecker(m,p)*kronecker(n,q) -
        (4.0*B16*W/L)*delta31(m,n,p,q) - (4.0*B26*L/W)*delta41(m,n,p,q))
        return val

    @property
    def K53_odd(self):
        """K53 matrix for odd pairs.
        
        Returns
        -------
        
        K53_odd : numpy.array
        
        """

        if self._K53_odd is not None:
            return self._K53_odd

        self._K53_odd = [[ self._calculate_K53(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K53_odd = np.array(self._K53_odd)
        return self._K53_odd

    @property
    def K53_even(self):
        """K53 matrix for even pairs.
        
        Returns
        -------
        
        K53_even : numpy.array
        
        """

        if self._K53_even is not None:
            return self._K53_even

        self._K53_even = [[ self._calculate_K53(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K53_even = np.array(self._K53_even)
        return self._K53_even

    @property
    def K53(self):
        """K53 matrix for all pairs.
        
        Returns
        -------
        
        K53 : numpy.array
        
        """

        if self._K53 is not None:
            return self._K53

        self._K53 = [[ self._calculate_K53(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K53 = np.array(self._K53)
        return self._K53

    def _calculate_K53(self, m, n, p, q):
        """Calculate K53 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        A44 = self.laminate.A44
        A45 = self.laminate.A45
        val = (0.25*(A44*b)*L*W*kronecker(m,p)*kronecker(n,q) - 
        (4.0*A45*W/pi)*delta69(m,n,p,q))
        return val

    @property
    def K54_odd(self):
        """K54 matrix for odd pairs.
        
        Returns
        -------
        
        K54_odd : numpy.array
        
        """

        if self._K54_odd is not None:
            return self._K54_odd

        self._K54_odd = [[ self._calculate_K54(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K54_odd = np.array(self._K54_odd)
        return self._K54_odd

    @property
    def K54_even(self):
        """K54 matrix for even pairs.
        
        Returns
        -------
        
        K54_even : numpy.array
        
        """

        if self._K54_even is not None:
            return self._K54_even

        self._K54_even = [[ self._calculate_K54(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K54_even = np.array(self._K54_even)
        return self._K54_even

    @property
    def K54(self):
        """K54 matrix for all pairs.
        
        Returns
        -------
        
        K54 : numpy.array
        
        """

        if self._K54 is not None:
            return self._K54

        self._K54 = [[ self._calculate_K54(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K54 = np.array(self._K54)
        return self._K54

    def _calculate_K54(self, m, n, p, q):
        """Calculate K54 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        A45 = self.laminate.A45
        D12 = self.laminate.D[0,1]
        D16 = self.laminate.D[0,2]
        D26 = self.laminate.D[1,2]
        D66 = self.laminate.D[2,2]
        val = (0.25*(D12+D66)*a*b*L*W*kronecker(m,p)*kronecker(n,q) - 
        (4.0*D16*W/L)*delta31(m,n,p,q) - (4.0*D26*L/W)*delta41(m,n,p,q) - 
        (4.0*A45*L*W/(pi**2))*delta91(m,n,p,q))
        return val

    @property
    def K55_odd(self):
        """K55 matrix for odd pairs.
        
        Returns
        -------
        
        K55_odd : numpy.array
        
        """

        if self._K55_odd is not None:
            return self._K55_odd

        self._K55_odd = [[ self._calculate_K55(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._K55_odd = np.array(self._K55_odd)
        return self._K55_odd

    @property
    def K55_even(self):
        """K55 matrix for even pairs.
        
        Returns
        -------
        
        K55_even : numpy.array
        
        """

        if self._K55_even is not None:
            return self._K55_even

        self._K55_even = [[ self._calculate_K55(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._K55_even = np.array(self._K55_even)
        return self._K55_even

    @property
    def K55(self):
        """K55 matrix for all pairs.
        
        Returns
        -------
        
        K55 : numpy.array
        
        """

        if self._K55 is not None:
            return self._K55

        self._K55 = [[ self._calculate_K55(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._K55 = np.array(self._K55)
        return self._K55

    def _calculate_K55(self, m, n, p, q):
        """Calculate K55 entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        a = m*pi/L
        b = n*pi/W
        A44 = self.laminate.A44
        D22 = self.laminate.D[1,1]
        D26 = self.laminate.D[1,2]
        D66 = self.laminate.D[2,2]
        val = (0.25*(A44 + D66*(a**2) + D22*(b**2))*L*W*kronecker(m,p)*
        kronecker(n,q) - (4.0*D26)*delta5(m,n,p,q))
        return val

    @property
    def X_odd(self):
        """X matrix for odd pairs.
        
        Returns
        -------
        
        X_odd : numpy.array
        
        """

        if self._X_odd is not None:
            return self._X_odd
        
        dim = len(self.odd)
        self._X_odd = np.zeros((4*dim, 4*dim))
        self._X_odd[0:dim, 0:dim] = self.K11_odd
        self._X_odd[0:dim, dim:2*dim] = self.K12_odd
        self._X_odd[0:dim, 2*dim:3*dim] = self.K14_odd
        self._X_odd[0:dim, 3*dim:4*dim] = self.K15_odd

        self._X_odd[dim:2*dim, 0:dim] = self.K21_odd
        self._X_odd[dim:2*dim, dim:2*dim] = self.K22_odd
        self._X_odd[dim:2*dim, 2*dim:3*dim] = self.K24_odd
        self._X_odd[dim:2*dim, 3*dim:4*dim] = self.K25_odd

        self._X_odd[2*dim:3*dim, 0:dim] = self.K14_odd
        self._X_odd[2*dim:3*dim, dim:2*dim] = self.K42_odd
        self._X_odd[2*dim:3*dim, 2*dim:3*dim] = self.K44_odd
        self._X_odd[2*dim:3*dim, 3*dim:4*dim] = self.K45_odd

        self._X_odd[3*dim:4*dim, 0:dim] = self.K51_odd
        self._X_odd[3*dim:4*dim, dim:2*dim] = self.K25_odd
        self._X_odd[3*dim:4*dim, 2*dim:3*dim] = self.K54_odd
        self._X_odd[3*dim:4*dim, 3*dim:4*dim] = self.K55_odd

        return self._X_odd

    @property
    def X(self):
        """X matrix for all pairs.
        
        Returns
        -------
        
        X : numpy.array
        
        """

        if self._X is not None:
            return self._X
        
        dim = len(self.pairs)
        self._X = np.zeros((4*dim, 4*dim))
        self._X[0:dim, 0:dim] = self.K11
        self._X[0:dim, dim:2*dim] = self.K12
        self._X[0:dim, 2*dim:3*dim] = self.K14
        self._X[0:dim, 3*dim:4*dim] = self.K15

        self._X[dim:2*dim, 0:dim] = self.K21
        self._X[dim:2*dim, dim:2*dim] = self.K22
        self._X[dim:2*dim, 2*dim:3*dim] = self.K24
        self._X[dim:2*dim, 3*dim:4*dim] = self.K25

        self._X[2*dim:3*dim, 0:dim] = self.K14
        self._X[2*dim:3*dim, dim:2*dim] = self.K42
        self._X[2*dim:3*dim, 2*dim:3*dim] = self.K44
        self._X[2*dim:3*dim, 3*dim:4*dim] = self.K45

        self._X[3*dim:4*dim, 0:dim] = self.K51
        self._X[3*dim:4*dim, dim:2*dim] = self.K25
        self._X[3*dim:4*dim, 2*dim:3*dim] = self.K54
        self._X[3*dim:4*dim, 3*dim:4*dim] = self.K55

        return self._X

    @property
    def XInv_odd(self):
        """Inverse of X_odd.
        
        Returns
        -------
        
        XInv_odd : numpy.array
        
        """

        if self._XInv_odd is not None:
            return self._XInv_odd

        self._XInv_odd = np.linalg.inv(self.X_odd)
        return self._XInv_odd

    @property
    def XInv(self):
        """Inverse of X.
        
        Returns
        -------
        
        XInv : numpy.array
        
        """

        if self._XInv is not None:
            return self._XInv

        self._XInv = np.linalg.inv(self.X)
        return self._XInv

    @property
    def Y_odd(self):
        """Y matrix for odd pairs.
        
        Returns
        -------
        
        Y_odd : numpy.array
        
        """

        if self._Y_odd is not None:
            return self._Y_odd

        dim = len(self.odd)
        self._Y_odd = np.zeros((4*dim, dim))
        self._Y_odd[2*dim:3*dim,0:dim] = self.K43_odd
        self._Y_odd[3*dim:4*dim,0:dim] = self.K53_odd

        return self._Y_odd

    @property
    def Y(self):
        """Y matrix for all pairs.
        
        Returns
        -------
        
        Y : numpy.array
        
        """

        if self._Y is not None:
            return self._Y

        dim = len(self.pairs)
        self._Y = np.zeros((4*dim, dim))
        self._Y[2*dim:3*dim,0:dim] = self.K43
        self._Y[3*dim:4*dim,0:dim] = self.K53

        return self._Y

    @property
    def Yp_odd(self):
        """Transpose of Y or Yprime for odd pairs.
        
        Returns
        -------
        
        Yp_odd : numpy.array
        
        """

        if self._Yp_odd is not None:
            return self._Yp_odd

        dim = len(self.odd)
        self._Yp_odd = np.zeros((dim, 4*dim))
        self._Yp_odd[0:dim, 2*dim:3*dim] = self.K34_odd
        self._Yp_odd[0:dim, 3*dim:4*dim] = self.K35_odd

        return self._Yp_odd

    @property
    def Yp(self):
        """Transpose of Y or Yprime for all pairs.
        
        Returns
        -------
        
        Yp : numpy.array
        
        """

        if self._Yp is not None:
            return self._Yp

        dim = len(self.pairs)
        self._Yp = np.zeros((dim, 4*dim))
        self._Yp[0:dim, 2*dim:3*dim] = self.K34
        self._Yp[0:dim, 3*dim:4*dim] = self.K35

        return self._Yp

    @property
    def YpXInvY_odd(self):
        """Yp @ XInv @ y for odd pairs.
        
        Returns
        -------
        
        YpXInvY_odd : numpy.array
        
        """

        if self._YpXInvY_odd is not None:
            return self._YpXInvY_odd

        self._YpXInvY_odd = self.Yp_odd @ self.XInv_odd @ self.Y_odd
        return self._YpXInvY_odd

    @property
    def YpXInvY(self):
        """Yp @ XInv @ Y for all pairs.
        
        Returns
        -------
        
        YpXInvY : numpy.array
        
        """

        if self._YpXInvY is not None:
            return self._YpXInvY

        self._YpXInvY = self.Yp @ self.XInv @ self.Y
        return self._YpXInvY

    @property
    def K_odd(self):
        """K matrix for odd pairs given as K_odd = K33_odd - Y@XInv@Y.
        
        Returns
        -------
        
        K_odd : numpy.array
        
        """

        if self._K_odd is not None:
            return self._K_odd
        
        self._K_odd = self.K33_odd - self.YpXInvY_odd
        return self._K_odd

    @property
    def K(self):
        """K matrix for all pairs given as K = K33 - Y@XInv@Y.
        
        Returns
        -------
        
        K : numpy.array
        
        """

        if self._K is not None:
            return self._K
        
        self._K = self.K33 - self.YpXInvY
        return self._K

    @property
    def X_even(self):
        """X matrix for even pairs.
        
        Returns
        -------
        
        X_even : numpy.array
        
        """

        if self._X_even is not None:
            return self._X_even
        
        dim = len(self.even)
        self._X_even = np.zeros((4*dim, 4*dim))
        self._X_even[0:dim, 0:dim] = self.K11_even
        self._X_even[0:dim, dim:2*dim] = self.K12_even
        self._X_even[0:dim, 2*dim:3*dim] = self.K14_even
        self._X_even[0:dim, 3*dim:4*dim] = self.K15_even

        self._X_even[dim:2*dim, 0:dim] = self.K21_even
        self._X_even[dim:2*dim, dim:2*dim] = self.K22_even
        self._X_even[dim:2*dim, 2*dim:3*dim] = self.K24_even
        self._X_even[dim:2*dim, 3*dim:4*dim] = self.K25_even

        self._X_even[2*dim:3*dim, 0:dim] = self.K14_even
        self._X_even[2*dim:3*dim, dim:2*dim] = self.K42_even
        self._X_even[2*dim:3*dim, 2*dim:3*dim] = self.K44_even
        self._X_even[2*dim:3*dim, 3*dim:4*dim] = self.K45_even

        self._X_even[3*dim:4*dim, 0:dim] = self.K51_even
        self._X_even[3*dim:4*dim, dim:2*dim] = self.K25_even
        self._X_even[3*dim:4*dim, 2*dim:3*dim] = self.K54_even
        self._X_even[3*dim:4*dim, 3*dim:4*dim] = self.K55_even

        return self._X_even

    @property
    def XInv_even(self):
        """Inverse of X_even.
        
        Returns
        -------
        
        XInv_even : numpy.array
        
        """

        if self._XInv_even is not None:
            return self._XInv_even

        self._XInv_even = np.linalg.inv(self.X_even)
        return self._XInv_even

    @property
    def Y_even(self):
        """Y matrix for even pairs.
        
        Returns
        -------
        
        Y_even : numpy.array
        
        """

        if self._Y_even is not None:
            return self._Y_even

        dim = len(self.even)
        self._Y_even = np.zeros((4*dim, dim))
        self._Y_even[2*dim:3*dim,0:dim] = self.K43_even
        self._Y_even[3*dim:4*dim,0:dim] = self.K53_even

        return self._Y_even

    @property
    def Yp_even(self):
        """Transpose of Y_even.
        
        Returns
        -------
        
        Yp_even : numpy.array
        
        """
        
        if self._Yp_even is not None:
            return self._Yp_even

        dim = len(self.even)
        self._Yp_even = np.zeros((dim, 4*dim))
        self._Yp_even[0:dim, 2*dim:3*dim] = self.K34_even
        self._Yp_even[0:dim, 3*dim:4*dim] = self.K35_even

        return self._Yp_even

    @property
    def YpXInvY_even(self):
        """Yp @ XInv @ y for even pairs.
        
        Returns
        -------
        
        YpXInvY_even : numpy.array
        
        """

        if self._YpXInvY_even is not None:
            return self._YpXInvY_even

        self._YpXInvY_even = self.Yp_even @ self.XInv_even @ self.Y_even
        return self._YpXInvY_even

    @property
    def K_even(self):
        """K matrix for even pairs given as K_odd = K33_odd - Y@XInv@Y.
        
        Returns
        -------
        
        K_even : numpy.array
        
        """

        if self._K_even is not None:
            return self._K_even
        
        self._K_even = self.K33_even - self.YpXInvY_even
        return self._K_even

    @property
    def M_odd(self):
        """M matrix for odd pairs.
        
        Returns
        -------
        
        M_odd : numpy.array
        
        """

        if self._M_odd is not None:
            return self._M_odd

        self._M_odd = [[ self._calculate_M(m, n, p, q)
        for p, q in self.odd] for m, n in self.odd]
        self._M_odd = np.array(self._M_odd)
        return self._M_odd

    @property
    def M_even(self):
        """M matrix for even pairs.
        
        Returns
        -------
        
        M_even : numpy.array
        
        """

        if self._M_even is not None:
            return self._M_even

        self._M_even = [[ self._calculate_M(m, n, p, q)
        for p, q in self.even] for m, n in self.even]
        self._M_even = np.array(self._M_even)
        return self._M_even

    @property
    def M(self):
        """M matrix for all pairs.
        
        Returns
        -------
        
        M : numpy.array
        
        """

        if self._M is not None:
            return self._M

        self._M = [[ self._calculate_M(m, n, p, q)
        for p, q in self.pairs] for m, n in self.pairs]
        self._M = np.array(self._M)
        return self._M

    def _calculate_M(self, m, n, p, q):
        """Calculate M entries for a given m, n, p, q."""

        L = self.length
        W = self.width
        rho = self.laminate.rho
        t = self.laminate.thickness
        val =  0.25*rho*t*L*W*kronecker(m,p)*kronecker(n,q)
        return val

    @property
    def KFull(self):
        """KFull matrix for all pairs.
        
        Returns
        -------
        
        KFull : numpy.array
        
        """

        if self._KFull is not None:
            return self._KFull
        
        dim = len(self.pairs)
        self._KFull = np.zeros((5*dim, 5*dim))
        self._KFull[0:dim, 0:dim] = self.K11
        self._KFull[0:dim, dim:2*dim] = self.K12
        self._KFull[0:dim, 3*dim:4*dim] = self.K14
        self._KFull[0:dim, 4*dim:5*dim] = self.K15

        self._KFull[dim:2*dim, 0:dim] = self.K21
        self._KFull[dim:2*dim, dim:2*dim] = self.K22
        self._KFull[dim:2*dim, 3*dim:4*dim] = self.K24
        self._KFull[dim:2*dim, 4*dim:5*dim] = self.K25

        self._KFull[2*dim:3*dim, 2*dim:3*dim] = self.K33
        self._KFull[2*dim:3*dim, 3*dim:4*dim] = self.K34
        self._KFull[2*dim:3*dim, 4*dim:5*dim] = self.K35

        self._KFull[3*dim:4*dim, 0:dim] = self.K14
        self._KFull[3*dim:4*dim, dim:2*dim] = self.K42
        self._KFull[3*dim:4*dim, 2*dim:3*dim] = self.K43
        self._KFull[3*dim:4*dim, 3*dim:4*dim] = self.K44
        self._KFull[3*dim:4*dim, 4*dim:5*dim] = self.K45

        self._KFull[4*dim:5*dim, 0:dim] = self.K51
        self._KFull[4*dim:5*dim, dim:2*dim] = self.K25
        self._KFull[4*dim:5*dim, 2*dim:3*dim] = self.K53
        self._KFull[4*dim:5*dim, 3*dim:4*dim] = self.K54
        self._KFull[4*dim:5*dim, 4*dim:5*dim] = self.K55

        return self._KFull