diff --git a/docs/tutorials/2015-06-14-data-structures.ipynb b/docs/tutorials/2015-06-14-data-structures.ipynb new file mode 100644 index 0000000..e06f96e --- /dev/null +++ b/docs/tutorials/2015-06-14-data-structures.ipynb @@ -0,0 +1,1315 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data structures\n", + "\n", + "**Author(s)**: Jukka Aho\n", + "\n", + "**Abstract**: Description of basis data structures. In this notebook the concepts of `Increment`, `TimeStep`, `Field`, `FieldSet`, `SpatialBasis`, `TemporalBasis` are intoduced. With combining these atomic structures one is able to form finite elements and interpolate it's fields in time and spatial domain.\n", + "\n", + "- `Increment` is the most atomic structure. It's a vector-like object with 1 dimension. Each element in `Increment` can be scalar, vector or tensor (2 or 4 order). It's easy to extend `Increment` to have other data types too.\n", + "- `TimeStep` is container for increments in certain time $t$.\n", + "- `Field` is container for timesteps for a single field.\n", + "- `FieldSet` is container for all fields.\n", + "\n", + "## Revision history\n", + "\n", + "### 2015-06-14\n", + "- Initial version.\n", + "\n", + "### 2015-09-25\n", + "- Complete rewrite. The main ideas proposed in earlier version didn't work.\n", + "\n", + "### 2015-10-29\n", + "- Third iteration." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Data fields on elements\n", + "\n", + "Typical element structure so far:\n", + "\n", + " type MyElement <: Element\n", + " connectivity :: Array{Int, 1} # describes how dofs of this element is connected to another elements in global level\n", + " basis :: Basis # describes how to interpolate fields\n", + " fields :: ???\n", + " end\n", + "\n", + "- Fields must be interpolable, in space $\\mathbb{C}^n \\times \\mathbb{R}$, i.e. $f(\\boldsymbol{\\xi}, t) = \\sum_i \\phi_i(\\boldsymbol{\\xi}) f_i(t) = \\sum_i \\phi_i(\\boldsymbol{\\xi}) \\sum_j \\varphi_j(t) f_{ij}$, where $f_{ij}$ is scalar, tensor or vector defined in element area $e$ by some basis functions $\\phi(\\boldsymbol{\\xi})$ and $\\varphi(t)$. Parameter $t$ is normally considered as \"time\" and $\\xi$ is dimensionless coordinate. Parameter $t$ has not necessarily to be time, it could be for example angle $\\alpha \\in [-2\\pi, 2\\pi]$ or similar.\n", + "- We store mainly three fields, scalar field, vector field, tensor field. Field may or may not be dependent from parameters $\\xi$ or $t$.\n", + "- $t$ is discretized to several steps $\\{t_0, t_1, \\ldots, t_n\\}$. Each discrete time $t_i$ may contain several iterations until convergence. We want to save and get access to all of this data if needed.\n", + "- So in practice we have a set of fields $f(\\boldsymbol{\\xi})$ over time domain $t$. Typically some fields, like Geometry, is introduced only in time $t_0$. Some other fields, like boundary load, may be \"active\" only on some time $\\hat{t} \\subset t$. Some care must be taken of how to extrapolate field variables.\n", + "- In the simplest case (simple nonlinear quasistatic analysis), we have for instance $t \\in [0, 1]$ where boundary conditions are set in $t_0$ and load is set in $t_1$. We may use adaptive strategies to shorten time if convergence issues araises." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's the strategy in short: each non-linear iteration is `Increment`, what is a vector-like object containing data. Elements of `Increment` can be scalars, vectors or tensors. `Increment` belongs to `TimeStep`. `TimeStep` contains one or more increments. Then we have `Field` which contains all timesteps. And finally we have `FieldSet` which contains all fields.\n", + "\n", + " FieldSet -> Field -> TimeStep -> Increment -> data\n", + "\n", + "for example,\n", + "\n", + " FieldSet -> Field -> TimeStep -> Increment -> data\n", + " FieldSet -> \"temperature\" -> 0.0 -> 1 -> [1,2,3,4]\n", + " 2 -> [2,3,4,5]\n", + " FieldSet -> \"temperature\" -> 1.0 -> 1 -> [3,4,5,6]\n", + " 2 -> [5,6,7,8]\n", + "\n", + "and so on." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Creating discrete fields\n", + "\n", + "Here we create a `FieldSet` containing one field `temperature` which contains two `TimeStep`s, two `Increment`s in both of them." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{ASCIIString,JuliaFEM.AbstractField} with 1 entry:\n", + " \"temperature\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[JuliaFEM.Inc…" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "using JuliaFEM: Increment, TimeStep, Field, FieldSet\n", + "\n", + "fs = FieldSet()\n", + "i1 = Increment([1, 2, 3])\n", + "i2 = Increment([2, 3, 4])\n", + "t1 = TimeStep(1.0, Increment[i1, i2])\n", + "i3 = Increment([2, 3, 4])\n", + "i4 = Increment([3, 4, 5])\n", + "t2 = TimeStep(2.0, Increment[i3, i4])\n", + "f1 = Field(TimeStep[t1, t2])\n", + "fs[\"temperature\"] = f1\n", + "fs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Accessing last increment of last timestep of temperature can be done in the following way:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "([3,4,5],JuliaFEM.Increment{Int64})" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "increment = fs[\"temperature\"][end][end]\n", + "increment, typeof(increment)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because the model is deep and quite heavy to type some \"shortcuts\" are provided, but keep in mind that everything is there in place. Here's the shortened version:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "([1,2,3,4],[1,2,3,4])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fs = FieldSet() # create empty fieldset\n", + "fs[\"temperature\"] = [1, 2, 3, 4] # create temperature field with time t=0\n", + "first(fs[\"temperature\"]), last(fs[\"temperature\"]) # pick first and last timestep of temperature" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This way we can easily (discrete) create scalar, vector and tensor fields." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{ASCIIString,JuliaFEM.AbstractField} with 5 entries:\n", + " \"fourth order tensor fi… => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"constant scalar field\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"vector field\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"scalar field\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"second order tensor fi… => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fs2 = FieldSet()\n", + "fs2[\"constant scalar field\"] = 1\n", + "fs2[\"scalar field\"] = [1, 2, 3, 4]\n", + "fs2[\"vector field\"] = reshape(collect(1:8), 2, 4)\n", + "fs2[\"second order tensor field\"] = reshape(collect(1:3*3*4), 3, 3, 4)\n", + "fs2[\"fourth order tensor field\"] = reshape(collect(1:3*3*3*3*4), 3, 3, 3, 3, 4)\n", + "fs2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Or even more compactly:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{ASCIIString,JuliaFEM.AbstractField} with 3 entries:\n", + " \"density\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[JuliaFEM.Inc…\n", + " \"geometry\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[JuliaFEM.Inc…\n", + " \"temperature\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[JuliaFEM.Inc…" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "FieldSet(\"geometry\" => [1, 2, 3, 4], \"temperature\" => [0, 0, 0, 0], \"density\" => 7850)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By default using this \"fast typing\" fields are defined at time $t=0.0$." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0.0" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fs2[\"vector field\"][end].time # pick last timestep of field \"vector field\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Creating new field with several time steps defined can also be done compactly. Each tuple has time and increment data." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{ASCIIString,JuliaFEM.AbstractField} with 6 entries:\n", + " \"fourth order tensor fi… => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"constant scalar field\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"vector field\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"scalar field\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"time series\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"second order tensor fi… => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fs2[\"time series\"] = (0.0, [1, 2, 3, 4]), (0.5, [2, 3, 4, 5]), (1.0, [1, 1, 1, 1])\n", + "fs2[\"time series\"][end].time\n", + "fs2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Or even without explicitly expressing time. In that case time step size is 1 second starting from 0." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{ASCIIString,JuliaFEM.AbstractField} with 7 entries:\n", + " \"fourth order tensor fi… => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"constant scalar field\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"vector field\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"scalar field\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"time series\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"time series 2\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…\n", + " \"second order tensor fi… => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[J…" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fs2[\"time series 2\"] = [1, 2, 3, 4], [2, 3, 4, 5], [1, 1, 1, 1]\n", + "fs2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To add another field, with different time." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "2-element Array{JuliaFEM.TimeStep{T},1}:\n", + " JuliaFEM.Increment[[1,2,3,4]]\n", + " JuliaFEM.Increment[[2,3,4,5]]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "T0 = first(fs[\"temperature\"]) # pick first timestep (or to be spesific, last increment of first timestep)\n", + "T1 = T0 + 1 # create new increment from old one\n", + "timestep = TimeStep(1.0, T1) # create new timestep at t=1.0\n", + "push!(fs[\"temperature\"], timestep) # push to field" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Normal stuff like dot product works:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(20,20,20)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "S1 = Increment([1, 2, 3])\n", + "S2 = Increment([2, 3, 4])\n", + "dot(S1, S2), dot([1, 2, 3], S2), dot(S1, [2, 3, 4])" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3-element Array{Float64,1}:\n", + " 1.5\n", + " 2.5\n", + " 3.5" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1/2*(S1 + S2)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3-element Array{Int64,1}:\n", + " 5\n", + " 8\n", + " 11" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dot([1, 2], Increment[S1, S2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Creating empty `Increment`:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "4-element JuliaFEM.Increment{Array{Float64,1}}:\n", + " [0.0,0.0]\n", + " [0.0,0.0]\n", + " [0.0,0.0]\n", + " [0.0,0.0]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f = zeros(Increment, 2, 4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create similar increment with new data:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "4-element JuliaFEM.Increment{Array{Float64,1}}:\n", + " [1.0,1.0]\n", + " [1.0,1.0]\n", + " [1.0,1.0]\n", + " [1.0,1.0]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "g = similar(f, ones(8))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Creating continuous and discrete fields\n", + "\n", + "In the last section the concept of fields was demonstrated. The `Field` is actually just a typealias for a `DefaultDiscreteField` and `DefaultDiscreteField` is subtype of `DiscreteField` which is subtype of `AbstractField`:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "true" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "using JuliaFEM: AbstractField, DiscreteField\n", + "Field <: DiscreteField <: AbstractField" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There exists another type of fields too, namely `ContinuousField`s. Like the name already suggests it stores continuous time and spatial domain and it can be used to write custom fields. It needs to be callable. In this example `ContinuousField` is created which returns 1x4 dimensional array defined in $\\boldsymbol\\xi \\in [-1,1]^2, t\\in[0,1]$:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "using JuliaFEM: FieldSet, ContinuousField" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "MyFunnyField()" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type MyFunnyField <: ContinuousField\n", + "end\n", + "function Base.call(f::MyFunnyField, xi::Vector, time::Number)\n", + " time/4*[(1-xi[1])*(1-xi[2]) (1+xi[1])*(1-xi[2]) (1+xi[1])*(1+xi[2]) (1-xi[1])*(1+xi[2])]\n", + "end\n", + "f = MyFunnyField()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1x4 Array{Float64,2}:\n", + " 0.25 0.25 0.25 0.25" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f([0.0, 0.0], 1.0)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{ASCIIString,JuliaFEM.AbstractField} with 1 entry:\n", + " \"basis\" => MyFunnyField()" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fs = FieldSet()\n", + "fs[\"basis\"] = MyFunnyField()\n", + "fs" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1x4 Array{Float64,2}:\n", + " 0.25 0.25 0.25 0.25" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fs[\"basis\"]([0.0, 0.0], 1.0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Naturally we can pass another fields or even fieldsets to continuous field to make fields depend from each other. Here's another example, where `ContinuousField` takes another field and operates it with some function." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "call (generic function with 1246 methods)" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type MyFunnyContinuousField <: ContinuousField\n", + " basis :: Function\n", + " discrete_field :: DiscreteField\n", + "end\n", + "function Base.call(field::MyFunnyContinuousField, xi::Vector, time::Number=1.0)\n", + " data = last(field.discrete_field) # get the last timestep last increment\n", + " basis = field.basis(xi) # evaluate basis at point ξ.\n", + " sum([basis[i]*data[i] for i=1:length(data)]) # sum results\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we create two fields, one is discrete and another is continuous which takes discrete field as parametes" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "MyFunnyContinuousField(basis,JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[JuliaFEM.Increment[[1,2,3,4]]]))" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fs = FieldSet()\n", + "fs[\"discrete field\"] = [1, 2, 3, 4]\n", + "basis(xi) = 1/4*[\n", + " (1-xi[1])*(1-xi[2]),\n", + " (1+xi[1])*(1-xi[2]),\n", + " (1+xi[1])*(1+xi[2]),\n", + " (1-xi[1])*(1+xi[2])]\n", + "fs[\"continuous field\"] = MyFunnyContinuousField(basis, fs[\"discrete field\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Results:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(2.5,[1,2,3,4])" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fs[\"continuous field\"]([0.0, 0.0], 1.0), last(fs[\"discrete field\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add another discrete field:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{ASCIIString,JuliaFEM.AbstractField} with 2 entries:\n", + " \"continuous field\" => MyFunnyContinuousField(basis,JuliaFEM.DefaultDiscreteFi…\n", + " \"discrete field\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[JuliaFE…" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "T0 = last(fs[\"discrete field\"])\n", + "push!(fs[\"discrete field\"], TimeStep(1.0, T0 + 1.0)) # push to field\n", + "fs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Updated results, notice how the value of continuous field changes according to the update of discrete field." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(3.5,[2,3,4,5])" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fs[\"continuous field\"]([0.0, 0.0], 1.0), last(fs[\"discrete field\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What we did is that we actually interpolated discrete field using continuous functions. We evaluated discrete field using bilinear basis at midpoint of \"element\":" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(2.5,3.5)" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1/4*(1+2+3+4), 1/4*(2+3+4+5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From continuous field we can also go back to discrete fields, if needed. Here we evaluate continuous field in four discrete points, let's call them to Gauss quadrature points." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "getindex (generic function with 126 methods)" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "using JuliaFEM: DiscreteField\n", + "type MyFunnyDiscreteField <: DiscreteField\n", + " discrete_points :: Vector\n", + " continuous_field :: ContinuousField\n", + "end\n", + "Base.length(field::MyFunnyDiscreteField) = length(field.discrete_points)\n", + "Base.endof(field::MyFunnyDiscreteField) = endof(field.discrete_points)\n", + "Base.last(field::MyFunnyDiscreteField) = Float64[field[i] for i=1:length(field)]\n", + "function Base.getindex(field::MyFunnyDiscreteField, idx::Int64)\n", + " field.continuous_field(field.discrete_points[idx])\n", + "end" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "4-element Array{Float64,1}:\n", + " 2.75598\n", + " 3.08932\n", + " 3.91068\n", + " 4.24402" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "discrete_points = 1.0/sqrt(3.0)*Vector[[-1, -1], [1, -1], [1, 1], [-1, 1]]\n", + "fs[\"discrete field 2\"] = MyFunnyDiscreteField(discrete_points, fs[\"continuous field\"])\n", + "last(fs[\"discrete field 2\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Basically summing the above values together we have just done numerical integration over element area. By using these two simple concepts we are able to construct very interesting results.\n", + "\n", + "## Interpolation\n", + "\n", + "In earlier the concepts of `DiscreteField` and `ContinuousField` were introduced, so that now we can define discrete set of values and continuous functions. It has also been shown how fields can depend from each other such a way that we interpolate continuous field from discrete field and vice versa. \n", + "\n", + "This motivates us to create continuous fields which are interpolated from discrete values with some proper basis. By thinking this way interpolation is nothing more than just an application of the earlier results already shown. \n", + "\n", + "Interpolation of fields works of course both in time and spatial dimension. Here's a simple example showing the main concept:" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "using JuliaFEM: TemporalBasis, SpatialBasis" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "2-element Array{Float64,1}:\n", + " 0.8\n", + " 0.2" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# first unanonymous function is the actual basis and second one is derivative with respect to time\n", + "temporalbasis = TemporalBasis((t) -> [1-t, t], (t) -> [-1, 1])\n", + "\n", + "basis(xi) = 1/4*[(1-xi[1])*(1-xi[2]) (1+xi[1])*(1-xi[2]) (1+xi[1])*(1+xi[2]) (1-xi[1])*(1+xi[2])]\n", + "dbasis(xi) = 1/4*[\n", + " -(1-xi[2]) (1-xi[2]) (1+xi[2]) -(1+xi[2])\n", + " -(1-xi[1]) -(1+xi[1]) (1+xi[1]) (1-xi[1])]\n", + "spatialbasis = SpatialBasis(basis, dbasis)\n", + "\n", + "temporalbasis(0.2)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1x4 Array{Float64,2}:\n", + " 0.25 0.25 0.25 0.25" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "spatialbasis([0.0, 0.0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Interpolation in time domain\n", + "\n", + "To interpolate in time domain, call `DefaultDiscreteField` with `TemporalBasis` and time. Result is a `Increment` interpolated to that time. Here we interpolate the position of particle moving $x = \\frac{1}{2}t^2$ at time $t=1.0$." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "([0.0,0.5,1.0,1.5,2.0],[0.0,0.125,0.5,1.125,2.0])" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fs = FieldSet()\n", + "t = collect(linspace(0, 2, 5))\n", + "x = 1/2*t.^2\n", + "t, x" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((0.0,0.0),(0.5,0.125),(1.0,0.5),(1.5,1.125),(2.0,2.0))" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x2 = tuple(collect(zip(t, x))...)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1-element Array{Float64,1}:\n", + " 0.5" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fs[\"particle position\"] = x2\n", + "temporalbasis = TemporalBasis((t) -> [1-t, t], (t) -> [-1, 1])\n", + "call(fs[\"particle position\"], temporalbasis, 1.0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It's also possible to take time derivatives. To do so, call `Field` with `TemporalBasis`, time, and additional argument `Val{:derivative}`. Again, same example:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Interpolation in spatial domain" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To interpolate in spatial domain, call `Increment` with `SpatialBasis` and coordinate $\\boldsymbol\\xi$. Increments to interpolate are the latest ones in each time step. Result depends from the content of the field. If it is scalar field, result will be scalar, if it's vector the result will be vector and so on.\n", + "\n", + "Let's have a $\\left[0,1\\right]\\times\\left[0,1\\right] \\in \\mathbb{R}^2$ domain and $u_5 = 0.25$ displacement in upper right corner pointint to the $x_1$ direction. We seek for a center point of this at time $t=1.0$." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{ASCIIString,JuliaFEM.AbstractField} with 2 entries:\n", + " \"geometry\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[JuliaFEM.In…\n", + " \"displacement\" => JuliaFEM.DefaultDiscreteField(JuliaFEM.TimeStep[JuliaFEM.In…" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fs = FieldSet()\n", + "fs[\"geometry\"] = Vector{Float64}[[0.0,0.0], [1.0,0.0], [1.0,1.0], [0.0,1.0]]\n", + "fs[\"displacement\"] = (0.0, zeros(2, 4)), (1.0, Vector[[0.0, 0.0], [0.0, 0.0], [0.25, 0.0], [0.0, 0.0]])\n", + "fs" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "2-element Array{Float64,1}:\n", + " 0.5625\n", + " 0.5 " + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X = call(last(fs[\"geometry\"]), spatialbasis, [0.0, 0.0])\n", + "u = call(last(fs[\"displacement\"]), spatialbasis, [0.0, 0.0])\n", + "x = X+u\n", + "x" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It's also possible to take gradient of field. To calculate gradient, call `Increment` with `SpatialBasis` and coordinate $\\boldsymbol\\xi$. Also, give another `Increment` to calculate Jacobian, typically geometry, \n", + "and add additional argument `Val{:gradient}'." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "2x2 Array{Float64,2}:\n", + " 0.125 0.125\n", + " 0.0 0.0 " + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gradu = call(last(fs[\"displacement\"]), spatialbasis, [0.0, 0.0], last(fs[\"geometry\"]), Val{:gradient})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we have:\n", + "- `FieldSet` which defines discrete values in time and space\n", + "- Interpolants `TemporalBasis` and `SpatialBasis` which defines how discrete fields values are interpolated to get continuous fields.\n", + "\n", + "Let's plug these in one new composite type and call it to *Finite Element*:" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "FiniteElement" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "abstract AbstractElement\n", + "\n", + "type FiniteElement <: AbstractElement\n", + " connectivity :: Array{Int, 1}\n", + " basis :: SpatialBasis\n", + " time :: TemporalBasis\n", + " fields :: Dict{ASCIIString, FieldSet}\n", + "end\n", + "\n", + "function FiniteElement(connectivity)\n", + " f(t) = [1-t, t]\n", + " df(t) = [-1, 1]\n", + " temporal_basis = TemporalBasis(f, df)\n", + "\n", + " h(xi) = 1/4*[(1-xi[1])*(1-xi[2]) (1+xi[1])*(1-xi[2]) (1+xi[1])*(1+xi[2]) (1-xi[1])*(1+xi[2])]\n", + " dh(xi) = 1/4*[\n", + " -(1-xi[2]) (1-xi[2]) (1+xi[2]) -(1+xi[2])\n", + " -(1-xi[1]) -(1+xi[1]) (1+xi[1]) (1-xi[1])]\n", + " spatial_basis = SpatialBasis(h, dh)\n", + "\n", + " return FiniteElement(connectivity, spatial_basis, temporal_basis, Dict())\n", + "end" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "LoadError", + "evalue": "LoadError: MethodError: `setindex!` has no method matching setindex!(::FiniteElement, ::Array{Array{Float64,1},1}, ::ASCIIString)\nwhile loading In[39], in expression starting on line 2", + "output_type": "error", + "traceback": [ + "LoadError: MethodError: `setindex!` has no method matching setindex!(::FiniteElement, ::Array{Array{Float64,1},1}, ::ASCIIString)\nwhile loading In[39], in expression starting on line 2", + "" + ] + } + ], + "source": [ + "fe = FiniteElement([1, 2, 3, 4])\n", + "fe[\"geometry\"] = Vector{Float64}[[0.0,0.0], [1.0,0.0], [1.0,1.0], [0.0,1.0]]\n", + "fe[\"displacement\"] = (0.0, zeros(2, 4)), (1.0, Vector[[0.0, 0.0], [0.0, 0.0], [0.25, 0.0], [0.0, 0.0]])\n", + "\n", + "basis = get_basis(fe)\n", + "basis(\"geometry\", [0.0, 0.0])\n", + "\n", + "dbasis = grad(basis)\n", + "dbasis(\"displacement\", [0.0, 0.0], 0.5)\n", + "dbasis(\"displacement\", [0.0, 0.0], 1.0)\n", + "\n", + "u = fe[\"displacement\"]\n", + "strain = 1/2(grad(u) + grad(u)')\n", + "strain_rate = diff(strain)\n", + "strain_rate([0.0, 0.0], 1.0)" + ] + } + ], + "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 +} diff --git a/docs/tutorials/2015-08-29-developing-juliafem.ipynb b/docs/tutorials/2015-08-29-developing-juliafem.ipynb index ab9fa46..d74f407 100644 --- a/docs/tutorials/2015-08-29-developing-juliafem.ipynb +++ b/docs/tutorials/2015-08-29-developing-juliafem.ipynb @@ -89,13 +89,13 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 69, "metadata": { "collapsed": false }, "outputs": [], "source": [ - "using JuliaFEM: Element, Field, FieldSet, Basis, Quad4" + "using JuliaFEM: Element, Field, FieldSet, Basis" ] }, { @@ -1724,6 +1724,109 @@ "source": [ "In this notebook the basic instructions how to develop JuliaFEM has been given. The most imporant concepts has been considered; how to develop own element with own basis, several ways how to define own equation, and how to finally assemble and calculate the problem using solver. Any comments and/or discussion about technical details, theory, programming, or from life in general is very desirable; our issue log is in address https://github.com/JuliaFEM/JuliaFEM.jl/issues" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Advanced stuff\n", + "\n", + "In last section of this tutorial we consider some of the more advanced things which may araise when developing own models.\n", + "\n", + "### Boundary element access to parent element + overriding equations in problems\n", + "\n", + "This kind of situation might happen when one is almost happy for some problem setting, but would like to change just one or two equations from it. For example boundary equation is not satisfying all the requirements and one would like to test something new. \n", + "\n", + "### Accessing integration points\n", + "\n", + "### Fields as a function of something.\n", + "- statistical variables\n", + "- field dependent from another field\n", + "- field dependent from time or spatial domain etc.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "using JuliaFEM: get_default_integration_points\n", + "\"\"\" 2-node radiation boundary element. \"\"\"\n", + "type DC2D2RAD <: Heat\n", + " element :: Seg2\n", + " integration_points :: Array{IntegrationPoint, 1}\n", + "end\n", + "function DC2D2RAD(element::Seg2)\n", + " integration_points = [\n", + " IntegrationPoint([0.0], 2.0)]\n", + " if !haskey(element, \"temperature\")\n", + " element[\"temperature\"] = FieldSet()\n", + " push!(element[\"temperature\"], Field([0.0, 0.0]))\n", + " end\n", + " DC2D2RAD(element, integration_points)\n", + "end\n", + "Base.size(equation::DC2D2RAD) = (1, 2)\n", + "\n", + "\"\"\" Calculate potential energy caused by radiation.\n", + "https://en.wikipedia.org/wiki/Stefan%E2%80%93Boltzmann_constant\n", + "\"\"\"\n", + "function JuliaFEM.get_potential_energy(equation::DC2D2RAD, ip, time; variation=nothing)\n", + " element = get_element(equation)\n", + " basis = get_basis(element)\n", + " eps = basis(\"emissivity\", ip, time)\n", + " #sig = basis(\"stefan-boltzmann constant\", ip, time)\n", + " sig = 5.670367e-8 # i guess stefan-boltzmann constant is constant ;)\n", + " T = basis(\"temperature\", ip, time, variation)\n", + " T_ext = basis(\"temperature external\", ip, time)\n", + " q = eps*sig*((T_ext+273.15)^4 - (T+273.15)^4)\n", + " println(ForwardDiff.value(q*T))\n", + " return q\n", + "end\n", + "JuliaFEM.has_potential_energy(equation::DC2D2RAD) = true\n", + "\n", + "Defining new equation mapping to old problem is one line command. Here we replace `DC2D2` $\\rightarrow$ `DC2DCRAD`\n", + "\n", + "function run_radiation_model()\n", + " # this is the same as before\n", + " element = Quad4([1, 2, 3, 4])\n", + " fieldset1 = FieldSet(\"geometry\", [Field(Vector[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]])])\n", + " fieldset2 = FieldSet(\"temperature thermal conductivity\", [Field(6.0)])\n", + " fieldset3 = FieldSet(\"temperature load\", [Field([12.0, 12.0, 12.0, 12.0])])\n", + " fieldset4 = FieldSet(\"density\", [Field(36.0)])\n", + " push!(element, fieldset1)\n", + " push!(element, fieldset2)\n", + " push!(element, fieldset3)\n", + " push!(element, fieldset4)\n", + "\n", + " # create boundary element\n", + " boundary_element = Seg2([1, 2])\n", + " push!(boundary_element, FieldSet(\"geometry\", [Field(Vector[[0.0, 0.0], [1.0, 0.0]])]))\n", + " push!(boundary_element, FieldSet(\"emissivity\", [Field(0.5)]))\n", + " push!(boundary_element, FieldSet(\"temperature external\", [Field(20.0)]))\n", + "\n", + " # set initial conditions\n", + " push!(element, FieldSet(\"temperature\", [Field([0.0, 0.0, 0.0, 0.0])]))\n", + " push!(boundary_element, FieldSet(\"temperature\", [Field([0.0, 1.0])]))\n", + " \n", + " # create problem, change element mapping\n", + " problem = PlaneHeatProblem()\n", + " problem[Seg2] = DC2D2RAD # Seg2 was previous DC2D2\n", + " push!(problem, element)\n", + " push!(problem, boundary_element)\n", + "\n", + " # run our \"unit test solver\"\n", + " free_dofs = [3, 4]\n", + " solve!(problem, free_dofs; max_iterations=4, dump_matrices=true)\n", + " basis = get_basis(boundary_element)\n", + " T = basis(\"temperature\", [0.0])\n", + " println(\"Temperature at the midpoint of element: $T\")\n", + "end\n", + "\n", + "run_radiation_model()" + ] } ], "metadata": { diff --git a/notebooks/2015-06-14-data-structures.ipynb b/notebooks/2015-06-14-data-structures.ipynb deleted file mode 100644 index aaa1d15..0000000 --- a/notebooks/2015-06-14-data-structures.ipynb +++ /dev/null @@ -1,716 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Data structures\n", - "\n", - "**Author(s)**: Jukka Aho\n", - "\n", - "**Abstract**: Description of data structures.\n", - "\n", - "## Revision history\n", - "\n", - "### 2015-06-14\n", - "- Initial version.\n", - "\n", - "### 2015-09-25\n", - "- Complete rewrite. The main ideas proposed in earlier version didn't work." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Data fields on elements\n", - "\n", - "Typical element structure so far:\n", - "\n", - " type MySuperElement <: Element\n", - " connectivity :: Array{Int, 1} # describes how dofs of this element is connected to another elements\n", - " fields :: ???\n", - " end\n", - "\n", - "- Fields must be interpolable, in space $\\mathbb{C}^n \\times \\mathbb{R}$, i.e. $f(\\boldsymbol{\\xi}, t) = \\sum_i \\phi_i(\\boldsymbol{\\xi}) f_i(t) = \\sum_i \\phi_i(\\boldsymbol{\\xi}) \\sum_j \\varphi_j(t) f_{ij}$, where $f_{ij}$ is scalar, tensor or vector defined in element area $e$ by some basis functions $\\phi(\\boldsymbol{\\xi})$ and $\\varphi(t)$. Parameter $t$ is normally considered as \"time\" and $\\xi$ is dimensionless coordinate. Parameter $t$ has not necessarily to be time, it could be for example angle $\\alpha \\in [-2\\pi, 2\\pi]$ or similar.\n", - "- We store mainly three fields, scalar field, vector field, tensor field. Field may or may not be dependent from parameters $\\xi$ or $t$.\n", - "- $t$ is discretized to several steps $\\{t_0, t_1, \\ldots, t_n\\}$. Each discrete time $t_i$ may contain several iterations until convergence. We want to save and get access to all of this data if needed.\n", - "- So in practice we have a set of fields $f(\\boldsymbol{\\xi})$ over time domain $t$. Typically some fields, like Geometry, is introduced only in time $t_0$. Some other fields, like boundary load, may be \"active\" only on some time $\\hat{t} \\subset t$. Some care must be taken of how to extrapolate field variables.\n", - "- In the simplest case (simple nonlinear quasistatic analysis), we have for instance $t \\in [0, 1]$ where boundary conditions are set in $t_0$ and load is set in $t_1$. We may use adaptive strategies to shorten time if convergence issues araises." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Without time domain\n", - "\n", - "Without time we have something like this" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "using ForwardDiff" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "type Field{T}\n", - " time :: Float64\n", - " increment :: Int64\n", - " values :: Array{T, 1}\n", - "end\n", - "\n", - "type Basis\n", - " basis :: Function\n", - " dbasisdxi :: Function\n", - "end" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "* (generic function with 158 methods)" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "function Field(time, values)\n", - " Field(time, 1, values)\n", - "end\n", - "function Basis(basis)\n", - " Basis(basis, ForwardDiff.jacobian(basis))\n", - "end\n", - "call(b::Basis, xi) = b.basis(xi)\n", - "Base.(:*)(x::Array{Float64, 1}, f::Field) = sum(x .* f.values)\n", - "Base.(:*)(b::Basis, f::Field) = (x) -> b(x)*f" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then we can do something like this" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "Field{Float64}(0.0,1,[0.0,1.0])" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "N = Basis((ξ) -> [0.5*(1.0-ξ[1]), 0.5*(1.0+ξ[1])])\n", - "u = Field(0.0, [0.0, 1.0])" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "0.5" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "N([0.0])*u" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "or" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "0.5" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "(N*u)([0.0])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Extending to time domain" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next we want to interpolate over time domain. Maybe something like this would do the job:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "Field{Float64}(0.0,1,[0.0,3.0])" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Base.(:*)(k::Float64, f::Field) = Field(f.time, k*f.values)\n", - "u1 = Field(0.0, [0.0, 1.0])\n", - "3.0*u1" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "Field{Float64}(0.0,1,[1.0,3.0])" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "function Base.(:+)(f1::Field, f2::Field)\n", - " @assert(f1.time == f2.time, \"Cannot add fields: time mismatch, $(f1.time) != $(f2.time)\")\n", - " Field(f1.time, f1.values + f2.values)\n", - "end\n", - "u1 = Field(0.0, [0.0, 1.0])\n", - "u2 = Field(0.0, [1.0, 2.0])\n", - "u1 + u2" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "Field{Float64}(0.0,1,[0.5,1.5])" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "u1 = Field(0.0, [0.0, 1.0])\n", - "u2 = Field(0.0, [1.0, 2.0])\n", - "\n", - "t = Basis((t) -> [1-t, t])\n", - "u = Field[u1, u2]\n", - "Base.(:*)(x::Array{Float64, 1}, f::Array{Field}) = sum(x .* f)\n", - "Base.(:*)(b::Basis, f::Array{Field}) = (t) -> b(t)*f\n", - "(t*u)(0.5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Semisummary\n", - "\n", - "Putting this together so far:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "1.0" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ϕ = Basis((ξ) -> [0.5*(1.0-ξ[1]), 0.5*(1.0+ξ[1])])\n", - "φ = Basis((t) -> [1-t, t])\n", - "u1 = Field(0.0, [0.0, 1.0])\n", - "u2 = Field(0.0, [1.0, 2.0])\n", - "# interpolate displacement u1 in mid-point ξ=[0.0] on element\n", - "(ϕ*u1)([0.0]) # => 0.5\n", - "# interpolate displacement field [u1, u2] in time t=0.5\n", - "(φ*Field[u1, u2])(0.5) # => Field{Float64}(:displacement,[0.5,1.5])\n", - "# interpolate displacement in element area ξ and time t\n", - "d(ξ, t) = (ϕ*(φ*Field[u1, u2])(t))(ξ)\n", - "d([0.0], 0.5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Makes sense, since deformation from $u_1$ to $u_2$ at $t=0.5$ is $\\begin{bmatrix}0.5 & 1.5\\end{bmatrix}$ and taking the midpoint of this makes exactly $1.0$." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Towards generalization\n", - "\n", - "So at this point we are able to interpolate $u(\\xi)$ in time $t$. This is a set of discrete fields and needs to figure out which fields are needed to interpolate. So we have $u_0, u_1, \\ldots, u_i$ fields. Each time needs to be aware of it's time $t_i$." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Field{" - ] - } - ], - "source": [ - "\"\"\"\n", - "Return a field in some time t.\n", - "\"\"\"\n", - "function call(fields::Array{Field, 1}, t::Float64)\n", - " if t <= fields[1].time\n", - " return fields[1]\n", - " end\n", - " if t >= fields[end].time\n", - " return fields[end]\n", - " end\n", - " i = length(fields)\n", - " while fields[i].time >= t\n", - " i -= 1\n", - " end\n", - " if fields[i].time == t\n", - " return fields[i]\n", - " end\n", - " #Logging.debug(\"doing linear interpolation between fields $i and $(i+1)\")\n", - " f1 = fields[i]\n", - " t1 = f1.time\n", - " f2 = fields[i+1]\n", - " t2 = f2.time\n", - " dt = t2 - t1\n", - " nw = (t2-t)/dt*f1.values + (t-t1)/dt*f2.values\n", - " f = Field(t, nw)\n", - " return f\n", - "end\n", - "function call(field::Field, t::Float64)\n", - " Field(t, field.increment, field.values)\n", - "end\n", - "\n", - "u1 = Field(0.0, [0.0, 0.0])\n", - "u2 = Field(1.0, [1.0, 2.0])\n", - "u3 = Field(2.0, [0.5, 1.5])\n", - "u = Field[u1, u2, u3]\n", - "for f in [u(-Inf), u(0.0), u(0.5), u(1.0), u(1.5), u(2.0), u(Inf)]\n", - " println(f)\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Handling of derivatives in multidimensional case" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "* (generic function with 163 methods)" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "∂(h::Basis) = h.dbasisdxi\n", - "diff(h::Basis) = h.dbasisdxi\n", - "derivative(h::Basis) = h.dbasisdxi\n", - "Base.(:*)(df::Function, fld::Field) = (ξ) -> df(ξ)*fld\n", - "Base.length(f::Field) = length(f.values)\n", - "Base.getindex(f::Field, i::Int64) = f.values[i]\n", - "Base.(:*)(x::Array{Float64, 2}, fld::Field) = sum([fld[i]*x[i,:] for i in 1:length(fld)])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Summary" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "WARNING: could not import Base.help into PyCall\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Float64}(0.0,1,[0.0,0.0])\n", - "Field{Float64}(0.0,1,[0.0,0.0])\n", - "Field{Float64}(0.5,1,[0.5,1.0])\n", - "Field{Float64}(1.0,1,[1.0,2.0])\n", - "Field{Float64}(1.5,1,[0.75,1.75])\n", - "Field{Float64}(2.0,1,[0.5,1.5])\n", - "Field{Float64}(2.0,1,[0.5,1.5])\n" - ] - } - ], - "source": [ - "using PyPlot" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Interpolate midpoint of some field in function of time, i.e., construct $x(\\xi, t)$:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "image/png": [ - "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" - ], - "text/plain": [ - "PyPlot.Figure(PyObject )" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "ϕ = Basis((ξ) -> [0.5*(1.0-ξ[1]), 0.5*(1.0+ξ[1])])\n", - "u1 = Field(0.0, [0.0, 0.0])\n", - "u2 = Field(1.0, [1.0, 2.0])\n", - "u3 = Field(2.0, [0.5, 1.5])\n", - "u = Field[u1, u2, u3]\n", - "x(ξ, t) = ϕ(ξ)*u(t)\n", - "t = linspace(-1.0, 4.0, 200)\n", - "midpnt = zeros(length(t))\n", - "for i =1:length(t)\n", - " midpnt[i] = x([0.0], t[i])\n", - "end\n", - "plot(t, midpnt, \"-k\", label=\"midpoint of some field\")\n", - "legend()\n", - "grid()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Multidimensional interpolation $x(\\boldsymbol\\xi, t)$:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "image/png": [ - "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" - ], - "text/plain": [ - "PyPlot.Figure(PyObject )" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "(-0.5,2.0,0.0,2.0)" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "h = Basis((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", - "X = Field(0.0, Vector[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]])\n", - "u1 = Field(0.0, Vector[[0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]])\n", - "u2 = Field(1.0, Vector[[-0.3, 0.2], [0.2, 0.0], [1.0, 0.0], [0.0, 1.0]])\n", - "u = Field[u1, u2]\n", - "N = 5\n", - "x(ξ, t) = h(ξ)*(X(t) + u(t))\n", - "for t in linspace(0, 1, N)\n", - " m = [x([-1, -1], t) x([1, -1], t) x([1, 1], t) x([-1, 1], t) x([-1, -1], t)]\n", - " midpnt = x([0, 0], t)\n", - " plot(m[1,:][:], m[2,:][:], \"-o\", label=\"t=$t\")\n", - " #plot(midpnt[1], midpnt[2], \"ko\")\n", - "end\n", - "legend()\n", - "axis(\"equal\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To interpolate field $X$ using basis $h$:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "2-element Array{Float64,1}:\n", - " 0.5\n", - " 0.5" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "(h*X)([0.0, 0.0])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "or" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "2-element Array{Float64,1}:\n", - " 0.5\n", - " 0.5" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "h([0.0, 0.0])*X" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To interpolate derivatives of field $X$ using basis $h$:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "2x2 Array{Float64,2}:\n", - " 0.5 0.0\n", - " 0.0 0.5" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "(∂(h)*X)([0.0, 0.0]) # ∂(⋅) is equivalent to diff(⋅) and derivative(⋅)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "or" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "2x2 Array{Float64,2}:\n", - " 0.5 0.0\n", - " 0.0 0.5" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "∂(h)([0.0, 0.0])*X" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Julia 0.5.0-dev", - "language": "julia", - "name": "julia-0.5" - }, - "language_info": { - "name": "julia", - "version": "0.5.0" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/src/JuliaFEM.jl b/src/JuliaFEM.jl index be86887..6c2524d 100644 --- a/src/JuliaFEM.jl +++ b/src/JuliaFEM.jl @@ -6,10 +6,14 @@ This is JuliaFEM -- Finite Element Package """ module JuliaFEM -using Logging -@Logging.configure(level=DEBUG) +#using Logging +#@Logging.configure(level=DEBUG) +#using Lexicon + +macro debug(msg) + return :( println("DEBUG: ", $msg) ) +end -using Lexicon using ForwardDiff autodiffcache = ForwardDiffCache() @@ -24,12 +28,12 @@ Examples [1.0] """ -function Base.linspace(X1, X2, n) +function Base.linspace{T<:Array}(X1::T, X2::T, n) [1/2*(1-ti)*X1 + 1/2*(1+ti)*X2 for ti in linspace(-1, 1, n)] end include("types.jl") # type definitions -include("interpolate.jl") # interpolation routines +#include("interpolate.jl") # interpolation routines ### ELEMENTS ### include("elements.jl") diff --git a/src/assembly.jl b/src/assembly.jl index 019c603..5f82b46 100644 --- a/src/assembly.jl +++ b/src/assembly.jl @@ -49,8 +49,8 @@ function calculate_global_assembly!(assembly::GlobalAssembly, problem::Problem, unknown_field_name = get_unknown_field_name(problem) initialize_global_assembly!(assembly, problem) # zero all dim, ndofs = size(problem) - Logging.info("assembling problem for $unknown_field_name") - Logging.info("dimension of unknown field: $dim, problem dofs: $ndofs") + info("assembling problem for $unknown_field_name") + info("dimension of unknown field: $dim, problem dofs: $ndofs") local_assembly = initialize_local_assembly() for (i, equation) in enumerate(get_equations(problem)) calculate_local_assembly!(local_assembly, equation, unknown_field_name, time) diff --git a/src/elasticity.jl b/src/elasticity.jl index f517b7c..0b77984 100644 --- a/src/elasticity.jl +++ b/src/elasticity.jl @@ -121,8 +121,7 @@ end function CPS4(element::Quad4) integration_points = get_default_integration_points(element) if !haskey(element, "displacement") - element["displacement"] = FieldSet() - push!(element["displacement"], Field(Vector[[0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]])) + element["displacement"] = zeros(2, 4) end CPS4(element, integration_points) end diff --git a/src/elements.jl b/src/elements.jl index 681dd6a..d3c00a3 100644 --- a/src/elements.jl +++ b/src/elements.jl @@ -69,56 +69,13 @@ end """Add new FieldSet to element. -Examples --------- ->>> field = Field(0.0, [1, 2, 3, 4]) ->>> fieldset = FieldSet("geometry", Field[field]) ->>> element["geometry"] = fieldset -JuliaFEM.Quad4([1,2,3,4],JuliaFEM.Basis(basis,dbasisdxi),Dict("geometry"=>JuliaFEM.FieldSet("geometry",JuliaFEM.Field[JuliaFEM.Field{Array{Int64,1}}(0.0,0,[1,2,3,4])]))) -""" -function Base.setindex!(element::Element, fieldset::FieldSet, fieldset_name) - fieldset.name = fieldset_name - element.fields[fieldset.name] = fieldset -end - -"""Add new FieldSet to element. - -Examples --------- ->>> field = Field(0.0, [1, 2, 3, 4]) ->>> element["geometry"] = field -JuliaFEM.Quad4([1,2,3,4],JuliaFEM.Basis(basis,dbasisdxi),Dict("geometry"=>JuliaFEM.FieldSet("geometry",JuliaFEM.Field[JuliaFEM.Field{Array{Int64,1}}(0.0,0,[1,2,3,4])]))) -""" -function Base.setindex!(element::Element, field::Field, fieldset_name) - element[fieldset_name] = FieldSet(field) -end - -"""Add new FieldSet to element. - Examples -------- >>> element["geometry"] = [1, 2, 3, 4] JuliaFEM.Quad4([1,2,3,4],JuliaFEM.Basis(basis,dbasisdxi),Dict("geometry"=>JuliaFEM.FieldSet("geometry",JuliaFEM.Field[JuliaFEM.Field{Array{Int64,1}}(0.0,0,[1,2,3,4])]))) """ -function Base.setindex!(element::Element, field_data::Union{Number, Array}, fieldset_name) - element[fieldset_name] = Field(field_data) -end - -"""Add new FieldSet to element. - -Notes ------ -This last version takes tuple and each cell in tuple is converted to new field. -Time in field is 0.0, 1.0, ..., n - -Examples --------- ->>> element["load"] = (1, 2) -JuliaFEM.Quad4([1,2,3,4],JuliaFEM.Basis(basis,dbasisdxi),Dict("load"=>JuliaFEM.FieldSet("load",JuliaFEM.Field[JuliaFEM.Field{Int64}(0.0,0,1),JuliaFEM.Field{Int64}(1.0,0,2)]))) -""" -function Base.setindex!(element::Element, field_data::Tuple, fieldset_name) - fields = Field[Field(Float64(i-1), field) for (i,field) in enumerate(field_data)] - element.fields[fieldset_name] = FieldSet(fieldset_name, fields) +function Base.setindex!(element::Element, field_data, field_name) + element.fields[field_name] = field_data end function get_connectivity(el::Element) @@ -156,10 +113,13 @@ end function call(u::FunctionSpace, field_name, xi::Vector, t::Number=Inf, variation=nothing) f = !isa(variation, Void) ? variation : u.element[field_name](t) if length(f) == 1 - return f.values + return f.data[1] end h = u.element.basis.basis(xi) - return dot(vec(h), f) + #@debug("vec(h) = $(vec(h)), size(h) = $(size(vec(h)))") + #@debug("f = $f, size(f) = $(size(f))") + #return dot(vec(h), f) + return sum(vec(h).*f) end """ If basis is called without a field, return basis functions evaluated at that point. """ diff --git a/src/equations.jl b/src/equations.jl index 25555d3..d53d8fa 100644 --- a/src/equations.jl +++ b/src/equations.jl @@ -182,7 +182,10 @@ function calculate_local_assembly!(assembly::LocalAssembly, equation::Equation, field = element[unknown_field_name](time) function residual_vector(data::Vector) fill!(assembly.residual_vector, 0.0) - df = similar(field, data) + #@debug("field: $field, length = $(size(field))") + #@debug("data: $data, size = $(size(data))") + #df = similar(field, data) + df = Increment(reshape(data, size(equation)...)) # integrate W for ip in get_integration_points(equation) dr = get_residual_vector(equation, ip, time; variation=df) diff --git a/src/heat.jl b/src/heat.jl index faf73b1..daaf3ca 100644 --- a/src/heat.jl +++ b/src/heat.jl @@ -46,8 +46,8 @@ function calculate_local_assembly!(assembly::LocalAssembly, equation::HeatEquati w = ip.weight * detJ(ip) N = basis(ip, time) if haskey(element, "density") - ρ = basis("density", ip, time) - assembly.mass_matrix += w * ρ*N'*N + rho = basis("density", ip, time) + assembly.mass_matrix += w * rho*N'*N end if haskey(element, "temperature thermal conductivity") dN = dbasis(ip, time) @@ -92,7 +92,7 @@ end function DC2D4(element::Quad4) integration_points = get_default_integration_points(element) if !haskey(element, "temperature") - element["temperature"] = FieldSet() + element["temperature"] = zeros(4) end DC2D4(element, integration_points) end @@ -106,7 +106,7 @@ end function DC2D2(element::Seg2) integration_points = get_default_integration_points(element) if !haskey(element, "temperature") - element["temperature"] = FieldSet() + element["temperature"] = zeros(2) end DC2D2(element, integration_points) end diff --git a/src/lagrange.jl b/src/lagrange.jl index 421ed0b..2d5a824 100644 --- a/src/lagrange.jl +++ b/src/lagrange.jl @@ -37,13 +37,14 @@ macro create_lagrange_element(element_name, element_description, X, P) type $eltype <: CG connectivity :: Array{Int, 1} basis :: Basis - fields :: Dict{ASCIIString, FieldSet} + fields :: FieldSet end function $eltype(connectivity, args...) - $eltype(connectivity, Basis(basis, dbasisdxi), Dict()) + $eltype(connectivity, Basis(basis, dbasisdxi), FieldSet()) end get_element_description(el::Type{$eltype}) = $element_description - Base.size(el::Type{$eltype}) = Base.size($X) + Base.size(element::Type{$eltype}) = Base.size($X) + Base.size(element::$eltype) = Base.size($X) end end diff --git a/src/solvers.jl b/src/solvers.jl index 6161b5f..e7d98d6 100644 --- a/src/solvers.jl +++ b/src/solvers.jl @@ -67,9 +67,7 @@ function solve!(problem::Problem, free_dofs::Array{Int, 1}, time::Number=Inf; gdofs = vec(vcat([dim*conn'-i for i=dim-1:-1:0]...)) old_field = element[field_name](Inf) new_field = similar(old_field, full(x[gdofs])) - new_field.time = time - new_field.increment = i - push!(element[field_name], new_field) + push!(element[field_name][end], new_field) end if norm(dx) < tolerance return diff --git a/src/types.jl b/src/types.jl index 5b34528..a4552c9 100644 --- a/src/types.jl +++ b/src/types.jl @@ -3,150 +3,272 @@ # https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/notebooks/2015-06-14-data-structures.ipynb -using ForwardDiff +#abstract AbstractField{T,N} <: AbstractArray{T,N} -""" Field is a fundamental data type which holds some values in some time t """ -type Field{T} - time :: Number - increment :: Int64 - values :: T +abstract AbstractField + +abstract DiscreteField <: AbstractField + +abstract ContinuousField <: AbstractField +abstract TimeContinuousField <: ContinuousField +abstract SpatialContinuousField <: ContinuousField +abstract TimeAndSpatialContinuousField <: ContinuousField +# should we introduce time and spatial discontinuous fields +# for discontinuous galerkin? + +### DEFAULT DISCRETE FIELD ### + +# 1. Increment + +# FIXME: This should be Vector. +#typealias Increment Vector +type Increment{T} <: AbstractVector{T} + data :: Vector{T} end -""" Initialize field. """ -function Field(time, values) - Field(time, 0, values) +Base.size(increment::Increment) = Base.size(increment.data) +Base.linearindexing(::Type{Increment}) = Base.LinearFast() +Base.getindex(increment::Increment, i::Int) = increment.data[i] +Base.setindex!(increment::Increment, v, i::Int) = (increment.data[i] = v) +Base.similar{T}(increment::Increment, ::Type{T}) = Increment(similar(increment.data)) +Base.dot(v::Number, i::Increment) = v*i + +function Base.convert(::Type{Increment}, data::Number) + Increment([data]) end -function Field(values) - Field(0.0, 0, values) +function Base.convert{T}(::Type{Increment}, data::Array{T, 2}) + Increment([data[:,i] for i=1:size(data, 2)]) end -""" Get length of a field (number of basis functions in practice). """ -function Base.length(f::Field) - length(f.values) +function Base.convert{T}(::Type{Increment}, data::Array{T, 3}) + Increment([data[:,:,i] for i=1:size(data, 3)]) end -""" Push value to field. """ -function Base.push!(f::Field, value) - push!(f.values, value) +function Base.convert{T}(::Type{Increment}, data::Array{T, 4}) + Increment([data[:,:,:,i] for i=1:size(data, 4)]) end -""" Get field discrete value at point i. """ -function Base.getindex(f::Field, i::Int64) - f.values[i] +function Base.convert{T}(::Type{Increment}, data::Array{T, 5}) + Increment([data[:,:,:,:,i] for i=1:size(data, 5)]) end -""" Multiply field with some constant k. """ -function Base.(:*)(k::Number, f::Field) - Field(f.time, k*f.values) +function Base.zeros(::Type{Increment}, dims...) + Increment(zeros(dims...)) +end +function Base.vec(increment::Increment) + [increment.data...;] +end +function Base.similar{T}(increment::Increment{Vector{T}}, data::Vector{T}) + Increment(reshape(data, round(Int, length(data)/length(increment)), length(increment))) end -""" Inner product of field and vector x. """ -function Base.dot(x::Vector, f::Field) - @assert length(x) == length(f) - sum([f[i]*x[i] for i in 1:length(f)]) +# 2. TimeStep + +type TimeStep{T} <: AbstractVector{T} + time :: Float64 + increments :: Vector{T} +end +Base.size(timestep::TimeStep) = Base.size(timestep.increments) +Base.linearindexing(::Type{TimeStep}) = Base.LinearFast() +Base.getindex(timestep::TimeStep, i::Int) = timestep.increments[i] + +function Base.convert(::Type{TimeStep}, time::Number, increment::Increment) + TimeStep(time, Increment[increment]) end -function Base.size(field::Field) - (length(field.values[1]), length(field.values)) +function Base.push!(timestep::TimeStep, increment::Increment) + push!(timestep.increments, increment) end -#""" Multiply field with some matrix x. """ -# function Base.(:*){T}(x::Matrix, f::Field{Vector{T}}) -#function Base.(:*)(x::Matrix, f::Field) -# sum([f[i]*x[:,i]' for i in 1:length(f)]) +# 3. DefaultDiscreteField + +type DefaultDiscreteField <: DiscreteField + timesteps :: Vector{TimeStep} +end +Base.size(field::DefaultDiscreteField) = Base.size(field.timesteps) +Base.linearindexing(::Type{DefaultDiscreteField}) = Base.LinearFast() +Base.getindex(field::DefaultDiscreteField, i::Int) = field.timesteps[i] +Base.length(field::DefaultDiscreteField) = length(field.timesteps) +Base.endof(field::DefaultDiscreteField) = endof(field.timesteps) +Base.first(field::DefaultDiscreteField) = field[1][end] +Base.last(field::DefaultDiscreteField) = field[end][end] +function Base.push!(field::DefaultDiscreteField, timestep::TimeStep) + push!(field.timesteps, timestep) +end + + +typealias Field DefaultDiscreteField + +### CONTINUOUS FIELDS ### + + +# fix print_matrix +#function Base.print_matrix(::Base.AbstractIOBuffer, field::ContinuousField, args...) + # TODO: anything nice to print? #end -""" Sum two fields. """ -function Base.(:+)(f1::Field, f2::Field) - @assert(f1.time == f2.time, "Cannot add fields: time mismatch, $(f1.time) != $(f2.time)") - Field(f1.time, f1.values + f2.values) -end +### FIELDSET ### -""" Return data from field as a long array. +typealias FieldSet Dict{ASCIIString, AbstractField} + +"""Quicky add discrete field to fieldset. Examples -------- ->>> f = Field(0.0, Vector[[1.0, 2.0], [3.0, 4.0]]) ->>> f[:] -[1.0, 2.0, 3.0, 4.0] - +>>> fs = FieldSet() +>>> fs["myfield"] = [1, 2, 3, 4] """ -function Base.getindex(field::Field, c::Colon) - [field.values...;] -end -function Base.vec(field::Field) - [field.values...;] +function Base.convert(::Type{AbstractField}, data::Union{Array, Number}) + increment = Increment(data) + timestep = TimeStep(0.0, Increment[increment]) + field = DefaultDiscreteField(TimeStep[timestep]) + return field end -""" Return field similar to input but with new data in it. +""" Quicky add several time steps at once in tuple. Examples -------- ->>> f = Field(0.5, Vector[[1.0, 2.0], [3.0, 4.0]]) ->>> similar(f, ones(4)) -JuliaFEM.Field{Array{Array{T,1},1}}(0.5,1,Array{T,1}[[1.0,1.0],[1.0,1.0]]) +>>> fs = FieldSet() +>>> fs["myfield"] = (0.0, [1, 2, 3, 4]), (0.5, [2, 3, 4, 5]) + +or + +>>> fs["myfield"] = [1, 2, 3, 4], [2, 3, 4, 5] """ -function Base.similar(field::Field, data::Vector) - fdim = round(Int, length(data)/length(field)) # dimension of field variable - if fdim == 1 - new_field = Field(field.time, data) - return new_field +function Base.convert(::Type{AbstractField}, data::Tuple) + timesteps = TimeStep[] + for (i, timestep) in enumerate(data) + if isa(timestep, Tuple) + push!(timesteps, TimeStep(Float64(timestep[1]), Increment(timestep[2]))) + else + push!(timesteps, TimeStep(Float64(i-1), Increment(timestep))) + end end - new_field = Field(field.time, similar(field.values)) - data = reshape(data, fdim, length(field)) - for i=1:length(new_field) - new_field.values[i] = data[:,i] - end - return new_field + return DefaultDiscreteField(timesteps) end +### BASIS ### +abstract AbstractBasis - - -""" FieldSet is set of fields, each field can have different time and/or increment. """ -type FieldSet - name :: ASCIIString - fields :: Array{Field, 1} -end -""" Initializer for FieldSet. """ -function FieldSet(field_name::ASCIIString) - FieldSet(field_name, []) -end -function FieldSet() - FieldSet("unknown field", []) -end -function FieldSet(fields::Array{Field, 1}) - FieldSet("unknown field", fields) -end -""" Add new field to fieldset. """ -function Base.push!(fs::FieldSet, field::Field) - push!(fs.fields, field) -end -""" Multiply fieldset with some vector x. """ -Base.(:*)(x::Array{Float64, 1}, fs::FieldSet) = sum(x .* fs.fields) -""" Get length of a fieldset. """ -function Base.length(fieldset::FieldSet) - length(fieldset.fields) -end -""" Return ith field from fieldset. """ -function Base.getindex(fieldset::FieldSet, i::Int64) - fieldset.fields[i] -end -#""" Return last field from fieldset. """ -function Base.endof(fieldset::FieldSet) - length(fieldset) -end -function Base.convert(fieldset::Type{FieldSet}, field::Field) - FieldSet(Field[field]) -end - - -""" Basis function. """ -type Basis +""" Defined to dimensionless coordinate ξ∈[-1,1]^n. """ +type SpatialBasis <: AbstractBasis basis :: Function dbasisdxi :: Function end -#""" Constructor of basis function. """ -#function Basis(basis) -# Basis(basis, ForwardDiff.jacobian(basis)) -#end + +typealias Basis SpatialBasis + +""" Defined to to interval t∈[0, 1]. """ +type TemporalBasis <: AbstractBasis + basis :: Function + dbasisdt :: Function +end +function TemporalBasis() + basis(t) = [1-t, t] + dbasis(t) = [-1, 1] + return TemporalBasis(basis, dbasis) +end + +function call(b::TemporalBasis, value::Number) + b.basis(value) +end + +function call(b::SpatialBasis, value::Vector) + b.basis(value) +end + +### INTERPOLATION IN TIME DOMAIN ### + +function Base.call(field::Field, basis::TemporalBasis, time) + # FieldSet -> Field -> TimeStep -> Increment -> data + # special cases, -Inf, +Inf and ~0.0 + if time > field[end].time + return field[end][end] + end + if (time < field[1].time) || abs(time-field[1].time) < 1.0e-12 + return field[1][end] + end + i = length(field) + while field[i].time >= time + i -= 1 + end + field[i].time == time && return field[i][end] + t1 = field[i].time + t2 = field[i+1].time + inc1 = field[i][end] + inc2 = field[i+1][end] + # TODO: may there be some reasons for "unphysical" jumps in + # fields w.r.t time which should be taken account in some way? + # i.e. dt between two fields → 0 + dt = t2 - t1 + b = basis.basis((time-t1)/dt) + r = Increment[inc1, inc2] + return dot(b, r) +end +function Base.call(field::DiscreteField, time) + return Base.call(field, TemporalBasis(), time) +end + +function Base.call(field::Field, basis::TemporalBasis, time, + derivative::Type{Val{:derivative}}) + # FieldSet -> Field -> TimeStep -> Increment -> data + + if length(field) == 1 + # just one timestep, time derivative cannot be evaluated. + error("Field length = $(length(field)), cannot evaluate time derivative") + end + + function eval_field(i, j) + timesteps = TimeStep[field[i], field[j]] + increments = Increment[timesteps[1][end], timesteps[2][end]] + J = norm(timesteps[2].time - timesteps[1].time) + dbasisdt = basis.dbasisdt( (time-timesteps[1].time)/J ) + return dot(dbasisdt, increments)/J + end + + # special cases, +Inf, -Inf, ~0.0 + if (time > field[end].time) || isapprox(time, field[end].time) + return eval_field(endof(field)-1, endof(field)) + end + if (time < field[1].time) || isapprox(time, field[1].time) + return eval_field(1, 2) + end + + # search for a correct "bin" between time steps + i = length(field) + #while field[i].time >= time + 1.0e-12 + while (field[i].time > time) && !isapprox(field[i].time, time) + i -= 1 + end + + if isapprox(field[i].time, time) + # This is the hard case, maybe discontinuous time + # derivative if linear approximation. + # we are on the "mid node" in time axis + field1 = eval_field(i-1,i) + field2 = eval_field(i,i+1) + return 1/2*(field1 + field2) + end + + return eval_field(i, i+1) + +end + +### INTERPOLATION IN SPATIAL DOMAIN ### + +function Base.call(increment::Increment, basis::SpatialBasis, xi::Vector) + basis = basis.basis(xi) + sum([basis[i]*increment[i] for i=1:length(increment)]) +end + +function Base.call(increment::Increment, basis::SpatialBasis, xi::Vector, + geometry::Increment, gradient::Type{Val{:gradient}}) + dbasis = basis.dbasisdxi(xi) + J = sum([dbasis[:,i]*geometry[i]' for i=1:length(geometry)]) + grad = inv(J)*dbasis + gradf = sum([grad[:,i]*increment[i]' for i=1:length(increment)])' + return gradf +end + +### INTEGRATIONPOINT ### """ Integration point @@ -160,7 +282,7 @@ attributes :: Dict{Any, Any} material models. """ type IntegrationPoint - xi :: Array{Float64, 1} + xi :: Vector weight :: Float64 fields :: Dict{ASCIIString, FieldSet} end @@ -168,20 +290,6 @@ function IntegrationPoint(xi, weight) IntegrationPoint(xi, weight, Dict()) end +call(b::SpatialBasis, ip::IntegrationPoint) = b.basis(ip.xi) - -# convenient functions -- maybe this is not correct place for them -""" Evaluate basis function in point ξ. """ -call(b::Basis, xi::Vector) = b.basis(xi) -call(b::Basis, ip::IntegrationPoint) = b.basis(ip.xi) -Base.(:*)(basis::Basis, fs::FieldSet) = (xi, t) -> basis(xi)*fs(t) - -#""" Interpolate field (h*f)(ξ) """ -#Base.(:*)(f::Function, fld::Field) = (x) -> f(x)*fld -#""" Interpolate from set of fields with basis b, i.e. f(t) = b(t)*[f1, f2] """ -#Base.(:*)(f::Function, fld::Field) = (x) -> f(x)*fld -#""" Interpolate field f using basis b. """ -#Base.(:*)(b::Basis, f::Field) = (x) -> b(x)*f -#Base.(:*)(b::Basis, f::Array{Field}) = (t) -> b(t)*f - diff --git a/src/xdmf.jl b/src/xdmf.jl index 6a5d9b3..446175b 100644 --- a/src/xdmf.jl +++ b/src/xdmf.jl @@ -155,7 +155,7 @@ function xdmf_new_field(grid, name, source, data) typ = string(typeof(data)) datatype = "unknown" - @debug("typeof: ", typ) + @debug("typeof: $typ") for j in ["Int", "Float"] @debug(j) if contains(typ, j) diff --git a/test/test_elasticity.jl b/test/test_elasticity.jl index 267cfe1..7a6ad38 100644 --- a/test/test_elasticity.jl +++ b/test/test_elasticity.jl @@ -1,26 +1,27 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md -# unit tests for heat equations -using FactCheck +using Base.Test + using JuliaFEM: Quad4, Field, FieldSet, CPS4, get_basis, solve!, PlaneStressElasticityProblem -facts("test plane elasticity on single element, volume load") do +function run() element = Quad4([1, 2, 3, 4]) - element["geometry"] = FieldSet(Field(Vector[[0.0, 0.0], [10.0, 0.0], [10.0, 1.0], [0.0, 1.0]])) - element["youngs modulus"] = FieldSet(Field(500.0)) - element["poissons ratio"] = FieldSet(Field(0.3)) - element["displacement load"] = FieldSet(Field(0.0, Vector[[0.0, -10.0], [0.0, -10.0], [0.0, -10.0], [0.0, -10.0]])) + element["geometry"] = Vector[[0.0, 0.0], [10.0, 0.0], [10.0, 1.0], [0.0, 1.0]] + element["youngs modulus"] = 500.0 + element["poissons ratio"] = 0.3 + element["displacement load"] = Vector[[0.0, -10.0], [0.0, -10.0], [0.0, -10.0], [0.0, -10.0]] equation = CPS4(element) free_dofs = [3, 4, 5, 6] problem = PlaneStressElasticityProblem([equation]) solve!(problem, free_dofs; max_iterations=10) #solve!(equation, "displacement", free_dofs; max_iterations=10) disp = get_basis(element)("displacement", [1.0, 1.0])[2] - Logging.info("displacement at tip: $disp") + info("displacement at tip: $disp") # verified using Code Aster. - @fact disp --> roughly(-8.77303119819776E+00) + @test disp ≈ -8.77303119819776 end +run() diff --git a/test/test_fields.jl b/test/test_fields.jl new file mode 100644 index 0000000..5632dc5 --- /dev/null +++ b/test/test_fields.jl @@ -0,0 +1,293 @@ +# This file is a part of JuliaFEM. +# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md + + +module TypesTests + +using JuliaFEM: Increment, TimeStep, AbstractField, DefaultDiscreteField, FieldSet +using JuliaFEM: TemporalBasis, SpatialBasis, ContinuousField, DiscreteField +using JuliaFEM: Field + + +using Base.Test + +function test_increment() + info("testing Increment") + # testing Increment + I1 = Increment([1, 2, 3]) + I2 = Increment([2, 3, 4]) + @test dot(I1, I2) == 20 + @test dot([1,2,3], I2) == 20 + @test dot(I1, [2,3,4]) == 20 + @test 1/2*(I1+I2) == [1.5, 2.5, 3.5] + @test I1 + 1 == [2, 3, 4] + @test I1 - 1 == [0, 1, 2] + @test I1*3 == [3, 6, 9] + @test I1+I2 == [3, 5, 7] + f = zeros(Increment, 2, 4) + @test length(f) == 4 + + g = similar(f, ones(8)) + @test typeof(f) == typeof(g) + @test length(f) == length(g) + + # promotion of increment + @test typeof(I1+1) == typeof(I1) + @test typeof(I1-1) == typeof(I1) + @test typeof(I1*3) == typeof(I1) + + # FIXME + #@test typeof(I1) == typeof(I1+I2) + #@test typeof(I1/2) == typeof(I1) + #@test typeof(1/2*S1) == typeof(I1) +end +test_increment() + +function test_timestep() + info("testing TimeStep") + i1 = Increment([1, 2, 3]) + i2 = Increment([2, 3, 4]) + i3 = Increment([2, 3, 4]) + i4 = Increment([3, 4, 5]) + t1 = TimeStep(1.0, Increment[i1, i2]) + t2 = TimeStep(2.0, Increment[i3, i4]) + @test length(t1) == length(t2) == 2 + t3 = TimeStep(3.0, i1+1) +end +test_timestep() + +function test_watta_fak() + # TODO: this test will fail if Increment is typealiased to Vector + fs = FieldSet() + fs["discrete field"] = [1, 2, 3, 4] + T0 = last(fs["discrete field"]) + info("last discrete field: $T0, ", typeof(T0)) + T1 = T0 + 1 + info("adding 1 to discrete field: $T1, ", typeof(T1)) + ts = TimeStep(1.0, T1) + info("creating time step: $ts, ", typeof(ts)) + push!(fs["discrete field"], ts) + info("last discrete field = ", last(fs["discrete field"])) + + info("fieldset: $fs") + + @test last(fs["discrete field"]) == [2, 3, 4, 5] +end +test_watta_fak() + +function test_default_discrete_field() + info("testing DefaultDiscreteField") + i1 = Increment([1, 2, 3]) + i2 = Increment([2, 3, 4]) + i3 = Increment([2, 3, 4]) + i4 = Increment([3, 4, 5]) + t1 = TimeStep(1.0, Increment[i1, i2]) + t2 = TimeStep(2.0, Increment[i3, i4]) + timesteps = TimeStep[t1, t2] + f1 = DefaultDiscreteField(timesteps) + @test length(f1) == 2 + @test isa(f1, AbstractField) == true +end +test_default_discrete_field() + +function test_fieldset() + i1 = Increment([1, 2, 3]) + i2 = Increment([2, 3, 4]) + i3 = Increment([2, 3, 4]) + i4 = Increment([3, 4, 5]) + t1 = TimeStep(1.0, Increment[i1, i2]) + t2 = TimeStep(2.0, Increment[i3, i4]) + timesteps = TimeStep[t1, t2] + f1 = DefaultDiscreteField(timesteps) + info("testing adding discrete field to FieldSet") + fs = FieldSet() + fs["temperature"] = f1 + @test length(fs) == 1 + + info("testing adding discrete fields quickly") + # the easy way + fs2 = FieldSet() + fs2["temperature"] = [1, 2, 3, 4] + @test fs2["temperature"][end][end] == [1, 2, 3, 4] + @test last(fs2["temperature"]) == [1, 2, 3, 4] + + fs2 = FieldSet() + fs2["constant scalar field"] = 1 + fs2["scalar field"] = [1, 2, 3, 4] + fs2["vector field"] = reshape(collect(1:8), 2, 4) + fs2["second order tensor field"] = reshape(collect(1:3*3*4), 3, 3, 4) + fs2["fourth order tensor field"] = reshape(collect(1:3*3*3*3*4), 3, 3, 3, 3, 4) + timestep = fs2["vector field"][end] + @test timestep.time == 0.0 + + info("testing adding timesteps") + # add another timestep + fs = FieldSet() + fs["temperature"] = [1, 2, 3, 4] + T0 = last(fs["temperature"]) # last increment of last field + info("last temperature = $T0") + T1 = T0 + 1 + @test typeof(T0) == typeof(T1) + timestep = TimeStep(1.0, Increment[T1]) # new list of increments for timestep + push!(fs["temperature"], timestep) + T2 = last(fs["temperature"]) + info("last temperature = $T2") + @test last(fs["temperature"]) == [2, 3, 4, 5] + # or more easily + timestep = TimeStep(2.0, T1) + push!(fs["temperature"], timestep) + @test length(fs["temperature"].timesteps) == 3 + + info("test adding several time steps at once") + fs3 = FieldSet() + fs3["time series 1"] = (0.0, [1, 2, 3, 4]), (0.5, [2, 3, 4, 5]), (1.0, [1, 1, 1, 1]) + @test fs3["time series 1"][end].time == 1.0 + fs3["time series 2"] = [1, 2, 3, 4], [2, 3, 4, 5], [1, 1, 1, 1] + @test fs3["time series 2"][end].time == 2.0 +end +test_fieldset() + +type MyFunnyContinuousField <: ContinuousField + basis :: Function + discretefield :: DiscreteField +end +function Base.call(field::MyFunnyContinuousField, xi::Vector, time::Number=1.0) + data = last(field.discretefield) # get the last timestep last increment + info("data = $data, typeof data = $(typeof(data))") + basis = time*field.basis(xi) # evaluate basis at point ξ. + sum([basis[i]*data[i] for i=1:length(data)]) # sum results +end +function test_continuous_field() + info("testing continuous field") + fs = FieldSet() + fs["discrete field"] = [1, 2, 3, 4] + basis(xi) = 1/4*[ + (1-xi[1])*(1-xi[2]), + (1+xi[1])*(1-xi[2]), + (1+xi[1])*(1+xi[2]), + (1-xi[1])*(1+xi[2])] + fs["continuous field"] = MyFunnyContinuousField(basis, fs["discrete field"]) + @test fs["continuous field"]([0.0, 0.0], 1.0) == 1/4*(1+2+3+4) + T0 = last(fs["discrete field"]) + T1 = T0 + 1.0 + ts = TimeStep(1.0, T1) + push!(fs["discrete field"], TimeStep(1.0, T0+1.0)) + @test fs["continuous field"]([0.0, 0.0], 1.0) == 1/4*(2+3+4+5) +end +test_continuous_field() + +type MyFunnyDiscreteField <: DiscreteField + discrete_points :: Vector + continuousfield :: ContinuousField +end +Base.length(field::MyFunnyDiscreteField) = length(field.discrete_points) +Base.endof(field::MyFunnyDiscreteField) = endof(field.discrete_points) +Base.last(field::MyFunnyDiscreteField) = Float64[field[i] for i=1:length(field)] +function Base.getindex(field::MyFunnyDiscreteField, idx::Int64) + field.continuousfield(field.discrete_points[idx]) +end + +function test_discrete_field() + info("testing discrete field") + fs = FieldSet() + fs["discrete field"] = [1, 2, 3, 4] + basis(xi) = 1/4*[ + (1-xi[1])*(1-xi[2]), + (1+xi[1])*(1-xi[2]), + (1+xi[1])*(1+xi[2]), + (1-xi[1])*(1+xi[2])] + fs["continuous field"] = MyFunnyContinuousField(basis, fs["discrete field"]) + discrete_points = 1.0/sqrt(3.0)*Vector[[-1, -1], [1, -1], [1, 1], [-1, 1]] + fs["discrete field 2"] = MyFunnyDiscreteField(discrete_points, fs["continuous field"]) + @test last(fs["discrete field 2"]) ≈ [ + 1.7559830641437073, + 2.0893163974770410, + 2.9106836025229590, + 3.2440169358562922] +end +test_discrete_field() + + +function test_interpolation_in_temporal_basis() + info("testing interpolation on temporal basis") + temporalbasis = TemporalBasis((t) -> [1-t, t], (t) -> [-1, 1]) + @test temporalbasis(0.2) == [0.8, 0.2] + i1 = Increment([0.0]) + i2 = Increment([1.0]) + i3 = Increment([2.0]) + t1 = TimeStep(0.0, Increment[i1]) + t2 = TimeStep(2.0, Increment[i2]) + t3 = TimeStep(4.0, Increment[i3]) + field = Field(TimeStep[t1, t2, t3]) + @test call(field, temporalbasis, -Inf) == [0.0] + @test call(field, temporalbasis, 0.0) == [0.0] + @test call(field, temporalbasis, 1.0) == [0.5] + @test call(field, temporalbasis, 2.0) == [1.0] + @test call(field, temporalbasis, 3.0) == [1.5] + @test call(field, temporalbasis, 4.0) == [2.0] + @test call(field, temporalbasis, +Inf) == [2.0] + @test call(field, temporalbasis, +Inf, Val{:derivative}) == [0.5] + @test call(field, temporalbasis, -Inf, Val{:derivative}) == [0.5] + @test call(field, temporalbasis, 0.0, Val{:derivative}) == [0.5] + @test call(field, temporalbasis, 0.5, Val{:derivative}) == [0.5] + @test call(field, temporalbasis, 1.0, Val{:derivative}) == [0.5] + @test call(field, temporalbasis, 1.5, Val{:derivative}) == [0.5] + @test call(field, temporalbasis, 2.0, Val{:derivative}) == [0.5] + fs = FieldSet() + + t = collect(linspace(0, 2, 5)) + x = 1/2*t.^2 + x2 = tuple(collect(zip(t, x))...) + # => ((0.0,0.0),(0.5,0.125),(1.0,0.5),(1.5,1.125),(2.0,2.0)) + fs["particle"] = x2 + position = call(fs["particle"], temporalbasis, 1.0)[1] + @test position ≈ 0.50 + velocity = call(fs["particle"], temporalbasis, 2.0, Val{:derivative})[1] + @test velocity ≈ (2.0-1.125)/0.5 # = 1.75 + velocity = call(fs["particle"], temporalbasis, 1.0, Val{:derivative})[1] + v1 = (0.500 - 0.125)/0.5 + v2 = (1.125 - 0.500)/0.5 + info("v1 = $v1, v2 = $v2") + info(mean([v1, v2])) + @test velocity ≈ mean([v1, v2]) # = 1.00 + + # FIXME, returns wrong type. + #= + @test isa(position, Increment) == true + @test isa(velocity, Increment) == true + =# +end +test_interpolation_in_temporal_basis() + +function test_interpolation_in_spatial_basis() + info("testing interpolation on spatial basis") + basis(xi) = 1/4*[ + (1-xi[1])*(1-xi[2]) + (1+xi[1])*(1-xi[2]) + (1+xi[1])*(1+xi[2]) + (1-xi[1])*(1+xi[2])]' + dbasis(xi) = 1/4*[ + -(1-xi[2]) (1-xi[2]) (1+xi[2]) -(1+xi[2]) + -(1-xi[1]) -(1+xi[1]) (1+xi[1]) (1-xi[1])] + spatialbasis = SpatialBasis(basis, dbasis) + @test spatialbasis.basis([0.0, 0.0]) == 1/4*[1 1 1 1] + + fs = FieldSet() + fs["geometry"] = Vector{Float64}[[0.0,0.0], [1.0,0.0], [1.0,1.0], [0.0,1.0]] + fs["displacement"] = (0.0, zeros(2, 4)), (1.0, Vector[[0.0, 0.0], [0.0, 0.0], [0.25, 0.0], [0.0, 0.0]]) + + X = call(last(fs["geometry"]), spatialbasis, [0.0, 0.0]) + u = call(last(fs["displacement"]), spatialbasis, [0.0, 0.0]) + x = X+u + @test X ≈ 1/2*[1, 1] + @test x ≈ [9/16, 1/2] + + gradu = call(last(fs["displacement"]), spatialbasis, [0.0, 0.0], last(fs["geometry"]), Val{:gradient}) + @test gradu ≈ [0.125 0.125; 0.0 0.0] +end +test_interpolation_in_spatial_basis() + + +println("test_fields.jl: all test passing.") + +end diff --git a/test/test_heat.jl b/test/test_heat.jl index 5f48c04..0fb5b4d 100644 --- a/test/test_heat.jl +++ b/test/test_heat.jl @@ -10,16 +10,16 @@ facts("tests on [0x1]x[0x1] domain") do # volume element element = Quad4([1, 2, 3, 4]) - element["geometry"] = FieldSet(Field(Vector[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]])) - element["temperature thermal conductivity"] = FieldSet(Field(0.0, 6.0)) - element["temperature load"] = FieldSet(Field(0.0, [12.0, 12.0, 12.0, 12.0])) - element["density"] = FieldSet(Field(0.0, 36.0)) + element["geometry"] = Vector[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]] + element["temperature thermal conductivity"] = 6.0 + element["temperature load"] = [12.0, 12.0, 12.0, 12.0] + element["density"] = 36.0 # boundary element boundary_element = Seg2([1, 2]) - boundary_element["geometry"] = FieldSet(Field(Vector[[0.0, 0.0], [1.0, 0.0]])) + boundary_element["geometry"] = Vector[[0.0, 0.0], [1.0, 0.0]] # linear ramp from 1 to 6 in time 0 to 1 - boundary_element["temperature flux"] = FieldSet(Field[Field(0.0, 0.0), Field(1.0, 6.0)]) + boundary_element["temperature flux"] = (0.0, 0.0), (1.0, 6.0) # Set constant source f=12 with k=6. Accurate solution is # T=1 on free boundary, u(x,y) = -1/6*(1/2*f*x^2 - f*x)