mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-20 10:08:31 +00:00
016e3cd8bf
force vector, autodiff takes care of linearization - elasticity equations are now solved using e.g. principle of minimum potential energy. syntax is quite good, see notebook. - updated how to interpolate fields, by introducing function spaces. syntax is now good. still have to figure out how to do time derivatives - etc. etc. tutorial is broken at the moment, i took of get_lhs and get_rhs because they didn't really work.
1938 lines
66 KiB
Plaintext
1938 lines
66 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Developing JuliaFEM\n",
|
||
"\n",
|
||
"Author(s): Jukka Aho\n",
|
||
"\n",
|
||
"**Abstract**: Developer notes. In this notebook we give the general guidelines how to implement basic fundamentals to JuliaFEM. The notebook is rapidly updated and should always represent the newest \"style\" how to develop things to JuliaFEM. At this phase of project interfaces are changing rapidly. For this reason we aim to develop couple of simple functions which aim to test that elements, equations, solvers etc. work as expected, in the sense that their interface hasn't any design problems. (Yes, we do believe in testing and follow also other practices proven to be good, like continuous integration). The aim of this document is not to be the guide to \"other contributors\" but is more like an guide line for all contributors, including ourselves, how things should be done.\n",
|
||
"\n",
|
||
"For demonstrational purposes we use the Poisson equation in our examples to demonstrate the main concepts of the package. Poisson equation has some analogies to solid mechanics. Consider for example $(EAu')' + q = 0$, which is nothing more than a Poisson equation defined in 1d.\n",
|
||
"\n",
|
||
"Because this is the second important document in this source repository, any questions or suggestions araising from this notebook are very welcome and desirable and should be adressed to the our issue log: https://github.com/JuliaFEM/JuliaFEM.jl/issues. Easiest way to make world better is to register GitHub (takes 1 min) and drop an issue. And the most important document? It's the \"how to contribute\" guide, see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/CONTRIBUTING.rst.\n",
|
||
"\n",
|
||
"In this notebook briefly we have defined the following \"objects\":\n",
|
||
"\n",
|
||
"**Element**: object which holds basis functions and fields. One is able to interpolate fields using element. Introduction of new basis functions needs new elements. Typical elements: Lagrange elements, hierarchical elements, etc.\n",
|
||
"\n",
|
||
"**Equation**: object which defines some field equation, like Poisson equation $\\Delta u = f$, elasticity equation $\\nabla \\cdot \\sigma = f$, etc., which needs to be solve. This is the physics we are trying to solve by approximating the equations in element area by some interpolation function provided by element.\n",
|
||
"\n",
|
||
"**Problem**: object which maps equations to elements. For example, HeatProblem which maps Poisson equation to Lagrange elements or ElasticityProblem which maps elasticity equation to Lagrange elements.\n",
|
||
"\n",
|
||
"**Solver**: object which takes one or more problems, solves them using some strategy (iterative methods, multigrid, direct methods, ...) and updates corresponding fields to elements. The is the section where any numerical crunching happens and is targeted to high-performance computing. \n",
|
||
"\n",
|
||
"These concepts are quite well separated so that development process can focus just one of thing of interest. In this notebook we will give instructions to all of the sections describing the whole development process from element level to global assembly and solution.\n",
|
||
"\n",
|
||
"We have\n",
|
||
"- [x] **`test_element`**\n",
|
||
"- [ ] **`test_equation`**\n",
|
||
"- [ ] **`test_problem`** \n",
|
||
"- [ ] **`test_solver`**\n",
|
||
"\n",
|
||
"which can be used to test that implementation has all necessary things defined.\n",
|
||
"\n",
|
||
"\n",
|
||
"Poisson equation is used in this notebook to demonstrate all phases of development process. It's weak form is: find $u\\in\\mathcal{U}$ such that\n",
|
||
"\\begin{equation}\n",
|
||
" \\int_{\\Omega}\\nabla u\\cdot\\nabla v\\,\\mathrm{d}x=\\int_{\\Omega}fv\\,\\mathrm{d}x+\\int_{\\Gamma_{\\mathrm{N}}}gv\\,\\mathrm{d}s\\quad\\forall v\\in\\mathcal{V}.\n",
|
||
"\\end{equation}\n",
|
||
"\n",
|
||
"## Definitions / nomenclature\n",
|
||
"\n",
|
||
"- weighted residual form: differential equation is multiplied by a weight function and integrating it.\n",
|
||
"- weak form, \"principle of virtual work\": typically established by partial integration of a weighted residual form. $\\delta\\mathcal{W}_{\\mathrm{int}}=\\delta\\mathcal{W}_{\\mathrm{ext}}$.\n",
|
||
"- variational form, \"principle of minimum potential energy\": there exists some functional or \"potential function\" $\\Pi$ we are minimizing"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"Logger(root,DEBUG,Base.PipeEndpoint(open, 0 bytes waiting),root)"
|
||
]
|
||
},
|
||
"execution_count": 2,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"using Logging\n",
|
||
"using FactCheck\n",
|
||
"Logging.configure(level=DEBUG)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Developing own element\n",
|
||
"\n",
|
||
"Element contains basis functions and one or several fieldsets.\n",
|
||
"\n",
|
||
"Minimum requirements for element:\n",
|
||
"- subclass it from Element, unless want to implement everything by youself\n",
|
||
"- define basis and partial derivatives of it, because we need to interpolate over it\n",
|
||
"- give connectivity information, how this element is connected to other elements\n",
|
||
"\n",
|
||
"Test the element using ``test_element`` function. It it passes, the element implementation should be fine."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"using JuliaFEM: Element, Field, FieldSet, Basis"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Here's the basic implementation for element:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"type MyQuad4 <: Element\n",
|
||
" connectivity :: Array{Int, 1}\n",
|
||
" basis :: Basis\n",
|
||
" fields :: Dict{Symbol, FieldSet}\n",
|
||
"end"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Default constructor takes only connectivity information as input argument. Most important thing here is to define `Basis` which is used to interpolate fields."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 6,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"MyQuad4"
|
||
]
|
||
},
|
||
"execution_count": 6,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"function MyQuad4(connectivity)\n",
|
||
" h(xi) = [\n",
|
||
" (1-xi[1])*(1-xi[2])/4\n",
|
||
" (1+xi[1])*(1-xi[2])/4\n",
|
||
" (1+xi[1])*(1+xi[2])/4\n",
|
||
" (1-xi[1])*(1+xi[2])/4]\n",
|
||
" dh(xi) = [\n",
|
||
" -(1-xi[2])/4.0 -(1-xi[1])/4.0\n",
|
||
" (1-xi[2])/4.0 -(1+xi[1])/4.0\n",
|
||
" (1+xi[2])/4.0 (1+xi[1])/4.0\n",
|
||
" -(1+xi[2])/4.0 (1-xi[1])/4.0]\n",
|
||
" basis = Basis(h, dh)\n",
|
||
" MyQuad4(connectivity, basis, Dict())\n",
|
||
"end"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Also some basic information like number of basis functions and element dimension is needed."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 7,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"get_element_dimension (generic function with 7 methods)"
|
||
]
|
||
},
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"JuliaFEM.get_number_of_basis_functions(el::Type{MyQuad4}) = 4\n",
|
||
"JuliaFEM.get_element_dimension(el::Type{MyQuad4}) = 2"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Next we check that everything is well defined:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"26-Oct 05:30:31:INFO:root:Testing element MyQuad4\n",
|
||
"26-Oct 05:30:31:INFO:root:number of basis functions in this element: 4\n",
|
||
"26-Oct 05:30:31:INFO:root:Initializing element\n",
|
||
"26-Oct 05:30:31:INFO:root:Element dimension: 2\n",
|
||
"26-Oct 05:30:31:INFO:root:Creating new scalar field JuliaFEM.Field{Array{Int64,1}}(0.0,0,[1,2,3,4])\n",
|
||
"26-Oct 05:30:31:INFO:root:basis at [0.0,0.0]: [0.25 0.25 0.25 0.25]\n",
|
||
"26-Oct 05:30:31:INFO:root:field val at [0.0,0.0]: 2.5\n",
|
||
"26-Oct 05:30:32:INFO:root:derivative of basis at [0.0,0.0]: [-0.5 0.5 0.5 -0.5\n",
|
||
" -0.5 -0.5 0.5 0.5]\n",
|
||
"26-Oct 05:30:32:INFO:root:field val at [0.0,0.0]: [0.0 2.0]\n",
|
||
"26-Oct 05:30:32:INFO:root:Element MyQuad4 passed tests.\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"using JuliaFEM: test_element\n",
|
||
"test_element(MyQuad4)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"If `test_element` passes, elements *interface* should be well defined. After building element, one can interpolate things in it. Here we create three new fieldsets `temperature`, `geometry` and `heat coefficient`, add some values for them in time $t=0.0$ and $t=1.0$ and interpolate:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 9,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"Dict{Symbol,JuliaFEM.FieldSet} with 4 entries:\n",
|
||
" symbol(\"heat coefficien… => JuliaFEM.FieldSet(symbol(\"heat coefficient\"),Juli…\n",
|
||
" :geometry => JuliaFEM.FieldSet(:geometry,JuliaFEM.Field[JuliaF…\n",
|
||
" :temperature => JuliaFEM.FieldSet(:temperature,JuliaFEM.Field[Jul…\n",
|
||
" :displacement => JuliaFEM.FieldSet(:displacement,JuliaFEM.Field[Ju…"
|
||
]
|
||
},
|
||
"execution_count": 9,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"using JuliaFEM: FieldSet, Field\n",
|
||
"element = MyQuad4([1, 2, 3, 4])\n",
|
||
"\n",
|
||
"geometry_field = Field(0.0, Vector[]) # Create empty field at time t=0.0\n",
|
||
"push!(geometry_field, [ 0.0, 0.0]) # push some values for field\n",
|
||
"push!(geometry_field, [ 1.0, 0.0])\n",
|
||
"push!(geometry_field, [ 1.0, 1.0])\n",
|
||
"push!(geometry_field, [ 0.0, 1.0])\n",
|
||
"geometry_fieldset = FieldSet(\"geometry\") # create fieldset \"geometry\"\n",
|
||
"push!(geometry_fieldset, geometry_field) # add field to fieldset\n",
|
||
"push!(element, geometry_fieldset) # add fieldset to element\n",
|
||
"\n",
|
||
"temperature_fieldset = FieldSet(\"temperature\")\n",
|
||
"push!(temperature_fieldset, Field(0.0, [0.0, 0.0, 0.0, 0.0]))\n",
|
||
"push!(temperature_fieldset, Field(1.0, [1.0, 2.0, 3.0, 4.0]))\n",
|
||
"push!(element, temperature_fieldset)\n",
|
||
"\n",
|
||
"displacement_fieldset = FieldSet(\"displacement\")\n",
|
||
"push!(displacement_fieldset, Field(0.0, Vector[[0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]]))\n",
|
||
"push!(displacement_fieldset, Field(1.0, Vector[[0.0, 0.0], [0.0, 0.0], [0.25, 0.0], [0.0, 0.0]]))\n",
|
||
"push!(element, displacement_fieldset)\n",
|
||
"\n",
|
||
"heat_coefficient_fieldset = FieldSet(\"heat coefficient\")\n",
|
||
"push!(heat_coefficient_fieldset, Field(0.0, 2))\n",
|
||
"push!(heat_coefficient_fieldset, Field(1.0, 3))\n",
|
||
"push!(element, heat_coefficient_fieldset)\n",
|
||
"element.fields"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"2-element Array{Float64,1}:\n",
|
||
" 0.5\n",
|
||
" 0.5"
|
||
]
|
||
},
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"# geometry midpoint of element\n",
|
||
"using JuliaFEM: get_basis, grad\n",
|
||
"basis = get_basis(element)\n",
|
||
"basis(\"geometry\", [0.0, 0.0], 0.0)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 11,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"1x2 Array{Float64,2}:\n",
|
||
" 0.0 1.0"
|
||
]
|
||
},
|
||
"execution_count": 11,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"# interpolate derivatives works too\n",
|
||
"dbasis = grad(basis)\n",
|
||
"dbasis(\"temperature\", [0.0, 0.0], 0.5) # temperature gradient at mid point of element at time t=0.5"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 12,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"2.5"
|
||
]
|
||
},
|
||
"execution_count": 12,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"# interpolating scalar -> scalar.\n",
|
||
"basis(\"heat coefficient\", [0.0, 0.0], 0.5)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 13,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"3"
|
||
]
|
||
},
|
||
"execution_count": 13,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"# set time to Inf to get very last field value and -Inf to get first one.\n",
|
||
"basis(\"heat coefficient\", [0.0, 0.0], Inf)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 14,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"2x2 Array{Float64,2}:\n",
|
||
" 0.132813 0.0703125\n",
|
||
" 0.0703125 0.0078125"
|
||
]
|
||
},
|
||
"execution_count": 14,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"# some continuum mechanics\n",
|
||
"∇u = dbasis(\"displacement\", [0.0, 0.0], 1.0)\n",
|
||
"ɛ = 1/2*(∇u + ∇u')\n",
|
||
"Ω = 1/2*(∇u - ∇u')\n",
|
||
"X = basis(\"geometry\", [0.0, 0.0], 1.0)\n",
|
||
"F = I + ∇u\n",
|
||
"C = F'*F\n",
|
||
"E = 1/2*(F'*F - I) # Green-Lagrange strain tensor"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Basically one could already write local stiffness matrices:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 15,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"(\n",
|
||
"4x4 Array{Float64,2}:\n",
|
||
" 4.0 -1.0 -2.0 -1.0\n",
|
||
" -1.0 4.0 -1.0 -2.0\n",
|
||
" -2.0 -1.0 4.0 -1.0\n",
|
||
" -1.0 -2.0 -1.0 4.0,\n",
|
||
"\n",
|
||
"4x4 Array{Float64,2}:\n",
|
||
" 4.0 2.0 1.0 2.0\n",
|
||
" 2.0 4.0 2.0 1.0\n",
|
||
" 1.0 2.0 4.0 2.0\n",
|
||
" 2.0 1.0 2.0 4.0,\n",
|
||
"\n",
|
||
"4x1 Array{Float64,2}:\n",
|
||
" 1.0\n",
|
||
" 1.0\n",
|
||
" 1.0\n",
|
||
" 1.0)"
|
||
]
|
||
},
|
||
"execution_count": 15,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"weights = [1.0, 1.0, 1.0, 1.0]\n",
|
||
"integration_points = 1.0/sqrt(3.0)*Vector[[-1, -1], [1, -1], [1, 1], [-1, 1]]\n",
|
||
"\n",
|
||
"M = zeros(4, 4)\n",
|
||
"K = zeros(4, 4)\n",
|
||
"f = zeros(4)\n",
|
||
"k = 6\n",
|
||
"rho = 36\n",
|
||
"q = 4\n",
|
||
"\n",
|
||
"for (w, xi) in zip(weights, integration_points)\n",
|
||
" N = get_basis(element)\n",
|
||
" ∇N = grad(N) # gradient is with respect to \"geometry\" field\n",
|
||
" detJ = det(N)(xi)\n",
|
||
" M += w*rho*N(xi)'*N(xi)*detJ # mass matrix\n",
|
||
" K += w*k*∇N(xi)'*∇N(xi)*detJ # stiffness matrix\n",
|
||
" f += w*N(xi)'*q*detJ # force vector\n",
|
||
"end\n",
|
||
"K, M, f"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"but we have an higher level interface for that also.\n",
|
||
"\n",
|
||
"### Summary of developing own elements\n",
|
||
"\n",
|
||
"Element itself if not calculating anything but only stores fields and basis functions so that the fields can be interpolated. We will provide command `test_element` which will ensure that everything necessary is defined. While a lot of things needs to be defined, by subclassing from `Element` most of these are already defined, thanks to multiple dispatch."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Developing own equation\n",
|
||
"\n",
|
||
"Let's consider a Poisson equation\n",
|
||
"\\begin{align}\n",
|
||
"-\\nabla \\cdot \\left( k \\nabla u\\right) &= f && \\text{on } \\Omega \\\\\n",
|
||
"\\frac{\\partial u}{\\partial n} &= g && \\text{on } \\Gamma_{\\mathrm{N}} \\\\\n",
|
||
"u &= u_0 && \\text{on } \\Gamma_{\\mathrm{D}} \\\\\n",
|
||
"\\end{align}\n",
|
||
"\n",
|
||
"Weak form is, find $u\\in\\mathcal{U}$ such that\n",
|
||
"\\begin{equation}\n",
|
||
" \\int_{\\Omega}k \\nabla u\\cdot\\nabla v\\,\\mathrm{d}x = \\int_{\\Omega}fv\\,\\mathrm{d}x + \\int_{\\Gamma_{\\mathrm{N}}}gv\\,\\mathrm{d}s \\quad\\forall v\\in\\mathcal{V}.\n",
|
||
"\\end{equation}\n",
|
||
"\n",
|
||
"Minimum requirements for equation: \n",
|
||
"- subclass from Equation, if not want to implement from scratch\n",
|
||
"- provide the name of the unknown field variable trying to solve\n",
|
||
"- default constructor takes the element as input argument\n",
|
||
"\n",
|
||
"This time we will have function `test_equation`, which we can use to test that everything is working as expected. "
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 16,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"get_unknown_field_name (generic function with 4 methods)"
|
||
]
|
||
},
|
||
"execution_count": 16,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"using JuliaFEM: Equation, IntegrationPoint, Quad4, Seg2, get_unknown_field_name\n",
|
||
"\n",
|
||
"abstract Heat <: Equation\n",
|
||
"JuliaFEM.get_unknown_field_name(eq::Heat) = symbol(\"temperature\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Basic data type needs to contain element, integration points and global degrees of freedom for global assembly:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 17,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"size (generic function with 64 methods)"
|
||
]
|
||
},
|
||
"execution_count": 17,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"\"\"\" Diffusive heat transfer for 4-node bilinear element. \"\"\"\n",
|
||
"type DC2D4 <: Heat\n",
|
||
" element :: Quad4\n",
|
||
" integration_points :: Array{IntegrationPoint, 1}\n",
|
||
" global_dofs :: Array{Int64, 1}\n",
|
||
"end\n",
|
||
"function DC2D4(element::Quad4)\n",
|
||
" integration_points = [\n",
|
||
" IntegrationPoint(1.0/sqrt(3.0)*[-1, -1], 1.0),\n",
|
||
" IntegrationPoint(1.0/sqrt(3.0)*[ 1, -1], 1.0),\n",
|
||
" IntegrationPoint(1.0/sqrt(3.0)*[ 1, 1], 1.0),\n",
|
||
" IntegrationPoint(1.0/sqrt(3.0)*[-1, 1], 1.0)]\n",
|
||
" push!(element, FieldSet(\"temperature\"))\n",
|
||
" DC2D4(element, integration_points, [])\n",
|
||
"end\n",
|
||
"Base.size(equation::DC2D4) = 4\n",
|
||
"\n",
|
||
"\"\"\" Diffusive heat transfer for 2-node linear segment. \"\"\"\n",
|
||
"type DC2D2 <: Heat\n",
|
||
" element :: Seg2\n",
|
||
" integration_points :: Array{IntegrationPoint, 1}\n",
|
||
" global_dofs :: Array{Int64, 1}\n",
|
||
"end\n",
|
||
"function DC2D2(element::Seg2)\n",
|
||
" integration_points = [\n",
|
||
" IntegrationPoint([0.0], 2.0)]\n",
|
||
" push!(element, FieldSet(\"temperature\"))\n",
|
||
" DC2D2(element, integration_points, [])\n",
|
||
"end\n",
|
||
"Base.size(equation::DC2D2) = 2"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Now the actual implementation for $\\int_{\\Omega}k \\nabla u\\cdot\\nabla v\\,\\mathrm{d}x = \\int_{\\Omega}fv\\,\\mathrm{d}x + \\int_{\\Gamma_{\\mathrm{N}}}gv\\,\\mathrm{d}s$.\n",
|
||
"\n",
|
||
"The low-level way is to write equations for local matrix representation directly:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 18,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"has_force_vector (generic function with 3 methods)"
|
||
]
|
||
},
|
||
"execution_count": 18,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"using JuliaFEM: get_basis, get_element\n",
|
||
"\n",
|
||
"\"\"\" Mass matrix for dynamical problems. Not used in this example. \"\"\"\n",
|
||
"function JuliaFEM.get_mass_matrix(equation::DC2D4, ip, time)\n",
|
||
" element = get_element(equation)\n",
|
||
" basis = get_basis(element)\n",
|
||
" ρ = basis(\"density\", ip, time)\n",
|
||
" return ρ * basis(ip,time)'*basis(ip,time)\n",
|
||
"end\n",
|
||
"\"\"\" Left hand side defined in integration point. \"\"\"\n",
|
||
"function JuliaFEM.get_stiffness_matrix(equation::DC2D4, ip, time)\n",
|
||
" element = get_element(equation)\n",
|
||
" basis = get_basis(element)\n",
|
||
" dbasis = grad(basis)\n",
|
||
" k = basis(\"temperature thermal conductivity\", ip, time)\n",
|
||
" return k * dbasis(ip,time)'*dbasis(ip,time)\n",
|
||
"end\n",
|
||
"\"\"\" Right hand side defined in integration point. \"\"\"\n",
|
||
"function JuliaFEM.get_force_vector(equation::DC2D4, ip, time)\n",
|
||
" element = get_element(equation)\n",
|
||
" basis = get_basis(element)\n",
|
||
" f = basis(\"temperature load\", ip, time)\n",
|
||
" return basis(ip,time)'*f\n",
|
||
"end\n",
|
||
"JuliaFEM.has_mass_matrix(equation::DC2D4) = true\n",
|
||
"JuliaFEM.has_stiffness_matrix(equation::DC2D4) = true\n",
|
||
"JuliaFEM.has_force_vector(equation::DC2D4) = true\n",
|
||
"\n",
|
||
"\"\"\" Right hand side defined in integration point. \"\"\"\n",
|
||
"function JuliaFEM.get_force_vector(equation::DC2D2, ip, time)\n",
|
||
" element = get_element(equation)\n",
|
||
" basis = get_basis(element)\n",
|
||
" g = basis(\"temperature flux\", ip, time)\n",
|
||
" return basis(ip,time)'*g\n",
|
||
"end\n",
|
||
"JuliaFEM.has_force_vector(equation::DC2D2) = true"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"It might at start to feel a bit ackward to pass integration point and time all around as function arguments, so a little reasoning why this is essential might be needed.\n",
|
||
"\n",
|
||
"First of all, equations are written in integration or gauss quadrature points for a certain reason. The reason is that for some incremental constitutive models internal variables are needed, which are saved to FieldSets inside integration points. We can always access these variables through `ip.fields` in the same way we can access all other field variables defined inside element via `element.fields`.\n",
|
||
"\n",
|
||
"Secondly, starting point for our equation is that everything can be time-dependent. FieldSet is, like name suggests, a set of fields with different times and/or increments. By transferring time parameter inside equation makes it possible to access not only current field values but earlier values too. In this example field variables thermal conductivity $k$, load $f$ and flux $g$ are constant in time but this way they can depend easily from spatial or temporal dimension. Actually let's change the parameter $g$ such a way that it is linear ramp from $g(t) = 6t$.\n",
|
||
"\n",
|
||
"If we want to try out this formulation, we must create element and assign this equation for it:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 19,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"# create volume element\n",
|
||
"element = Quad4([1, 2, 3, 4])\n",
|
||
"fieldset1 = FieldSet(\"geometry\", [Field(0.0, Vector[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]])])\n",
|
||
"fieldset2 = FieldSet(\"temperature thermal conductivity\", [Field(0.0, 6.0)])\n",
|
||
"fieldset3 = FieldSet(\"temperature load\", [Field(0.0, [12.0, 12.0, 12.0, 12.0])])\n",
|
||
"fieldset4 = FieldSet(\"density\", [Field(0.0, 1.0)])\n",
|
||
"push!(element, fieldset1)\n",
|
||
"push!(element, fieldset2)\n",
|
||
"push!(element, fieldset3)\n",
|
||
"push!(element, fieldset4)\n",
|
||
"equation = DC2D4(element)\n",
|
||
"\n",
|
||
"# create boundary element with \n",
|
||
"boundary_element = Seg2([1, 2])\n",
|
||
"push!(boundary_element, FieldSet(\"geometry\", [Field(0.0, Vector[[0.0, 0.0], [1.0, 0.0]])]))\n",
|
||
"# linear ramp from 1 to 6 in time 0 to 1\n",
|
||
"push!(boundary_element, FieldSet(\"temperature flux\", [Field(0.0, 0.0), Field(1.0, 6.0)]))\n",
|
||
"boundary_equation = DC2D2(boundary_element);"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 20,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"4x4 Array{Float64,2}:\n",
|
||
" 4.0 -1.0 -2.0 -1.0\n",
|
||
" -1.0 4.0 -1.0 -2.0\n",
|
||
" -2.0 -1.0 4.0 -1.0\n",
|
||
" -1.0 -2.0 -1.0 4.0"
|
||
]
|
||
},
|
||
"execution_count": 20,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"using JuliaFEM: initialize_local_assembly, calculate_local_assembly!\n",
|
||
"\n",
|
||
"local_assembly = initialize_local_assembly(equation)\n",
|
||
"calculate_local_assembly!(local_assembly, equation)\n",
|
||
"local_assembly.stiffness_matrix"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 21,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"4x1 Array{Float64,2}:\n",
|
||
" 3.0\n",
|
||
" 3.0\n",
|
||
" 3.0\n",
|
||
" 3.0"
|
||
]
|
||
},
|
||
"execution_count": 21,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"local_assembly.force_vector"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Let's set homogeneous Dirichlet boundary condition ($u=0$) on boundary on $\\Gamma_\\mathrm{D} = \\{(x,y) | 0 \\leq x \\leq 1, y=1\\}$. With source term $f=12$ and $k=6$ we should have constant $T=1$ on free boundary. According to my math we should have $u\\left(x,y\\right)=-\\frac{1}{6}\\left(\\frac{1}{2}fx^{2}-fx\\right)$ which equals to one if $x=1$."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 22,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"2-element Array{Float64,1}:\n",
|
||
" 1.0\n",
|
||
" 1.0"
|
||
]
|
||
},
|
||
"execution_count": 22,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"A = local_assembly.stiffness_matrix\n",
|
||
"b = local_assembly.force_vector\n",
|
||
"u = zeros(4)\n",
|
||
"fdofs = [1,2]\n",
|
||
"u[fdofs] = A[fdofs, fdofs] \\ b[fdofs]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Notice that we created a separate boundary element which integrates heat flux. Defining boundary elements goes identically with other elements. With flux $g=6$ we should have constant $T=1$ on free boundary. This should equal to $u(x,y) = x$, i.e. 1d bar stretched from other side while other one is fixed."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 23,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"2-element Array{Float64,1}:\n",
|
||
" 1.0\n",
|
||
" 1.0"
|
||
]
|
||
},
|
||
"execution_count": 23,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"boundary_assembly = JuliaFEM.initialize_local_assembly(boundary_equation)\n",
|
||
"JuliaFEM.calculate_local_assembly!(boundary_assembly, boundary_equation)\n",
|
||
"b = zeros(4)\n",
|
||
"b[fdofs] = boundary_assembly.force_vector\n",
|
||
"u = zeros(4)\n",
|
||
"u[fdofs] = A[fdofs, fdofs] \\ b[fdofs]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Some alternative ways to define equations - principle of minimum potential energy\n",
|
||
"\n",
|
||
"Fundamentally we are always looking for a solution of systems of equation $Ax = b$, despite the fact that mathematicians doesn't like this \"engineering approach\" at all. For some equations it may be too hard to write left hand side, i.e., stiffness matrix $A$. (At least for me). For this reason JuliaFEM supports automatic differentiation using package called `ForwardDiff`, which makes the analytical linearization unnecessary.\n",
|
||
"\n",
|
||
"Let's consider the following functional\n",
|
||
"\\begin{equation}\n",
|
||
"\\min\\, J\\left(u\\right)=\\int_{\\Omega}(k+6u)\\left|\\nabla u\\right|^{2}\\,\\mathrm{d}x-Pu,\n",
|
||
"\\end{equation}\n",
|
||
"where $P$ contains point loads at the free corners of the domain. This is nothing more but a redefinition of the earlier example problem defined such that the source term $k$, which in previous example was constant, is no more constant, but is replaced a term which depend from field $u$: $q(u) = k + 6u$.\n",
|
||
"\n",
|
||
"This functional doesn't actually have any or very little physical meaning, but it's non-linear due to the source term $q(u) = k+6u$ and therefore suits to our needs in demonstration purposes. In mechanical world this could be some sort of elastic spring with a spring \"constant\" depending on displacement (non-linear elasticity problem), which is compressed using point force $P$. The system is still *conservative*, i.e. it has a potential energy and can be solved using standard variational methods, finding a minimum of potential energy in suitable finite set of functions.\n",
|
||
"\n",
|
||
"In JuliaFEM we simply define it's potential energy functional $\\Pi(u)$ and let the `ForwardDiff`-package to handle the rest of the hard things. Because the problem is nonlinear, linearization and iterative solution method (=Newton's method) is needed. Accurate solution at the free end is\n",
|
||
"\\begin{equation}\n",
|
||
"u = \\frac{2}{3}.\n",
|
||
"\\end{equation}"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 24,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"size (generic function with 65 methods)"
|
||
]
|
||
},
|
||
"execution_count": 24,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"\"\"\" Diffusive heat transfer for 4-node bilinear element, with a nonlinear source term. \"\"\"\n",
|
||
"type DC2D4NL <: Heat\n",
|
||
" element :: Quad4\n",
|
||
" integration_points :: Array{IntegrationPoint, 1}\n",
|
||
" global_dofs :: Array{Int64, 1}\n",
|
||
"end\n",
|
||
"function DC2D4NL(element::Quad4)\n",
|
||
" integration_points = [\n",
|
||
" IntegrationPoint(1.0/sqrt(3.0)*[-1, -1], 1.0),\n",
|
||
" IntegrationPoint(1.0/sqrt(3.0)*[ 1, -1], 1.0),\n",
|
||
" IntegrationPoint(1.0/sqrt(3.0)*[ 1, 1], 1.0),\n",
|
||
" IntegrationPoint(1.0/sqrt(3.0)*[-1, 1], 1.0)]\n",
|
||
" push!(element, FieldSet(\"temperature\"))\n",
|
||
" # Initial configuration needs to be defined\n",
|
||
" push!(element[\"temperature\"], Field(0.0, [0.0, 0.0, 0.0, 0.0]))\n",
|
||
" DC2D4NL(element, integration_points, [])\n",
|
||
"end\n",
|
||
"Base.size(equation::DC2D4NL) = 4"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"This time we don't write stiffness matrix, etc. explicitly but just write potential energy and let `ForwardDiff` take care of rest of the stuff."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 32,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"has_potential_energy (generic function with 2 methods)"
|
||
]
|
||
},
|
||
"execution_count": 32,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"\"\"\" Calculate a potential Π = Wint - Wext of system. \"\"\"\n",
|
||
"function JuliaFEM.get_potential_energy(equation::DC2D4NL, ip, time; variation=nothing)\n",
|
||
" element = get_element(equation)\n",
|
||
" basis = get_basis(element)\n",
|
||
" k = basis(\"temperature thermal conductivity\", ip, time)\n",
|
||
" f = basis(\"temperature load\", ip, time)\n",
|
||
" T = basis(\"temperature\", ip, time, variation)\n",
|
||
" ∇T = grad(basis)(\"temperature\", ip, time, variation)\n",
|
||
" Wint = (k + 6*T) * 1/2*∇T*∇T'\n",
|
||
" Wext = f*T\n",
|
||
" return Wint - Wext\n",
|
||
"end\n",
|
||
"JuliaFEM.has_potential_energy(eq::DC2D4NL) = true"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Solving non-linear variational problem is rather complicated thing due to the nature of non-linearity. Here's a heavily commented version of simple Newton iteration."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 34,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"increment 1, |du| = 1.41421, |r| = 4.243\n",
|
||
"increment 2, |du| = 0.42426, |r| = 4.243\n",
|
||
"increment 3, |du| = 0.04657, |r| = 4.243\n",
|
||
"increment 4, |du| = 0.00057, |r| = 4.243\n",
|
||
"increment 5, |du| = 0.00000, |r| = 4.243\n",
|
||
"elapsed time: 0.001905274 seconds\n",
|
||
"error: 1.6653345369377348e-15\n",
|
||
"temperature at free end: [0.6666666666666683,0.6666666666666683], should be 0.6666666666666666\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"function run_simulation()\n",
|
||
" # create model -- start\n",
|
||
" element = Quad4([1, 2, 3, 4])\n",
|
||
" fieldset1 = FieldSet(\"geometry\")\n",
|
||
" field1 = Field(0.0, Vector[[0.0,0.0], [1.0,0.0], [1.0,1.0], [0.0,1.0]])\n",
|
||
" push!(fieldset1, field1)\n",
|
||
" fieldset2 = FieldSet(\"temperature thermal conductivity\")\n",
|
||
" push!(fieldset2, Field(0.0, 6.0))\n",
|
||
" fieldset3 = FieldSet(\"temperature load\")\n",
|
||
" push!(fieldset3, Field(0.0, 0*[12.0, 12.0, 12.0, 12.0]))\n",
|
||
" fieldset4 = FieldSet(\"temperature nodal load\")\n",
|
||
" push!(fieldset4, Field(0.0, [3.0, 3.0, 0.0, 0.0])) # <-- P is defined here\n",
|
||
" push!(element, fieldset1)\n",
|
||
" push!(element, fieldset2)\n",
|
||
" push!(element, fieldset3)\n",
|
||
" push!(element, fieldset4)\n",
|
||
" equation = DC2D4NL(element)\n",
|
||
" # create model -- end\n",
|
||
"\n",
|
||
" T0 = element[\"temperature\"][1] # initial temperature field, we need something to \"variate\"\n",
|
||
" la = initialize_local_assembly(equation) # create workspace for local matrices\n",
|
||
" T = zeros(4) # create workspace for solution vector\n",
|
||
" ΔT = zeros(4) # \n",
|
||
" fd = [1, 2]\n",
|
||
" tic()\n",
|
||
" # start loops, in principle solve ∂r(u)/∂uΔu = -r(u) and update.\n",
|
||
" for i=1:5\n",
|
||
" la = initialize_local_assembly(equation,la) # empty workspace -- every iteration should start with this\n",
|
||
" calculate_local_assembly!(la, equation) # calculate local matrices\n",
|
||
" ΔT[fd] = la.stiffness_matrix[fd,fd] \\ la.force_vector[fd] # <-- note sign convention, more on this below\n",
|
||
" T = T + ΔT # add increment to previous value\n",
|
||
" # create a new field \"similar\" to field T0 (i.e., same dimension of field variable with new data)\n",
|
||
" new_field = similar(T0, T)\n",
|
||
" new_field.time = 1.0\n",
|
||
" new_field.increment = i\n",
|
||
" push!(element[\"temperature\"], new_field) # add new field to \"temperature\" fieldset of element\n",
|
||
" # print some convergence information\n",
|
||
" @printf(\"increment %2d, |du| = %8.5f, |r| = %8.3f\\n\", i, norm(ΔT), norm(b[fd]))\n",
|
||
" end\n",
|
||
" toc()\n",
|
||
" err = element[\"temperature\"][end].values[1] - 2/3\n",
|
||
" println(\"error: $err\")\n",
|
||
" blaa = element[\"temperature\"][end].values[1:2]\n",
|
||
" acc = 2/3\n",
|
||
" println(\"temperature at free end: $blaa, should be $acc\")\n",
|
||
"end\n",
|
||
"run_simulation()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"#### A short note about sign convention\n",
|
||
"\n",
|
||
"When solving a non-linear equations like $\\mathbf{r}\\left(\\mathbf{u}\\right) = 0$ it is necessary to iterate, i.e. solve several linearized solutions using e.g. Newton iterations until residual is small enough. Using truncated Taylor series the solution algorithm is in general\n",
|
||
"\\begin{equation}\n",
|
||
"\\mathbf{r}\\left(\\mathbf{u}+\\Delta\\mathbf{u}\\right)=\\mathbf{r}\\left(\\mathbf{u}\\right)+\\frac{\\partial\\mathbf{r}\\left(\\mathbf{u}\\right)}{\\partial\\mathbf{u}}\\Delta\\mathbf{u}+\\cdots\\approx\\mathbf{r}\\left(\\mathbf{u}\\right)+\\mathbf{K}\\Delta\\mathbf{u}=0\n",
|
||
"\\end{equation}\n",
|
||
"\\begin{equation}\n",
|
||
"\\mathbf{K}\\Delta\\mathbf{u}=-\\mathbf{r}\\left(\\mathbf{u}\\right)\n",
|
||
"\\end{equation}\n",
|
||
"with a *negative* sign in front of residual vector.\n",
|
||
"\n",
|
||
"We do however want to solve something like\n",
|
||
"\\begin{equation}\n",
|
||
"\\mathbf{A}\\Delta\\mathbf{x}^{\\left(i+1\\right)}=\\mathbf{b},\\qquad\\mathbf{x}^{\\left(i+1\\right)}=\\mathbf{x}^{\\left(i\\right)}+\\Delta\\mathbf{x}^{\\left(i+1\\right)},\n",
|
||
"\\end{equation}\n",
|
||
"where new increment is *added* to the old result. If this doesn't say anything, compare the equation to the 1d Newton iteration (https://en.wikipedia.org/wiki/Newton's_method)\n",
|
||
"\\begin{equation}\n",
|
||
"x_{n+1}=x_{n}-\\frac{f\\left(x_{n}\\right)}{f'\\left(x_{n}\\right)}\n",
|
||
"\\end{equation}\n",
|
||
"\n",
|
||
"Our convention is that the negative sign of residual vector is positive. It makes sense because this way the \"normal\" linear system force vector is like it's used to be in school books. Typically the residual vector is defined $\\mathbf{r}=\\mathbf{f}_{\\mathrm{int}}-\\mathbf{f}_{\\mathrm{ext}}=0$. In our case the residual vector must be multiplied by $-1$ after the linearization so that the increment can be *added* to old result. In some text books this has already be taken into account by defining right hand side as $\\mathbf{r}=\\mathbf{f}_{\\mathrm{ext}}-\\mathbf{f}_{\\mathrm{int}}$ while linearization is still done for vector $\\mathbf{r}=\\mathbf{f}_{\\mathrm{int}}-\\mathbf{f}_{\\mathrm{ext}}$. Quite confusing!\n",
|
||
"\n",
|
||
"Just keep in mind that there is always minus somewhere and it should be. In our variational implementation the sign of residual vector is automatically changed so that next increment is $\\mathbf{A} \\Delta\\mathbf{x}=\\mathbf{b}$. Like in the simple example above.\n",
|
||
"\n",
|
||
"### Principle of virtual work\n",
|
||
"\n",
|
||
"Defining variational form or \"energy form\", or in other words, using principle of minimum potential energy, requires system to be *conservative*, i.e. there is some energy functional describing the system. Indeed it's a very elegant approach to model e.g. hyperelasticity and other systems without a loss of energy. When system has energy dissipation, principle of minimum potential energy cannot be used for obvious reasons. There is no any \"potential function\" $\\Pi$ which could be defined and variated around it's equilibrium state. Possible situations when this happens include e.g. material plasticity or friction. In these situations the principle of virtual work is useful.\n",
|
||
"\n",
|
||
"In this situation we find equation $\\mathbf{r}=\\mathbf{f}_{\\mathrm{int}}-\\mathbf{f}_{\\mathrm{ext}}=0$ which needs to be solved. Again we let `ForwardDiff` to do the linearization of the right hand side. Let's demonstrate this too."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 37,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"has_residual_vector (generic function with 2 methods)"
|
||
]
|
||
},
|
||
"execution_count": 37,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"using JuliaFEM: get_field\n",
|
||
"\n",
|
||
"\"\"\" Diffusive heat transfer for 4-node bilinear element,\n",
|
||
"with a nonlinear term to be added later.. \"\"\"\n",
|
||
"type DC2D4NLY <: Heat\n",
|
||
" element :: Quad4\n",
|
||
" integration_points :: Array{IntegrationPoint, 1}\n",
|
||
" global_dofs :: Array{Int64, 1}\n",
|
||
"end\n",
|
||
"function DC2D4NLY(element::Quad4)\n",
|
||
" integration_points = [\n",
|
||
" IntegrationPoint(1.0/sqrt(3.0)*[-1, -1], 1.0),\n",
|
||
" IntegrationPoint(1.0/sqrt(3.0)*[ 1, -1], 1.0),\n",
|
||
" IntegrationPoint(1.0/sqrt(3.0)*[ 1, 1], 1.0),\n",
|
||
" IntegrationPoint(1.0/sqrt(3.0)*[-1, 1], 1.0)]\n",
|
||
" push!(element, FieldSet(\"temperature\"))\n",
|
||
" # Initial configuration needs to be defined\n",
|
||
" push!(element[\"temperature\"], Field(0.0, [0.0, 0.0, 0.0, 0.0]))\n",
|
||
" DC2D4NLY(element, integration_points, [])\n",
|
||
"end\n",
|
||
"Base.size(equation::DC2D4NLY) = 4\n",
|
||
"\n",
|
||
"function JuliaFEM.get_residual_vector(equation::DC2D4NLY, ip, time; variation=nothing)\n",
|
||
" element = get_element(equation)\n",
|
||
" basis = get_basis(element)\n",
|
||
" dbasis = grad(basis)\n",
|
||
" k = basis(\"temperature thermal conductivity\", ip, time)\n",
|
||
" f = basis(\"temperature load\", ip, time)\n",
|
||
" T = get_field(basis, \"temperature\", time, variation)\n",
|
||
" f_int = k * dbasis(ip,time)'*dbasis(ip,time) * T\n",
|
||
" f_ext = f * basis(ip,time)'\n",
|
||
" r = f_int[:] - f_ext[:]\n",
|
||
" return r\n",
|
||
"end\n",
|
||
"JuliaFEM.has_residual_vector(equation::DC2D4NLY) = true"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 38,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"increment "
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"0.020793035"
|
||
]
|
||
},
|
||
"execution_count": 38,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
},
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"1, |du| = 2.82843, |r| = 4.243\n",
|
||
"increment 2, |du| = 0.00000, |r| = 4.243\n",
|
||
"increment 3, |du| = 0.00000, |r| = 4.243\n",
|
||
"increment 4, |du| = 0.00000, |r| = 4.243\n",
|
||
"increment 5, |du| = 0.00000, |r| = 4.243\n",
|
||
"temperature field: [2.0000000000000004,2.0000000000000004,0.0,0.0]\n",
|
||
"elapsed time: 0.020793035 seconds\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"function run_simulation_2()\n",
|
||
" # create model -- start\n",
|
||
" element = Quad4([1, 2, 3, 4])\n",
|
||
" fieldset1 = FieldSet(\"geometry\")\n",
|
||
" field1 = Field(0.0, Vector[[0.0,0.0], [1.0,0.0], [1.0,1.0], [0.0,1.0]])\n",
|
||
" push!(fieldset1, field1)\n",
|
||
" fieldset2 = FieldSet(\"temperature thermal conductivity\")\n",
|
||
" push!(fieldset2, Field(0.0, 6.0))\n",
|
||
" fieldset3 = FieldSet(\"temperature load\")\n",
|
||
" push!(fieldset3, Field(0.0, [12.0, 12.0, 12.0, 12.0]))\n",
|
||
" fieldset4 = FieldSet(\"temperature nodal load\")\n",
|
||
" push!(fieldset4, Field(0.0, [3.0, 3.0, 0.0, 0.0])) # <-- P is defined here\n",
|
||
" push!(element, fieldset1)\n",
|
||
" push!(element, fieldset2)\n",
|
||
" push!(element, fieldset3)\n",
|
||
" push!(element, fieldset4)\n",
|
||
" equation = DC2D4NLY(element)\n",
|
||
" # create model -- end\n",
|
||
"\n",
|
||
" T0 = element[\"temperature\"][1] # initial temperature field, we need something to \"variate\"\n",
|
||
" la = initialize_local_assembly(equation) # create workspace for local matrices\n",
|
||
" T = zeros(4) # create workspace for solution vector\n",
|
||
" ΔT = zeros(4) # \n",
|
||
" fd = [1, 2]\n",
|
||
" tic()\n",
|
||
" # start loops, in principle solve ∂r(u)/∂uΔu = -r(u) and update.\n",
|
||
" for i=1:5\n",
|
||
" la = initialize_local_assembly(equation,la) # empty workspace -- every iteration should start with this\n",
|
||
" calculate_local_assembly!(la, equation) # calculate local matrices\n",
|
||
" ΔT[fd] = la.stiffness_matrix[fd,fd] \\ la.force_vector[fd] # <-- note sign convention, more on this below\n",
|
||
" T = T + ΔT # add increment to previous value\n",
|
||
" # create a new field \"similar\" to field T0 (i.e., same dimension of field variable with new data)\n",
|
||
" new_field = similar(T0, T)\n",
|
||
" new_field.time = 1.0\n",
|
||
" new_field.increment = i\n",
|
||
" push!(element[\"temperature\"], new_field) # add new field to \"temperature\" fieldset of element\n",
|
||
" # print some convergence information\n",
|
||
" @printf(\"increment %2d, |du| = %8.5f, |r| = %8.3f\\n\", i, norm(ΔT), norm(b[fd]))\n",
|
||
" end\n",
|
||
" println(\"temperature field: \",element[\"temperature\"](Inf).values)\n",
|
||
" toc()\n",
|
||
"end\n",
|
||
"run_simulation_2()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Manually assembling local matrices\n",
|
||
"\n",
|
||
"There might still be situations where all the above methods writing field equations are just not enough. This situation can happen for example when calculating tangent stiffness matrix analytically for a nonlinear problem. In this case all matrices are usually calculated at the same time. Or maybe for educational purposes it's important to show how matrices are actually calculated. Or for debugging. \n",
|
||
"\n",
|
||
"Anyway, it's possible to override `calculate_local_assembly!` function for your own equation and after that access all the low level stuff. Here's example how to do that:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 39,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"Success :: (line:-1) :: fact was true\n",
|
||
" Expression: la.stiffness_matrix[free_dofs,free_dofs] \\ la.force_vector[free_dofs] --> roughly([1.0,1.0])\n",
|
||
" Expected: [1.0,1.0]\n",
|
||
" Occurred: [1.0000000000000002,1.0000000000000002]"
|
||
]
|
||
},
|
||
"execution_count": 39,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"using JuliaFEM: LocalAssembly, get_integration_points\n",
|
||
"\n",
|
||
"function JuliaFEM.calculate_local_assembly!(assembly::LocalAssembly, equation::DC2D4, time::Number=Inf)\n",
|
||
" initialize_local_assembly(assembly, equation) # zero all workspaces\n",
|
||
" element = get_element(equation)\n",
|
||
" basis = get_basis(element)\n",
|
||
" dbasis = grad(basis)\n",
|
||
" detJ = det(basis)\n",
|
||
" for ip in get_integration_points(equation)\n",
|
||
" w = ip.weight * detJ(ip)\n",
|
||
" # evaluate fields in integration point\n",
|
||
" ρ = basis(\"density\", ip, time)\n",
|
||
" k = basis(\"temperature thermal conductivity\", ip, time)\n",
|
||
" f = basis(\"temperature load\", ip, time)\n",
|
||
" # evaluate basis functions and gradient in integration point\n",
|
||
" N = basis(ip, time)\n",
|
||
" ∇N = dbasis(ip, time)\n",
|
||
" # do assembly\n",
|
||
" assembly.mass_matrix += w * ρ*N'*N\n",
|
||
" assembly.stiffness_matrix += w * k*∇N'*∇N\n",
|
||
" assembly.force_vector += w * (N'*f)[:]\n",
|
||
" end\n",
|
||
"end\n",
|
||
"\n",
|
||
"function test_local_assembly()\n",
|
||
" # create volume element\n",
|
||
" element = Quad4([1, 2, 3, 4])\n",
|
||
" fieldset1 = FieldSet(\"geometry\", [Field(0.0, Vector[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]])])\n",
|
||
" fieldset2 = FieldSet(\"temperature thermal conductivity\", [Field(0.0, 6.0)])\n",
|
||
" fieldset3 = FieldSet(\"temperature load\", [Field(0.0, [12.0, 12.0, 12.0, 12.0])])\n",
|
||
" fieldset4 = FieldSet(\"density\", [Field(0.0, 1.0)])\n",
|
||
" push!(element, fieldset1)\n",
|
||
" push!(element, fieldset2)\n",
|
||
" push!(element, fieldset3)\n",
|
||
" push!(element, fieldset4)\n",
|
||
" equation = DC2D4(element)\n",
|
||
" la = initialize_local_assembly(equation)\n",
|
||
" calculate_local_assembly!(la, equation)\n",
|
||
" free_dofs = [1, 2]\n",
|
||
" @fact la.stiffness_matrix[free_dofs, free_dofs] \\ la.force_vector[free_dofs] --> roughly([1.0, 1.0])\n",
|
||
"end\n",
|
||
"\n",
|
||
"test_local_assembly()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"To be continued (stuff below this is a little broken at the moment...)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Defining own problem\n",
|
||
"\n",
|
||
"Main objective of \"problem\" is to take a set of elements and map corresponding field equations to them. In this way we can solve several different fields at the same time, like for example temperature + displacement."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 34,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"PlaneHeatProblem"
|
||
]
|
||
},
|
||
"execution_count": 34,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"using JuliaFEM: Problem, get_equation, get_dimension\n",
|
||
"\n",
|
||
"type PlaneHeatProblem <: Problem\n",
|
||
" equations :: Array{Equation, 1}\n",
|
||
"end\n",
|
||
"PlaneHeatProblem() = PlaneHeatProblem([])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 35,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"get_equation (generic function with 6 methods)"
|
||
]
|
||
},
|
||
"execution_count": 35,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"JuliaFEM.get_dimension(pr::Type{PlaneHeatProblem}) = 1\n",
|
||
"JuliaFEM.get_equation(pr::Type{PlaneHeatProblem}, el::Type{Quad4}) = DC2D4\n",
|
||
"JuliaFEM.get_equation(pr::Type{PlaneHeatProblem}, el::Type{Seg2}) = DC2D2"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Our solution procedure so far is therefore"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 33,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"25-Oct 23:10:39:DEBUG:root:total dofs: 4\n"
|
||
]
|
||
},
|
||
{
|
||
"ename": "LoadError",
|
||
"evalue": "LoadError: MethodError: `has_lhs` has no method matching has_lhs(::DC2D4)\nwhile loading In[33], in expression starting on line 30",
|
||
"output_type": "error",
|
||
"traceback": [
|
||
"LoadError: MethodError: `has_lhs` has no method matching has_lhs(::DC2D4)\nwhile loading In[33], in expression starting on line 30",
|
||
""
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"using JuliaFEM: get_connectivity, set_global_dofs!, get_global_dofs\n",
|
||
"using JuliaFEM: add_element!, get_equations, get_matrix_dimension, calculate_global_dofs\n",
|
||
"using JuliaFEM: assign_global_dofs!\n",
|
||
"\n",
|
||
"# create elements and add necessary properties like connectivity and geometry\n",
|
||
"el1 = Quad4([2, 3, 4, 5])\n",
|
||
"fs1geom = FieldSet(\"geometry\")\n",
|
||
"push!(fs1geom, Field(0.0, Vector[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]))\n",
|
||
"fs1temp = FieldSet(\"temperature thermal conductivity\")\n",
|
||
"push!(fs1temp, Field(0.0, 6.0))\n",
|
||
"push!(el1, fs1geom)\n",
|
||
"push!(el1, fs1temp)\n",
|
||
"\n",
|
||
"el2 = Seg2([2, 3])\n",
|
||
"fs2geom = FieldSet(\"geometry\")\n",
|
||
"push!(fs2geom, Field(0.0, Vector[[0.0, 0.0], [0.0, 1.0]]))\n",
|
||
"fs2temp = FieldSet(\"temperature flux\")\n",
|
||
"push!(fs2temp, Field(1.0, 600.0))\n",
|
||
"push!(el2, fs2geom)\n",
|
||
"push!(el2, fs2temp)\n",
|
||
"\n",
|
||
"problem = PlaneHeatProblem()\n",
|
||
"push!(problem, el1)\n",
|
||
"push!(problem, el2)\n",
|
||
"\n",
|
||
"dofmap = calculate_global_dofs(problem)\n",
|
||
"assign_global_dofs!(problem, dofmap)\n",
|
||
"\n",
|
||
"t = 1.0\n",
|
||
"A = sparse(get_lhs(problem, t)...)\n",
|
||
"b = sparsevec(get_rhs(problem, t)..., size(A, 1))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 30,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"ename": "LoadError",
|
||
"evalue": "LoadError: UndefVarError: A not defined\nwhile loading In[30], in expression starting on line 2",
|
||
"output_type": "error",
|
||
"traceback": [
|
||
"LoadError: UndefVarError: A not defined\nwhile loading In[30], in expression starting on line 2",
|
||
""
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"fdofs = [1, 2]\n",
|
||
"A[fdofs, fdofs] \\ b[fdofs]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"We still need to consider Dirichlet boundary conditions:\n",
|
||
"\\begin{align}\n",
|
||
"u &= u_0 && \\text{on } \\Gamma_{\\mathrm{D}} \\\\\n",
|
||
"\\end{align}"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 31,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"1-element Array{JuliaFEM.DirichletEquation,1}:\n",
|
||
" JuliaFEM.DBC2D2(JuliaFEM.Seg2([4,5],JuliaFEM.Basis(basis,j),Dict(symbol(\"reaction force\")=>JuliaFEM.FieldSet(symbol(\"reaction force\"),JuliaFEM.Field[]),:geometry=>JuliaFEM.FieldSet(:geometry,JuliaFEM.Field[JuliaFEM.Field{Array{Array{T,1},1}}(0.0,0,Array{T,1}[[0.0,0.0],[0.0,1.0]])]))),[JuliaFEM.IntegrationPoint([-0.5773502691896257],1.0,Dict{Symbol,JuliaFEM.FieldSet}()),JuliaFEM.IntegrationPoint([0.5773502691896257],1.0,Dict{Symbol,JuliaFEM.FieldSet}())],Int64[],fieldval)"
|
||
]
|
||
},
|
||
"execution_count": 31,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"using JuliaFEM: DirichletProblem\n",
|
||
"\n",
|
||
"# create elements and add necessary properties like connectivity and geometry\n",
|
||
"el3 = Seg2([4, 5])\n",
|
||
"push!(el3, FieldSet(\"geometry\", [Field(0.0, Vector[[0.0, 0.0], [0.0, 1.0]])]))\n",
|
||
"\n",
|
||
"bc1 = DirichletProblem()\n",
|
||
"push!(bc1, el3)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 32,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"ename": "LoadError",
|
||
"evalue": "LoadError: UndefVarError: integrate_lhs not defined\nwhile loading In[32], in expression starting on line 5",
|
||
"output_type": "error",
|
||
"traceback": [
|
||
"LoadError: UndefVarError: integrate_lhs not defined\nwhile loading In[32], in expression starting on line 5",
|
||
"",
|
||
" in get_lhs at /home/jukka/.julia/v0.4/JuliaFEM/src/problems.jl:119"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"assign_global_dofs!(bc1, dofmap)\n",
|
||
"\n",
|
||
"# integrate and assembly\n",
|
||
"t = 1.0\n",
|
||
"A2 = sparse(get_lhs(bc1, t)...)\n",
|
||
"b2 = sparsevec(get_rhs(bc1, t)...)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"source": [
|
||
"Now we have two problems defined, "
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 33,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"ename": "LoadError",
|
||
"evalue": "LoadError: UndefVarError: A not defined\nwhile loading In[33], in expression starting on line 1",
|
||
"output_type": "error",
|
||
"traceback": [
|
||
"LoadError: UndefVarError: A not defined\nwhile loading In[33], in expression starting on line 1",
|
||
""
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"full(A)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 34,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"ename": "LoadError",
|
||
"evalue": "LoadError: UndefVarError: b not defined\nwhile loading In[34], in expression starting on line 1",
|
||
"output_type": "error",
|
||
"traceback": [
|
||
"LoadError: UndefVarError: b not defined\nwhile loading In[34], in expression starting on line 1",
|
||
""
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"full(b)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 35,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"ename": "LoadError",
|
||
"evalue": "LoadError: UndefVarError: A2 not defined\nwhile loading In[35], in expression starting on line 1",
|
||
"output_type": "error",
|
||
"traceback": [
|
||
"LoadError: UndefVarError: A2 not defined\nwhile loading In[35], in expression starting on line 1",
|
||
""
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"full(A2)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 36,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"ename": "LoadError",
|
||
"evalue": "LoadError: UndefVarError: b2 not defined\nwhile loading In[36], in expression starting on line 1",
|
||
"output_type": "error",
|
||
"traceback": [
|
||
"LoadError: UndefVarError: b2 not defined\nwhile loading In[36], in expression starting on line 1",
|
||
""
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"full(b2)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 37,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"ename": "LoadError",
|
||
"evalue": "LoadError: UndefVarError: A not defined\nwhile loading In[37], in expression starting on line 1",
|
||
"output_type": "error",
|
||
"traceback": [
|
||
"LoadError: UndefVarError: A not defined\nwhile loading In[37], in expression starting on line 1",
|
||
""
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"Atot = [A A2; A2 zeros(A2)]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 38,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"ename": "LoadError",
|
||
"evalue": "LoadError: UndefVarError: b not defined\nwhile loading In[38], in expression starting on line 1",
|
||
"output_type": "error",
|
||
"traceback": [
|
||
"LoadError: UndefVarError: b not defined\nwhile loading In[38], in expression starting on line 1",
|
||
""
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"btot = [b; b2]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Problem is that now are total matrix Atot has zero rows which needs to be removed."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 39,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"ename": "LoadError",
|
||
"evalue": "LoadError: UndefVarError: Atot not defined\nwhile loading In[39], in expression starting on line 1",
|
||
"output_type": "error",
|
||
"traceback": [
|
||
"LoadError: UndefVarError: Atot not defined\nwhile loading In[39], in expression starting on line 1",
|
||
""
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"full(Atot)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 40,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"ename": "LoadError",
|
||
"evalue": "LoadError: UndefVarError: Atot not defined\nwhile loading In[40], in expression starting on line 1",
|
||
"output_type": "error",
|
||
"traceback": [
|
||
"LoadError: UndefVarError: Atot not defined\nwhile loading In[40], in expression starting on line 1",
|
||
""
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"r = unique(rowvals(Atot))\n",
|
||
"println(\"Non-zero rows: $r\")\n",
|
||
"xtot = zeros(btot)\n",
|
||
"F = lufact(Atot[r,r])\n",
|
||
"s = full(btot[r])\n",
|
||
"xtot[r] = F \\ s\n",
|
||
"full(xtot)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"collapsed": true
|
||
},
|
||
"source": [
|
||
"## Developing own solver\n",
|
||
"\n",
|
||
"Last part. Defining own solver.\n",
|
||
"\n",
|
||
"- takes a set of problems (typically main problem + boundary problems)\n",
|
||
"- solves them, updates fields"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 41,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"call (generic function with 1288 methods)"
|
||
]
|
||
},
|
||
"execution_count": 41,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"using JuliaFEM: Solver, get_problems\n",
|
||
"\n",
|
||
"\"\"\" Simple solver for educational purposes. \"\"\"\n",
|
||
"type SimpleSolver <: Solver\n",
|
||
" problems\n",
|
||
"end\n",
|
||
"\n",
|
||
"\"\"\" Default initializer. \"\"\"\n",
|
||
"function SimpleSolver()\n",
|
||
" SimpleSolver(Problem[])\n",
|
||
"end\n",
|
||
"\n",
|
||
"\"\"\"\n",
|
||
"Call solver to solve a set of problems.\n",
|
||
"\n",
|
||
"This is simple serial solver for demonstration purposes. It handles the most\n",
|
||
"common situation, i.e., some main field problem and it's Dirichlet boundary.\n",
|
||
"\"\"\"\n",
|
||
"function call(solver::SimpleSolver, t)\n",
|
||
" problems = get_problems(solver)\n",
|
||
" problem1 = problems[1]\n",
|
||
" problem2 = problems[2]\n",
|
||
"\n",
|
||
" # calculate order of degrees of freedom in global matrix\n",
|
||
" # and set the ordering to problems\n",
|
||
" dofmap = calculate_global_dofs(problem1)\n",
|
||
" assign_global_dofs!(problem1, dofmap)\n",
|
||
" assign_global_dofs!(problem2, dofmap)\n",
|
||
"\n",
|
||
" # assemble problem 1\n",
|
||
" A1 = sparse(get_lhs(problem1, t)...)\n",
|
||
" b1 = sparsevec(get_rhs(problem1, t)..., size(A1, 1))\n",
|
||
"\n",
|
||
" # assemble problem 2\n",
|
||
" A2 = sparse(get_lhs(problem2, t)...)\n",
|
||
" b2 = sparsevec(get_rhs(problem2, t)..., size(A2, 1))\n",
|
||
" \n",
|
||
" # make one monolithic assembly\n",
|
||
" A = [A1 A2; A2' zeros(A2)]\n",
|
||
" b = [b1; b2]\n",
|
||
"\n",
|
||
" # solve problem\n",
|
||
" nz = unique(rowvals(A))\n",
|
||
" x = zeros(b)\n",
|
||
" x[nz] = lufact(A[nz,nz]) \\ full(b[nz])\n",
|
||
"\n",
|
||
" # get \"problem-wise\" solution vectors\n",
|
||
" x1 = x[1:length(b1)]\n",
|
||
" x2 = x[length(b1)+1:end]\n",
|
||
"\n",
|
||
" # check residual\n",
|
||
" R1 = A1*x1 - b1\n",
|
||
" R2 = A2*x2 - b2\n",
|
||
" println(\"Residual norm: $(norm(R1+R2))\")\n",
|
||
"\n",
|
||
" # update field for elements in problem 1\n",
|
||
" for equation in get_equations(problem1)\n",
|
||
" gdofs = get_global_dofs(equation)\n",
|
||
" element = get_element(equation)\n",
|
||
" field_name = get_unknown_field_name(equation) # field we are solving\n",
|
||
" field = Field(t, full(x1[gdofs])[:])\n",
|
||
" push!(element[field_name], field)\n",
|
||
" end\n",
|
||
"\n",
|
||
" # update field for elements in problem 2\n",
|
||
" for equation in get_equations(problem2)\n",
|
||
" gdofs = get_global_dofs(equation)\n",
|
||
" element = get_element(equation)\n",
|
||
" field_name = get_unknown_field_name(equation)\n",
|
||
" field = Field(t, full(x2[gdofs]))\n",
|
||
" push!(element[field_name], field)\n",
|
||
" end\n",
|
||
"end"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Summary\n",
|
||
"\n",
|
||
"Next we collect all things together and solve Poisson equation in domain $\\Omega = [0,1]\\times[0,1]$ with some Neumann boundary on $\\Gamma_1$ and Dirichlet boundary on $\\Gamma_2$."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 42,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"25-Oct 20:27:59:DEBUG:root:total dofs: 4\n"
|
||
]
|
||
},
|
||
{
|
||
"ename": "LoadError",
|
||
"evalue": "LoadError: MethodError: `has_lhs` has no method matching has_lhs(::DC2D4)\nwhile loading In[42], in expression starting on line 37",
|
||
"output_type": "error",
|
||
"traceback": [
|
||
"LoadError: MethodError: `has_lhs` has no method matching has_lhs(::DC2D4)\nwhile loading In[42], in expression starting on line 37",
|
||
""
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"# Define Problem 1:\n",
|
||
"# - Field function: Laplace equation Δu=0 in Ω={u∈R²|(x,y)∈[0,1]×[0,1]}\n",
|
||
"# - Neumann boundary on Γ₁={0<=x<=1, y=0}, ∂u/∂n=600 on Γ₁\n",
|
||
"\n",
|
||
"el1 = Quad4([1, 2, 3, 4])\n",
|
||
"push!(el1, FieldSet(\"geometry\", [Field(0.0, Vector[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]])]))\n",
|
||
"push!(el1, FieldSet(\"temperature thermal conductivity\", [Field(0.0, 6.0)]))\n",
|
||
"\n",
|
||
"el2 = Seg2([1, 2])\n",
|
||
"push!(el2, FieldSet(\"geometry\", [Field(0.0, Vector[[0.0, 0.0], [1.0, 0.0]])]))\n",
|
||
"\n",
|
||
"# Boundary load, linear ramp 0 -> 600 at time 0 -> 1\n",
|
||
"load = FieldSet(\"temperature flux\")\n",
|
||
"push!(load, Field(0.0, 0.0))\n",
|
||
"push!(load, Field(1.0, 600.0))\n",
|
||
"push!(el2, load)\n",
|
||
"\n",
|
||
"problem1 = PlaneHeatProblem()\n",
|
||
"push!(problem1, el1)\n",
|
||
"push!(problem1, el2)\n",
|
||
"\n",
|
||
"# Define Problem 2:\n",
|
||
"# - Dirichlet boundary Γ₂={0<=x<=1, y=1}, u=0 on Γ₂\n",
|
||
"\n",
|
||
"el3 = Seg2([3, 4])\n",
|
||
"push!(el3, FieldSet(\"geometry\", [Field(0.0, Vector[[1.0, 1.0], [0.0, 1.0]])]))\n",
|
||
"\n",
|
||
"problem2 = DirichletProblem()\n",
|
||
"push!(problem2, el3)\n",
|
||
"\n",
|
||
"# Create a solver for a set of problems\n",
|
||
"solver = SimpleSolver()\n",
|
||
"push!(solver, problem1)\n",
|
||
"push!(solver, problem2)\n",
|
||
"\n",
|
||
"# Solve problem at time t=1.0 and update fields\n",
|
||
"call(solver, 1.0)\n",
|
||
"\n",
|
||
"# Postprocess.\n",
|
||
"# Interpolate temperature field along boundary of Γ₁ at time t=1.0\n",
|
||
"xi = linspace([-1.0], [1.0], 5)\n",
|
||
"X = interpolate(el2, \"geometry\", xi, 1.0)\n",
|
||
"T = interpolate(el2, \"temperature\", xi, 1.0)\n",
|
||
"println(X)\n",
|
||
"println(T)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 43,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"Error :: (line:-1)\n",
|
||
" Expression: mean(T) --> roughly(100.0)\n",
|
||
" UndefVarError: T not defined\n",
|
||
" in anonymous at /home/jukka/.julia/v0.4/FactCheck/src/FactCheck.jl:271\n",
|
||
" in do_fact at /home/jukka/.julia/v0.4/FactCheck/src/FactCheck.jl:333\n",
|
||
" in include_string at loading.jl:266\n",
|
||
" in execute_request_0x535c5df2 at /home/jukka/.julia/v0.4/IJulia/src/execute_request.jl:177\n",
|
||
" in eventloop at /home/jukka/.julia/v0.4/IJulia/src/IJulia.jl:141\n",
|
||
" in anonymous at task.jl:447"
|
||
]
|
||
},
|
||
"execution_count": 43,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"@fact mean(T) --> roughly(100.0)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## TODO\n",
|
||
"\n",
|
||
"- dynamics\n",
|
||
"- different basis for trial and test (Petrov-Galerkin)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Frequently asked questions\n",
|
||
"\n",
|
||
"Any question about data structures, better ideas and improvements are very welcome."
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": "Julia 0.4.0",
|
||
"language": "julia",
|
||
"name": "julia-0.4"
|
||
},
|
||
"language_info": {
|
||
"file_extension": ".jl",
|
||
"mimetype": "application/julia",
|
||
"name": "julia",
|
||
"version": "0.4.0"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 0
|
||
}
|