def calculate_stiffness_matrix(self, test=False, rank=False):
        """Computes the 4th order stiffness tensor for the element using Gauss quadrature. Runs for each deformed
        configuration in the analysis. Uses the 2D effective covariant tangent moduli, deformed midsurface basis
        vectors, kirchhoff stress, and differential area to account for the curvilinear coordinate system.

        :param bool test: whether to perform numerical differentiation check on the result
        :param bool rank: whether to check the rank of the stiffness matrix
        """
        # Initialize stiffness matrix to be computed using Gauss quadrature
        dimensions = (self.degrees_of_freedom, self.node_quantity, self.degrees_of_freedom, self.node_quantity)
        stiffness_matrix = numpy.zeros(dimensions)
        # Sum over quadrature points
        for quadrature_point in self.quadrature_points:
            # Initialize integrand to be computed for this quadrature point
            integrand = numpy.zeros(dimensions)
            for dof_1 in range(self.degrees_of_freedom):
                for node_index_1 in range(self.node_quantity):
                    for dof_2 in range(self.degrees_of_freedom):
                        for node_index_2 in range(self.node_quantity):
                            # Sum over repeated indices
                            for coordinate_index_1 in range(self.dimension):
                                for coordinate_index_2 in range(self.dimension):
                                    for coordinate_index_3 in range(self.dimension):
                                        for coordinate_index_4 in range(self.dimension):
                                            integrand[dof_1][node_index_1][dof_2][node_index_2] += (
                                                (2 * quadrature_point.tangent_moduli_effective_2d[coordinate_index_1][
                                                    coordinate_index_2][coordinate_index_3][coordinate_index_4]
                                                 * numpy.outer(quadrature_point.current_configuration.midsurface_basis[
                                                                   coordinate_index_2],
                                                               quadrature_point.current_configuration.midsurface_basis[
                                                                   coordinate_index_4])[dof_1][dof_2]
                                                 + .5 * quadrature_point.kirchhoff_stress[coordinate_index_1][
                                                     coordinate_index_2] * (dof_1 == dof_2)
                                                 * (coordinate_index_2 == coordinate_index_3)
                                                ) * self.shape_function_derivatives(node_index=node_index_1,
                                                                                    position=quadrature_point.position,
                                                                                    coordinate_index=coordinate_index_1)
                                                * self.shape_function_derivatives(node_index=node_index_2,
                                                                                  position=quadrature_point.position,
                                                                                  coordinate_index=coordinate_index_3)
                                            )
            # Weight the integrand
            integrand *= quadrature_point.weight
            # Add the integrand to the stiffness matrix
            stiffness_matrix += integrand
        # Scale the stiffness matrix for isoparametric triangle and multiply by the thickness and differential area
        stiffness_matrix *= .5 * self.thickness * self.reference_configuration.differential_area
        if test:
            tests.numerical_differentiation_stiffness_matrix(element=self, stiffness_matrix=stiffness_matrix)
        if rank:
            tests.rank_stiffness_matrix(element=self, stiffness_matrix=stiffness_matrix)
        return stiffness_matrix
    def calculate_stiffness_matrix_old(self, test=False, rank=True):
        """Computes the 4th order stiffness tensor for the element using Gauss quadrature. Runs for each deformed
        configuration in the analysis.

        OLD: uses inverse jacobian and 2D tangent moduli C_iJkL.

        :param bool test: whether to perform numerical differentiation check on the result
        :param bool rank: whether to check the rank of the stiffness matrix
        """
        # Initialize stiffness matrix to be computed using Gauss quadrature
        dimensions = (self.degrees_of_freedom, self.node_quantity, self.degrees_of_freedom, self.node_quantity)
        stiffness_matrix = numpy.zeros(dimensions)
        # Sum over quadrature points
        for quadrature_point in self.quadrature_points:
            # Initialize integrand to be computed for this quadrature point
            integrand = numpy.zeros(dimensions)
            for dof_1 in range(self.degrees_of_freedom):
                for node_index_1 in range(self.node_quantity):
                    for dof_3 in range(self.degrees_of_freedom):
                        for node_index_2 in range(self.node_quantity):
                            # Sum over repeated indices
                            for dof_2 in range(self.degrees_of_freedom):
                                for dof_4 in range(self.degrees_of_freedom):
                                    for coordinate_index_1 in range(self.dimension):
                                        for coordinate_index_2 in range(self.dimension):
                                            integrand[dof_1][node_index_1][dof_3][node_index_2] += (
                                                quadrature_point.tangent_moduli[dof_1][dof_2][dof_3][dof_4]
                                                * self.shape_function_derivatives(
                                                    node_index=node_index_1,
                                                    position=quadrature_point.position,
                                                    coordinate_index=coordinate_index_1)
                                                * self.shape_function_derivatives(
                                                    node_index=node_index_2,
                                                    position=quadrature_point.position,
                                                    coordinate_index=coordinate_index_2)
                                                * self.jacobian_matrix_inverse[coordinate_index_1][dof_2]
                                                * self.jacobian_matrix_inverse[coordinate_index_2][dof_4])
            # Weight the integrand
            integrand *= quadrature_point.weight
            # Add the integrand to the stiffness matrix
            stiffness_matrix += integrand
        # Scale the stiffness matrix for isoparametric triangle and multiply by the thickness (assumed to be constant)
        stiffness_matrix *= .5 * self.thickness
        if test:
            tests.numerical_differentiation_stiffness_matrix(element=self, stiffness_matrix=stiffness_matrix)
        if rank:
            tests.rank_stiffness_matrix(element=self, stiffness_matrix=stiffness_matrix)
        return stiffness_matrix