Fields
From the beginning of a project we had a clear concept in our mind: "everything is a field". That is, everything can vary temporally and spatially. We think that constant is just a special case of field which does not vary in temporal nor spatial direction. Fields can vary in spatial direction, i.e. can be either constant or variable, and in temporal direction, i.e. can be time variant or time invariant. From this pondering we can think that there exists four kind of (discrete) fields:
- discrete, constant, time invariant (DCTI)
- discrete, variable, time invariant (DVTI)
- discrete, constant, time variant (DCTV)
- discrete, variable, time variant (DVTV)
Discrete, in this context, means that field is defined in point-wise in $1 \ldots n$ locations, from where it is then interpolated to whole domain using some interpolation polynomials, i.e.
math where $N_{i}(\xi, t)$ is the basis function or interpolation polymial corresponding to $i$^{th} discrete value and $u_{i}$ is the discrete value.
Then we have continuous fields, which are defined in whole domain, or at least not point-wise. By following the already used abbreviations, we have four more fields:
- continuous, constant, time invariant (CCTI)
- continuous, variable, time invariant (CVTI)
- continuous, constant, time variant (DCTV)
- continuous, variable, time variant (CVTV)
Continuous, again in this context, does not mean that field has to be defined everywhere. It's enough that it's defined in function of spatial and/or temporal coordinates, i.e. we have $u \equiv u(\xi, t)$, without a some spesific basis needed to interpolate from discrete values.
Field itself can be in principle anything. However, usually either scalar, vector or tensor (matrix). Time does not to have be real, it can be for example angle of some rotating machine or even complex value.
From these starting points, we assume that the mentioned field system can describe all imaginable situations.
Creating new fields
For discrete fields that are varying in spatial direction, value for each discrete point is defined using NTuple. The order of points is implicitly assumed to be same than node ordering in ABAQUS. That is, first corner nodes in anti-clockwise direction and after that middle nodes.
For example, (1, 2, 3, 4)
is a scalar field having length of 4 and ([1,2],[2,3],[3,4],[4,5])
is a vector field having length of 4.
For fields that are varying in temporal direction, time => value
syntax is used. The first item in pair is time (or similar) and second item is value assigned to that time. For example, 0.0 => 1.0
is a time-dependent scalar field having value 1.0 at time 0.0.
The most simple field is a field that is constant in both time and spatial direction. Discrete, constant, time invariant DCTI
:
julia> a = DCTI(1.0)
ERROR: UndefVarError: DCTI not defined
Then we have discrete, variable, time invariant fields DVTI
. Notice the use of Tuple
when defining field.
julia> b = DVTI( (1.0, 2.0) )
ERROR: UndefVarError: DVTI not defined
Discrete, constant, time variant field DCTV
is constant in spatial direction $\partial u/\partial x = 0$ but can vary in temporal direction, $\partial u/\partial t\neq 0$. Here, =>
syntax is used. New values can be added to field using function update!
. If there already exists a value for that particular time, it will be overridden. It is assumed that content of field in time direction is monotonically increasing, i.e.
For the sake of clarity let's also mention that update!
works for time invariant fields as well if content needs to be updated.
julia> c = DCTV(0.0 => 1.0, 1.0 => 2.0)
ERROR: UndefVarError: DCTV not defined
julia> update!(c, 2.0 => 3.0)
ERROR: UndefVarError: update! not defined
Discrete, variable, time variant DVTV
field is the most general one, allowing values of field to vary in both spatial and time direction.
julia> d = DVTV(0.0 => (1.0, 2.0), 1.0 => (2.0, 3.0))
ERROR: UndefVarError: DVTV not defined
julia> update!(d, 2.0 => (3.0, 4.0))
ERROR: UndefVarError: update! not defined
In examples above, all fields defined was scalar fields. Defining vector or tensor fields goes in the same way. The only difference is that now we define vectors and tensors instead of a single scalar value. They can vary in spatial and time direction in the same way than scalar fields. Here is example of defining the abovementioned vector fields:
julia> a = DCTI([1.0, 2.0])
ERROR: UndefVarError: DCTI not defined
julia> b = DVTI(([1.0, 2.0], [2.0, 3.0]))
ERROR: UndefVarError: DVTI not defined
julia> c = DCTV(0.0 => [1.0, 2.0], 1.0 => [2.0, 3.0])
ERROR: UndefVarError: DCTV not defined
julia> d = DVTV(0.0 => ([1.0, 2.0], [2.0, 3.0]), 1.0 => ([2.0, 3.0], [3.0, 4.0]))
ERROR: UndefVarError: DVTV not defined
Accessing fields
Accessing fields in time direction is done using a function interpolate
. For example, if we have (constant) $[1,2]$ at time $t=0.0$ and $[3,4]$ at time $t=1.0$, linear interpolation in time direction yields
julia> c = DCTV(0.0 => [1.0,2.0], 1.0 => [3.0,4.0])
ERROR: UndefVarError: DCTV not defined
julia> interpolate(c, 0.5)
ERROR: UndefVarError: interpolate not defined
If field is spatially varying, a Tuple
will be returned, having one value for each "node". This can then be interpolated in spatial direction, typically using basis functions defined on element, i.e. $u = N_{i} u_{i}$:
julia> d = DVTV(0.0 => (1.0,2.0), 1.0 => (3.0,4.0))
ERROR: UndefVarError: DVTV not defined
julia> interpolate(d, 0.5)
ERROR: UndefVarError: interpolate not defined
Although the two fields above looks quite same, the key difference is that in DCTV field we have a constant vectorial value (defined using square brackets []) and in latter DVTV field we have a scalar value (defined using round brackets) changing in spatial direction from 1.0 to 2.0 at time $t=0.0$ and changing from 3.0 to 4.0 at time $t=1.0$.
If a field is time invariant, interpolation in time direction returns a trivial solution:
julia> interpolate(DCTI(1.0), 0.5)
ERROR: UndefVarError: DCTI not defined
julia> interpolate(DVTI((1.0,2.0)), 0.5)
ERROR: UndefVarError: DVTI not defined
For spatially varying fields, one can access to ith element using getindex:
julia> a = DVTI((1.0,2.0))
ERROR: UndefVarError: DVTI not defined
julia> getindex(a, 1)
ERROR: UndefVarError: a not defined
For field varying both temporally and spatially, one has first to interpolate in time direction to get iterable tuple:
julia> d = DVTV(0.0 => (1.0,2.0), 1.0 => (3.0,4.0))
ERROR: UndefVarError: DVTV not defined
julia> result = interpolate(d, 0.5)
ERROR: UndefVarError: interpolate not defined
julia> getindex(result, 1)
ERROR: UndefVarError: result not defined
Internally, each field is a subtype of AbstractField
having a field data
, which be accessed directly for hacking purposes.
julia> d.data
ERROR: UndefVarError: d not defined
Continuous fields
Continuous fields may be useful when defining analytical boundary conditions. For that we have CVTV
, where "C" stands for continuous.
julia> f(xi,t) = xi[1]*xi[2]*t
f (generic function with 1 method)
julia> g = CVTV(f)
ERROR: UndefVarError: CVTV not defined
julia> g((1.0,2.0), 3.0)
ERROR: UndefVarError: g not defined
Dictionary fields
Usually it is assumed that size of length of discrete field matches to the number of basis functions of a single element, typically something small like 1-10.
However, there might be cases where it is more practical to define field in a sense that indexing is not continuous or starting from 1. For example, we might want to define field common for a set of elements. In that case it's natural to think that each index in field corresponds to the certain id-number of node. For example, if we have a triangle element connecting nodes 1, 1000 and 100000, we still want to access that field naturally using getindex
, e.g. f[1]
, f[1000]
and f[100000]
. In that case, more appropriate internal structure for field is based on a dictionary, not tuple.
It only makes sense to define dictionary fields for spatially varying fields. Two new fields are introduced: DVTId
and DVTVd
, where the last character "d" stands for "dictionary".
Keep on mind, that this type of field has one restriction. If and when this field is typically defined on nodes of several elements, field must be continuous between elements. That is, if field value in node 1000 is for example 1.0, then it's 1.0 in all elements connecting to that node. To define jumps on field, one must define field element-wise.
Define eg. "geometry" for nodes 1,1000,100000:
julia> X = Dict(1=>[0.0,0.0], 1000=>[1.0,0.0], 100000=>[1.0,1.0])
Dict{Int64,Array{Float64,1}} with 3 entries:
100000 => [1.0, 1.0]
1000 => [1.0, 0.0]
1 => [0.0, 0.0]
julia> G = DVTId(X)
ERROR: UndefVarError: DVTId not defined
julia> G[1], G[1000], G[100000]
ERROR: UndefVarError: G not defined
Interpolation in time directions works in a same way than with other fields depends from time.
julia> Y = Dict(1=>[1.0,1.0], 1000=>[2.0,1.0], 100000=>[2.0,2.0])
Dict{Int64,Array{Float64,1}} with 3 entries:
100000 => [2.0, 2.0]
1000 => [2.0, 1.0]
1 => [1.0, 1.0]
julia> F = DVTVd(0.0 => X, 1.0 => Y)
ERROR: UndefVarError: DVTVd not defined
julia> interpolate(F,0.5)[100000]
ERROR: UndefVarError: interpolate not defined
Using common constructor field
Now we have introduced total of 7 fields: DCTI, DCTV, DVTI, DVTV, CVTV, DVTId, DVTVd. A good question arises that how to remember all this stuff and is it even necessary? Luckily not, because one can use a single constructor called field
to create all kind of fields. Type of field is inspected from data type. It's not necessary to remember all this technical stuff, just declare new field using more of less intuitive syntax and field
-function.
julia> f1 = field(1)
ERROR: UndefVarError: field not defined
julia> f2 = field(1, 2)
ERROR: UndefVarError: field not defined
julia> f3 = field(0.0 => 1)
ERROR: UndefVarError: field not defined
julia> f4 = field(0.0 => (1, 2), 1.0 => (2, 3))
ERROR: UndefVarError: field not defined
julia> f5 = field((xi,t) -> xi[1]*t)
ERROR: UndefVarError: field not defined
julia> f6 = field(1 => 1, 2 => 2)
ERROR: UndefVarError: field not defined
julia> f7 = field(0.0 => (1=>1, 10=>2), 1.0 => (1=>2,10=>3))
ERROR: UndefVarError: field not defined
Developing new fields
If the FEMBase ones are not enough, it's always possible to define new ones. Minimum requirements is that field is a subtype of AbstractField
and interpolate
, getindex
, has been defined to it. Field can, for example fetch data from random.org or market stocks, read data from hard drive or add some stochastics behavior to it.
Functions and types related to fields
Types
Missing docstring for AbstractField
. Check Documenter's build log for details.
Missing docstring for DCTI
. Check Documenter's build log for details.
Missing docstring for DVTI
. Check Documenter's build log for details.
Missing docstring for DCTV
. Check Documenter's build log for details.
Missing docstring for DVTV
. Check Documenter's build log for details.
Missing docstring for CVTV
. Check Documenter's build log for details.
Missing docstring for DVTId
. Check Documenter's build log for details.
Missing docstring for DVTVd
. Check Documenter's build log for details.
Functions (internal)
These functions needs to be defined when developing new fields:
Missing docstring for new_field
. Check Documenter's build log for details.
Missing docstring for update_field!
. Check Documenter's build log for details.
Missing docstring for interpolate_field
. Check Documenter's build log for details.
Functions (public)
Missing docstring for field(x)
. Check Documenter's build log for details.
Missing docstring for update!(field::F, data) where {F<:AbstractField}
. Check Documenter's build log for details.
Missing docstring for interpolate(field::F, time) where {F<:AbstractField}
. Check Documenter's build log for details.