Boundary Conditions

Now that we have the global stiffness matrix \(\boldsymbol{K}\) we can write out the system of linear equations in the following way:

\[\boldsymbol{K} \boldsymbol{u} = \boldsymbol{f},\]

where \(\boldsymbol{u}\) denotes the displacement and \(\boldsymbol{f}\) the forces. Just to remind you, the structure of \(\boldsymbol{u}\) and \(\boldsymbol{f}\) is as follows:

\[\begin{split}\boldsymbol{u} = \begin{bmatrix} u_{0x} \\ u_{0y} \\ \vdots \\ u_{Nx} \\ u_{Ny} \end{bmatrix} \quad \boldsymbol{f} = \begin{bmatrix} f_{0x} \\ f_{0y} \\ \vdots \\ f_{Nx} \\ f_{Ny} \end{bmatrix}\end{split}\]

where \(u_{ix}\) and \(u_{iy}\) denote the \(x\) and \(y\) displacement of the \(i\)th node, respectively. \(f_{ix}\) and \(f_{iy}\) denote the force acting on the \(i\)th node in \(x\) and \(y\) direction, respectively.

Consider a system defined via

from fem.node import Node
from fem.element import Element
from fem.system import System

nodes = [Node(0, 1),
         Node(0, 0),
         Node(1, 0),
         Node(1, 1),
         Node(2, 1),
         Node(2, 0)]

elements = [Element(nodes[:4], 3e7, 0.3),
            Element(nodes[-1:1:-1], 3e7, 0.3)]

system = System(elements)
../_images/simple_mesh_plot.png

Now you can take a look at the global stiffness matrix:

../_images/simple_mesh_stiffness_matrix.png

What is missing are \(\boldsymbol{u}\) and \(\boldsymbol{f}\). As \(\boldsymbol{u}\) is the solution only \(\boldsymbol{f}\) – the loading – has to be defined. And as always there is a special name, even for this kind of boundary condition: Neumann boundary conditions.

Neumann Boundary Conditions

When a loading is prescribed it is a so-called Neumann boundary condition. If the loading is prescribed directly at the nodes in form of a point force it is sufficient to just enter the value in the force vector \(\boldsymbol{f}\). Assume a force of magnitude 10 acts on node 5 in \(x\) direction and 0 at all other degrees of freedom. Hence \(\boldsymbol{f}\) looks like this:

\[\boldsymbol{f} = \begin{bmatrix} 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 10 & 0 \end{bmatrix}^{\mathrm{T}}\]

In the code this should be handled by an attribute of the system with the name neumann_bc. This attribute should be a dict whose keys are the indices of the degrees of freedom and whose values are the forces that are prescribed. This attribute shall then be used by the following method that you shall implement:

System.assemble_force_vector()

Return the right hand side of the system taking Neumann boundary conditions into account.

Returns:The global right hand side of the system.
Return type:numpy.ndarray

With this you are able to prescribe forces on nodes. This is, however, not sufficient to solve the system, even though a solver routine might not give you an error or even a warning a quick look at the result should tell you that something is really, really fishy:

../_images/rigid_body_motion.png

Note how the size of the body suddenly is of the size \(10^9\). So by only applying one force at a single node in your body you blew up the body tremendously? The error is that by only applying Neumann boundary conditions the system has an infinite amount of solutions. The body could be moving in \(x\) direction, in \(y\) direction,it could be turning in the \(xy\) plane, or a combination of those. These are called the rigid body motions. To remedy this you have to “pin” the body somewhere – that is you have to prescribe the displacements of certain nodes so that your system has a unique solution. And like prescribing forces prescribing displacements has a name: Dirichlet boundary conditions.

Dirichlet Boundary Conditions

Prescribing displacements directly is a bit more involved than simply prescribing point loads. Suppose you wanted to pin node 0 in \(x\) and \(y\) direction and node 3 in \(x\) direction to eliminate the three rigid body modes (three constraints for three rigid body modes). They correspond to the degrees of freedom 0, 1 and 6. In order to constrain them the displacement vector \(\boldsymbol{u}\) will be split in two parts – the prescribed part \(\boldsymbol{u}_{\Gamma}\) and the unknown part \(\hat{\boldsymbol{u}}\). It follows

\[\boldsymbol{K} \boldsymbol{u} = \boldsymbol{K} (\boldsymbol{u}_{\Gamma} + \hat{\boldsymbol{u}}) = \boldsymbol{f}\]

which after reordering results in

\[\boldsymbol{K} \hat{\boldsymbol{u}} = \boldsymbol{f} - \boldsymbol{K} \boldsymbol{u}_{\Gamma}.\]

Subsequently the global stiffness matrix on the left hand side has to be reduced by setting the columns and rows of the degrees of freedoms that are prescribed to zero, except for the common element which is set to one.

\[\boldsymbol{K}_{\mathrm{r}} \hat{\boldsymbol{u}} = \boldsymbol{f} - \boldsymbol{K} \boldsymbol{u}_{\Gamma},\]

where

\[\begin{split}\boldsymbol{K}_{\mathrm{r}} = \begin{bmatrix} 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & K_{2\,2} & K_{2\,3} & K_{2\,4} & K_{2\,5} & 0 & K_{2\,7} & K_{2\,8} & K_{2\,9} & K_{2\,10} & K_{2\,11} \\ 0 & 0 & K_{3\,2} & K_{3\,3} & K_{3\,4} & K_{3\,5} & 0 & K_{3\,7} & K_{3\,8} & K_{3\,9} & K_{3\,10} & K_{3\,11} \\ 0 & 0 & K_{4\,2} & K_{4\,3} & K_{4\,4} & K_{4\,5} & 0 & K_{4\,7} & K_{4\,8} & K_{4\,9} & K_{4\,10} & K_{4\,11} \\ 0 & 0 & K_{5\,2} & K_{5\,3} & K_{5\,4} & K_{5\,5} & 0 & K_{5\,7} & K_{5\,8} & K_{5\,9} & K_{5\,10} & K_{5\,11} \\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & K_{7\,2} & K_{7\,3} & K_{7\,4} & K_{7\,5} & 0 & K_{7\,7} & K_{7\,8} & K_{7\,9} & K_{7\,10} & K_{7\,11} \\ 0 & 0 & K_{8\,2} & K_{8\,3} & K_{8\,4} & K_{8\,5} & 0 & K_{8\,7} & K_{8\,8} & K_{8\,9} & K_{8\,10} & K_{8\,11} \\ 0 & 0 & K_{9\,2} & K_{9\,3} & K_{9\,4} & K_{9\,5} & 0 & K_{9\,7} & K_{9\,8} & K_{9\,9} & K_{9\,10} & K_{9\,11} \\ 0 & 0 & K_{10\,2} & K_{10\,3} & K_{10\,4} & K_{10\,5} & 0 & K_{10\,7} & K_{10\,8} & K_{10\,9} & K_{10\,10} & K_{10\,11} \\ 0 & 0 & K_{11\,2} & K_{11\,3} & K_{11\,4} & K_{11\,5} & 0 & K_{11\,7} & K_{11\,8} & K_{11\,9} & K_{11\,10} & K_{11\,11} \end{bmatrix}\end{split}\]

At this point you may implement the get_reduced_system() method according to the specifications given. Use the other methods you already implemented inside of this method to retrieve the system without applied boundary conditions.

System.get_reduced_system()

Return the reduced global stiffness matrix and right hand side taking all boundary conditions into account.

Returns:
  • K_reduced (numpy.ndarray) – The reduced global stiffness matrix
  • rhs (numpy.ndarray) – The reduced global right hand side

Now you can calculate the right hand side and get your solution using a solver for systems of linear equations, for example a direct solver, a conjugate gradient solver or the solver that comes with NumPy – numpy.linalg.solve(). Use this in a solve() method that is specified as follows:

System.solve()

Return the solution vector of the system.

Returns:Solution vector of the system.
Return type:numpy.ndarray