diff --git a/examples/structural_mechanics/python_interface/structural_mechanics_python_interface_test_dynamics.ipynb b/examples/structural_mechanics/python_interface/structural_mechanics_python_interface_test_dynamics.ipynb index 14c7b164f..98c1f67c7 100644 --- a/examples/structural_mechanics/python_interface/structural_mechanics_python_interface_test_dynamics.ipynb +++ b/examples/structural_mechanics/python_interface/structural_mechanics_python_interface_test_dynamics.ipynb @@ -1,643 +1,751 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Test of Structural Mechanics\n", "In this example a beam, consisting of two elements, three nodes, is created.\n", "The left most node is fixed and a force is applied at the right most node.\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import akantu as pyaka\n", " \n", "import copy\n", "import numpy" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import matplotlib\n", "import matplotlib.pyplot as plt\n", "import numpy as np" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Creating the Mesh" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# Create a mesh for the two dimensional case\n", - "el_type = pyaka._bernoulli_beam_3\n", - "beam = pyaka.Mesh(3)" + "el_type = pyaka._bernoulli_beam_2\n", + "beam = pyaka.Mesh(2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now create the connectivity array for the beam." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "beam.addConnectivityType(el_type)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We need a `MeshAccessor` in order to change the size of the mesh entities." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "beamAcc = pyaka.MeshAccessor(beam)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we create the array to store the nodes and the connectivities and give them their size. " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "nb_elem = 30\n", + "nb_elem = 40\n", "L = 2\n", "beamAcc.resizeConnectivity(nb_elem, el_type)\n", "beamAcc.resizeNodes(nb_elem + 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Setting the Nodes" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "Nodes = beam.getNodes()\n", "length = L / nb_elem\n", - "for n in range(nb_elem + 1):\n", - " Nodes[n, :] = [n * length, 0., 0.];" + "Nodes[:, :] = 0.\n", + "Nodes[:, 0] = np.arange(nb_elem+1) * length" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Setting the Connections" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "Conn = beam.getConnectivity(el_type)\n", "\n", "for e in range(nb_elem):\n", " Conn[e, :] = [e, e + 1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Ready\n", "We have to make the mesh ready." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "beamAcc.makeReady()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Creating the Model" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "model = pyaka.StructuralMechanicsModel(beam)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ - "normal = beam.getDataReal(\"extra_normal\", el_type)\n", + "if el_type == pyaka._bernoulli_beam_3:\n", + " normal = beam.getDataReal(\"extra_normal\", el_type)\n", "\n", - "for e in range(nb_elem):\n", - " normal[e, :] = [0, 0, 1]" + " for e in range(nb_elem):\n", + " normal[e, :] = [0, 0, 1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Setting up the Modell" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Creating and Inserting the Materials" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mat1 = pyaka.StructuralMaterial()\n", "mat1.E = 1e9\n", "mat1.rho = 10.\n", "mat1.I = 1.\n", "mat1.Iz = 1.\n", "mat1.Iy = 1.\n", "mat1.A = 1.\n", "mat1.GJ = 1.\n", "model.addMaterial(mat1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Initializing the Model" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "model.initFull(pyaka.AnalysisMethod._implicit_dynamic)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Assigning the Materials" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ - "materials = model.getElementMaterialMap(pyaka.ElementType._bernoulli_beam_3)" + "materials = model.getElementMaterial(el_type)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "materials[:, :] = 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Setting Boundaries" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "16\n" + "20\n" ] } ], "source": [ "# Neumann\n", "F = 1e4\n", - "no_print = int(nb_elem / 2 + 1)\n", + "no_print = int(nb_elem / 2)\n", "\n", "print(no_print)\n", "\n", "# Apply a force of `10` at the last (right most) node.\n", "forces = model.getExternalForce()\n", "forces[:, :] = 0\n", "forces[no_print, 1] = F" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "# Dirichlets\n", "# Block all dofs of the first node, since it is fixed.\n", "# All other nodes have no restrictions\n", "boundary = model.getBlockedDOFs()\n", "boundary[:, :] = False\n", "\n", "boundary[0, 0] = True\n", "boundary[0, 1] = True\n", - "boundary[0, 2] = True\n", + "\n", + "if el_type == pyaka._bernoulli_beam_3:\n", + " boundary[0, 2] = True\n", "\n", "boundary[nb_elem, 1] = True" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Solving the System" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "# Set up the system\n", "deltaT = 1e-6\n", "model.setTimeStep(deltaT)\n", "solver = model.getNonLinearSolver()\n", "solver.set(\"max_iterations\", 100)\n", "solver.set(\"threshold\", 1e-8)\n", "solver.set(\"convergence_type\", pyaka.SolveConvergenceCriteria.solution)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "model.assembleMatrix(\"M\")\n", - "M = pyaka.AkantuSparseMatrix(model.getDOFManager().getMatrix(\"M\"))" + "M_ = model.getDOFManager().getMatrix(\"M\")\n", + "M = pyaka.AkantuSparseMatrix(M_)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "model.assembleMatrix(\"K\")\n", - "K = pyaka.AkantuSparseMatrix(model.getDOFManager().getMatrix(\"K\"))" + "K_ = model.getDOFManager().getMatrix(\"K\")\n", + "K = pyaka.AkantuSparseMatrix(K_)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], + "source": [ + "C_ = model.getDOFManager().getMatrix(\"C\")\n", + "C_.add(M_, 0.00001)\n", + "C_.add(K_, 0.00001)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], "source": [ "def Mmat2D(L=1, A=1, rho=1):\n", " return rho*A*L/420 * np.matrix([\n", " [140, 0, 0, 70, 0, 0],\n", " [ 0, 156, 22*L, 0, 54, -13*L],\n", " [ 0, 22*L, 4*L**2, 0, 13*L, -3*L**2],\n", " [ 70, 0, 0, 140, 0, 0],\n", " [ 0, 54, 13*L, 0, 156, -22*L],\n", " [ 0,-13*L, -3*L**2, 0, -22*L, 4*L**2]])" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "def Kmat2D(L=1, A=1, E=1, I=1):\n", " return np.matrix([\n", " [ E*A/L, 0, 0, -E*A/L, 0, 0],\n", " [ 0, 12*E*I/L**3, 6*E*I/L**2, 0, -12*E*I/L**3, 6*E*I/L**2],\n", " [ 0, 6*E*I/L**2, 4*E*I/L, 0, -6*E*I/L**2, 2*E*I/L],\n", " [-E*A/L, 0, 0, E*A/L, 0, 0],\n", " [ 0, -12*E*I/L**3, -6*E*I/L**2, 0, 12*E*I/L**3, -6*E*I/L**2],\n", " [ 0, 6*E*I/L**2, 2*E*I/L, 0, -6*E*I/L**2, 4*E*I/L]])" ] }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "def Mmat3D(L=1, A=1, rho=1):\n", " return rho*A*L/420 * np.matrix([\n", " [140, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 0],\n", " [ 0, 156, 0, 0, 0, 22*L, 0, 54, 0, 0, 0, -13*L],\n", " [ 0, 0, 156, 0, -22*L, 0, 0, 0, 54, 0, 13*L, 0],\n", " [ 0, 0, 0, 140, 0, 0, 0, 0, 0, 70, 0, 0],\n", " [ 0, 0, -22*L, 0, 4*L**2, 0, 0, 0, -13*L, 0, -3*L**2, 0],\n", " [ 0, 22*L, 0, 0, 0, 4*L**2, 0, 13*L, 0, 0, 0, -3*L**2],\n", " [ 70, 0, 0, 0, 0, 0, 140, 0, 0, 0, 0, 0],\n", " [ 0, 54, 0, 0, 0, 13*L, 0, 156, 0, 0, 0, -22*L],\n", " [ 0, 0, 54, 0, -13*L, 0, 0, 0, 156, 0, 22*L, 0],\n", " [ 0, 0, 0, 70, 0, 0, 0, 0, 0, 140, 0, 0],\n", " [ 0, 0, 13*L, 0, -3*L**2, 0, 0, 0, 22*L, 0, 4*L**2, 0],\n", " [ 0, -13*L, 0, 0, 0, -3*L**2, 0, -22*L, 0, 0, 0, 4*L**2]])" ] }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "def Kmat3D(L=1, A=1, E=1, Iy=1, Iz=1, GJ=1):\n", " a1 = E * A / L\n", " b1 = 12 * E * Iz / L**3\n", " b2 = 6 * E * Iz / L**2\n", " b3 = 4 * E * Iz / L\n", " b4 = 2 * E * Iz / L\n", " \n", " c1 = 12 * E * Iy / L**3\n", " c2 = 6 * E * Iy / L**2\n", " c3 = 4 * E * Iy / L\n", " c4 = 2 * E * Iy / L\n", " \n", " d1 = GJ / L\n", " \n", " return np.matrix([\n", " [ a1, 0, 0, 0, 0, 0, -a1, 0, 0, 0, 0, 0],\n", " [ 0, b1, 0, 0, 0, b2, 0, -b1, 0, 0, 0, b2],\n", " [ 0, 0, c1, 0, -c2, 0, 0, 0, -c1, 0, -c2, 0],\n", " [ 0, 0, 0, d1, 0, 0, 0, 0, 0, -d1, 0, 0],\n", " [ 0, 0, -c2, 0, c3, 0, 0, 0, c2, 0, c4, 0],\n", " [ 0, b2, 0, 0, 0, b3, 0, -b2, 0, 0, 0, b4],\n", " [ -a1, 0, 0, 0, 0, 0, a1, 0, 0, 0, 0, 0],\n", " [ 0, -b1, 0, 0, 0, -b2, 0, b1, 0, 0, 0, -b2],\n", " [ 0, 0, -c1, 0, c2, 0, 0, 0, c1, 0, c2, 0],\n", " [ 0, 0, 0, -d1, 0, 0, 0, 0, 0, d1, 0, 0],\n", " [ 0, 0, -c2, 0, c4, 0, 0, 0, c2, 0, c3, 0],\n", " [ 0, b2, 0, 0, 0, b4, 0, -b2, 0, 0, 0, b3]])\n" ] }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "if el_type == pyaka._bernoulli_beam_2:\n", " kmat1 = Kmat2D(E=mat1.E, A=mat1.A, I=mat1.I, L=length)\n", " mmat1 = Mmat2D(A=mat1.A, L=length, rho=mat1.rho)\n", " nb_dofs = 3\n", "else:\n", " kmat1 = Kmat3D(E=mat1.E, A=mat1.A,\n", " Iy=mat1.Iy, Iz=mat1.Iz,\n", " GJ=mat1.GJ, L=length)\n", " mmat1 = Mmat3D(A=mat1.A, L=length, rho=mat1.rho)\n", " nb_dofs = 6" ] }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "def assemble(Mg, Me, n1, n2, nd):\n", " s1 = n1 * nd\n", " s2 = n2 * nd\n", " \n", " Mg[s1:s1+nd, s1:s1+nd] += Me[ 0:nd , 0:nd ]\n", " Mg[s2:s2+nd, s2:s2+nd] += Me[nd:2*nd, nd:2*nd]\n", " Mg[s1:s1+nd, s2:s2+nd] += Me[ 0:nd , nd:2*nd]\n", " Mg[s2:s2+nd, s1:s1+nd] += Me[nd:2*nd, 0:nd ]" ] }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "total_nb_dofs = nb_dofs * (nb_elem + 1)\n", "\n", "Ka = np.zeros((total_nb_dofs, total_nb_dofs))\n", "Ma = np.zeros((total_nb_dofs, total_nb_dofs))\n", "\n", "for e in range(nb_elem):\n", " n1, n2 = Conn[e, :]\n", " \n", " assemble(Ka, kmat1, n1, n2, nb_dofs)\n", " assemble(Ma, mmat1, n1, n2, nb_dofs)\n" ] }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "1.581304729629375e-15\n", - "4.999022389403736e-16\n" + "3.2757195579646225e-15\n", + "9.075211880647167e-16\n" ] } ], "source": [ "print(np.linalg.norm(Ka - K.todense())/np.linalg.norm(Ka))\n", "print(np.linalg.norm(Ma - M.todense())/np.linalg.norm(Ma))" ] }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "def analytical_solution(time, L, rho, E, A, I, F):\n", - " def w(n):\n", - " return n**2 * np.pi**2 / L**2 * np.sqrt(E * I / (rho * A));\n", + " omega = np.pi**2 / L**2 * np.sqrt(E * I / rho)\n", + " sum = 0.\n", + " N = 110\n", + " for n in range(1, N, 2):\n", + " sum += (1. - np.cos(n * n * omega * time)) / n**4\n", "\n", - " return 2 * F * L**3 / (np.pi**4 * E * I) * \\\n", - " ((1. - np.cos(w(1) * time)) + (1. - np.cos(w(3) * time)) / 81. +\n", - " (1. - np.cos(w(5) * time)) / 625.);\n" + " return 2. * F * L**3 / np.pi**4 / E / I * sum" ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 31, "metadata": {}, "outputs": [], "source": [ "# Perform N time steps.\n", "# At each step records the displacement of all three nodes in x direction.\n", - "N = 300\n", + "N = 900\n", "\n", "disp = model.getDisplacement()\n", + "velo = model.getVelocity()\n", "disp[:, :] = 0.\n", "\n", "displs = np.zeros(N)\n", "\n", + "ekin = np.zeros(N)\n", + "epot = np.zeros(N)\n", + "ework = np.zeros(N)\n", + "_ework = 0.\n", + "\n", + "\n", + "\n", "for i in range(1, N):\n", " model.solveStep() \n", - " displs[i] = disp[no_print, 1]\n" + " displs[i] = disp[no_print, 1]\n", + " \n", + " _ework += F * velo[no_print, 1] * deltaT\n", + " \n", + " ekin[i] = model.getEnergy(\"kinetic\")\n", + " epot[i] = model.getEnergy(\"potential\")\n", + " ework[i] = _ework\n", + " \n" ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[]" + "[]" ] }, - "execution_count": 34, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXQAAAEDCAYAAAAlRP8qAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAABWs0lEQVR4nO29d3xj1Zn//z6Su+XePa5jezrTZxiGDqEGSAFCDalLSNskv/RsdtN3N8luki8pS0hCIIFA6CGB0AcYYIDpwzT37rHlbkuyLUs6vz+O5PF4XGRb0pWu7vv18ms80tW9j46lz33Oc57zPEJKiYGBgYFB5GPS2gADAwMDg8BgCLqBgYGBTjAE3cDAwEAnGIJuYGBgoBMMQTcwMDDQCYagGxgYGOgETQVdCHGPEMIqhDgcoPOVCCGeF0IcE0IcFUKUBeK8BgYGBpGA1h76vcDlATzfn4CfSilXAlsBawDPbWBgYBDWaCroUsrXgL7JjwkhKoQQzwoh9gohdgohVvhzLiHEKiBGSvmC99w2KaUj8FYbGBgYhCdae+jTcTfweSnlJuArwG/8fN0yYEAI8bgQYr8Q4qdCCHPQrDQwMDAIM2K0NmAyQggLsB14RAjhezje+9wHge9P87J2KeVlqPdyLrABaAH+CnwU+ENwrTYwMDAID8JK0FEzhgEp5fqpT0gpHwcen+W1bcB+KWUDgBDiSWAbhqAbGBhECWEVcpFSDgGNQojrAYRinZ8v3w1kCCFyvP+/CDgaBDMNDAwMwhKt0xYfBHYBy4UQbUKITwC3AJ8QQhwEjgDv8+dcUko3Kub+khDiXUAAvwuO5QYGBgbhhzDK5xoYGBjog7AKuRgYGBgYLBzNFkWzs7NlWVmZVpc3MDAwiEj27t3bI6XMme45zQS9rKyMPXv2aHV5AwMDg4hECNE803NGyMXAwMBAJxiCbmBgYKATDEE3MDAw0AmGoBsYGBjoBEPQDQwMDHSCIegGBgYGOsEQdAMDAwOdYAh6EOm1jfHnXU10Do5qbYqucLo8PL6vjXfbBrU2RVdIKXnm3RPsrO3W2hTdsb+ln8f2tuH2BLfUSriVz9UN1uFRPvDrN2kfGOFXO+p44jNnU5ieqLVZEY/bI/nEfbvZWdtDrFlw38e3sr0iW2uzdMGPn63mrlfrAfj2e1fyyXOXamyRPvj7wQ7+9aH9SAmv1XbzixvWM6nfQ0AxPPQg8fVHD9FrH+O/PngGw6Mu/r+HD+AJ8t05GvjVy3XsrO3hy5csozQrmS/99QADDqfWZkU8r1RbuevVeq7dWMSlq/L4z2eOcahtQGuzIp6OgRG+9ughNpVk8KnzlvK3Ax38dXdr0K5nCHoQeKXayo7qbr5y6XJu2lrCd65exVsNfTyxv11r0yKall4Hv95RxzXrCvn8xVX84ob19Nqc/OS5aq1Ni2jGXG6+/eRhqnIt/OgDa/ifD60jMzmO7/39KEY11sXxk2eP45aSn9+wnq9fvoLtFVn86Olj9NuD44QYgh5gXG4P//nMMcqykrjtrDIArt9UzBlL0vjZCzWMudzaGhjB3L1ThQP+7b0rAVizJI2btpbw8O5WWnqNfuAL5cG3W2jrH+HbV60iIdZMakIsX71sOXub+3nuSKfW5kUsB1oHePJAB588p5zizCRMJsF3rl6N3eniN6/UBeWahqAHmL8d6KCmy8Y3rlhBXIwaXpNJ8LXLl9M+MMJD7wRvuqVnBkfGeXxfO9esLyQvNWHi8c9dVInZJPjFSzUaWhe5jI67+eXLdZy1NIvzqk6uRVy7sYil2cn84sVaI1S4QP7nuWqyLfF85sLKiceW56fw/fet4aatJUG5piHoAeaBt5upyEnmstX5pzx+TmU264rTuf+tZmMauwAe2dOKw+nmo9vLTnk8LzWBm88s4akDHfTYxrQxLoJ55t0T9NqdfP7iylMW6mLMJv714iqOdw7zao2R9TJfGnvsvF7Xw0e3l2KJPzX35NZtpSzNsQTluoagB5DqzmH2tQxw09aS01axhRDcuKWYWquNA60D2hgYobg9kj/tamZzaQZrlqSd9vzNW0tweSRP7DPWKObLg++0UJ6dzFlLs0577sozCshKjuPBd1o0sCyyeWh3C2aT4PrNxSG9riHoAeSh3S3EmU18cGPRtM9ftbaAxFhzUFe59ciO41Za+hx89OyyaZ+vykthQ0k6f93Tasx+5kGddZjdTf3cuKV42jS6uBgT120q4qXjVqzDxl4Kfxl3e3hsbxsXr8g9JTwYCgxBDxCj426e2N/OpavzyEyOm/aYlIRYrlpbwFMHO7CNuUJsYeRy364m8lMTTgtjTeaGzcXUWW3saxkInWERzkPvtBJrFly7aXoHBOCGLcW4PZJH97aF0LLI5qVjVnpsTm7cGlrvHAxBDxjPHelkwDHOjVtmX+y46cwSHE43Tx3oCJFlkU1t1zA7a3u4dVsJseaZP65XrSskKc7Mw8bsxy/GXG4e29fGJavyyLbEz3jc0hwLW8sz+etuY/bjL3/d3UJeajznVU3bJS6oGIIeIO59s4myrCS2V5wei5zMhuJ0VuSnGIujfnLfribiYkxzZgVY4mN47xkF/ONQB3Zj9jMnzx3pot8PBwTgpq3FNPc62NXQGwLLIpvGHjs7qru5cUsJMbM4IMHCEPQAcKB1gP0tA3xkexkm0+xbeoUQ3LqtlKMnhthvLI7OyuDIOI/tbeeadYVkzeJF+rhhSzF2p5unD50IgXWRzb1vNFKWlcQ5lXOXTbhiTQFpibE88JaxODoX973ZRKxZcMu24KQlzoUh6AHg3jcascTHcN0sscjJvH/DEizxMdz/1oy9Xg1QqYoj46enKs7EptIMluYk89c9RthlNg62DrDPTwcEICHWzI1bivnn4RO09RsbuGZiaHScR/a0cvXaQnJTQrsY6sMQ9EXSaxvj6XdPcP3mIlISYv16jSU+hg9uXMI/Dp2gL0hbgCMdKSUPvtPCxpL0aVMVp0MIwQ2bi9nb3E+ddTjIFkYu973ZNC8HBOAj28sQQvBnwwmZkSf3t2N3umfMxgoFhqAvkheOdjHully/aX4r2rduK8Xp8vCI4U1OS02XjfpuOx/YsGRer/vgxiJiTIKH9xhZGdMx5nLz3JFOrlpb4LcDAlCYnsgFy3L4+4EOY+1nBp493EllroW1Rema2WAI+iJ59kgnxZmJrCxImdfrluWlsK44nX8eNmplTMfT755ACLhszcypitORkxLPRStyeXxfG+NuT5Csi1zebujD7nRzyaq8eb/2yjMK6BgcNTbGTcOgY5y3G/sWNK6BxBD0RTA8Os6bdb1cvjp/QfWNL16Ry8G2AXqNLeun8dzhTraUZS4oFnnDlmJ6bE6jUcM0vHSsi4RYE2f7sRg6lYtW5CIERimAadhRbcXtkeEv6EKIBCHEO0KIg0KII0KI701zjBBC3CmEqBNCHBJCbAyOueHFm/W9ON0eLl65sD/ihctzkdL4gkylc3CU6q5hLlqRu6DXn12ZTUKsiddqegJsWWQjpeTFY1bOqcwhIdY879dnJMextiid14zP62m8cLSLnJR41msYbgH/PPQx4CIp5TpgPXC5EGLblGOuAKq8P7cD/xdII8OVnbXdJMWZ2ViSsaDXry5MJSclnh3VxhdkMj7PeqEbMxJizWxbmmUIzxSOdw7TPjDCe1Yu7EYJcF5VNgdaBxgcGQ+gZZHNmMvNK9VW3rMy16+soWAyp6BLhc3731jvz9RVkfcBf/Ie+xaQLoQoCKyp4cfrtT2ctTRrokzufDGZBBcsy+G1mu6g9xqMJF6r7SEnJX7e6xKTObcqh4YeO619Rpqdjx3VVoAFz3wAzluWg0fCm3XG7MfHvuYB7E43F63QNtwCfsbQhRBmIcQBwAq8IKV8e8ohS4DJ6Rpt3semnud2IcQeIcSe7u7I9p5aeh009To4t2px/Sy3V2YxODJOdaeRZgeqsuLrtd2cW5W9qL6L5y9Tf5edtYbw+Hi7oY+qXAu5iygYtb44HUt8DK8Z4zrBWw29mARsLc/U2hT/BF1K6ZZSrgeKgK1CiDVTDpnum3eayymlvFtKuVlKuTknJ/R1DgLJzjp1Qzp32eLex+ZS9SHY09y3aJv0wPHOIfod44u+UVbkWChIS+ANw5ME1I1yX3M/WxYpOrFmE9srjHDWZN5q6GV1YRppif6ngQaLecUKpJQDwCvA5VOeagMmJ2IXAbquPrWnqZ9sSzxLs5MXdZ6ijEQK0hJ4p9EQdIC9zf0AbClbnPAIIdhSlsme5j4jbxo4dmKI4TEXWxc5rgBnVWTRPjDCicGRAFgW2YyOu9nfOsC2pdp75+BflkuOECLd+3si8B7g+JTDngJu82a7bAMGpZS6Lqixr6WfjSXpiwoLwEnh2d1kCA8oQc9LjWdJeuKiz7WpNIOuoTE6Bo1a3nualMOwWA8dmEgC2Nc8sOhzRTrvtg/idHnYWj57Ub5Q4Y+HXgDsEEIcAnajYuj/EELcIYS4w3vMM0ADUAf8DvhMUKwNE3psYzT3OthYurDslqlsKc+ka2iM1j7D49nb3M+m0oxF3yhBCbrvnNHO7qZ+CtMSAnKjXFmQSnyMiX0txrge9G6yWlfsX3mKYBMz1wFSykPAhmkev2vS7xL4bGBNC18OeJsoLDRdcSpbyrzC09JHSVZSQM4ZiViHRmnrH/G7GNdcrMhPITHWzN6mPq5ZVxiQc0Yq+1v62RSAcAuoTkZnLEljvyHoHGobpCAtQbNiXFMxdoougINtA5hNgjP8LBo1F5U5FuJjTBxpHwrI+SKVoyfU+/e3GNdcxJhNrFmSyuGO6B7XAYeTjsFRVhWkBuycG0rSOdw+FPXlFQ62DbC2KDy8czAEfUFUdw5TmpVEYtz8d9tNR4zZxIqCVA53DAbkfJFKbZfa7rA8b+H551NZVZDK8RNDeKI4z/+4NyV2MXn9U1lZkIrT7aG51x6wc0YaAw4nzb0O1hWna23KBIagL4Baqy2gogOwpjCVIx1DUb0wWtM1TLYlnowZerIuhFWFqdidblqieIPRMe/MJ5Ae+jLv57+60zbHkfrl2Al1o1xdaHjoEcvouJvmXjtVARb0FfkpDI+66ByK3oyMmq5hluVZAnrOlV4R84laNHLsxBBZyXHkpMzd9clfKnMtmARUd0Xvhri6bnUzq8oN7Gd2MRiCPk/qu214JAEXngrvh6LOGp0ej8cjqbXaJjy/QFGVm4IQalYVrRzvHGZlQWpAMod8JMSaKctOpiaKdzjXW20kx5kpSAuPBVEwBH3e+OK8gRaeyigX9PaBERxOd8DHNTHOTFFGYtQKuscjqe0K/I0S1FpHVHvoVhsVuZaA3igXy5xpiwanUtM1TIxJUJblxw7Rlreh9jmQHlh6IZSfBzP88XMs8aQlxkatoNd4hSHQMx9QXnptlApPx+AII+PuCYchkFTlpfDskU5Gx90LKscb6dRahxdUVz6YGII+T2q6bJRnJ89eYdE1Bn//Ahx8EEwxgIDXfw7L3wsf+D9IOH0RRQhBZa4ligXdG48MgidZlWvh9boeXG4PMebompT6Pk/BEPRleRakhIZuO6sKA7fgGgkMjY7TNTQWlHFdDNH16Q4Atdbh2aevHjc8fJsS8/O+Ct9ogW+2wSXfV976nz8IY9OLdmVO9Ap6bdcw+akJQSlwVJlrweny0NoffTtxgynoVbnqe1AbhQ25633jmmMIesQy4k1/q5otLPDaT6HmWbjyf+Cib0NcMsQmwNlfgOvvg459ynufJj2xMtdCr91Jv90ZxHcRnlR3Dc8+rovA5/VHY9ilvttGZnIcmQFMBfVRlp2E2SSi0gkJ5o1yMRiCPg/qu21IOcuCaNcRePXHsPYG2Povpz+/8iq48N/g8KPw7qOnPT2xMNodXV8Qt0dSF4Tcfh++cY3GhdE6qy1oXmR8jJnSrKSJRIFooq7bRpzZRElmeJXqMAR9Hsx6V5YSnv0GJKTDFT+e+STnfAkKN8Jz34LRU3eGRmumS2ufgzGXJyiZGACW+BgK0xKiblylVKmgFUH0IqtyLVEZcqnzrqWF25pMeFkT5tRZbZhnynBp2gmNr8EF34TEWYp2mcxw1c/AboVdvz7lqSXpiSTEmqJOeHyec2WQQi7q3ClRJzy9dicDjvGghgWqclNo6nXgdEVXTZe6blvYhVvAEPR5UWe1UZqZNH2Gy+u/gORc2Hjb3Ccq3AArr4FdvwHHycYWJpOgIgoXRkMRj6zyZhBFU02XkIxrngW3R9IURTVdRsfdtPY5gjrzWSiGoM+Duu4Zpq+d70L9S7DtDrUA6g8XfBOcttO89GhMXayz2shNiSc1IXgtvKpyLYyOe2gfiJ5Ml1AI+sT6RBTF0Rt77Hhk+C2IgiHofjPu9tDUY5/+j7j79xCTCJs/4f8J81bBivfCnntg/KTIVOZYaB8YwT7mCoDVkUEopq++DJqaKMp0qbPaSIozUxjErekVORZvaYXoGdfaME1ZBEPQ/aa514HLI0//IzodcPhxWPU+SEyf30nP/BSM9MHhxyYe8glbQ3d0TGGllNRbgy/olRM509HjSdZ327yCG7yt6QmxZkoyk6JqXOusNkwCluYsrp9wMDAE3U9mnL4e/weMDcGGW+d/0rJzIXcVvP3bibz0k6mL0eHxdA2NYRtzBV3Q0xJjyUuNj6rQQF0IbpTgXZ+IonGtt9oozkwKy3IHhqD7Sb03N/y0GPqBByC9FErPnv9JhYDNH4fOQ+oHKM1KjqrNGnUhnL5W5aZQFyWhAduYixODoyER9MrcFBp6bLiipHtRMHP7F4sh6H5SZ7VRkJaAJX5S+RtHHzTuhDXXgmmBQ7nmWjDHwcGHANWvsTQrKYoEXQlsaITHQq3VFhVNRHxb0ytCcqO0MO6WNEdBExGX20PjTGtpYYAh6H4y7fS1+p8g3bDy6oWfOCkTll0Ohx4G9zhwMsUuGqjrtpGSEBPQ5gszUZVnweF00zGo/yYiodya7ltwjoZwVkufA6fbE5Ypi+CHoAshioUQO4QQx4QQR4QQX5jmmAuEEINCiAPen/8Ijrna4PHIiQWmUzj+D0gtUnnli2H9zeDogbqXAPUlbI6SzRq+G2UoakpPFJOKgkyXum4bMSZBaVbwt6b7vhfREM7y3SjDqUvRZPzx0F3Al6WUK4FtwGeFEKumOW6nlHK99+f7AbVSY04MjeJwTqkp7bRD/cuqPstixajyPZCUDYdU2KUy14LLI6OiAW+d1R6yeKTvSxgNs586q9qaHhuCrenJ8TEsSY+OJiJ1M62lhQlz/rWllCeklPu8vw8Dx4AlwTYsnJh2+lr3IrhGYcVVi7+AOVaFbWqeh/ERKnNSTrmuXhl0jNNjC11N6YzkOLItcVERGghVhouPqjxL1IxrXmpwN8EthnndvoUQZcAG4O1pnj5LCHFQCPFPIcTqGV5/uxBijxBiT3d39/yt1YhpBb32edWoouSswFxk5dUwbof6HVTkJp9yXb3iS80MpfBURkExqTGXamQeUkHPtVDfbcOt89IKodgzsRj8FnQhhAV4DPiilHJqC/V9QKmUch3wS+DJ6c4hpbxbSrlZSrk5JydngSaHnjqrjfSkWLJ8NaWlhPodUH4+mAPU9KnsXHWDOPZ3kuLUFFbvZXS1qCldlZui+0yXph5HyLemV+WmMOby0Nav30wXKVWZZ99aTDjil6ALIWJRYv6AlPLxqc9LKYeklDbv788AsUKI8Gq2twjqvXmnEwt3PbUw1A4VFwXuIjFxsPxKqH4G3ONUREGmS53VRlyMiaKM0NWUrsqzMDzqwjo8FrJrhpq6EKYs+qiMgkyXE4Oj2J3usI2fg39ZLgL4A3BMSvmzGY7J9x6HEGKr97y9gTRUK6SUp9caadih/q24MLAXW3k1jA5A086JKayeqwPWWlXmkNkUuq7pvr+jnmu61FltCBFiQY+CJiKh3AS3UPzx0M8GPgxcNCkt8UohxB1CiDu8x1wHHBZCHATuBG6UOpnT9tic9NmdpzZfqH8ZMsohoyywF6u4CGKT4OhTVEZBdcDaLhvLglgDfTpOpi7qV3hqrcMsSU8kMS50W9NTE2LJT03Q9fpEuLadm8ycAWAp5evArC6UlPJXwK8CZVQ44fPklud7Bd09Dk2vw9oPBf5isYlK1Gufp3LNvwPqQ1QcZm2uAoFtzEX7wAg355WE9LrZljjSk2J17UnWdA2zIj/0cd6qPH2HCWutNtISY8m2BL4/a6AwdorOQXWnEvQJD719n6pjvvSC4Fyw6lIYameZaAP0m+milbcjhGCZjmu6OF0eGrrtQWvnNxu+Wv56DRPWdA2zPD8lJJvgFooh6HNQ0zVMpjd/GYCWXerfhRTj8ofK9wCQ1v4KWclxuhV038xHE+HJs1DTpc9Ml8YeOy6PPDmjDCFVuSne0gr6CxNKKanpHA5aI/NAYQj6HFR3DbMsb1KGS8suyKqC5CAl8aQtgbw1UPuCynTRaepibdcwcTHadE2vyrUwODJOj80Z8msHm2oNb5QTNV106IR0DI4yPObS5EY5HwxBn4XT7soeD7S8BSXbgnvhyvdAyy5WZ6kyunr0JLXIcPExsTCqw7BLTecwZpPQpPmCL/tDj7XRqzvV1htD0COY9oER7E43y3x/xO7jKq2wdHtwL1x1KXhcnGs6rFtPUosMFx8+T1KP4azqrmHKs5OJjwl98wVVWiFelzfK6k71WdFi5jMfDEGfhYkMF98f0Rc/D7aHXrwV4lNZaVcVFvQmPMOj47QPjGj25chNiSclIUaXqYs1XdrGeau8Nef1RnXnEAVpCaQlhmcNFx+GoM+C765cNSHob4ElX+WgBxNzLFRcSG7XTkDqLiOjxiukWgmPEMIrPPoa1xGnm5Y+h6ZeZFWeakentzBhdZct7MMtYAj6rNR0DZ96V27ZpbzzUKQtVb4Hs+0E6+I7deehn5bbrwGqHZ2+xlWtt6BZKAuUhz485qJrSD+lFcbdHuqttrDPcAFD0Gelpmv4pLcz2AaDrYGrrjgX5ecDcJWlRneZLtWdwyTFmVmSnqiZDVV5loldwHphIsNFwxtlpQ4XnJt77TjdHsNDj2TcHkmtddI0q223+rd4a2gMyCiFjDLOEod150lWd6obpUmDDBcflTpsdlHjTQUt1XBnsW/BuUZH6xPHp24uDGMMQZ+B5l47Tpdn0g7RvaqZc96a0Bmx9AKqRg7QM+RgaHQ8dNcNMlov3MHJdRE9Femq6RpmaXYyMSHoUjQT2ZZ4MpPjdNXmr6ZzGJMI7xouPgxBnwGfhzHRO7B9P+Sfocrchory84l32zlDNE50cY90emxj9NqdmoYFAArTEkiOM+vKQ6+z2k4u4GtIVa5FVzfKWquN0qxkEmJDnwo6XwLUnUF/NPSoL/rSnGTwuKFjP2y4JbRGlJ8HwNkmFXbZUJIR2usHgZrOKamg82XMpnqv1u+AgWZAQM5yqLoMVl0DMfF+nUYIMVF7RA84nKrY2Yc2F2ttCsvzU3hiXztSyrCue+Iv0zaID1MMD30G6q128lLjSUmIhZ4a1R5uyabQGpGcjcxbwznmI7oRnpMLd/P8gkgJe++Fn6+Gp78MXYchtQgsedC4Ex7/JPxiLRx6RB3rB5W5KbpZvGvotiND3KVoJqryUhgec3FicFRrUxaN2yNp6nFQocHO24VgeOgz0NBjY2m2L9yyV/1buDHkdoilF7Cp67fc16mLfiHUdA2TkRRLjsU/TxqA8VF44nY4+jfVqu/i70DxlpPPezyq6chL31fCXvscXH0nxM2+OFiZa+GxfW0MjoyH/YaRufDd8KvCQNCXTWoiUqhhJlMgaOt34HR7DA89kpFSUm+1TTRrpn0vxKdCVmXojSk/nzjGSbLuCf21g0BDt52Kye385sLpgPuvVWJ+yffhtqdOFXMAkwkqL4Z/eRku+ja8+yj86RoYHZz11FU6ynSptaoaLqVZ2nuSvkQCPYxrvTdleEILwhxD0Keh1+5kaNQ1yUPfB4XrlXCEmtLtuIWZKtteRsfdob9+gGnssVOe7eeXw+2CRz8OLW/CB38PZ39h9r+ByQznfRU+9CfoOAB//sCson6ypkvkh13qrDZKs5KIi9H+K52RrJqINPTYtTZl0TR0q/cwoQVhjvZ//TDEl1FSkWtR0/2uw6GPn/uItzCQsY6zTIdpjPAviG1MNWcu81fQX/4B1PwTrvgJrL3e/wutugZu+DOcOAiPfFTdGKahKCOJWLPQhfDUWm1hEW7xUZaVTJMOxrW+20ZmchwZyeHbpWgyhqBPQ733rlyRk6zE3OPSJH7uw1N+PmtFI81t7ZrZEAh8X/Cl/gh63Uvwxi9g00dh67/M/2LLr4CrfqH6vz779WkPMZsExZlJNPc45n/+MGLc7aGl1xFWcd7ybL0Iut2/z2uYYAj6NDR020iINVGYlqi8PFAhF41IXXUxJiEZq39dMxsCgW+GUT5XxoCjD564A3JWwmX/tfALbvwwbP9X2P17FVefhvKsZJp6I1t42vpHcHmk/zOfEFCenUzH4GjEhwkbIihlEfwQdCFEsRBihxDimBDiiBDiC9McI4QQdwoh6oQQh4QQ2rmzAaC+20Z5tkVtTe88BAnpkKZdfm986VZGiSO1803NbAgEPkEvm2vh7oX/gJE+uPZ3c2aqzMnF34HibfD3L0Jfw2lPl2Yl09zriOjqgL4bkt9rEyHAd3Np7o3c2c+gQ/UiiJQFUfDPQ3cBX5ZSrgS2AZ8VQqyacswVQJX353bg/wJqZYhp6LGf7PjS+a7aIarlBomYeOoT1lA6tE87GwJAY4+dwrSE2XfcNe+C/X+GbZ9R475YzDHqxmAywWOfPC2eXp6dxMi4G+tw5FYHbPL3RhlCyr22RPK6T71vc2GELIiCH4IupTwhpdzn/X0YOAYsmXLY+4A/ScVbQLoQoiDg1oaAcbeH1j6Hipu5XdB1BPLXam0W3dlnstTThGvIqrUpC6ahxz57uMXjhme+omZDF3wjcBdOL1Hx9Pa98NavT3nKl+YXyfHeph47lviYk43Mw4CybDWzimRBb/bOfMIplDUX84qhCyHKgA3A21OeWgK0Tvp/G6eLPkKI24UQe4QQe7q7u+dpamjoGBjBI1HNi3vrwDUKBdoLuqv0HAB6j76ssSULQ0pJY7dt9rDAu4+qRej3fBfiAvwlWv0BWHEV7PhP6K2feNhnTyTH0Rt7HZRlJ4XVNvuUhFiyLXERfaNs6R1BCCjKiJzNUX4LuhDCAjwGfFFKOTT16WleclpQUkp5t5Rys5Ryc05OzvwsDREtfSrmV5KZpOLnEJip/yLJrDqTYZmIs/YVrU1ZEH3e3P7ymaavLifs+KEa69UfDLwBQsCVP1UVM//xxYnyAAVpCcSaBU0RHOtt6rGHVbjFR3l2Mo0RfKNs6XOQnzpHiDDM8EvQhRCxKDF/QEr5+DSHtAGTVw2LgI7Fmxd6JgQ9yyvo5njIXqaxVVCZn847nhVYOnZpbcqCOLlwN8Mi5957YaAFLv5u8DZwpRbCJd+Dxtfg4IMAxJhNFGckRawn6XR5aOt3hNWCqI9Iz0Vv7XNQrGFt+YXgT5aLAP4AHJNS/myGw54CbvNmu2wDBqWUJwJoZ8ho6XMQZzaRl5IAJw5B7krV41NjUhNiORy3loyRJhiKvHulb8fdtB76+Cjs/B8oPVtt4Q8mGz8KRVvghe/AqJpolmUnR6yH3tbvwCPDa0HUR1l2MtbhMexj02/sCnda+hxqph5B+OMKnQ18GLhICHHA+3OlEOIOIcQd3mOeARqAOuB3wGeCY27wae1zUJSZiEmgMlzCIH7uoyvL2y2pcae2hiyAxh47MSYxfTzy4INg64Lzvx78bCKTCS7/Mdit6iYClGYl0dxrj8jUxaaJhbvwEx7frCESF0ZHx910Do1GnKDPWW1RSvk608fIJx8jgc8GyigtaelzUJyRBEPtKhc6DDJcfIj8Mxi0JpPW9Bqsu0Frc+ZFU6+d4swkYqd20/G44Y3/p3bieuu/B52iTbD+Ftj1G9j4Ecqzk3E43XQPj5GbmhAaGwJEo3eXa0A89KETKqurrwGcNhAmSM6B7CrVqWueewJ8NjX12lmzJG3x9oWQtv4RAP0JerTR0utgQ3GG8s4hrAS9OMvCLvcqLm14LeK2+M44fT36N+hvVJUUQ5mlcfF/qGs/9y1KN/8KgKZeR8QJelOPnZSEGDIXWmtkuAv2/QmOPqkyjGbCFKMal696H6y5FuLnzs32zRoiMY7e6l1Li7QYuiHokxh0jDM06lLCc+IQICBvtdZmTVCSmcQuzyouH9wN/U2QUaa1SX4zcaOcjJSqXktWlUopDCUp+aoy44vfYcWKtwFBU6+dreWZobVjkTT1quqV805ZtPfAK/+tFqM942r94pLvq/WFrEpVLtrjUqGprqPQsguO/wP+/q/wwr/Dpo+psgrJWTNeIikuhvzUhIlZRCRxSrZbBGEI+iRaJt+VDx+CrAq/PJFQUZKZxM893htM486IEfRTbpSTaX1b1cq56ufalCbe9mnYey+5b/6AeNO3I9KTbOq1n36jnA0pYe8f1aKw0w4bb4OzPgfZM9T6j7dA5lJYeRVc+kNo2w27fg1v3gl7/gjnfxW23j5j67+y7KSIzPFv6XOQGGsOq81a/hBpM/egcmoO+rthkX8+mZKsJGrlEhyxmdAUOQujrf0zTF/f+R3Ep8FajdYDYuLhku8jeo7zqZTXI67uyLjbQ3v/CGVZfnqR9h548Eb4x5egcAN8Zhdc/YuZxXwqQkDxVvjQffDpXVByJjz/bbjr3JNdvaZQkpk0Eb6IJHwhwnDarOUPhqBPYkJ4kl2qAXEYhVtApS6mJ8VRl7xB5VJHSFbGtNPX4U4Vt91wS+B3hc6HlVdD6dl8cvwhrN2RVVbhxMAoHglF/oQFOg/D3Req5tqX/xg+/KRqrr1QclfALY/ALY+qBdTfXwIv/UBtEJtEcUYS1uGxiKu6GIk56GAI+im09DnITI4jZci7NTw3vAQdlCjuEWtg+IQqTRABnAxlTUpZ3HufitFu+aRGVnkRAi77EameAS7vfyCiUhcnHJCMOYSn9kW45zIVK//4s7DtjsCFuKougU+/CetuVGmg914Jg20TTxd5/+a+rJFIQEoZkTnoYAj6KUzcla1H1QO5K7U1aBpKMpN4adTrWTW+pq0xfjJxo0zwbtByj6s4bsXFap1Cawo3UFdwFbfyDL3tNVpb4zdtXkGftdbI0adUmCVzqeq5uiQIla0T0+H9v4Hr7wPrcRWCqXsROHmz8dkaCfTanTicbkoyg1TDRUqVrhsEDEGfxMRd2XoMYpMgvVRrk06jJDOJtwfTkalLIkbQT5u+Hv+HmmFsvV07o6Zg3fo1PJgwvfg9rU3xm7b+EcwmQUHaDKmW7z6qWvAVboCP/F2VPggmq98Pt78CKQVw/3Xw2v9QlK5EsTWCPPRTyn8sBClVFtrhx+H5f4e/3Khucj+pgB/mwffS4eUfBszeyRhZLl5c3gWmq9YWQOdRyFmhTebFHJRkJuHygKPwLJKbXgGPJyztnExLn4O1ReknH9h7L6SVqOl6mJBXVMFv3VfxxabHoeVtteAX5rT2OShISyBm6mYtgOPPwOP/AiXb4eaHID4lNEZlV8InX1TpjS//gDzrcVLMV0eUh966kJRFjxsaX4XqZ6HmWbUGB6oYXFYlpBWpG2tCGsQkQOn2IFhuCPoEJwZHcXmk+iMeOgZVl2pt0rT4PmQdGVuoOv4odB8Lu8XbyZxyowQYaIWGV9U2f1P4VLFbkp7I3e6r+GTSa1ie+yZ84sWwv1G29Y9MH25peuOkZ37zX0OfehuXBB/8HeSsQLz8Ax6OP8CfrP8FhF8Iczpaen2hLD8EvbceDjwABx6E4Q4l1ksvgO2fh6LNah0uJnSpj4age/HdlcuTRtRmijCMn8PJ1L9jCeupApWPHsaCfsqNEuDgQ4CE9TdpatdUEmLNpKWm84+sf+HG9v+Cw4/B2uu1NmtWWvsdnFs1pQz1iUMqZp5RCjc/ot0+CiHgvK9AznKWPvwJvtx8B3Q8pmlvXn9p6XOQlxo/e9nclrfg9V9AzT9ViYTKS+CK/1aOYKx29dPD2wUJIb64WbnH26cjTAW9MD2RGJPg+Ei62lgU5nH0U7ZQS6m8mbJzw3JTVHFmEk+4z1XlHl78LoyHb9x3zOWma2js1AyXwXZ44Dq1y/PDT8y6izNkrLya31bexbgHuOdyOPKk1hbNyawZLvU71Pu45zJofUvNNL90FG55WJVF0FDMwRD0CVr6HMSYBFl2X8ri1Lap4YHZJChMT1RpYOXnQfPrQVsxDwSnpNa1vKXqtqy/RWOrpqckM4nm/lG47D9hqE3tiAxTOgZGgUkZLk4HPHST+vfWR1XMNkyIWbKWq0d/gDtvDTzyEbUg6PFobdaMtPoK9E2m8zD8+YPw5/ertMzLfwxfOgIXfgtSw6fbpiHoXlr6HBRlJGLqPgYJ6arWR5hSlJGohLLsPBgdPNlZKQxp7ZuUiXHgfoizwKprtDZrWkoyk+gcGmW0aLuqLfP6z1XxqjDEN/MpykhUM5+/fUaFW677Q9jNLosykughjforHoT1t8JrP4WHbp6oRx9OjLncnBgaPZmVNdQBT34W7jpH7Ya99Efw+b0ql1/LDXEzYAi6l5M56MeUdx7GW36LMnwe+rnqgTAOu7T1ezMx3CNqur3q/WH5RYCTC85t/SOqUJVrFHb8SGOrpse3Uac4M0kJ5JEnVDemZZdpbNnpFHtnEa3DHnjfr+CKn0Lt8/D7i6GnVmPrTqW9fwQpYWmqVDOJOzfCuw/DWZ+Ff90P2z83Y92acMAQdC8tfQ5KMhK9gh5eHs5UijOS6B4eYzQhR7XHC+OGF639I2r6evQptUV8Q3iGW+DkgnNrn0NteNp6O+z/s5puhxmt/SpEmNf+vLrprL1RVT8MQ3zZIq19DuUonXk73PY3cPTC7y5SKZZhQmvPILeaX+DKHVeoG+WKK+Fzu+GyH0FS+FfiNAQdGBodp98xzopkG4wNhr2gn7Kduvw8aH5T7b4MQ1q9oSwOPAAZ5VByltYmzYjPQ/ctkHPeV9UC43PfCru6OW39I5yXegLzk3eokrdX/7+wnVVmW+JIjDXT0jdpkbn8XLUJKaNMxf6f+aq2i9BSwvGnWf+PK/lh7B+RWcvUztrr7gnLBfyZMASdk/HI5SZvDYowXRD1ccp26vLzYNwOHfs1tup0RsfdWIfHWJU0oKpDrr8lbEUHJguPV9CTMuGCb6oNI7XPa2vcFAZ7OvjJ+H9DYgbc8ADEhm9jDiEExZmJEwvkE6SXqE1I2z4D79ytvPWuI6E3sOUtlbXy0M2MuyWfdn+FmI8/A0s2hd6WRWIIOmrhDqDY1aQeCHcPPWNSrLf0HPVg46saWjQ97QNqXM8ceg4QqoBTGCOEoCQz6aSgA2z5hNrp9/y3w2cW5HLypd7vk+oZhBv/Ail5Wls0JzOW0Y2Jh8v/S1VttHfDb89TKaPOEOwstR6DB29SYt7fDFf/P/698HfUZpyHabrdtxFAZFodYHwftCx7PVjywz5WlpsST5zZpAQ9OQvyzgjLOHpb/wgCD0vbn1IzifRirU2ak+KpwmOOhUt+AD01qmSB1kiJ8+9fYgPHeWXldyNiow6ocW3pc8xczbLqEvjM22ot4PWfw2+2qc1dwUhvbNsLD92irtH0umpH+K/7YdNHaep3RmSVRR+GoKNipmmJscT1Hg977xzAZBIsyZg0hS0/V3X/GR/V1rAptPY52CqqSbC1woZbtTbHL0qmE57lV6jNUDt+BDaNa6a/8zviDt7Pr1zvw7XyA9raMg9KMpNwON302p0zH5ScBe//NXz0aZUJ9ejH4bfnqiJXrlle5w/jo3DoEfjje+H3FykhP/8b8IWDcO6XIS4JKSWtEVo218ecgi6EuEcIYRVCTLvUL4S4QAgxKIQ44P35j8CbGVya+xyUZcRDd3XYx899TKQugvJ+XaOqPVgY0drv4IbYV5HxqaHvGbpASjITTxceIeC9/6tatj39Ze2Ma3gFnv0G1oIL+V/X9RElPKctOM9G2Tlwx+tw7R/UQumjH4Ofr1ahmLa9/nvtTgdU/xOe+jz8bAU8/kkYbFUzri8dhgu/ecpsvM/uxDbmisjGFj78qeVyL/Ar4E+zHLNTShkZ39hpaOqxc3GeDfpGIsJDBxVHf+Fop/pP6XZVT6Jp58nc9DCgu7ePL5reQay+QRVsigB8JVNb+hxkWyblG+cshwu+AS99X+V8rw6xd9zXoApuZVfxdNX3kI1tESU8JZNSQjeW+NED1WSGM65T41z3Euy5B974fyock5StCo/lr4GUwpOi7BpTZZkHWlSSgPWoaqISlwLLLoUNH4by82csuubrfVqeHTnjOpU5BV1K+ZoQoiwEtmiC0+Whrd/B+uIO9UAEeeg9NicOp4ukhDQoWK82GF34La1Nm6Cs83kSGQ3brf7TMavwbP+Cyqd/+isqBJOcHRqjHH2qpraUcNOD1L9mJy0xlrTE2NBcPwD4FvJb5tu31WRWYrzsUjUOtS+omcqJg1D/Mshpyl4kZkLBWjj7C1B6tvpb+VHxsKlH2VaaFZ4b3/whUNUWzxJCHAQ6gK9IKafNPRJC3A7cDlBSUhKgSy+O9oERPBIq8RblWkyfxRDiq+HR1j/CsrwUFXbZ9WsVFgiTnZhn256nO66YnOKtWpviN7MKjzlGdea5+wJ44g64+eHgl9gdH1ULeP2NcOvjkLmUlr53IircApAYZyY3Jd6/kMtMJGXCuhvUD6jQy0if2qCEUAvYlrwFzwabeu2YhB8t/cKYQHwa9wGlUsp1wC+BJ2c6UEp5t5Rys5Ryc05OzkyHhZSmHjXNKnA2qg5FWpUbnSdlXi/CZz/l56mekc1vamjVSRydtWziKHVLrgnr3POpJMTOITx5q1XxrroXYNcvg2uMxwNPfApa3oT3/99EOK21z7HwbjoaclpK6GIxmdQsKWc55CyDzPJFhfaaeh0syUgkLiZyc0UWbbmUckhKafP+/gwQK4QI0Vx08fjiZqlDdRETboFJgu61n9LtEJOopqRhgO3tP+ORgpEV4V1TfDrmFJ4tn1SlUl/6fvBuoFLCM1+Bo0+qRbwzrgPA7ZG09UdmJsaMuehhQnOvfeJ7FaksWtCFEPlCKBdMCLHVe87exZ43VDT12MmIB3N/XcQsiAKkJcWSkRRLky80EJsIS8+H2ue036bu8WA5/jCve9aQXxwGTaDnyZzCIwRc80tVyuChm6GnLrAGSAn//Brs+YOqz7L98xNPnRgcYdwtI1LQizOTODE0ypgr/Mo9Sylp7IkCQRdCPAjsApYLIdqEEJ8QQtwhhLjDe8h1wGFvDP1O4EY54+6B8KOp18H29D6ExxVRHjpAWXbyyZALqM0Z/U3aV7Breo2kkRM84j6fsgjMGPBLeBLSVFMDYYK/XA/2nsBc3ONRYv7O3XDW51TVx0khq4kGxhEo6CWZSUipKhqGG/2OcYZHXZRGYChrMnMKupTyJillgZQyVkpZJKX8g5TyLinlXd7nfyWlXC2lXCel3CalDI8grp809drZnOiteR1BHjpAedZUQff2QdW67sj++3GYLBxIPpukuMjrcui38GQuhZsegqETcO9Vi990ND4Kj370pJhf+sPT1h98i7WRKOi+m3vzfDNdQkBjjy9lUeceup4Zd3to6x9hhbkNhBmyq7Q2aV6UZSfTMTjK6LjXk0wvgZyVKuyiFSMDcOzvvBJ/AYXZfuQbhyGTc9HnpHir8tQHmuGPV6imwQthoBXuuwqO/k01UZhGzAHqrDYSYk0Upmvb6mwhlGerhIOGyU5ImNDsXYuK5JRFiHJBb+8fwe2RlLiaVQGmMC5cPx2+6eEpHs+yS9VCnVbdYA4/Bq5RHhg7l6U5kfnlmJyL7hfl56mUQkefSmk8+pT/F5NSbUm/6xywHocP/Uk1UZghM6jWamNptgWzKXIyh3xkJKnc+cYem9amnEZTr0OlLGZG3o1yMlEt6I3eu3KWoz7iwi1wcnrYeErY5TK1O67hFW2M2n8/7pzVvOEoitgFphxLPPExpvml2JWeBZ96VYVhHv6w2ghkPT7z8VJCw6sqVPP4J1VDjU+9qrJnZqHOaqMyNzJSa6cihKA8O/nUz2uY0NRjpzA9kfgYs9amLIrIC3AGkOYeO4mMEj/cArmRs5vRR1n2lNRFUCGA+DSoeTb0vTu7jkLHPjrP/A60ign7Ig2TSUxUB5wXvvreb/0GXvlv+M2Zaqdi5cWQvVxlIo30qx6w1c9CT7Xaxn7Vz2HjR9SuyFlwOF20D4xww5bwr1o5E+XZybzdEH5JcHpIWYQoF/SmXgdr47sQyIj00FMTYslKjjt1YdQcq/pKVj+j6nebQ7g9/MADYIrlYMalQHNELzCVZCYtbPHOHKu2nK+/Ffb+UfVRfen7px5jilGdm7Z/Ds74kN/NKRq61d85Uj10UIL+xP52RsfdJMSGhzfsS1m8Zn2h1qYsmigXdDvbLF1gJ+JSFn2UZiWd6qGDmra/+7AqEVpxYWgMcY/DwYdg+RVUD8chRGRmYvgozUpiV30vHo/EtJB4dXIWnPcV9TPSr9JJXU6IT1FhmQV0GKqzqthzpAs6qO/eivxUja1RDDjGGRp16cJDj+oYelOPnTPiOsAcr7YNRyAqF32KJ1l5McQmw7F5LM4tlppnwdEDGz5MU6+dwrTEsPHAFsLSHAsj4246hwJQYz4xQ1UHLDkT8lYtuF1cndWG2SQiWngm1n26wyeO7nOIInlcfUStoPtSFis8LaoWxBzxy3ClPCuZzqFRRpyTNsHEJqpNRsf+Dp4Q7crbcw+kLoGKi2jqsUd0uAWgwpuh0xBGwlNntVGalRTRtUZ8n4twSl2cEPQI3AQ3lcj9ZCySjoERXB5J3lhjxIZbYIaFUVALovZu1QA32PTWq1Kmmz6GNJnVFuoI/3JU5PhypsMnxa7WOkxlTuSGWwCS42PITYk/dd1HY5p6HAhxstJmJBO1gt7YYycVG0mjXRG5IOrDl+td3z1FeKouVaGko38LvhF77lELfRtvo8/u1EU8MjclHkt8DPXW8BD0cbeH5l5HRMfPfYRb6qIeQoQ+olrQl4k29Z8I9tArciwIAbVdU4QnPkVluxx5PLjd6p0O2H8/rLwGUvImdX2JbEEXQrA0JzlsQgPNvXZcHqkLQV+aE16C3tAd+SFCH1Er6LVWGxsSfF2KVmhrzCJIiDVTnJFE3VQPHWDdTSrsUv9y8Aw48jiMDqiSskCjd4E2UnPQJ1ORYwkbD10PGS4+yrOT6bU7GXQE0dHwE49HUme1UZUX+eMKUSzodVYbmxI7Vb/BtMjdqAHqSz6t8FS+B5Ky4OCDwbmwlPD2b1X9mNLtgIrzxplNEZ2y6GOpt1aOw+nS2pQJQa+I8Bg6TK7pov3Nsn1ghJFxt+r6pQOiWtCXiVYVP4+gjjrTUZVroaHbjss9pRt6TBysuQ6OP6OKZgWaxlfVrsdtn54Yw5rOYZbmJBNrjvyPVoXXGw6HTJc6q43CtASS4yN/60iVd1xPCxNqQE3XMHDSpkgn8r91C6DXNkaffYxCZ5PKC45wKnItON0eWqcr97ruRnCPwbuPBP7Cb9wJybmw9oaJh2q6bLrxdnwLzuEQR6/rtlGpk3EtzkwiIdZEtVdMtaTWO/Op0snYRqWg11pt5DJAwvhgRC+I+vB5F3XThV0KN0DBetj9+8B2Mup8F+pfgm13TGyUsY+pWiPLdBKPLMtKRgg0j6N7PJJ6qz3iUxZ9mE2CZXkpE96xltR0DZOXGk9aYghLZASRqBX05aZW9Z8ITln04QsN1Fqn+YIIAVv/BbqPq1IAgeLNX6rdqJs/PvGQ3rydhFgzJZlJ098oQ4gvzquHBVEfy/JSON6pvaDX6mhGCVEq6HVdw5wR68twiXwPPTUhlvzUhJljkmuuVdvP37k7MBfsrlEhnM0fU+f14vO49PQFWZaXonlowJfBpCdBX56XQvfwGH12p2Y2+DJc9DSuUSnotVYbGxNPqPhvcrbW5gSEZfmzTGFjE2HjbXD8aehrWPzFdvwIYpPgnC+d8nCd1UZ8jD4yXHwsz0uhsceuaWPjeh2lLPpYnq9u+lqGXXwzn6pc/TggUSvoy2nVRbjFx/I8C7VWG27PDHHybZ9RpV1f+9/FXejEQTj6pDrflJthbdcwFTmR2U1nJpblp+D2SE0zXeqsNjKT48hMjtPMhkATDoLuC1HqZc0H/BB0IcQ9QgirEOLwDM8LIcSdQog6IcQhIcTGwJsZOAYcTnqGRyhwNkHeaq3NCRjL8lJwujwTvRFPIyUfNn1M5aQv1EuXEp79lgqzbP/caU/X6mz6CspDB22Fp85q082CqI/cFLUQqWUc3Rei1NNn1h8P/V7g8lmevwKo8v7cDvzf4s0KHnVWG8WimxjPqL48dH88nnO+qLz0V3+ysIu8+wg0vw4X/wckpJ3ylMPpoq1/RDf5vD7Ks5OJMQmqNRIeKaU3ZVFf4yqEYHleCjVaCrrVRk5KPOlJ+pn5zCnoUsrXgL5ZDnkf8CepeAtIF0IUBMrAQFNrtbFc+DJcIn9B1EdlrqrpUt05S0ZGSj6c+Snlpbe8Pb8LjAzAc/8GhRtVu7Qp1FvVzEAvW6h9xMWYWJqTrJmH3mt3MuAY152HDrAs30J11zAykOm086DWatOdAxKIGPoSoHXS/9u8j4UltV021sR4i3LlLNfWmACSFBdDSWbS3MJz3tdU3fKnv6w66PiDlPD3L4CjF6762bS1433xyEodLTD5UDnT2qQu6jEs4GNZXgrDoy66hsZCfm0pJXVdw7rKyILACPp0K2DT3nKFELcLIfYIIfZ0d3cH4NLzp9Y6zIaETkgvVRUJdYRfKXbxFrjyp9D1Lrz0Pf9OvOcetRB60bfVRqVpqLXaiDULSrP0k+HiY3leCi19Dk1quugxZdGHL7tk2v0TQebE4Ch2p75y+yEwgt4GTK5uVQR0THeglPJuKeVmKeXmnJycAFx6/tR22agSrboKt/hYnpdCkz8pdiveC1tvh12/gncfnf3Y6mfhma+qQl9nf3HGw2q7bJRn66OGy1SWedcntKg9Um+1kRxnpiBtYW3rwhlfeE6L2c/EJjhD0E/jKeA2b7bLNmBQSnkiAOcNOEOj4/QO2ch1tuhqQdTHsvwUXB7pX63pS38IJdvhiU/Bwb9Of8yhR+DhD0PBWrj+PjDN/HGpsw7rKp93Mr5puRYbjOqsNipyLYgILyA3HdmWeDKT46jTwEOv9RXliraQixDiQWAXsFwI0SaE+IQQ4g4hxB3eQ54BGoA64HfAZ4Jm7SKps9pYKjowS7euUhZ9+FLs/MrIiImHm/8Kxdvgidvh4Y9A85sw1AGNr8FDt8Djn4Qlm+HWx1WoZgZGx9209Omjm850lGQmER9j0iQjQ48pi5OpzLVo4qHXWW1k6Sy3H2DOWpxSypvmeF4Cnw2YRUGkrsvG8okuRfrz0OedYpeQCrc9CTv/V1VOPPrkyefiU+HCf1NhlpjZP/QN3XY8Un8ZLj7MJkFVniXkHvrw6DidQ6MTtXr0yLI8C08d6EBKGdJZiB73TIAfgq4naq3DrIxpQ5piEFlVWpsTcOJiTJRlJ0/EB/3CHAsXfEPVNG/cCbYulQVTds6sXvlkfItaeg25gAq7vF7bE9Jr1nt3p+otzjuZqtwUhkZdWIfHyEsNzTqBlJLarmGuWV8YkuuFkigTdBt3xHcg0qvm9Dojlcocy8JyphPSYOVVC7pmndWG2SQoy9ZfhouPqtwUHt/XztDoOKkJoSm1qqe2czNxcmF0OGSC3j08xtCoS5cOiP5SEmahtstGlWzWRVOLmajMtdDc58Dp8sx9cICo7bJRmpVEfEzkd02ficrZas4HCT2185sJbcZVnxkuEEWCbh9zMTzQTZarC/LP0NqcoFGZa8HtkTTNVNMlCNRah3X55ZiMFsJTb7VRlp1EjA5TQX3kWOJJTYgJraB7Z7B6K6cAUSTo9d02Vvq2/OfpW9AhdMLjdHlo6nXocvo6meKMROLMppB2L9Jbre7pEEJQmWsJuYeelhhLjiU+ZNcMFVEj6LVdNlaZmtR/dOyh+/pghuoL0tRrx+2Rus1w8RFjNlGenRyycZ1IBdVxyqKPylwL9d2hFfQqneb2R42g11iHWW1uQSbnQEqe1uYEjaS4GJakJ4ZMePRca2QqlbmWia34waapV6WC6jll0UdlroUem5MBR2i6F9VZbbp1QKJG0Ou6bKyLaUXo2Dv3UZUXuilsrXUYIaAiCjzJilwLrX0ORseD372obmLhTt+hLAhtmLDXptre6bGIHESRoDd0DVAmWyFvjdamBJ3KHAsNPTY8M3UvCiC1VhslmUkkxOo3w8VHZa4Fj8S/0gqLpM5qQ4iTITQ9U5mjxDUUgq7nDBeIEkEfcbqJH6wjVo5D/lqtzQk6lbkWRsc9tA+MBP1adV36qyk9E754dqiEpzgjOm6USzISiY8xhWZcJ2q46PMzGxWCXt9tYwUt6j/5UeChewU22GVJXW4PDT023U5fp7I0JxkhQiPo9VGQ4eLDbBIszQnN+kSt1YYlPob8EG1iCjVRIeh1VhurTM14zPGgwy3/UwlVTLK5z8G4W0aNh54Qa6Y4IynowuP2SBp67FEj6EDIUhdru2ze7l76y3CBKBH0Wuswq0zNqiCXWf/VDtKT4si2xAX9C+LLcNHr9HU6qnItQc9Fb/Xu9I2GlEUflTkW2gdGGHEGd8FZj23nJhMdgt45zBpzC6YoyHDxUZETfI/HV8c6GjJcfFTmWmjoUbn3wcL3d4uGlEUflbkWpCSo+ej9dic9tjFdOyBRIegDXS2kyyFdbyiaim8KG8wGvLVWG0vSE0mO1/+sx0dFrgWny0NrnyNo19Bz27mZ8L3XYAq6b1z11tRiMroX9DGXm7TBI+o/Bes1tSWUVOZaGBp10W0LXgPe2i79btCYiVCsT9RZbeSmxJOWGJqqjuFAWXYSpiAvOE+ECHV8o9S9oDf22FkjGpCYos5Dh+B9QdweSX23vuOR0zExrsH0JKMow8VHfIyZsqzkoPZtrbUOkxhrpjAtMWjX0BrdC3pNl421ooGxzGUQp98ypFOZmMIGSdDb+h2MuTxRsZNxMqkJseSmxAftRimljEpBB7W4XhPEVFvfln+TSZ8ZLhAFgl7XOcQZpkZiijZqbUpIyU9NwBIfvLKkEzVcoizkAkp4aoPUjq5raAzbmCsqBX1ZXgrNvcErreBLWdQzuhf03o56ssUQMcWbtDYlpAghqAhiMSnfFmq9f0Gmoyo3hVprcEorTHQpiqLMIR9VeSkqB7878KUVhrz9WfX+edW9oMdZD6lfCjdoa4gGVAYxdbHWOkx+akLI2rGFE8vyUnA43UEpreBLBdW78EzHcm/2STB2ONdP1HDRd4jQL0EXQlwuhKgWQtQJIb4xzfMXCCEGhRAHvD//EXhT54/T5SHPdhS3iImKolxTqcy10DU0xtDoeMDPHa1xXlCd6iE4wlPXbSMlIYacFP01X5iL8uxkYkxiYT1x50DvRbl8zCnoQggz8GvgCmAVcJMQYrqmnDullOu9P98PsJ0LornXzmoaGEpdBjHR9wUJ1sKoxyOp6RpmmY7zeWfDl8dcE4SMjDodN1+Yi7gYE2XZyVR3Bmdc42JMFOu4Pyv456FvBeqklA1SSifwEPC+4JoVGGq7hllrakBGUf75ZHzeSKA9ntZ+B6PjHpbn69vbmYm0xFjyUuOD4knWWaOrhstUluelBGXmU9s1TEWOBbOOM1zAP0FfArRO+n+b97GpnCWEOCiE+KcQYvV0JxJC3C6E2COE2NPd3b0Ac+dHV/Mx0oSD5PItQb9WOFKSmURSnJljJwL7BanuVOeLVg8d1HsPdM70gENtTY9mQa/Ks9DS5wh4TRe913Dx4Y+gT3dLm7q8vw8olVKuA34JPDndiaSUd0spN0spN+fk5MzL0AXR+jYA8WXbgn+tMMRkEizPT+HYiaGAnrdmoqZ09Aq6ynQZDmimi+9GqfeFu9lYlpeClIHdEOdwumjrHzEE3UsbUDzp/0VAx+QDpJRDUkqb9/dngFghRHbArFwgGX37cZiSIWeF1qZoxsqCVI53Dge0pkt1l42ijEQsUVTDZSrL8lQTkdb+wNV0OdKhbryrl6QG7JyRxrKJ9YnAzSproqgqqD+CvhuoEkKUCyHigBuBpyYfIITIF95VHCHEVu95ewNt7HwYGh1nhfMY3WnrwKT77MwZWZmfwuDIOCcGRwN2zuMnhiZSzKKVZfnq/fu86kBwuGOQnJR4clP02XzBH8qykogzmwIq6AdbBwBYW5QesHOGK3MqnZTSBXwOeA44BjwspTwihLhDCHGH97DrgMNCiIPAncCNMphl/vzgWGMry0Qb7qLojJ/7WFmgvL2jHYEJuwyNjlPXbYuKL8dsrMxPJcYkONg2ELBzHmkfYnVh9HrnADFmExW5Fo4GMEx4sHWA3JR4CtL0f6P0a87sDaM8M+Wxuyb9/ivgV4E1bXF0HdmJSUhyV52vtSmasrowjRiTYF9LP+9Zlbfo8x1sHUBK2FiavnjjIpjEODMrC1LZ1zwQkPMNjoxTYx3mvWsLAnK+SGZjSTp/O9CB2yMDkpVyoG2AdcXpUZEKqttYhGx9GzcmLEvP1NoUTUmMM7NmSRq7m/oCcr59zQMIAeuK0wNyvkhmY0k6B9sGcLk9iz7X3uY+pITNZRkBsCyy2VSagW3MFZCwy+DIOA3ddtZHyedVl4Lu8UjyBg/RmVAB8fpfCJmLLWUZHGwdDEjRo30t/VTlWqJyy/9UNpZm4HC6qQ6A8Oxu6ifWLNhQbAj65tJMAPY29y/6XPu85zAEPYJp6OxlnazGkR/d8XMfW8oycbo9HGobXNR5xt0e9jX3s8n7hYt2NpUq8X2ncfGzn92NfaxZkkZinHnR54p0ijMTybbEsycAs8pdDb3EmU0Tfyu9o0tBr977ConCSfrqS7Q2JSzYUqYEeLFhlwOtAwyPuTivSvOM1LCgKCOJsqwkXq/tWdR5RsfdHGobnPg7RTtCCLYtzeSN+t5Fp9vuqu9lfUk6CbHRcaPUpaCP1+3AjYmcNRdpbUpYkJEcx7I8C7vqF5dJurOmG5OA7RWGoPs4tyqHXQ29OF0Lj6Pva+nH6fYYgj6J85bl0D08tqhdzoMj4xzpGOSspVkBtCy80Z2gj467KRrYTWfSckhM19qcsOGC5bm83djL8CIqL75a28P64nTSkoz4uY9zq7JxON3sa1l4vPeV6m5izYKzKqJHeObivCq1k/y12oWXCNlV34NHwvYoGlfdCfr+ujbWUcd46blamxJWXLwil3G3ZOcCwwP9dieH2gY4tyoEJRsiiLMqsjCbBDsXITw7jls5szwrqnfeTiU/LYHleSmLGtcXj1lJTYiJmvg56FDQG/e+SKxwk7fuMq1NCSs2lWaQlhjLi8e6FvT6N+p7kFJNhQ1OkpIQy6aSDF46Zl3Q61v7HNRabVyw3BjXqZxblc3uxn5sY655v9btkew4buWC5bnEmHUnczOiq3fqdHlw1+9gnFgSK87W2pywIsZs4sLlObxS3Y17AQWlXj5mJS0xlnVFaUGwLrK5dHUexzuHae6df+u0V6rVjeDCFbmBNiviuXR1Pk63h5cW4IQcbBug1+7k4pXRNa66EvSXjnZyjvsdhgrOgthErc0JOy5emUef3TnveO/ouJsXjnZx6aq8qPJ2/OWy1fkAPHekc96vfWxfO5W5FpZmJwfarIhnc2kGuSnxPPPuiXm/9vkjXZhNgvOjbEapq2/na2+9Sbmpi4wNEdF/I+ScvzyHxFgzj+xpnfvgSTx3pJPhMRdXrSsMkmWRTXFmEmcsSeOJ/R3zSrN7t22QA60D3HJmSVRsS58vJpPgyjMKeKW6e15tFMfdHh7b18b5y3JIT4oLooXhh24E/cTgCGktLwJgWn6FxtaEJ6kJsXxg4xKePNBBn93p12uklNzzeiNLs5M5t9JIV5yJm88s4diJId6exyaj+99qJjHWzAc3FgXRssjm2o1FjLk8PLGv3e/XvHSsi+7hMW7eWhJEy8IT3Qj6o3vauNi0F2fOGZA2XUMlA4CPbS/D6fLw4Dstfh2/r2WAg22DfOzsMkw6b9+1GN6/fgnpSbH88Y1Gv44fdIzzt4PtvH9DIWmJRhroTJxRlMbaojTuf6vZ79nPA2+3UJiWEJXrEroQdI9H8srufWwx1RC3xgi3zEZVXgpnV2bxwFvNfi2OPvhOC8lxhhc5F4lxZm7aWsILR7to7Zu76cVj+9oYHfdwy5mlIbAusrn1zFJqrTZ2N8299tPca2dnbQ83bi3Rff/Q6dCFoL/V0MvW4ZfVf864TltjIoCbt5bSMTg656aNodFxnj50gqvXFZJs5EjPyW1nlSKE4C9zzH6klPzlnRbWF6ezZomRNTQXV68rJCUhhvvfap7z2L+804LZJLhhS/Gcx+oRXQj6X3e3cG3s63iKzoTMcq3NCXsuWZVHVnIcD80hPE8d6GBk3M2NURiLXAgFaYlcuDyHx/a2zVpSd09zP3VWW1TGeBdCYpyZazcW8c/DJ+ixjc143JjLzSN72rhkZR55qfpvZjEdES/og45xWo+8SSVtmNbfqLU5EUFcjInrNhXx4jEr1qGZW9M9tLuFFfkpRu75PPjQ5mKsw2PsqJ559vPgOy1Y4mO4ap3RzMJfbt1Wwrhb8siethmPefZwJ312J7dsi94bZcQL+pMH2rlFPIs7JglWf1BrcyKGG7YU4/ZIHtk7/RfkcPsgh9uHuHFLsZFSNw8uXJFLbkr8jLOfodFxnnn3BNesLyQpzghj+Utlbgpnlmfyl3dmXvt54O0WSrOSODuKi8dFtKBLKXnu7UNcY96FeeOtRjGuebA0x8K2pZn8dXcrnmm+IPe/1UxcjIkPbDAWQ+dDrNnE9ZuL2FFtpWNg5LTn/3HwBKPjHj60OTpjvIvhw2eV0to3wo7jp5dZqO4c5p3GPm7eWhLV2VgRLegvHbNySe/9mIWEM++Y+wUGp3DLmaW09Dl4espOvNY+B4/ubeNDm4uMyooL4MYtJUjgntdPTWEcd3v43c4GVhakGmGsBXDZ6nwK0hK4+7WG01IY73ypluQ4M9dH+Y0yYgXd45Hc/89XuTXmJVh/K2RVaG1SxHHlGQWsyE/hf5+vZnzSIt6dL9ViEoLPXlipoXWRS3FmEtdvKuK+XU009Zys7/LInjYae+x8+ZJlRhhrAcSaTXzmwkreaerj2cMnyywcbh/k6XdP8IlzyslMjq6doVPxS9CFEJcLIaqFEHVCiG9M87wQQtzpff6QEGJj4E09lYd3N3PH4M8gJh7TBV8P9uV0idkk+Oply2nqdfCnXSol7FDbAI/ua+Mj20spSDPq4SyUr1y6nFizif/+53FALd7/4sUaNpVmRF3BqEBy05ZiVuSn8MOnjzE67sbjkfzw6aOkJsTwiXOXam2e5swp6EIIM/Br4ApgFXCTEGLVlMOuAKq8P7cD/xdgO0+h3jrE6DPfYpvpGOYr/svYGboILlqRy4XLc/jJs8fZ3dTH1x49RFZyHJ+/uEpr0yKa3NQEPn1+Bc8e6eTJ/e1864l36bM7+c7VqwzvfBHEmE38x9WraB8Y4cfPHud3Oxt4q6GPb1250thxC/izzL4VqJNSNgAIIR4C3gccnXTM+4A/SRXYeksIkS6EKJBSzr9M2hwcfPkhcl/7Fh8VvdjWfgzLxtsCfYmoQgjBT65bx3vv3Mn1d+3CJODej20lNcH4ciyWT51fwWu13XzxrwcA+MYVK1hblK6pTXpge0U2H91exh/faALg8tX5UbuRaCr+CPoSYHJ5vjbgTD+OWQKcIuhCiNtRHjwlJQvLFc3KL6UtaTXi7BvI334LGN7OoslJieexT2/nD683ctnqfKMVWoCIizFx78e2cter9RRnJHH9ZiNjKFD8+1WrqMi10G938qnzlxqzHi/+CPp0IzU1z82fY5BS3g3cDbB58+YFtfMuWnUWRav+vpCXGsxCcWYS371mtdZm6I7k+Bi+fOlyrc3QHWaT4MPbjDo4U/FnUbQNmDyfKQI6FnCMgYGBgUEQ8UfQdwNVQohyIUQccCPw1JRjngJu82a7bAMGgxE/NzAwMDCYmTlDLlJKlxDic8BzgBm4R0p5RAhxh/f5u4BngCuBOsABfCx4JhsYGBgYTIdfxSSklM+gRHvyY3dN+l0Cnw2saQYGBgYG8yFid4oaGBgYGJyKIegGBgYGOsEQdAMDAwOdYAi6gYGBgU4Q/nbSDviFhegG5m4SOD3ZQE8AzYl0jPE4FWM8TsUYj5PoYSxKpZQ50z2hmaAvBiHEHinlZq3tCBeM8TgVYzxOxRiPk+h9LIyQi4GBgYFOMATdwMDAQCdEqqDfrbUBYYYxHqdijMepGONxEl2PRUTG0A0MDAwMTidSPXQDAwMDgykYgm5gYGCgEzQR9MU0nZ7ptUKITCHEC0KIWu+/GZOe+6b3+GohxGXBf4fzI5TjIYS4RAixVwjxrvffi0LzLv0n1J8P7/MlQgibEOIrwX1380eD78taIcQuIcQR7+ckIfjv0j9C/F2JFULc5x2DY0KIb4bmXS4CKWVIf1AleOuBpUAccBBYNeWYK4F/ojohbQPenuu1wE+Ab3h//wbwY+/vq7zHxQPl3tebQ/2+w2g8NgCF3t/XAO1aj4GW4zHpnI8BjwBf0XoMNP58xACHgHXe/2eFy/dFg7G4GXjI+3sS0ASUaT0Os/1o4aFPNJ2WUjoBX9PpyUw0nZZSvgWkCyEK5njt+4D7vL/fB7x/0uMPSSnHpJSNqJrtW4P03hZCSMdDSrlfSunrJnUESBBCxAfpvS2EUH8+EEK8H2hAjUe4EerxuBQ4JKU8CCCl7JVSuoP03uZLqMdCAslCiBggEXACQ8F5a4FBC0GfqaG0P8fM9to86e2S5P03dx7X05JQj8dkrgX2SynHFmx94AnpeAghkoGvA98LkP2BJtSfj2WAFEI8J4TYJ4T4WkDeRWAI9Vg8CthRze5bgP+RUvYt/m0ED78aXASYxTSd9qsZ9QKupyWhHg91QiFWAz9GeWThRKjH43vAz6WUNhGeneNDPR4xwDnAFlT3sZeEEHullC/NZWgICPVYbAXcQCGQAewUQrwopWyYy1Ct0ELQF9N0Om6W13YJIQqklCe8UyzrPK6nJaEeD4QQRcATwG1SyvqAvIvAEerxOBO4TgjxEyAd8AghRqWUvwrEmwkAWnxfXpVS9gAIIZ4BNgLhIOihHoubgWellOOAVQjxBrAZFZ4LT0IdtEfdRBpQC5S+xYnVU455L6cubLwz12uBn3LqwsZPvL+v5tRF0QbCZJFHo/FI9x53rdbvPRzGY8p5v0v4LYqG+vORAexDLQLGAC8C79V6HDQai68Df/SeKxk4CqzVehxmHSON/jBXAjWoVed/8z52B3CH93cB/Nr7/LvA5tle6308C+VF1Hr/zZz03L95j68GrtB60LUcD+DbqLjggUk/uVqPgZafj0nHfJcwE3QtxgO4FbVAfJhpbnzRMhaABZX5dAQl5l/V+v3P9WNs/TcwMDDQCcZOUQMDAwOdYAi6gYGBgU4wBN3AwMBAJxiCbmBgYKATDEE3MDAw0AmGoBsYGBjoBEPQDQwMDHTC/w/SwHVW4snJJgAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "def sol(x):\n", " return analytical_solution(x, L, mat1.rho, mat1.E,\n", " mat1.A, mat1.I, F)\n", "\n", "times = np.arange(N) * deltaT\n", - "plt.plot(times, displs, 'r')\n", - "plt.plot(times, sol(times))" + "plt.plot(times, sol(times))\n", + "plt.plot(times, displs)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(times, displs - sol(times))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What I do not fully understand is why the middle node first go backwards until it goes forward.\n", "I could imagine that there is some vibration, because everything is in rest." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 34, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "1.5108795139938372e-06" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.max(displs - sol(times)) " + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ - "np.max(displs - sol(times))" + "plt.plot(times, ekin+epot)\n", + "plt.plot(times, ework)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.1+" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git a/python/py_structural_mechanics_model.cc b/python/py_structural_mechanics_model.cc index d33e87f35..e42f63790 100644 --- a/python/py_structural_mechanics_model.cc +++ b/python/py_structural_mechanics_model.cc @@ -1,119 +1,126 @@ /* -------------------------------------------------------------------------- */ #include "py_aka_array.hh" /* -------------------------------------------------------------------------- */ #include /* -------------------------------------------------------------------------- */ #include /* -------------------------------------------------------------------------- */ namespace py = pybind11; /* -------------------------------------------------------------------------- */ namespace akantu { /* -------------------------------------------------------------------------- */ #define def_deprecated(func_name, mesg) \ def(func_name, [](py::args, py::kwargs) { AKANTU_ERROR(mesg); }) #define def_function_nocopy(func_name) \ def( \ #func_name, \ [](StructuralMechanicsModel & self) -> decltype(auto) { \ return self.func_name(); \ }, \ py::return_value_policy::reference) #define def_function_(func_name) \ def(#func_name, [](StructuralMechanicsModel & self) -> decltype(auto) { \ return self.func_name(); \ }) #define def_plainmember(M) def_readwrite(#M, &StructuralMaterial::M) /* -------------------------------------------------------------------------- */ void register_structural_mechanics_model(pybind11::module & mod) { /* First we have to register the material class * The wrapper aims to mimic the behaviour of the real material. */ py::class_(mod, "StructuralMaterial") .def(py::init<>()) .def(py::init()) .def_plainmember(E) .def_plainmember(A) .def_plainmember(I) .def_plainmember(Iz) .def_plainmember(Iy) .def_plainmember(GJ) .def_plainmember(rho) .def_plainmember(t) .def_plainmember(nu); /* Now we create the structural model wrapper * Note that this is basically a port from the solid mechanic part. */ py::class_(mod, "StructuralMechanicsModel") .def(py::init(), py::arg("mesh"), py::arg("spatial_dimension") = _all_dimensions, py::arg("id") = "structural_mechanics_model", py::arg("memory_id") = 0) .def( "initFull", [](StructuralMechanicsModel & self, const AnalysisMethod & analysis_method) -> void { self.initFull(_analysis_method = analysis_method); }, py::arg("_analysis_method")) .def("initFull", [](StructuralMechanicsModel & self) -> void { self.initFull(); }) .def_function_nocopy(getExternalForce) .def_function_nocopy(getDisplacement) .def_function_nocopy(getInternalForce) .def_function_nocopy(getVelocity) .def_function_nocopy(getAcceleration) .def_function_nocopy(getInternalForce) .def_function_nocopy(getBlockedDOFs) .def_function_nocopy(getMesh) .def("setTimeStep", &StructuralMechanicsModel::setTimeStep, py::arg("time_step"), py::arg("solver_id") = "") .def( "getElementMaterial", [](StructuralMechanicsModel & self, const ElementType & type, GhostType ghost_type) -> decltype(auto) { return self.getElementMaterial(type, ghost_type); }, "This function returns the map that maps elements to materials.", py::arg("type"), py::arg("ghost_type") = _not_ghost, py::return_value_policy::reference) .def( "getMaterialByElement", [](StructuralMechanicsModel & self, Element element) -> decltype(auto) { return self.getMaterialByElement(element); }, "This function returns the `StructuralMaterial` instance that is " "associated with element `element`.", py::arg("element"), py::return_value_policy::reference) .def( "addMaterial", [](StructuralMechanicsModel & self, StructuralMaterial & mat, const ID & name) -> UInt { return self.addMaterial(mat, name); }, "This function adds the `StructuralMaterial` `mat` to `self`." " The function returns the ID of the new material.", py::arg("mat"), py::arg("name") = "") .def( "getMaterial", [](StructuralMechanicsModel & self, UInt material_index) -> decltype(auto) { return self.getMaterial(material_index); }, "This function returns the `i`th material of `self`", py::arg("i"), py::return_value_policy::reference) .def( "getMaterial", [](StructuralMechanicsModel & self, const ID & name) -> decltype(auto) { return self.getMaterial(name); }, "This function returns the `i`th material of `self`", py::arg("i"), py::return_value_policy::reference) .def( "getNbMaterials", [](StructuralMechanicsModel & self) { return self.getNbMaterials(); }, - "Returns the number of different materials inside `self`."); + "Returns the number of different materials inside `self`.") + .def("getKineticEnergy", &StructuralMechanicsModel::getKineticEnergy, + "Compute kinetic energy") + .def("getPotentialEnergy", &StructuralMechanicsModel::getPotentialEnergy, + "Compute potential energy") + .def("getEnergy", &StructuralMechanicsModel::getEnergy, + "Compute the specified energy"); + } // End: register structural mechanical model } // namespace akantu diff --git a/src/model/common/dof_manager/dof_manager.cc b/src/model/common/dof_manager/dof_manager.cc index f5b0847bf..5624242b7 100644 --- a/src/model/common/dof_manager/dof_manager.cc +++ b/src/model/common/dof_manager/dof_manager.cc @@ -1,1017 +1,1018 @@ /** * @file dof_manager.cc * * @author Nicolas Richart * * @date creation: Tue Aug 18 2015 * @date last modification: Wed Feb 21 2018 * * @brief Implementation of the common parts of the DOFManagers * * * Copyright (©) 2015-2018 EPFL (Ecole Polytechnique Fédérale de Lausanne) * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * * Akantu is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * Akantu is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with Akantu. If not, see . * */ /* -------------------------------------------------------------------------- */ #include "dof_manager.hh" #include "communicator.hh" #include "mesh.hh" #include "mesh_utils.hh" #include "node_group.hh" #include "node_synchronizer.hh" #include "non_linear_solver.hh" #include "periodic_node_synchronizer.hh" #include "time_step_solver.hh" /* -------------------------------------------------------------------------- */ #include /* -------------------------------------------------------------------------- */ namespace akantu { /* -------------------------------------------------------------------------- */ DOFManager::DOFManager(const ID & id, const MemoryID & memory_id) : Memory(id, memory_id), dofs_flag(0, 1, std::string(id + ":dofs_type")), global_equation_number(0, 1, "global_equation_number"), communicator(Communicator::getStaticCommunicator()) {} /* -------------------------------------------------------------------------- */ DOFManager::DOFManager(Mesh & mesh, const ID & id, const MemoryID & memory_id) : Memory(id, memory_id), mesh(&mesh), dofs_flag(0, 1, std::string(id + ":dofs_type")), global_equation_number(0, 1, "global_equation_number"), communicator(mesh.getCommunicator()) { this->mesh->registerEventHandler(*this, _ehp_dof_manager); } /* -------------------------------------------------------------------------- */ DOFManager::~DOFManager() = default; /* -------------------------------------------------------------------------- */ std::vector DOFManager::getDOFIDs() const { std::vector keys; for (const auto & dof_data : this->dofs) { keys.push_back(dof_data.first); } return keys; } /* -------------------------------------------------------------------------- */ void DOFManager::assembleElementalArrayLocalArray( const Array & elementary_vect, Array & array_assembeled, ElementType type, GhostType ghost_type, Real scale_factor, const Array & filter_elements) { AKANTU_DEBUG_IN(); UInt nb_element; UInt nb_nodes_per_element = Mesh::getNbNodesPerElement(type); UInt nb_degree_of_freedom = elementary_vect.getNbComponent() / nb_nodes_per_element; UInt * filter_it = nullptr; if (filter_elements != empty_filter) { nb_element = filter_elements.size(); filter_it = filter_elements.storage(); } else { nb_element = this->mesh->getNbElement(type, ghost_type); } AKANTU_DEBUG_ASSERT(elementary_vect.size() == nb_element, "The vector elementary_vect(" << elementary_vect.getID() << ") has not the good size."); const Array & connectivity = this->mesh->getConnectivity(type, ghost_type); Array::const_matrix_iterator elem_it = elementary_vect.begin(nb_degree_of_freedom, nb_nodes_per_element); for (UInt el = 0; el < nb_element; ++el, ++elem_it) { UInt element = el; if (filter_it != nullptr) { // conn_it = conn_begin + *filter_it; element = *filter_it; } // const Vector & conn = *conn_it; const Matrix & elemental_val = *elem_it; for (UInt n = 0; n < nb_nodes_per_element; ++n) { UInt offset_node = connectivity(element, n) * nb_degree_of_freedom; Vector assemble(array_assembeled.storage() + offset_node, nb_degree_of_freedom); Vector elem_val = elemental_val(n); assemble.aXplusY(elem_val, scale_factor); } if (filter_it != nullptr) { ++filter_it; } // else // ++conn_it; } AKANTU_DEBUG_OUT(); } /* -------------------------------------------------------------------------- */ void DOFManager::assembleElementalArrayToResidual( const ID & dof_id, const Array & elementary_vect, ElementType type, GhostType ghost_type, Real scale_factor, const Array & filter_elements) { AKANTU_DEBUG_IN(); UInt nb_nodes_per_element = Mesh::getNbNodesPerElement(type); UInt nb_degree_of_freedom = elementary_vect.getNbComponent() / nb_nodes_per_element; Array array_localy_assembeled(this->mesh->getNbNodes(), nb_degree_of_freedom); array_localy_assembeled.zero(); this->assembleElementalArrayLocalArray( elementary_vect, array_localy_assembeled, type, ghost_type, scale_factor, filter_elements); this->assembleToResidual(dof_id, array_localy_assembeled, 1); AKANTU_DEBUG_OUT(); } /* -------------------------------------------------------------------------- */ void DOFManager::assembleElementalArrayToLumpedMatrix( const ID & dof_id, const Array & elementary_vect, const ID & lumped_mtx, ElementType type, GhostType ghost_type, Real scale_factor, const Array & filter_elements) { AKANTU_DEBUG_IN(); UInt nb_nodes_per_element = Mesh::getNbNodesPerElement(type); UInt nb_degree_of_freedom = elementary_vect.getNbComponent() / nb_nodes_per_element; Array array_localy_assembeled(this->mesh->getNbNodes(), nb_degree_of_freedom); array_localy_assembeled.zero(); this->assembleElementalArrayLocalArray( elementary_vect, array_localy_assembeled, type, ghost_type, scale_factor, filter_elements); this->assembleToLumpedMatrix(dof_id, array_localy_assembeled, lumped_mtx, 1); AKANTU_DEBUG_OUT(); } /* -------------------------------------------------------------------------- */ void DOFManager::assembleMatMulDOFsToResidual(const ID & A_id, Real scale_factor) { for (auto & pair : this->dofs) { const auto & dof_id = pair.first; auto & dof_data = *pair.second; this->assembleMatMulVectToResidual(dof_id, A_id, *dof_data.dof, scale_factor); } } /* -------------------------------------------------------------------------- */ void DOFManager::splitSolutionPerDOFs() { for (auto && data : this->dofs) { auto & dof_data = *data.second; dof_data.solution.resize(dof_data.dof->size() * dof_data.dof->getNbComponent()); this->getSolutionPerDOFs(data.first, dof_data.solution); } } /* -------------------------------------------------------------------------- */ void DOFManager::getSolutionPerDOFs(const ID & dof_id, Array & solution_array) { AKANTU_DEBUG_IN(); this->getArrayPerDOFs(dof_id, this->getSolution(), solution_array); AKANTU_DEBUG_OUT(); } /* -------------------------------------------------------------------------- */ void DOFManager::getLumpedMatrixPerDOFs(const ID & dof_id, const ID & lumped_mtx, Array & lumped) { AKANTU_DEBUG_IN(); this->getArrayPerDOFs(dof_id, this->getLumpedMatrix(lumped_mtx), lumped); AKANTU_DEBUG_OUT(); } /* -------------------------------------------------------------------------- */ void DOFManager::assembleToResidual(const ID & dof_id, Array & array_to_assemble, Real scale_factor) { AKANTU_DEBUG_IN(); // this->makeConsistentForPeriodicity(dof_id, array_to_assemble); this->assembleToGlobalArray(dof_id, array_to_assemble, this->getResidual(), scale_factor); AKANTU_DEBUG_OUT(); } /* -------------------------------------------------------------------------- */ void DOFManager::assembleToLumpedMatrix(const ID & dof_id, Array & array_to_assemble, const ID & lumped_mtx, Real scale_factor) { AKANTU_DEBUG_IN(); // this->makeConsistentForPeriodicity(dof_id, array_to_assemble); auto & lumped = this->getLumpedMatrix(lumped_mtx); this->assembleToGlobalArray(dof_id, array_to_assemble, lumped, scale_factor); AKANTU_DEBUG_OUT(); } /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ DOFManager::DOFData::DOFData(const ID & dof_id) : support_type(_dst_generic), group_support("__mesh__"), solution(0, 1, dof_id + ":solution"), local_equation_number(0, 1, dof_id + ":local_equation_number"), associated_nodes(0, 1, dof_id + "associated_nodes") {} /* -------------------------------------------------------------------------- */ DOFManager::DOFData::~DOFData() = default; /* -------------------------------------------------------------------------- */ template auto DOFManager::countDOFsForNodes(const DOFData & dof_data, UInt nb_nodes, Func && getNode) { auto nb_local_dofs = nb_nodes; decltype(nb_local_dofs) nb_pure_local = 0; for (auto n : arange(nb_nodes)) { UInt node = getNode(n); // http://www.open-std.org/jtc1/sc22/open/n2356/conv.html // bool are by convention casted to 0 and 1 when promoted to int nb_pure_local += this->mesh->isLocalOrMasterNode(node); nb_local_dofs -= this->mesh->isPeriodicSlave(node); } const auto & dofs_array = *dof_data.dof; nb_pure_local *= dofs_array.getNbComponent(); nb_local_dofs *= dofs_array.getNbComponent(); return std::make_pair(nb_local_dofs, nb_pure_local); } /* -------------------------------------------------------------------------- */ auto DOFManager::getNewDOFDataInternal(const ID & dof_id) -> DOFData & { auto it = this->dofs.find(dof_id); if (it != this->dofs.end()) { AKANTU_EXCEPTION("This dof array has already been registered"); } std::unique_ptr dof_data_ptr = this->getNewDOFData(dof_id); DOFData & dof_data = *dof_data_ptr; this->dofs[dof_id] = std::move(dof_data_ptr); return dof_data; } /* -------------------------------------------------------------------------- */ void DOFManager::registerDOFs(const ID & dof_id, Array & dofs_array, const DOFSupportType & support_type) { auto & dofs_storage = this->getNewDOFDataInternal(dof_id); dofs_storage.support_type = support_type; this->registerDOFsInternal(dof_id, dofs_array); resizeGlobalArrays(); } /* -------------------------------------------------------------------------- */ void DOFManager::registerDOFs(const ID & dof_id, Array & dofs_array, const ID & support_group) { auto & dofs_storage = this->getNewDOFDataInternal(dof_id); dofs_storage.support_type = _dst_nodal; dofs_storage.group_support = support_group; this->registerDOFsInternal(dof_id, dofs_array); resizeGlobalArrays(); } /* -------------------------------------------------------------------------- */ std::tuple DOFManager::registerDOFsInternal(const ID & dof_id, Array & dofs_array) { DOFData & dof_data = this->getDOFData(dof_id); dof_data.dof = &dofs_array; UInt nb_local_dofs = 0; UInt nb_pure_local = 0; const auto & support_type = dof_data.support_type; switch (support_type) { case _dst_nodal: { const auto & group = dof_data.group_support; std::function getNode; if (group == "__mesh__") { AKANTU_DEBUG_ASSERT( dofs_array.size() == this->mesh->getNbNodes(), "The array of dof is too short to be associated to nodes."); std::tie(nb_local_dofs, nb_pure_local) = countDOFsForNodes( dof_data, this->mesh->getNbNodes(), [](auto && n) { return n; }); } else { const auto & node_group = this->mesh->getElementGroup(group).getNodeGroup().getNodes(); AKANTU_DEBUG_ASSERT( dofs_array.size() == node_group.size(), "The array of dof is too shot to be associated to nodes."); std::tie(nb_local_dofs, nb_pure_local) = countDOFsForNodes(dof_data, node_group.size(), [&node_group](auto && n) { return node_group(n); }); } break; } case _dst_generic: { nb_local_dofs = nb_pure_local = dofs_array.size() * dofs_array.getNbComponent(); break; } default: { AKANTU_EXCEPTION("This type of dofs is not handled yet."); } } dof_data.local_nb_dofs = nb_local_dofs; dof_data.pure_local_nb_dofs = nb_pure_local; dof_data.ghosts_nb_dofs = nb_local_dofs - nb_pure_local; this->pure_local_system_size += nb_pure_local; this->local_system_size += nb_local_dofs; auto nb_total_pure_local = nb_pure_local; communicator.allReduce(nb_total_pure_local, SynchronizerOperation::_sum); this->system_size += nb_total_pure_local; // updating the dofs data after counting is finished switch (support_type) { case _dst_nodal: { const auto & group = dof_data.group_support; if (group != "__mesh__") { auto & support_nodes = this->mesh->getElementGroup(group).getNodeGroup().getNodes(); this->updateDOFsData( dof_data, nb_local_dofs, nb_pure_local, support_nodes.size(), [&support_nodes](UInt node) -> UInt { return support_nodes[node]; }); } else { this->updateDOFsData(dof_data, nb_local_dofs, nb_pure_local, mesh->getNbNodes(), [](UInt node) -> UInt { return node; }); } break; } case _dst_generic: { this->updateDOFsData(dof_data, nb_local_dofs, nb_pure_local); break; } } return std::make_tuple(nb_local_dofs, nb_pure_local, nb_total_pure_local); } /* -------------------------------------------------------------------------- */ void DOFManager::registerDOFsPrevious(const ID & dof_id, Array & array) { DOFData & dof = this->getDOFData(dof_id); if (dof.previous != nullptr) { AKANTU_EXCEPTION("The previous dofs array for " << dof_id << " has already been registered"); } dof.previous = &array; } /* -------------------------------------------------------------------------- */ void DOFManager::registerDOFsIncrement(const ID & dof_id, Array & array) { DOFData & dof = this->getDOFData(dof_id); if (dof.increment != nullptr) { AKANTU_EXCEPTION("The dofs increment array for " << dof_id << " has already been registered"); } dof.increment = &array; } /* -------------------------------------------------------------------------- */ void DOFManager::registerDOFsDerivative(const ID & dof_id, UInt order, Array & dofs_derivative) { DOFData & dof = this->getDOFData(dof_id); std::vector *> & derivatives = dof.dof_derivatives; if (derivatives.size() < order) { derivatives.resize(order, nullptr); } else { if (derivatives[order - 1] != nullptr) { AKANTU_EXCEPTION("The dof derivatives of order " << order << " already been registered for this dof (" << dof_id << ")"); } } derivatives[order - 1] = &dofs_derivative; } /* -------------------------------------------------------------------------- */ void DOFManager::registerBlockedDOFs(const ID & dof_id, Array & blocked_dofs) { DOFData & dof = this->getDOFData(dof_id); if (dof.blocked_dofs != nullptr) { AKANTU_EXCEPTION("The blocked dofs array for " << dof_id << " has already been registered"); } dof.blocked_dofs = &blocked_dofs; } /* -------------------------------------------------------------------------- */ SparseMatrix & DOFManager::registerSparseMatrix(const ID & matrix_id, std::unique_ptr & matrix) { auto it = this->matrices.find(matrix_id); if (it != this->matrices.end()) { AKANTU_EXCEPTION("The matrix " << matrix_id << " already exists in " << this->id); } auto & ret = *matrix; this->matrices[matrix_id] = std::move(matrix); return ret; } /* -------------------------------------------------------------------------- */ /// Get an instance of a new SparseMatrix SolverVector & DOFManager::registerLumpedMatrix(const ID & matrix_id, std::unique_ptr & matrix) { auto it = this->lumped_matrices.find(matrix_id); if (it != this->lumped_matrices.end()) { AKANTU_EXCEPTION("The lumped matrix " << matrix_id << " already exists in " << this->id); } auto & ret = *matrix; this->lumped_matrices[matrix_id] = std::move(matrix); ret.resize(); return ret; } /* -------------------------------------------------------------------------- */ NonLinearSolver & DOFManager::registerNonLinearSolver( const ID & non_linear_solver_id, std::unique_ptr & non_linear_solver) { NonLinearSolversMap::const_iterator it = this->non_linear_solvers.find(non_linear_solver_id); if (it != this->non_linear_solvers.end()) { AKANTU_EXCEPTION("The non linear solver " << non_linear_solver_id << " already exists in " << this->id); } NonLinearSolver & ret = *non_linear_solver; this->non_linear_solvers[non_linear_solver_id] = std::move(non_linear_solver); return ret; } /* -------------------------------------------------------------------------- */ TimeStepSolver & DOFManager::registerTimeStepSolver( const ID & time_step_solver_id, std::unique_ptr & time_step_solver) { TimeStepSolversMap::const_iterator it = this->time_step_solvers.find(time_step_solver_id); if (it != this->time_step_solvers.end()) { AKANTU_EXCEPTION("The non linear solver " << time_step_solver_id << " already exists in " << this->id); } TimeStepSolver & ret = *time_step_solver; this->time_step_solvers[time_step_solver_id] = std::move(time_step_solver); return ret; } /* -------------------------------------------------------------------------- */ SparseMatrix & DOFManager::getMatrix(const ID & id) { ID matrix_id = this->id + ":mtx:" + id; SparseMatricesMap::const_iterator it = this->matrices.find(matrix_id); if (it == this->matrices.end()) { AKANTU_SILENT_EXCEPTION("The matrix " << matrix_id << " does not exists in " << this->id); } return *(it->second); } /* -------------------------------------------------------------------------- */ bool DOFManager::hasMatrix(const ID & id) const { ID mtx_id = this->id + ":mtx:" + id; auto it = this->matrices.find(mtx_id); return it != this->matrices.end(); } /* -------------------------------------------------------------------------- */ SolverVector & DOFManager::getLumpedMatrix(const ID & id) { ID matrix_id = this->id + ":lumped_mtx:" + id; LumpedMatricesMap::const_iterator it = this->lumped_matrices.find(matrix_id); if (it == this->lumped_matrices.end()) { AKANTU_SILENT_EXCEPTION("The lumped matrix " << matrix_id << " does not exists in " << this->id); } return *(it->second); } /* -------------------------------------------------------------------------- */ const SolverVector & DOFManager::getLumpedMatrix(const ID & id) const { ID matrix_id = this->id + ":lumped_mtx:" + id; auto it = this->lumped_matrices.find(matrix_id); if (it == this->lumped_matrices.end()) { AKANTU_SILENT_EXCEPTION("The lumped matrix " << matrix_id << " does not exists in " << this->id); } return *(it->second); } /* -------------------------------------------------------------------------- */ bool DOFManager::hasLumpedMatrix(const ID & id) const { ID mtx_id = this->id + ":lumped_mtx:" + id; auto it = this->lumped_matrices.find(mtx_id); return it != this->lumped_matrices.end(); } /* -------------------------------------------------------------------------- */ NonLinearSolver & DOFManager::getNonLinearSolver(const ID & id) { ID non_linear_solver_id = this->id + ":nls:" + id; NonLinearSolversMap::const_iterator it = this->non_linear_solvers.find(non_linear_solver_id); if (it == this->non_linear_solvers.end()) { AKANTU_EXCEPTION("The non linear solver " << non_linear_solver_id << " does not exists in " << this->id); } return *(it->second); } /* -------------------------------------------------------------------------- */ bool DOFManager::hasNonLinearSolver(const ID & id) const { ID solver_id = this->id + ":nls:" + id; auto it = this->non_linear_solvers.find(solver_id); return it != this->non_linear_solvers.end(); } /* -------------------------------------------------------------------------- */ TimeStepSolver & DOFManager::getTimeStepSolver(const ID & id) { ID time_step_solver_id = this->id + ":tss:" + id; TimeStepSolversMap::const_iterator it = this->time_step_solvers.find(time_step_solver_id); if (it == this->time_step_solvers.end()) { AKANTU_EXCEPTION("The non linear solver " << time_step_solver_id << " does not exists in " << this->id); } return *(it->second); } /* -------------------------------------------------------------------------- */ bool DOFManager::hasTimeStepSolver(const ID & solver_id) const { ID time_step_solver_id = this->id + ":tss:" + solver_id; auto it = this->time_step_solvers.find(time_step_solver_id); return it != this->time_step_solvers.end(); } /* -------------------------------------------------------------------------- */ void DOFManager::savePreviousDOFs(const ID & dofs_id) { this->getPreviousDOFs(dofs_id).copy(this->getDOFs(dofs_id)); } /* -------------------------------------------------------------------------- */ void DOFManager::zeroResidual() { this->residual->zero(); } /* -------------------------------------------------------------------------- */ void DOFManager::zeroMatrix(const ID & mtx) { this->getMatrix(mtx).zero(); } /* -------------------------------------------------------------------------- */ void DOFManager::zeroLumpedMatrix(const ID & mtx) { this->getLumpedMatrix(mtx).zero(); } /* -------------------------------------------------------------------------- */ /* Mesh Events */ /* -------------------------------------------------------------------------- */ std::pair DOFManager::updateNodalDOFs(const ID & dof_id, const Array & nodes_list) { auto & dof_data = this->getDOFData(dof_id); UInt nb_new_local_dofs; UInt nb_new_pure_local; std::tie(nb_new_local_dofs, nb_new_pure_local) = countDOFsForNodes(dof_data, nodes_list.size(), [&nodes_list](auto && n) { return nodes_list(n); }); this->pure_local_system_size += nb_new_pure_local; this->local_system_size += nb_new_local_dofs; UInt nb_new_global = nb_new_pure_local; communicator.allReduce(nb_new_global, SynchronizerOperation::_sum); this->system_size += nb_new_global; dof_data.solution.resize(local_system_size); updateDOFsData(dof_data, nb_new_local_dofs, nb_new_pure_local, nodes_list.size(), [&nodes_list](UInt pos) -> UInt { return nodes_list[pos]; }); return std::make_pair(nb_new_local_dofs, nb_new_pure_local); } /* -------------------------------------------------------------------------- */ void DOFManager::resizeGlobalArrays() { // resize all relevant arrays this->residual->resize(); this->solution->resize(); this->data_cache->resize(); for (auto & lumped_matrix : lumped_matrices) { lumped_matrix.second->resize(); } for (auto & matrix : matrices) { matrix.second->clearProfile(); } } /* -------------------------------------------------------------------------- */ void DOFManager::onNodesAdded(const Array & nodes_list, const NewNodesEvent & /*unused*/) { for (auto & pair : this->dofs) { const auto & dof_id = pair.first; auto & dof_data = this->getDOFData(dof_id); if (dof_data.support_type != _dst_nodal) { continue; } const auto & group = dof_data.group_support; if (group == "__mesh__") { this->updateNodalDOFs(dof_id, nodes_list); } else { const auto & node_group = this->mesh->getElementGroup(group).getNodeGroup(); Array new_nodes_list; for (const auto & node : nodes_list) { if (node_group.find(node) != UInt(-1)) { new_nodes_list.push_back(node); } } this->updateNodalDOFs(dof_id, new_nodes_list); } } this->resizeGlobalArrays(); } /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ class GlobalDOFInfoDataAccessor : public DataAccessor { public: using size_type = typename std::unordered_map>::size_type; GlobalDOFInfoDataAccessor(DOFManager::DOFData & dof_data, DOFManager & dof_manager) : dof_data(dof_data), dof_manager(dof_manager) { for (auto && pair : zip(dof_data.local_equation_number, dof_data.associated_nodes)) { UInt node; Int dof; std::tie(dof, node) = pair; dofs_per_node[node].push_back(dof); } } UInt getNbData(const Array & nodes, const SynchronizationTag & tag) const override { if (tag == SynchronizationTag::_ask_nodes or tag == SynchronizationTag::_giu_global_conn) { return nodes.size() * dof_data.dof->getNbComponent() * sizeof(Int); } return 0; } void packData(CommunicationBuffer & buffer, const Array & nodes, const SynchronizationTag & tag) const override { if (tag == SynchronizationTag::_ask_nodes or tag == SynchronizationTag::_giu_global_conn) { for (const auto & node : nodes) { const auto & dofs = dofs_per_node.at(node); for (const auto & dof : dofs) { buffer << dof_manager.global_equation_number(dof); } } } } void unpackData(CommunicationBuffer & buffer, const Array & nodes, const SynchronizationTag & tag) override { if (tag == SynchronizationTag::_ask_nodes or tag == SynchronizationTag::_giu_global_conn) { for (const auto & node : nodes) { const auto & dofs = dofs_per_node[node]; for (const auto & dof : dofs) { Int global_dof; buffer >> global_dof; AKANTU_DEBUG_ASSERT( (dof_manager.global_equation_number(dof) == -1 or dof_manager.global_equation_number(dof) == global_dof), "This dof already had a global_dof_id which is different from " "the received one. " << dof_manager.global_equation_number(dof) << " != " << global_dof); dof_manager.global_equation_number(dof) = global_dof; dof_manager.global_to_local_mapping[global_dof] = dof; } } } } protected: std::unordered_map> dofs_per_node; DOFManager::DOFData & dof_data; DOFManager & dof_manager; }; /* -------------------------------------------------------------------------- */ auto DOFManager::computeFirstDOFIDs(UInt nb_new_local_dofs, UInt nb_new_pure_local) { // determine the first local/global dof id to use UInt offset = 0; this->communicator.exclusiveScan(nb_new_pure_local, offset); auto first_global_dof_id = this->first_global_dof_id + offset; auto first_local_dof_id = this->local_system_size - nb_new_local_dofs; offset = nb_new_pure_local; this->communicator.allReduce(offset); this->first_global_dof_id += offset; return std::make_pair(first_local_dof_id, first_global_dof_id); } /* -------------------------------------------------------------------------- */ void DOFManager::updateDOFsData(DOFData & dof_data, UInt nb_new_local_dofs, UInt nb_new_pure_local, UInt nb_node, const std::function & getNode) { auto nb_local_dofs_added = nb_node * dof_data.dof->getNbComponent(); auto first_dof_pos = dof_data.local_equation_number.size(); dof_data.local_equation_number.reserve(dof_data.local_equation_number.size() + nb_local_dofs_added); dof_data.associated_nodes.reserve(dof_data.associated_nodes.size() + nb_local_dofs_added); this->dofs_flag.resize(this->local_system_size, NodeFlag::_normal); this->global_equation_number.resize(this->local_system_size, -1); std::unordered_map, UInt> masters_dofs; // update per dof info UInt local_eq_num; UInt first_global_dof_id; std::tie(local_eq_num, first_global_dof_id) = computeFirstDOFIDs(nb_new_local_dofs, nb_new_pure_local); for (auto d : arange(nb_local_dofs_added)) { auto node = getNode(d / dof_data.dof->getNbComponent()); auto dof_flag = this->mesh->getNodeFlag(node); dof_data.associated_nodes.push_back(node); auto is_local_dof = this->mesh->isLocalOrMasterNode(node); auto is_periodic_slave = this->mesh->isPeriodicSlave(node); auto is_periodic_master = this->mesh->isPeriodicMaster(node); if (is_periodic_slave) { dof_data.local_equation_number.push_back(-1); continue; } // update equation numbers this->dofs_flag(local_eq_num) = dof_flag; dof_data.local_equation_number.push_back(local_eq_num); if (is_local_dof) { this->global_equation_number(local_eq_num) = first_global_dof_id; this->global_to_local_mapping[first_global_dof_id] = local_eq_num; ++first_global_dof_id; } else { this->global_equation_number(local_eq_num) = -1; } if (is_periodic_master) { auto node = getNode(d / dof_data.dof->getNbComponent()); auto dof = d % dof_data.dof->getNbComponent(); masters_dofs.insert( std::make_pair(std::make_pair(node, dof), local_eq_num)); } ++local_eq_num; } // correct periodic slave equation numbers if (this->mesh->isPeriodic()) { auto assoc_begin = dof_data.associated_nodes.begin(); for (auto d : arange(nb_local_dofs_added)) { auto node = dof_data.associated_nodes(first_dof_pos + d); if (not this->mesh->isPeriodicSlave(node)) { continue; } auto master_node = this->mesh->getPeriodicMaster(node); auto dof = d % dof_data.dof->getNbComponent(); dof_data.local_equation_number(first_dof_pos + d) = masters_dofs[std::make_pair(master_node, dof)]; } } // synchronize the global numbering for slaves nodes if (this->mesh->isDistributed()) { GlobalDOFInfoDataAccessor data_accessor(dof_data, *this); if (this->mesh->isPeriodic()) { mesh->getPeriodicNodeSynchronizer().synchronizeOnce( data_accessor, SynchronizationTag::_giu_global_conn); } auto & node_synchronizer = this->mesh->getNodeSynchronizer(); node_synchronizer.synchronizeOnce(data_accessor, SynchronizationTag::_ask_nodes); } } /* -------------------------------------------------------------------------- */ void DOFManager::updateDOFsData(DOFData & dof_data, UInt nb_new_local_dofs, UInt nb_new_pure_local) { dof_data.local_equation_number.reserve(dof_data.local_equation_number.size() + nb_new_local_dofs); UInt first_local_dof_id; UInt first_global_dof_id; std::tie(first_local_dof_id, first_global_dof_id) = computeFirstDOFIDs(nb_new_local_dofs, nb_new_pure_local); this->dofs_flag.resize(this->local_system_size, NodeFlag::_normal); this->global_equation_number.resize(this->local_system_size, -1); // update per dof info for (auto _ [[gnu::unused]] : arange(nb_new_local_dofs)) { // update equation numbers this->dofs_flag(first_local_dof_id) = NodeFlag::_normal; dof_data.local_equation_number.push_back(first_local_dof_id); this->global_equation_number(first_local_dof_id) = first_global_dof_id; this->global_to_local_mapping[first_global_dof_id] = first_local_dof_id; ++first_global_dof_id; ++first_local_dof_id; } } /* -------------------------------------------------------------------------- */ void DOFManager::onNodesRemoved(const Array & /*unused*/, const Array & /*unused*/, const RemovedNodesEvent & /*unused*/) {} /* -------------------------------------------------------------------------- */ void DOFManager::onElementsAdded(const Array & /*unused*/, const NewElementsEvent & /*unused*/) {} /* -------------------------------------------------------------------------- */ void DOFManager::onElementsRemoved(const Array & /*unused*/, const ElementTypeMapArray & /*unused*/, const RemovedElementsEvent & /*unused*/) {} /* -------------------------------------------------------------------------- */ void DOFManager::onElementsChanged(const Array & /*unused*/, const Array & /*unused*/, const ElementTypeMapArray & /*unused*/, const ChangedElementsEvent & /*unused*/) {} /* -------------------------------------------------------------------------- */ void DOFManager::updateGlobalBlockedDofs() { this->previous_global_blocked_dofs.copy(this->global_blocked_dofs); this->global_blocked_dofs.reserve(this->local_system_size, 0); this->previous_global_blocked_dofs_release = this->global_blocked_dofs_release; for (auto & pair : dofs) { if (!this->hasBlockedDOFs(pair.first)) { continue; } DOFData & dof_data = *pair.second; for (auto && data : zip(dof_data.getLocalEquationsNumbers(), make_view(*dof_data.blocked_dofs))) { const auto & dof = std::get<0>(data); const auto & is_blocked = std::get<1>(data); if (is_blocked) { this->global_blocked_dofs.push_back(dof); } } } std::sort(this->global_blocked_dofs.begin(), this->global_blocked_dofs.end()); auto last = std::unique(this->global_blocked_dofs.begin(), this->global_blocked_dofs.end()); this->global_blocked_dofs.resize(last - this->global_blocked_dofs.begin()); auto are_equal = global_blocked_dofs.size() == previous_global_blocked_dofs.size() and std::equal(global_blocked_dofs.begin(), global_blocked_dofs.end(), previous_global_blocked_dofs.begin()); if (not are_equal) { ++this->global_blocked_dofs_release; } } /* -------------------------------------------------------------------------- */ void DOFManager::applyBoundary(const ID & matrix_id) { auto & J = this->getMatrix(matrix_id); if (this->jacobian_release == J.getRelease()) { auto are_equal = this->global_blocked_dofs_release == this->previous_global_blocked_dofs_release; // std::equal(global_blocked_dofs.begin(), global_blocked_dofs.end(), // previous_global_blocked_dofs.begin()); if (not are_equal) { J.applyBoundary(); } previous_global_blocked_dofs.copy(global_blocked_dofs); } else { J.applyBoundary(); } this->jacobian_release = J.getRelease(); } /* -------------------------------------------------------------------------- */ void DOFManager::assembleMatMulVectToGlobalArray(const ID & dof_id, const ID & A_id, const Array & x, SolverVector & array, Real scale_factor) { auto & A = this->getMatrix(A_id); + data_cache->resize(); data_cache->zero(); this->assembleToGlobalArray(dof_id, x, *data_cache, 1.); A.matVecMul(*data_cache, array, scale_factor, 1.); } /* -------------------------------------------------------------------------- */ void DOFManager::assembleMatMulVectToResidual(const ID & dof_id, const ID & A_id, const Array & x, Real scale_factor) { assembleMatMulVectToGlobalArray(dof_id, A_id, x, *residual, scale_factor); } } // namespace akantu diff --git a/src/model/structural_mechanics/structural_mechanics_model.cc b/src/model/structural_mechanics/structural_mechanics_model.cc index de1ef7b7a..750eac7c2 100644 --- a/src/model/structural_mechanics/structural_mechanics_model.cc +++ b/src/model/structural_mechanics/structural_mechanics_model.cc @@ -1,482 +1,542 @@ /** * @file structural_mechanics_model.cc * * @author Fabian Barras * @author Lucas Frerot * @author Sébastien Hartmann * @author Nicolas Richart * @author Damien Spielmann * * @date creation: Fri Jul 15 2011 * @date last modification: Wed Feb 21 2018 * * @brief Model implementation for Structural Mechanics elements * * * Copyright (©) 2010-2018 EPFL (Ecole Polytechnique Fédérale de Lausanne) * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * * Akantu is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * Akantu is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with Akantu. If not, see . * */ /* -------------------------------------------------------------------------- */ #include "structural_mechanics_model.hh" #include "dof_manager.hh" #include "integrator_gauss.hh" #include "mesh.hh" #include "shape_structural.hh" #include "sparse_matrix.hh" #include "time_step_solver.hh" /* -------------------------------------------------------------------------- */ #ifdef AKANTU_USE_IOHELPER #include "dumpable_inline_impl.hh" #include "dumper_elemental_field.hh" #include "dumper_internal_material_field.hh" #include "dumper_iohelper_paraview.hh" #include "group_manager_inline_impl.hh" #endif /* -------------------------------------------------------------------------- */ #include "structural_element_bernoulli_beam_2.hh" #include "structural_element_bernoulli_beam_3.hh" #include "structural_element_kirchhoff_shell.hh" /* -------------------------------------------------------------------------- */ //#include "structural_mechanics_model_inline_impl.hh" /* -------------------------------------------------------------------------- */ namespace akantu { /* -------------------------------------------------------------------------- */ inline UInt StructuralMechanicsModel::getNbDegreeOfFreedom(ElementType type) { UInt ndof = 0; #define GET_(type) ndof = ElementClass::getNbDegreeOfFreedom() AKANTU_BOOST_KIND_ELEMENT_SWITCH(GET_, _ek_structural); #undef GET_ return ndof; } /* -------------------------------------------------------------------------- */ StructuralMechanicsModel::StructuralMechanicsModel(Mesh & mesh, UInt dim, const ID & id, const MemoryID & memory_id) : Model(mesh, ModelType::_structural_mechanics_model, dim, id, memory_id), f_m2a(1.0), stress("stress", id, memory_id), element_material("element_material", id, memory_id), set_ID("beam sets", id, memory_id), rotation_matrix("rotation_matices", id, memory_id) { AKANTU_DEBUG_IN(); registerFEEngineObject("StructuralMechanicsFEEngine", mesh, spatial_dimension); if (spatial_dimension == 2) { nb_degree_of_freedom = 3; } else if (spatial_dimension == 3) { nb_degree_of_freedom = 6; } else { AKANTU_TO_IMPLEMENT(); } #ifdef AKANTU_USE_IOHELPER this->mesh.registerDumper("structural_mechanics_model", id, true); #endif this->mesh.addDumpMesh(mesh, spatial_dimension, _not_ghost, _ek_structural); this->initDOFManager(); this->dumper_default_element_kind = _ek_structural; mesh.getElementalData("extra_normal") .initialize(mesh, _element_kind = _ek_structural, _nb_component = spatial_dimension, _with_nb_element = true, _default_value = 0.); AKANTU_DEBUG_OUT(); } /* -------------------------------------------------------------------------- */ StructuralMechanicsModel::~StructuralMechanicsModel() = default; /* -------------------------------------------------------------------------- */ void StructuralMechanicsModel::initFullImpl(const ModelOptions & options) { Model::initFullImpl(options); // Initializing stresses ElementTypeMap stress_components; /// TODO this is ugly af, maybe add a function to FEEngine for (auto && type : mesh.elementTypes(_spatial_dimension = _all_dimensions, _element_kind = _ek_structural)) { UInt nb_components = 0; // Getting number of components for each element type #define GET_(type) nb_components = ElementClass::getNbStressComponents() AKANTU_BOOST_STRUCTURAL_ELEMENT_SWITCH(GET_); #undef GET_ stress_components(nb_components, type); } stress.initialize( getFEEngine(), _spatial_dimension = _all_dimensions, _element_kind = _ek_structural, _nb_component = [&stress_components](ElementType type, GhostType /*unused*/) -> UInt { return stress_components(type); }); } /* -------------------------------------------------------------------------- */ void StructuralMechanicsModel::initFEEngineBoundary() { /// TODO: this function should not be reimplemented /// we're just avoiding a call to Model::initFEEngineBoundary() } /* -------------------------------------------------------------------------- */ void StructuralMechanicsModel::setTimeStep(Real time_step, const ID & solver_id) { Model::setTimeStep(time_step, solver_id); #if defined(AKANTU_USE_IOHELPER) this->mesh.getDumper().setTimeStep(time_step); #endif } /* -------------------------------------------------------------------------- */ /* Initialisation */ /* -------------------------------------------------------------------------- */ void StructuralMechanicsModel::initSolver( TimeStepSolverType time_step_solver_type, NonLinearSolverType /*unused*/) { AKANTU_DEBUG_IN(); this->allocNodalField(displacement_rotation, nb_degree_of_freedom, "displacement"); this->allocNodalField(external_force, nb_degree_of_freedom, "external_force"); this->allocNodalField(internal_force, nb_degree_of_freedom, "internal_force"); this->allocNodalField(blocked_dofs, nb_degree_of_freedom, "blocked_dofs"); auto & dof_manager = this->getDOFManager(); if (not dof_manager.hasDOFs("displacement")) { dof_manager.registerDOFs("displacement", *displacement_rotation, _dst_nodal); dof_manager.registerBlockedDOFs("displacement", *this->blocked_dofs); } if (time_step_solver_type == TimeStepSolverType::_dynamic || time_step_solver_type == TimeStepSolverType::_dynamic_lumped) { this->allocNodalField(velocity, nb_degree_of_freedom, "velocity"); this->allocNodalField(acceleration, nb_degree_of_freedom, "acceleration"); if (!dof_manager.hasDOFsDerivatives("displacement", 1)) { dof_manager.registerDOFsDerivative("displacement", 1, *this->velocity); dof_manager.registerDOFsDerivative("displacement", 2, *this->acceleration); } } AKANTU_DEBUG_OUT(); } /* -------------------------------------------------------------------------- */ void StructuralMechanicsModel::initModel() { element_material.initialize(mesh, _element_kind = _ek_structural, _default_value = 0, _with_nb_element = true); getFEEngine().initShapeFunctions(_not_ghost); getFEEngine().initShapeFunctions(_ghost); } /* -------------------------------------------------------------------------- */ void StructuralMechanicsModel::assembleStiffnessMatrix() { AKANTU_DEBUG_IN(); if (not need_to_reassemble_stiffness) { return; } if (not getDOFManager().hasMatrix("K")) { getDOFManager().getNewMatrix("K", getMatrixType("K")); } this->getDOFManager().zeroMatrix("K"); for (const auto & type : mesh.elementTypes(spatial_dimension, _not_ghost, _ek_structural)) { #define ASSEMBLE_STIFFNESS_MATRIX(type) assembleStiffnessMatrix(); AKANTU_BOOST_STRUCTURAL_ELEMENT_SWITCH(ASSEMBLE_STIFFNESS_MATRIX); #undef ASSEMBLE_STIFFNESS_MATRIX } need_to_reassemble_stiffness = false; AKANTU_DEBUG_OUT(); } /* -------------------------------------------------------------------------- */ void StructuralMechanicsModel::computeStresses() { AKANTU_DEBUG_IN(); for (const auto & type : mesh.elementTypes(spatial_dimension, _not_ghost, _ek_structural)) { #define COMPUTE_STRESS_ON_QUAD(type) computeStressOnQuad(); AKANTU_BOOST_STRUCTURAL_ELEMENT_SWITCH(COMPUTE_STRESS_ON_QUAD); #undef COMPUTE_STRESS_ON_QUAD } AKANTU_DEBUG_OUT(); } /* -------------------------------------------------------------------------- */ std::shared_ptr StructuralMechanicsModel::createNodalFieldBool( const std::string & field_name, const std::string & group_name, __attribute__((unused)) bool padding_flag) { std::map *> uint_nodal_fields; uint_nodal_fields["blocked_dofs"] = blocked_dofs; return mesh.createNodalField(uint_nodal_fields[field_name], group_name); } /* -------------------------------------------------------------------------- */ std::shared_ptr StructuralMechanicsModel::createNodalFieldReal(const std::string & field_name, const std::string & group_name, bool padding_flag) { UInt n; if (spatial_dimension == 2) { n = 2; } else { n = 3; } UInt padding_size = 0; if (padding_flag) { padding_size = 3; } if (field_name == "displacement") { return mesh.createStridedNodalField(displacement_rotation, group_name, n, 0, padding_size); } if (field_name == "velocity") { return mesh.createStridedNodalField(velocity, group_name, n, 0, padding_size); } if (field_name == "acceleration") { return mesh.createStridedNodalField(acceleration, group_name, n, 0, padding_size); } if (field_name == "rotation") { return mesh.createStridedNodalField(displacement_rotation, group_name, nb_degree_of_freedom - n, n, padding_size); } if (field_name == "force") { return mesh.createStridedNodalField(external_force, group_name, n, 0, padding_size); } if (field_name == "external_force") { return mesh.createStridedNodalField(external_force, group_name, n, 0, padding_size); } if (field_name == "momentum") { return mesh.createStridedNodalField( external_force, group_name, nb_degree_of_freedom - n, n, padding_size); } if (field_name == "internal_force") { return mesh.createStridedNodalField(internal_force, group_name, n, 0, padding_size); } if (field_name == "internal_momentum") { return mesh.createStridedNodalField( internal_force, group_name, nb_degree_of_freedom - n, n, padding_size); } return nullptr; } /* -------------------------------------------------------------------------- */ std::shared_ptr StructuralMechanicsModel::createElementalField( const std::string & field_name, const std::string & group_name, bool /*unused*/, UInt spatial_dimension, ElementKind kind) { std::shared_ptr field; if (field_name == "element_index_by_material") { field = mesh.createElementalField( field_name, group_name, spatial_dimension, kind); } if (field_name == "stress") { ElementTypeMap nb_data_per_elem = this->mesh.getNbDataPerElem(stress); field = mesh.createElementalField( stress, group_name, this->spatial_dimension, kind, nb_data_per_elem); } return field; } /* -------------------------------------------------------------------------- */ /* Virtual methods from SolverCallback */ /* -------------------------------------------------------------------------- */ /// get the type of matrix needed MatrixType StructuralMechanicsModel::getMatrixType(const ID & /*id*/) { return _symmetric; } /// callback to assemble a Matrix void StructuralMechanicsModel::assembleMatrix(const ID & id) { if (id == "K") { assembleStiffnessMatrix(); } else if (id == "M") { assembleMassMatrix(); } } /// callback to assemble a lumped Matrix void StructuralMechanicsModel::assembleLumpedMatrix(const ID & /*id*/) {} /// callback to assemble the residual StructuralMechanicsModel::(rhs) void StructuralMechanicsModel::assembleResidual() { AKANTU_DEBUG_IN(); auto & dof_manager = getDOFManager(); assembleInternalForce(); dof_manager.assembleToResidual("displacement", *external_force, 1); dof_manager.assembleToResidual("displacement", *internal_force, 1); AKANTU_DEBUG_OUT(); } /* -------------------------------------------------------------------------- */ void StructuralMechanicsModel::assembleResidual(const ID & residual_part) { AKANTU_DEBUG_IN(); if ("external" == residual_part) { this->getDOFManager().assembleToResidual("displacement", *this->external_force, 1); AKANTU_DEBUG_OUT(); return; } if ("internal" == residual_part) { this->assembleInternalForce(); this->getDOFManager().assembleToResidual("displacement", *this->internal_force, 1); AKANTU_DEBUG_OUT(); return; } AKANTU_CUSTOM_EXCEPTION( debug::SolverCallbackResidualPartUnknown(residual_part)); AKANTU_DEBUG_OUT(); } /* -------------------------------------------------------------------------- */ /* Virtual methods from Model */ /* -------------------------------------------------------------------------- */ /// get some default values for derived classes std::tuple StructuralMechanicsModel::getDefaultSolverID(const AnalysisMethod & method) { switch (method) { case _static: { return std::make_tuple("static", TimeStepSolverType::_static); } case _implicit_dynamic: { return std::make_tuple("implicit", TimeStepSolverType::_dynamic); } default: return std::make_tuple("unknown", TimeStepSolverType::_not_defined); } } /* ------------------------------------------------------------------------ */ ModelSolverOptions StructuralMechanicsModel::getDefaultSolverOptions( const TimeStepSolverType & type) const { ModelSolverOptions options; switch (type) { case TimeStepSolverType::_static: { options.non_linear_solver_type = NonLinearSolverType::_linear; options.integration_scheme_type["displacement"] = IntegrationSchemeType::_pseudo_time; options.solution_type["displacement"] = IntegrationScheme::_not_defined; break; } case TimeStepSolverType::_dynamic: { options.non_linear_solver_type = NonLinearSolverType::_newton_raphson; options.integration_scheme_type["displacement"] = IntegrationSchemeType::_trapezoidal_rule_2; options.solution_type["displacement"] = IntegrationScheme::_displacement; break; } default: AKANTU_EXCEPTION(type << " is not a valid time step solver type"); } return options; } /* -------------------------------------------------------------------------- */ void StructuralMechanicsModel::assembleInternalForce() { internal_force->zero(); computeStresses(); for (auto type : mesh.elementTypes(_spatial_dimension = _all_dimensions, _element_kind = _ek_structural)) { assembleInternalForce(type, _not_ghost); // assembleInternalForce(type, _ghost); } } /* -------------------------------------------------------------------------- */ void StructuralMechanicsModel::assembleInternalForce(ElementType type, GhostType gt) { auto & fem = getFEEngine(); auto & sigma = stress(type, gt); auto ndof = getNbDegreeOfFreedom(type); auto nb_nodes = mesh.getNbNodesPerElement(type); auto ndof_per_elem = ndof * nb_nodes; Array BtSigma(fem.getNbIntegrationPoints(type) * mesh.getNbElement(type), ndof_per_elem, "BtSigma"); fem.computeBtD(sigma, BtSigma, type, gt); Array intBtSigma(0, ndof_per_elem, "intBtSigma"); fem.integrate(BtSigma, intBtSigma, ndof_per_elem, type, gt); getDOFManager().assembleElementalArrayLocalArray(intBtSigma, *internal_force, type, gt, -1.); } + +/* -------------------------------------------------------------------------- */ +Real StructuralMechanicsModel::getKineticEnergy() { + + if (not this->getDOFManager().hasMatrix("M")) { + return 0.; + } + + Real ekin = 0.; + UInt nb_nodes = mesh.getNbNodes(); + + Array Mv(nb_nodes, nb_degree_of_freedom); + this->getDOFManager().assembleMatMulVectToArray("displacement", "M", + *this->velocity, Mv); + + for (auto && data : zip(arange(nb_nodes), make_view(Mv, nb_degree_of_freedom), + make_view(*this->velocity, nb_degree_of_freedom))) { + ekin += std::get<2>(data).dot(std::get<1>(data)) * + static_cast(mesh.isLocalOrMasterNode(std::get<0>(data))); + } + + mesh.getCommunicator().allReduce(ekin, SynchronizerOperation::_sum); + + return ekin / 2.; +} + +/* -------------------------------------------------------------------------- */ +Real StructuralMechanicsModel::getPotentialEnergy() { + Real epot = 0.; + UInt nb_nodes = mesh.getNbNodes(); + + Array Ku(nb_nodes, nb_degree_of_freedom); + this->getDOFManager().assembleMatMulVectToArray( + "displacement", "K", *this->displacement_rotation, Ku); + + for (auto && data : + zip(arange(nb_nodes), make_view(Ku, nb_degree_of_freedom), + make_view(*this->displacement_rotation, nb_degree_of_freedom))) { + epot += std::get<2>(data).dot(std::get<1>(data)) * + static_cast(mesh.isLocalOrMasterNode(std::get<0>(data))); + } + + mesh.getCommunicator().allReduce(epot, SynchronizerOperation::_sum); + + return epot / 2.; +} + +/* -------------------------------------------------------------------------- */ +Real StructuralMechanicsModel::getEnergy(const ID & energy) { + if (energy == "kinetic") { + return getKineticEnergy(); + } + + if (energy == "potential") { + return getPotentialEnergy(); + } + + return 0; +} + /* -------------------------------------------------------------------------- */ } // namespace akantu diff --git a/src/model/structural_mechanics/structural_mechanics_model.hh b/src/model/structural_mechanics/structural_mechanics_model.hh index 2e5ccd72f..bf35ca14f 100644 --- a/src/model/structural_mechanics/structural_mechanics_model.hh +++ b/src/model/structural_mechanics/structural_mechanics_model.hh @@ -1,329 +1,339 @@ /** * @file structural_mechanics_model.hh * * @author Fabian Barras * @author Lucas Frerot * @author Sébastien Hartmann * @author Nicolas Richart * @author Damien Spielmann * * @date creation: Fri Jul 15 2011 * @date last modification: Tue Feb 20 2018 * * @brief Particular implementation of the structural elements in the * StructuralMechanicsModel * * * Copyright (©) 2010-2018 EPFL (Ecole Polytechnique Fédérale de Lausanne) * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * * Akantu is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * Akantu is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with Akantu. If not, see . * */ /* -------------------------------------------------------------------------- */ #include "aka_named_argument.hh" #include "boundary_condition.hh" #include "model.hh" /* -------------------------------------------------------------------------- */ #ifndef AKANTU_STRUCTURAL_MECHANICS_MODEL_HH_ #define AKANTU_STRUCTURAL_MECHANICS_MODEL_HH_ /* -------------------------------------------------------------------------- */ namespace akantu { class Material; class MaterialSelector; class DumperIOHelper; class NonLocalManager; template class IntegratorGauss; template class ShapeStructural; } // namespace akantu namespace akantu { struct StructuralMaterial { Real E{0}; Real A{1}; Real I{0}; Real Iz{0}; Real Iy{0}; Real GJ{0}; Real rho{0}; Real t{0}; Real nu{0}; }; class StructuralMechanicsModel : public Model { /* ------------------------------------------------------------------------ */ /* Constructors/Destructors */ /* ------------------------------------------------------------------------ */ public: using MyFEEngineType = FEEngineTemplate; StructuralMechanicsModel(Mesh & mesh, UInt dim = _all_dimensions, const ID & id = "structural_mechanics_model", const MemoryID & memory_id = 0); ~StructuralMechanicsModel() override; /// Init full model void initFullImpl(const ModelOptions & options) override; /// Init boundary FEEngine void initFEEngineBoundary() override; /* ------------------------------------------------------------------------ */ /* Virtual methods from SolverCallback */ /* ------------------------------------------------------------------------ */ /// get the type of matrix needed MatrixType getMatrixType(const ID & matrix_id) override; /// callback to assemble a Matrix void assembleMatrix(const ID & matrix_id) override; /// callback to assemble a lumped Matrix void assembleLumpedMatrix(const ID & matrix_id) override; /// callback to assemble the residual (rhs) void assembleResidual() override; void assembleResidual(const ID & residual_part) override; bool canSplitResidual() override { return false; } + + /// compute kinetic energy + Real getKineticEnergy(); + + /// compute potential energy + Real getPotentialEnergy(); + + /// compute the specified energy + Real getEnergy(const ID & energy); + /* ------------------------------------------------------------------------ */ /* Virtual methods from Model */ /* ------------------------------------------------------------------------ */ protected: /// get some default values for derived classes std::tuple getDefaultSolverID(const AnalysisMethod & method) override; ModelSolverOptions getDefaultSolverOptions(const TimeStepSolverType & type) const override; static UInt getNbDegreeOfFreedom(ElementType type); /* ------------------------------------------------------------------------ */ /* Methods */ /* ------------------------------------------------------------------------ */ void initSolver(TimeStepSolverType time_step_solver_type, NonLinearSolverType non_linear_solver_type) override; /// initialize the model void initModel() override; /// compute the stresses per elements void computeStresses(); /// compute the nodal forces void assembleInternalForce(); /// compute the nodal forces for an element type void assembleInternalForce(ElementType type, GhostType gt); /// assemble the stiffness matrix void assembleStiffnessMatrix(); /// assemble the mass matrix for consistent mass resolutions void assembleMassMatrix(); protected: /// assemble the mass matrix for either _ghost or _not_ghost elements void assembleMassMatrix(GhostType ghost_type); /// computes rho void computeRho(Array & rho, ElementType type, GhostType ghost_type); /// finish the computation of residual to solve in increment void updateResidualInternal(); /* ------------------------------------------------------------------------ */ private: template void assembleStiffnessMatrix(); template void computeStressOnQuad(); template void computeTangentModuli(Array & tangent_moduli); /* ------------------------------------------------------------------------ */ /* Dumpable interface */ /* ------------------------------------------------------------------------ */ public: std::shared_ptr createNodalFieldReal(const std::string & field_name, const std::string & group_name, bool padding_flag) override; std::shared_ptr createNodalFieldBool(const std::string & field_name, const std::string & group_name, bool padding_flag) override; std::shared_ptr createElementalField(const std::string & field_name, const std::string & group_name, bool padding_flag, UInt spatial_dimension, ElementKind kind) override; /* ------------------------------------------------------------------------ */ /* Accessors */ /* ------------------------------------------------------------------------ */ public: /// set the value of the time step void setTimeStep(Real time_step, const ID & solver_id = "") override; /// return the dimension of the system space AKANTU_GET_MACRO(SpatialDimension, spatial_dimension, UInt); /// get the StructuralMechanicsModel::displacement vector AKANTU_GET_MACRO(Displacement, *displacement_rotation, Array &); /// get the StructuralMechanicsModel::velocity vector AKANTU_GET_MACRO(Velocity, *velocity, Array &); /// get the StructuralMechanicsModel::acceleration vector, updated /// by /// StructuralMechanicsModel::updateAcceleration AKANTU_GET_MACRO(Acceleration, *acceleration, Array &); /// get the StructuralMechanicsModel::external_force vector AKANTU_GET_MACRO(ExternalForce, *external_force, Array &); /// get the StructuralMechanicsModel::internal_force vector (boundary forces) AKANTU_GET_MACRO(InternalForce, *internal_force, Array &); /// get the StructuralMechanicsModel::boundary vector AKANTU_GET_MACRO(BlockedDOFs, *blocked_dofs, Array &); AKANTU_GET_MACRO_BY_ELEMENT_TYPE_CONST(RotationMatrix, rotation_matrix, Real); AKANTU_GET_MACRO_BY_ELEMENT_TYPE_CONST(Stress, stress, Real); AKANTU_GET_MACRO_BY_ELEMENT_TYPE(ElementMaterial, element_material, UInt); AKANTU_GET_MACRO_BY_ELEMENT_TYPE(Set_ID, set_ID, UInt); /** * \brief This function adds the `StructuralMaterial` material to the list of * materials managed by *this. * * It is important that this function might invalidate all references to * structural materials, that were previously optained by `getMaterial()`. * * \param material The new material. * * \return The ID of the material that was added. * * \note The return type is is new. */ UInt addMaterial(StructuralMaterial & material, const ID & name = ""); const StructuralMaterial & getMaterialByElement(const Element & element) const; /** * \brief Returns the ith material of *this. * \param i The ith material */ const StructuralMaterial & getMaterial(UInt material_index) const; const StructuralMaterial & getMaterial(const ID & name) const; /** * \brief Returns the number of the different materials inside *this. */ UInt getNbMaterials() const { return materials.size(); } /* ------------------------------------------------------------------------ */ /* Boundaries (structural_mechanics_model_boundary.cc) */ /* ------------------------------------------------------------------------ */ public: /// Compute Linear load function set in global axis template void computeForcesByGlobalTractionArray(const Array & tractions); /// Compute Linear load function set in local axis template void computeForcesByLocalTractionArray(const Array & tractions); /// compute force vector from a function(x,y,momentum) that describe stresses // template // void computeForcesFromFunction(BoundaryFunction in_function, // BoundaryFunctionType function_type); /* ------------------------------------------------------------------------ */ /* Class Members */ /* ------------------------------------------------------------------------ */ private: /// time step Real time_step; /// conversion coefficient form force/mass to acceleration Real f_m2a; /// displacements array Array * displacement_rotation{nullptr}; /// velocities array Array * velocity{nullptr}; /// accelerations array Array * acceleration{nullptr}; /// forces array Array * internal_force{nullptr}; /// forces array Array * external_force{nullptr}; /// lumped mass array Array * mass{nullptr}; /// boundaries array Array * blocked_dofs{nullptr}; /// stress array ElementTypeMapArray stress; ElementTypeMapArray element_material; // Define sets of beams ElementTypeMapArray set_ID; /// number of degre of freedom UInt nb_degree_of_freedom; // Rotation matrix ElementTypeMapArray rotation_matrix; // /// analysis method check the list in akantu::AnalysisMethod // AnalysisMethod method; /// flag defining if the increment must be computed or not bool increment_flag; bool need_to_reassemble_mass{true}; bool need_to_reassemble_stiffness{true}; /* ------------------------------------------------------------------------ */ std::vector materials; std::map materials_names_to_id; }; } // namespace akantu #include "structural_mechanics_model_inline_impl.hh" #endif /* AKANTU_STRUCTURAL_MECHANICS_MODEL_HH_ */