Files
JuliaFEM.jl/docs/tutorials/2015-08-29-developing-juliafem.ipynb
T

1451 lines
37 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Developing JuliaFEM\n",
"\n",
"Author(s): Jukka Aho\n",
"\n",
"**Abstract**: Developer notes. In this notebook we give general guidelines how to implement things to JuliaFEM.\n",
"\n",
"In short, we have\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 which needs to be solve. One element can have several equations but not vice versa. Typical equations: Poisson equation $\\Delta u = f$, elasticity equation $\\nabla \\cdot \\sigma = f$, etc.\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.\n",
"\n",
"Moreover, we have (will have) **`test_element`**, **`test_equation`**, **`test_problem`** and **`test_solver`** which can be used to test that implementation has all necessary things defined."
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"Logger(root,DEBUG,Base.PipeEndpoint(open, 0 bytes waiting),root)"
]
},
"execution_count": 39,
"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": 40,
"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": 41,
"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": 42,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"MyQuad4"
]
},
"execution_count": 42,
"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": 43,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"get_element_dimension (generic function with 7 methods)"
]
},
"execution_count": 43,
"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": 44,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"20-loka 15:49:17:INFO:root:Testing element MyQuad4\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": 45,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"JuliaFEM.FieldSet(symbol(\"heat coefficient\"),JuliaFEM.Field[JuliaFEM.Field{Int64}(0.0,1,2),JuliaFEM.Field{Int64}(1.0,1,3)])"
]
},
"execution_count": 45,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"using JuliaFEM: FieldSet, Field, interpolate, dinterpolate\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, 0.0]) # push some values for field\n",
"push!(geometry_field, [10.0, 0.0, 0.0])\n",
"push!(geometry_field, [10.0, 1.0, 0.0])\n",
"push!(geometry_field, [ 0.0, 1.0, 0.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",
"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)"
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"1.25"
]
},
"execution_count": 46,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# temperature at the middle poinf of the element, 1/4*(1+2+3+4) at t=0.5\n",
"interpolate(element, \"temperature\", [0.0, 0.0], 0.5)"
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"3-element Array{Float64,1}:\n",
" 5.0\n",
" 0.5\n",
" 0.0"
]
},
"execution_count": 47,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# geometry midpoint of element\n",
"interpolate(element, \"geometry\", [0.0, 0.0], 0.0)"
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"3x2 Array{Float64,2}:\n",
" 5.0 0.0\n",
" 0.0 0.5\n",
" 0.0 0.0"
]
},
"execution_count": 48,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# interpolate derivatives works too\n",
"dinterpolate(element, \"geometry\", [0.0, 0.0], 0.0)"
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"2.5"
]
},
"execution_count": 49,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# interpolating scalar -> scalar.\n",
"interpolate(element, \"heat coefficient\", [0.0, 0.0], 0.5)"
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"3"
]
},
"execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"interpolate(element, \"heat coefficient\", [0.0, 0.0], Inf)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 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 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 Laplace equation\n",
"\\begin{align}\n",
"\\Delta{u} &= 0 && \\text{on } \\Omega \\\\\n",
"\\frac{\\partial u}{\\partial n} &= g && \\text{on } \\Gamma_{\\mathrm{N}}\n",
"\\end{align}\n",
"\n",
"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_{\\Gamma_{\\mathrm{N}}}g v\\,\\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",
"- it needs to have lhs and rhs functions\n",
"- provide the name of the unknown field variable trying to solve\n",
"- default constructor takes the element as input argument\n",
"\n",
"Now we have function `test_equation`, which we can use to test that everything is working as expected. \n",
"\n",
"Again thanks to multiple dispatch, you are free to code your weak form however you want as long as it returns lhs and rhs sides for element dofs. This kind of freedom gives good opportunities to wrap e.g. Fortran code from some other projects. And again we have some suggestions ad following these ideas you get a lot of stuff for free. First we look the left hand side of the equation, that is,\n",
"\\begin{equation}\n",
" \\int_{\\Omega}\\nabla u\\cdot\\nabla v\\,\\mathrm{d}x\n",
"\\end{equation}"
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"get_unknown_field_name (generic function with 4 methods)"
]
},
"execution_count": 51,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"using JuliaFEM: Equation, IntegrationPoint, Quad4, get_unknown_field_name\n",
"\n",
"abstract Heat <: Equation\n",
"\n",
"# it's important to define for which fieldset to save unknown values when solving\n",
"JuliaFEM.get_unknown_field_name(eq::Heat) = symbol(\"temperature\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Our basic data type often looks something like this:"
]
},
{
"cell_type": "code",
"execution_count": 52,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"\"\"\"\n",
"Diffusive heat transfer for 4-node bilinear element.\n",
"\"\"\"\n",
"type DC2D4 <: Heat\n",
" element :: Quad4\n",
" integration_points :: Array{IntegrationPoint, 1}\n",
" global_dofs :: Array{Int64, 1}\n",
"end"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We must provide default constructor which takes element as input argument:"
]
},
{
"cell_type": "code",
"execution_count": 53,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"DC2D4"
]
},
"execution_count": 53,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"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"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now the actual implementation for $\\int_{\\Omega}\\nabla u\\cdot\\nabla v\\,\\mathrm{d}x$:"
]
},
{
"cell_type": "code",
"execution_count": 54,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"has_lhs (generic function with 4 methods)"
]
},
"execution_count": 54,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"using JuliaFEM: get_element, get_dbasisdX, has_lhs, has_rhs\n",
"\n",
"\"\"\"\n",
"Left hand side defined in integration point\n",
"\"\"\"\n",
"function JuliaFEM.get_lhs(eq::DC2D4, ip, t)\n",
" el = get_element(eq)\n",
" dNdX = get_dbasisdX(el, ip.xi, t)\n",
" hc = interpolate(el, \"temperature thermal conductivity\", ip.xi, t)\n",
" return dNdX*hc*dNdX'\n",
"end\n",
"JuliaFEM.has_lhs(eq::DC2D4) = true"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And that's it. If we want to play with this formulation, we must create element and assign this equation for it:"
]
},
{
"cell_type": "code",
"execution_count": 55,
"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": 55,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"using JuliaFEM: integrate, integrate_lhs, integrate_rhs\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",
"push!(element, fieldset1)\n",
"push!(element, fieldset2)\n",
"equation = DC2D4(element)\n",
"integrate_lhs(equation, 1.0)"
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 56,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"JuliaFEM.has_lhs(equation)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If rhs or lhs is not defined, integration returns nothing."
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 57,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"integrate_rhs(equation, 1.0) == nothing"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next heat flux on boundary:"
]
},
{
"cell_type": "code",
"execution_count": 58,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"has_rhs (generic function with 4 methods)"
]
},
"execution_count": 58,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"using JuliaFEM: get_basis, Seg2\n",
"\n",
"\"\"\"\n",
"Diffusive heat transfer for 2-node linear segment.\n",
"\"\"\"\n",
"type DC2D2 <: Heat\n",
" element :: Seg2\n",
" integration_points :: Array{IntegrationPoint, 1}\n",
" global_dofs :: Array{Int64, 1}\n",
"end\n",
"\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",
"\n",
"\"\"\"\n",
"Right hand side defined in integration point\n",
"\"\"\"\n",
"function JuliaFEM.get_rhs(eq::DC2D2, ip, t)\n",
" el = get_element(eq)\n",
" h = get_basis(el, ip.xi)\n",
" #f = el[\"temperature flux\"]\n",
" f = interpolate(el, \"temperature flux\", ip.xi, t)\n",
" return h*f\n",
"end\n",
"JuliaFEM.has_rhs(eq::DC2D2) = true"
]
},
{
"cell_type": "code",
"execution_count": 59,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"2-element Array{Float64,1}:\n",
" 50.0\n",
" 50.0"
]
},
"execution_count": 59,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"element = Seg2([1, 2])\n",
"push!(element, FieldSet(\"geometry\", [Field(0.0, Vector[[0.0,0.0], [0.0,1.0]])]))\n",
"push!(element, FieldSet(\"temperature flux\", [Field(0.0, 100.0)]))\n",
"equation = DC2D2(element)\n",
"integrate_rhs(equation, 1.0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Defining own problem\n",
"\n",
"- main object: takes a set of elements and maps corresponding field equations to them"
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"PlaneHeatProblem"
]
},
"execution_count": 60,
"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": 61,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"get_equation (generic function with 6 methods)"
]
},
"execution_count": 61,
"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": 62,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"20-loka 15:49:17:DEBUG:root:total dofs: 4\n"
]
},
{
"data": {
"text/plain": [
"4x1 sparse matrix with 2 Float64 entries:\n",
"\t[1, 1] = 300.0\n",
"\t[2, 1] = 300.0"
]
},
"execution_count": 62,
"metadata": {},
"output_type": "execute_result"
}
],
"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!, get_lhs, get_rhs\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": 63,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"2x1 sparse matrix with 2 Float64 entries:\n",
"\t[1, 1] = 100.0\n",
"\t[2, 1] = 100.0"
]
},
"execution_count": 63,
"metadata": {},
"output_type": "execute_result"
}
],
"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": 64,
"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,1,Array{T,1}[[0.0,0.0],[0.0,1.0]])]))),[JuliaFEM.IntegrationPoint([-0.5773502691896257],1.0,Dict{Any,Any}()),JuliaFEM.IntegrationPoint([0.5773502691896257],1.0,Dict{Any,Any}())],Int64[],fieldval)"
]
},
"execution_count": 64,
"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": 65,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"4x1 sparse matrix with 2 Float64 entries:\n",
"\t[3, 1] = 0.0\n",
"\t[4, 1] = 0.0"
]
},
"execution_count": 65,
"metadata": {},
"output_type": "execute_result"
}
],
"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": 66,
"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": 66,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"full(A)"
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"4x1 Array{Float64,2}:\n",
" 300.0\n",
" 300.0\n",
" 0.0\n",
" 0.0"
]
},
"execution_count": 67,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"full(b)"
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"4x4 Array{Float64,2}:\n",
" 0.0 0.0 0.0 0.0 \n",
" 0.0 0.0 0.0 0.0 \n",
" 0.0 0.0 0.333333 0.166667\n",
" 0.0 0.0 0.166667 0.333333"
]
},
"execution_count": 68,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"full(A2)"
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"4x1 Array{Float64,2}:\n",
" 0.0\n",
" 0.0\n",
" 0.0\n",
" 0.0"
]
},
"execution_count": 69,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"full(b2)"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"8x8 sparse matrix with 24 Float64 entries:\n",
"\t[1, 1] = 4.0\n",
"\t[2, 1] = -1.0\n",
"\t[3, 1] = -2.0\n",
"\t[4, 1] = -1.0\n",
"\t[1, 2] = -1.0\n",
"\t[2, 2] = 4.0\n",
"\t[3, 2] = -1.0\n",
"\t[4, 2] = -2.0\n",
"\t[1, 3] = -2.0\n",
"\t[2, 3] = -1.0\n",
"\t⋮\n",
"\t[8, 3] = 0.166667\n",
"\t[1, 4] = -1.0\n",
"\t[2, 4] = -2.0\n",
"\t[3, 4] = -1.0\n",
"\t[4, 4] = 4.0\n",
"\t[7, 4] = 0.166667\n",
"\t[8, 4] = 0.333333\n",
"\t[3, 7] = 0.333333\n",
"\t[4, 7] = 0.166667\n",
"\t[3, 8] = 0.166667\n",
"\t[4, 8] = 0.333333"
]
},
"execution_count": 70,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Atot = [A A2; A2 zeros(A2)]"
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"8x1 sparse matrix with 4 Float64 entries:\n",
"\t[1, 1] = 300.0\n",
"\t[2, 1] = 300.0\n",
"\t[7, 1] = 0.0\n",
"\t[8, 1] = 0.0"
]
},
"execution_count": 71,
"metadata": {},
"output_type": "execute_result"
}
],
"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": 72,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"8x8 Array{Float64,2}:\n",
" 4.0 -1.0 -2.0 -1.0 0.0 0.0 0.0 0.0 \n",
" -1.0 4.0 -1.0 -2.0 0.0 0.0 0.0 0.0 \n",
" -2.0 -1.0 4.0 -1.0 0.0 0.0 0.333333 0.166667\n",
" -1.0 -2.0 -1.0 4.0 0.0 0.0 0.166667 0.333333\n",
" 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n",
" 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n",
" 0.0 0.0 0.333333 0.166667 0.0 0.0 0.0 0.0 \n",
" 0.0 0.0 0.166667 0.333333 0.0 0.0 0.0 0.0 "
]
},
"execution_count": 72,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"full(Atot)"
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Non-zero rows: [1,2,3,4,7,8]"
]
},
{
"data": {
"text/plain": [
"8x1 Array{Float64,2}:\n",
" 100.0\n",
" 100.0\n",
" 0.0\n",
" 0.0\n",
" 0.0\n",
" 0.0\n",
" 600.0\n",
" 600.0"
]
},
"execution_count": 73,
"metadata": {},
"output_type": "execute_result"
}
],
"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": 74,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"call (generic function with 1270 methods)"
]
},
"execution_count": 74,
"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": 75,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"20-loka 15:49:17:DEBUG:root:total dofs: 4\n"
]
}
],
"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": 76,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"Success :: (line:-1) :: fact was true\n",
" Expression: mean(T) --> roughly(100.0)\n",
" Expected: 100.0\n",
" Occurred: 100.00000000000003"
]
},
"execution_count": 76,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Residual norm: 9.845568954283847e-14\n",
"Array{T,1}[[0.0,0.0],[0.25,0.0],[0.5,0.0],[0.75,0.0],[1.0,0.0]]\n",
"[100.00000000000003,100.00000000000003,100.00000000000003,100.00000000000003,100.00000000000003]\n"
]
}
],
"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
}